diff options
Diffstat (limited to 'src/bluetooth')
26 files changed, 4076 insertions, 5 deletions
diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro index 2bcdee12..eedd4864 100644 --- a/src/bluetooth/bluetooth.pro +++ b/src/bluetooth/bluetooth.pro @@ -245,6 +245,27 @@ qtConfig(bluez) { DEFINES += QT_WINRT_LIMITED_SERVICEDISCOVERY DEFINES += QT_UCRTVERSION=$$WINDOWS_SDK_VERSION } +} else:win32 { + QT_PRIVATE = concurrent + DEFINES += QT_WIN_BLUETOOTH + LIBS += -lbthprops -lws2_32 -lsetupapi + + include(windows/windows.pri) + + # remove dummy warning once platform port is complete + include(dummy/dummy.pri) + + SOURCES += \ + qbluetoothdevicediscoveryagent_win.cpp \ + qbluetoothlocaldevice_win.cpp \ + qbluetoothserviceinfo_win.cpp \ + qbluetoothservicediscoveryagent_win.cpp \ + qbluetoothsocket_win.cpp \ + qbluetoothserver_win.cpp \ + qlowenergycontroller_win.cpp + + PRIVATE_HEADERS += qlowenergycontroller_win_p.h \ + qbluetoothsocket_win_p.h } else { message("Unsupported Bluetooth platform, will not build a working QtBluetooth library.") message("Either no Qt D-Bus found or no BlueZ headers available.") diff --git a/src/bluetooth/qbluetooth.cpp b/src/bluetooth/qbluetooth.cpp index 6eab11fb..7b5fd266 100644 --- a/src/bluetooth/qbluetooth.cpp +++ b/src/bluetooth/qbluetooth.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2014 Denis Shienkov <denis.shienkov@gmail.com> ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. @@ -101,6 +102,7 @@ namespace QBluetooth { Q_LOGGING_CATEGORY(QT_BT, "qt.bluetooth") 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") QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent.h b/src/bluetooth/qbluetoothdevicediscoveryagent.h index 59a8b456..e566d895 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent.h +++ b/src/bluetooth/qbluetoothdevicediscoveryagent.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2014 Denis Shienkov <denis.shienkov@gmail.com> ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h index ce31392f..97beced3 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2014 Denis Shienkov <denis.shienkov@gmail.com> ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. @@ -78,7 +79,20 @@ class QDBusVariant; QT_END_NAMESPACE #endif -#ifdef QT_WINRT_BLUETOOTH +#ifdef QT_WIN_BLUETOOTH +QT_BEGIN_NAMESPACE +class QThread; + +class ThreadWorkerDeviceDiscovery : public QObject +{ + Q_OBJECT +signals: + void discoveryCompleted(const QVariant res); +}; + +QT_END_NAMESPACE + +#elif defined(QT_WINRT_BLUETOOTH) #include <QtCore/QPointer> #include <QtCore/QTimer> #endif @@ -90,7 +104,7 @@ class QWinRTBluetoothDeviceDiscoveryWorker; #endif class QBluetoothDeviceDiscoveryAgentPrivate -#if defined(QT_ANDROID_BLUETOOTH) || defined(QT_WINRT_BLUETOOTH) +#if defined(QT_ANDROID_BLUETOOTH) || defined(QT_WINRT_BLUETOOTH) || defined(QT_WIN_BLUETOOTH) : public QObject { Q_OBJECT @@ -164,6 +178,33 @@ private: QTimer extendedDiscoveryTimer; #endif +#ifdef QT_WIN_BLUETOOTH +public: + static QString discoveredLeDeviceSystemPath(const QBluetoothAddress &deviceAddress); + +private: + void cancelDiscovery(); + void restartDiscovery(); + void finishDiscovery(QBluetoothDeviceDiscoveryAgent::Error errorCode, const QString &errorText); + + void startLeDevicesDiscovery(); + void completeLeDevicesDiscovery(const QVariant &res); + void startClassicDevicesDiscovery(Qt::HANDLE hSearch = nullptr); + void completeClassicDevicesDiscovery(const QVariant &res); + + void processDiscoveredDevice(const QBluetoothDeviceInfo &foundDevice); + + QBluetoothAddress adapterAddress; + bool pendingCancel; + bool pendingStart; + bool active; + + QThread *threadLE = nullptr; + QThread *threadClassic = nullptr; + ThreadWorkerDeviceDiscovery *threadWorkerLE = nullptr; + ThreadWorkerDeviceDiscovery *threadWorkerClassic = nullptr; +#endif + #ifdef QT_WINRT_BLUETOOTH private slots: void registerDevice(const QBluetoothDeviceInfo &info); diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_win.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_win.cpp new file mode 100644 index 00000000..7509c5f6 --- /dev/null +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_win.cpp @@ -0,0 +1,564 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2014 Denis Shienkov <denis.shienkov@gmail.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 "qbluetoothdevicediscoveryagent.h" +#include "qbluetoothuuid.h" +#include "qbluetoothdevicediscoveryagent_p.h" +#include "qbluetoothlocaldevice_p.h" + +#include <QtCore/qmutex.h> +#include <QtCore/QThread> +#include <QtCore/QLoggingCategory> + +#include <qt_windows.h> +#include <setupapi.h> +#include <bluetoothapis.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS) + +struct LeDeviceEntry { + QString devicePath; + QBluetoothAddress deviceAddress; +}; + +Q_GLOBAL_STATIC(QVector<LeDeviceEntry>, cachedLeDeviceEntries) +Q_GLOBAL_STATIC(QMutex, cachedLeDeviceEntriesGuard) + +static QString devicePropertyString( + HDEVINFO hDeviceInfo, + const PSP_DEVINFO_DATA deviceInfoData, + DWORD registryProperty) +{ + DWORD propertyRegDataType = 0; + DWORD propertyBufferSize = 0; + QByteArray propertyBuffer; + + while (!::SetupDiGetDeviceRegistryProperty( + hDeviceInfo, + deviceInfoData, + registryProperty, + &propertyRegDataType, + propertyBuffer.isEmpty() ? nullptr : reinterpret_cast<PBYTE>(propertyBuffer.data()), + propertyBuffer.size(), + &propertyBufferSize)) { + + const DWORD error = ::GetLastError(); + if (error != ERROR_INSUFFICIENT_BUFFER + || (propertyRegDataType != REG_SZ + && propertyRegDataType != REG_EXPAND_SZ)) { + return QString(); + } + + // add +2 byte to allow to successfully convert to qstring + propertyBuffer.fill(0, propertyBufferSize + sizeof(wchar_t)); + } + + return QString::fromWCharArray(reinterpret_cast<const wchar_t *>( + propertyBuffer.constData())); +} + +static QString deviceName(HDEVINFO hDeviceInfo, PSP_DEVINFO_DATA deviceInfoData) +{ + return devicePropertyString(hDeviceInfo, deviceInfoData, SPDRP_FRIENDLYNAME); +} + +static QString deviceSystemPath(const PSP_INTERFACE_DEVICE_DETAIL_DATA detailData) +{ + return QString::fromWCharArray(detailData->DevicePath); +} + +static QBluetoothAddress deviceAddress(const QString &devicePath) +{ + const int firstbound = devicePath.indexOf(QStringLiteral("dev_")); + const int lastbound = devicePath.indexOf(QLatin1Char('#'), firstbound); + const QString hex = devicePath.mid(firstbound + 4, lastbound - firstbound - 4); + bool ok = false; + return QBluetoothAddress(hex.toULongLong(&ok, 16)); +} + +static QBluetoothDeviceInfo createClassicDeviceInfo(const BLUETOOTH_DEVICE_INFO &foundDevice) +{ + QBluetoothDeviceInfo deviceInfo( + QBluetoothAddress(foundDevice.Address.ullLong), + QString::fromWCharArray(foundDevice.szName), + foundDevice.ulClassofDevice); + + deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration); + + if (foundDevice.fRemembered) + deviceInfo.setCached(true); + return deviceInfo; +} + +static QBluetoothDeviceInfo findFirstClassicDevice( + DWORD *systemErrorCode, HBLUETOOTH_DEVICE_FIND *hSearch) +{ + BLUETOOTH_DEVICE_SEARCH_PARAMS searchParams = {0}; + searchParams.dwSize = sizeof(searchParams); + searchParams.cTimeoutMultiplier = 10; // 12.8 sec + searchParams.fIssueInquiry = TRUE; + searchParams.fReturnAuthenticated = TRUE; + searchParams.fReturnConnected = TRUE; + searchParams.fReturnRemembered = TRUE; + searchParams.fReturnUnknown = TRUE; + searchParams.hRadio = nullptr; + + BLUETOOTH_DEVICE_INFO deviceInfo = {0}; + deviceInfo.dwSize = sizeof(deviceInfo); + + const HBLUETOOTH_DEVICE_FIND hFind = ::BluetoothFindFirstDevice( + &searchParams, &deviceInfo); + + QBluetoothDeviceInfo foundDevice; + if (hFind) { + *hSearch = hFind; + *systemErrorCode = NO_ERROR; + foundDevice = createClassicDeviceInfo(deviceInfo); + } else { + *systemErrorCode = ::GetLastError(); + } + + return foundDevice; +} + +static QBluetoothDeviceInfo findNextClassicDevice( + DWORD *systemErrorCode, HBLUETOOTH_DEVICE_FIND hSearch) +{ + BLUETOOTH_DEVICE_INFO deviceInfo = {0}; + deviceInfo.dwSize = sizeof(deviceInfo); + + QBluetoothDeviceInfo foundDevice; + if (!::BluetoothFindNextDevice(hSearch, &deviceInfo)) { + *systemErrorCode = ::GetLastError(); + } else { + *systemErrorCode = NO_ERROR; + foundDevice = createClassicDeviceInfo(deviceInfo); + } + + return foundDevice; +} + +static void closeClassicSearch(HBLUETOOTH_DEVICE_FIND hSearch) +{ + if (hSearch) + ::BluetoothFindDeviceClose(hSearch); +} + +static QVector<QBluetoothDeviceInfo> enumerateLeDevices( + DWORD *systemErrorCode) +{ + const QUuid deviceInterfaceGuid("781aee18-7733-4ce4-add0-91f41c67b592"); + const HDEVINFO hDeviceInfo = ::SetupDiGetClassDevs( + reinterpret_cast<const GUID *>(&deviceInterfaceGuid), + nullptr, + nullptr, + DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + if (hDeviceInfo == INVALID_HANDLE_VALUE) { + *systemErrorCode = ::GetLastError(); + return QVector<QBluetoothDeviceInfo>(); + } + + QVector<QBluetoothDeviceInfo> foundDevices; + DWORD index = 0; + + QVector<LeDeviceEntry> cachedEntries; + + for (;;) { + SP_DEVICE_INTERFACE_DATA deviceInterfaceData = {0}; + deviceInterfaceData.cbSize = sizeof(deviceInterfaceData); + + if (!::SetupDiEnumDeviceInterfaces( + hDeviceInfo, + nullptr, + reinterpret_cast<const GUID *>(&deviceInterfaceGuid), + index++, + &deviceInterfaceData)) { + *systemErrorCode = ::GetLastError(); + break; + } + + DWORD deviceInterfaceDetailDataSize = 0; + if (!::SetupDiGetDeviceInterfaceDetail( + hDeviceInfo, + &deviceInterfaceData, + nullptr, + deviceInterfaceDetailDataSize, + &deviceInterfaceDetailDataSize, + nullptr)) { + if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + *systemErrorCode = ::GetLastError(); + break; + } + } + + SP_DEVINFO_DATA deviceInfoData = {0}; + deviceInfoData.cbSize = sizeof(deviceInfoData); + + QByteArray deviceInterfaceDetailDataBuffer( + deviceInterfaceDetailDataSize, 0); + + PSP_INTERFACE_DEVICE_DETAIL_DATA deviceInterfaceDetailData = + reinterpret_cast<PSP_INTERFACE_DEVICE_DETAIL_DATA> + (deviceInterfaceDetailDataBuffer.data()); + + deviceInterfaceDetailData->cbSize = + sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA); + + if (!::SetupDiGetDeviceInterfaceDetail( + hDeviceInfo, + &deviceInterfaceData, + deviceInterfaceDetailData, + deviceInterfaceDetailDataBuffer.size(), + &deviceInterfaceDetailDataSize, + &deviceInfoData)) { + *systemErrorCode = ::GetLastError(); + break; + } + + const QString systemPath = deviceSystemPath(deviceInterfaceDetailData); + const QBluetoothAddress address = deviceAddress(systemPath); + if (address.isNull()) + continue; + const QString name = deviceName(hDeviceInfo, &deviceInfoData); + + QBluetoothDeviceInfo deviceInfo(address, name, QBluetoothDeviceInfo::MiscellaneousDevice); + deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration); + deviceInfo.setCached(true); + + foundDevices << deviceInfo; + cachedEntries << LeDeviceEntry{systemPath, address}; + } + + QMutexLocker locker(cachedLeDeviceEntriesGuard()); + cachedLeDeviceEntries()->swap(cachedEntries); + + ::SetupDiDestroyDeviceInfoList(hDeviceInfo); + return foundDevices; +} + +struct DiscoveryResult { + QVector<QBluetoothDeviceInfo> devices; + DWORD systemErrorCode; + HBLUETOOTH_DEVICE_FIND hSearch; // Used only for classic devices +}; + +QString QBluetoothDeviceDiscoveryAgentPrivate::discoveredLeDeviceSystemPath( + const QBluetoothAddress &deviceAddress) +{ + // update LE devices cache + DWORD dummyErrorCode; + enumerateLeDevices(&dummyErrorCode); + + QMutexLocker locker(cachedLeDeviceEntriesGuard()); + for (const LeDeviceEntry &e: *cachedLeDeviceEntries) { + if (e.deviceAddress == deviceAddress) + return e.devicePath; + } + return QString(); +} + +QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate( + const QBluetoothAddress &deviceAdapter, + QBluetoothDeviceDiscoveryAgent *parent) + : inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry) + , lastError(QBluetoothDeviceDiscoveryAgent::NoError) + , adapterAddress(deviceAdapter) + , pendingCancel(false) + , pendingStart(false) + , active(false) + , lowEnergySearchTimeout(-1) // remains -1 -> timeout not supported + , q_ptr(parent) +{ + threadLE = new QThread; + threadWorkerLE = new ThreadWorkerDeviceDiscovery; + threadWorkerLE->moveToThread(threadLE); + connect(threadWorkerLE, &ThreadWorkerDeviceDiscovery::discoveryCompleted, this, &QBluetoothDeviceDiscoveryAgentPrivate::completeLeDevicesDiscovery); + connect(threadLE, &QThread::finished, threadWorkerLE, &ThreadWorkerDeviceDiscovery::deleteLater); + connect(threadLE, &QThread::finished, threadLE, &QThread::deleteLater); + threadLE->start(); + + threadClassic = new QThread; + threadWorkerClassic = new ThreadWorkerDeviceDiscovery; + threadWorkerClassic->moveToThread(threadClassic); + connect(threadWorkerClassic, &ThreadWorkerDeviceDiscovery::discoveryCompleted, this, &QBluetoothDeviceDiscoveryAgentPrivate::completeClassicDevicesDiscovery); + connect(threadClassic, &QThread::finished, threadWorkerClassic, &ThreadWorkerDeviceDiscovery::deleteLater); + connect(threadClassic, &QThread::finished, threadClassic, &QThread::deleteLater); + threadClassic->start(); +} + +QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate() +{ + if (active) + stop(); + if (threadLE) + threadLE->quit(); + if (threadClassic) + threadClassic->quit(); +} + +bool QBluetoothDeviceDiscoveryAgentPrivate::isActive() const +{ + if (pendingStart) + return true; + if (pendingCancel) + return false; + return active; +} + +QBluetoothDeviceDiscoveryAgent::DiscoveryMethods QBluetoothDeviceDiscoveryAgent::supportedDiscoveryMethods() +{ + return (LowEnergyMethod | ClassicMethod); +} + +void QBluetoothDeviceDiscoveryAgentPrivate::start(QBluetoothDeviceDiscoveryAgent::DiscoveryMethods methods) +{ + requestedMethods = methods; + + if (pendingCancel == true) { + pendingStart = true; + return; + } + + const QList<QBluetoothHostInfo> foundLocalAdapters = + QBluetoothLocalDevicePrivate::localAdapters(); + + Q_Q(QBluetoothDeviceDiscoveryAgent); + + if (foundLocalAdapters.isEmpty()) { + qCWarning(QT_BT_WINDOWS) << "Device does not support Bluetooth"; + lastError = QBluetoothDeviceDiscoveryAgent::InputOutputError; + errorString = QBluetoothDeviceDiscoveryAgent::tr("Device does not support Bluetooth"); + emit q->error(lastError); + return; + } + + // Check for the local adapter address. + auto equals = [this](const QBluetoothHostInfo &adapterInfo) { + return adapterAddress == QBluetoothAddress() + || adapterAddress == adapterInfo.address(); + }; + const auto end = foundLocalAdapters.cend(); + const auto it = std::find_if(foundLocalAdapters.cbegin(), end, equals); + if (it == end) { + qCWarning(QT_BT_WINDOWS) << "Incorrect local adapter passed."; + lastError = QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError; + errorString = QBluetoothDeviceDiscoveryAgent::tr("Passed address is not a local device."); + emit q->error(lastError); + return; + } + + discoveredDevices.clear(); + active = true; + + // We run LE search first, as it is fast on windows. + if (requestedMethods & QBluetoothDeviceDiscoveryAgent::LowEnergyMethod) + startLeDevicesDiscovery(); + else if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod) + startClassicDevicesDiscovery(); +} + +void QBluetoothDeviceDiscoveryAgentPrivate::stop() +{ + if (!active) + return; + + pendingCancel = true; + pendingStart = false; +} + +void QBluetoothDeviceDiscoveryAgentPrivate::cancelDiscovery() +{ + Q_Q(QBluetoothDeviceDiscoveryAgent); + active = false; + pendingCancel = false; + emit q->canceled(); +} + +void QBluetoothDeviceDiscoveryAgentPrivate::restartDiscovery() +{ + pendingStart = false; + pendingCancel = false; + start(requestedMethods); +} + +void QBluetoothDeviceDiscoveryAgentPrivate::finishDiscovery(QBluetoothDeviceDiscoveryAgent::Error errorCode, const QString &errorText) +{ + active = false; + pendingStart = false; + pendingCancel = false; + lastError = errorCode; + errorString = errorText; + + Q_Q(QBluetoothDeviceDiscoveryAgent); + if (errorCode == QBluetoothDeviceDiscoveryAgent::NoError) + emit q->finished(); + else + emit q->error(lastError); +} + +void QBluetoothDeviceDiscoveryAgentPrivate::startLeDevicesDiscovery() +{ + const auto threadWorker = threadWorkerLE; + QMetaObject::invokeMethod(threadWorkerLE, [threadWorker]() + { + DiscoveryResult result; // Do not use hSearch here! + result.systemErrorCode = NO_ERROR; + result.devices = enumerateLeDevices(&result.systemErrorCode); + emit threadWorker->discoveryCompleted(QVariant::fromValue(result)); + }, Qt::QueuedConnection); +} + +void QBluetoothDeviceDiscoveryAgentPrivate::completeLeDevicesDiscovery(const QVariant &res) +{ + if (pendingCancel && !pendingStart) { + cancelDiscovery(); + } else if (pendingStart) { + restartDiscovery(); + } else { + const DiscoveryResult result = res.value<DiscoveryResult>(); + if (result.systemErrorCode == NO_ERROR || result.systemErrorCode == ERROR_NO_MORE_ITEMS) { + for (const QBluetoothDeviceInfo &device : result.devices) + processDiscoveredDevice(device); + + // We run classic search at second, as it is slow on windows. + if (requestedMethods & QBluetoothDeviceDiscoveryAgent::ClassicMethod) + startClassicDevicesDiscovery(); + else + finishDiscovery(QBluetoothDeviceDiscoveryAgent::NoError, qt_error_string(NO_ERROR)); + } else { + const QBluetoothDeviceDiscoveryAgent::Error error = (result.systemErrorCode == ERROR_INVALID_HANDLE) + ? QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError + : QBluetoothDeviceDiscoveryAgent::InputOutputError; + finishDiscovery(error, qt_error_string(result.systemErrorCode)); + } + } +} + +void QBluetoothDeviceDiscoveryAgentPrivate::startClassicDevicesDiscovery(Qt::HANDLE hSearch) +{ + const auto threadWorker = threadWorkerClassic; + QMetaObject::invokeMethod(threadWorker, [threadWorker, hSearch]() + { + DiscoveryResult result; + result.hSearch = hSearch; + result.systemErrorCode = NO_ERROR; + + const QBluetoothDeviceInfo device = hSearch + ? findNextClassicDevice(&result.systemErrorCode, result.hSearch) + : findFirstClassicDevice(&result.systemErrorCode, &result.hSearch); + + result.devices.append(device); + emit threadWorker->discoveryCompleted(QVariant::fromValue(result)); + }, Qt::QueuedConnection); +} + +void QBluetoothDeviceDiscoveryAgentPrivate::completeClassicDevicesDiscovery(const QVariant &res) +{ + const DiscoveryResult result = res.value<DiscoveryResult>(); + if (pendingCancel && !pendingStart) { + closeClassicSearch(result.hSearch); + cancelDiscovery(); + } else if (pendingStart) { + closeClassicSearch(result.hSearch); + restartDiscovery(); + } else { + if (result.systemErrorCode == ERROR_NO_MORE_ITEMS) { + closeClassicSearch(result.hSearch); + finishDiscovery(QBluetoothDeviceDiscoveryAgent::NoError, QString()); + } else if (result.systemErrorCode == NO_ERROR) { + if (result.hSearch) { + for (const QBluetoothDeviceInfo &device : result.devices) + processDiscoveredDevice(device); + + startClassicDevicesDiscovery(result.hSearch); + } else { + finishDiscovery(QBluetoothDeviceDiscoveryAgent::NoError, QString()); + } + } else { + closeClassicSearch(result.hSearch); + const QBluetoothDeviceDiscoveryAgent::Error error = (result.systemErrorCode == ERROR_INVALID_HANDLE) + ? QBluetoothDeviceDiscoveryAgent::InvalidBluetoothAdapterError + : QBluetoothDeviceDiscoveryAgent::InputOutputError; + finishDiscovery(error, qt_error_string(result.systemErrorCode)); + } + } +} + +void QBluetoothDeviceDiscoveryAgentPrivate::processDiscoveredDevice( + const QBluetoothDeviceInfo &foundDevice) +{ + Q_Q(QBluetoothDeviceDiscoveryAgent); + + auto equalAddress = [foundDevice](const QBluetoothDeviceInfo &targetDevice) { + return foundDevice.address() == targetDevice.address(); }; + auto end = discoveredDevices.end(); + auto deviceIt = std::find_if(discoveredDevices.begin(), end, equalAddress); + if (deviceIt == end) { + qCDebug(QT_BT_WINDOWS) << "Emit: " << foundDevice.address(); + discoveredDevices.append(foundDevice); + emit q->deviceDiscovered(foundDevice); + } else if (*deviceIt == foundDevice + || deviceIt->coreConfigurations() == foundDevice.coreConfigurations()) { + qCDebug(QT_BT_WINDOWS) << "Duplicate: " << foundDevice.address(); + } else { + // We assume that if the existing device it is low energy, it means that + // the found device should be as classic, because it is impossible to get + // same low energy device. + if (deviceIt->coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) + *deviceIt = foundDevice; + + // We assume that it is impossible to have multiple devices with same core + // configurations, which have one address. This possible only in case a device + // provided both low energy and classic features at the same time. + deviceIt->setCoreConfigurations(QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration); + deviceIt->setCached(foundDevice.isCached()); + + Q_Q(QBluetoothDeviceDiscoveryAgent); + qCDebug(QT_BT_WINDOWS) << "Updated: " << deviceIt->address(); + emit q->deviceDiscovered(*deviceIt); + } +} + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(DiscoveryResult) diff --git a/src/bluetooth/qbluetoothlocaldevice_p.h b/src/bluetooth/qbluetoothlocaldevice_p.h index 98c62151..e18169f9 100644 --- a/src/bluetooth/qbluetoothlocaldevice_p.h +++ b/src/bluetooth/qbluetoothlocaldevice_p.h @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2014 Denis Shienkov <denis.shienkov@gmail.com> ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. @@ -208,6 +209,29 @@ private: void initializeAdapter(); void initializeAdapterBluez5(); }; + +#elif defined(QT_WIN_BLUETOOTH) + +class QBluetoothLocalDevicePrivate : public QObject +{ + Q_OBJECT + Q_DECLARE_PUBLIC(QBluetoothLocalDevice) +public: + QBluetoothLocalDevicePrivate(QBluetoothLocalDevice *q, + const QBluetoothAddress &address = QBluetoothAddress()); + + ~QBluetoothLocalDevicePrivate(); + bool isValid() const; + void initialize(const QBluetoothAddress &address); + + static QList<QBluetoothHostInfo> localAdapters(); + + QBluetoothAddress deviceAddress; + QString deviceName; + bool deviceValid; +private: + QBluetoothLocalDevice *q_ptr; +}; #elif !defined(QT_OSX_BLUETOOTH) // winrt and dummy backend class QBluetoothLocalDevicePrivate : public QObject { diff --git a/src/bluetooth/qbluetoothlocaldevice_win.cpp b/src/bluetooth/qbluetoothlocaldevice_win.cpp new file mode 100644 index 00000000..a3f3c339 --- /dev/null +++ b/src/bluetooth/qbluetoothlocaldevice_win.cpp @@ -0,0 +1,262 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2014 Denis Shienkov <denis.shienkov@gmail.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 "qbluetoothlocaldevice.h" +#include "qbluetoothaddress.h" + +#include "qbluetoothlocaldevice_p.h" + +#include <QtCore/QLoggingCategory> + +#include <qt_windows.h> +#include <bluetoothapis.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS) + +QBluetoothLocalDevice::QBluetoothLocalDevice(QObject *parent) : + QObject(parent), + d_ptr(new QBluetoothLocalDevicePrivate(this)) +{ +} + +QBluetoothLocalDevice::QBluetoothLocalDevice( + const QBluetoothAddress &address, QObject *parent) : + QObject(parent), + d_ptr(new QBluetoothLocalDevicePrivate(this, address)) +{ +} + +QString QBluetoothLocalDevice::name() const +{ + Q_D(const QBluetoothLocalDevice); + return d->deviceName; +} + +QBluetoothAddress QBluetoothLocalDevice::address() const +{ + Q_D(const QBluetoothLocalDevice); + return d->deviceAddress; +} + +void QBluetoothLocalDevice::powerOn() +{ + if (hostMode() != HostPoweredOff) + return; + + setHostMode(HostConnectable); +} + +void QBluetoothLocalDevice::setHostMode( + QBluetoothLocalDevice::HostMode requestedMode) +{ + if (!isValid()) { + qCWarning(QT_BT_WINDOWS) << "The local device is not initialized correctly"; + return; + } + + if (requestedMode == HostDiscoverableLimitedInquiry) + requestedMode = HostDiscoverable; + + if (requestedMode == hostMode()) + return; + + if (requestedMode == QBluetoothLocalDevice::HostPoweredOff) { + if (::BluetoothIsDiscoverable(NULL) + && !::BluetoothEnableDiscovery(NULL, FALSE)) { + qCWarning(QT_BT_WINDOWS) << "Unable to disable the discoverable mode"; + emit error(QBluetoothLocalDevice::UnknownError); + return; + } + if (::BluetoothIsConnectable(NULL) + && !::BluetoothEnableIncomingConnections(NULL, FALSE)) { + qCWarning(QT_BT_WINDOWS) << "Unable to disable the connectable mode"; + emit error(QBluetoothLocalDevice::UnknownError); + return; + } + } else if (requestedMode == QBluetoothLocalDevice::HostConnectable) { + if (::BluetoothIsDiscoverable(NULL)) { + if (!::BluetoothEnableDiscovery(NULL, FALSE)) { + qCWarning(QT_BT_WINDOWS) << "Unable to disable the discoverable mode"; + emit error(QBluetoothLocalDevice::UnknownError); + return; + } + } else if (!::BluetoothEnableIncomingConnections(NULL, TRUE)) { + qCWarning(QT_BT_WINDOWS) << "Unable to enable the connectable mode"; + emit error(QBluetoothLocalDevice::UnknownError); + return; + } + } else if (requestedMode == QBluetoothLocalDevice::HostDiscoverable + || requestedMode == QBluetoothLocalDevice::HostDiscoverableLimitedInquiry) { + if (!::BluetoothIsConnectable(NULL) + && !::BluetoothEnableIncomingConnections(NULL, TRUE)) { + qCWarning(QT_BT_WINDOWS) << "Unable to enable the connectable mode"; + emit error(QBluetoothLocalDevice::UnknownError); + return; + } + if (!::BluetoothEnableDiscovery(NULL, TRUE)) { + qCWarning(QT_BT_WINDOWS) << "Unable to enable the discoverable mode"; + emit error(QBluetoothLocalDevice::UnknownError); + return; + } + } + + emit hostModeStateChanged(requestedMode); +} + +QBluetoothLocalDevice::HostMode QBluetoothLocalDevice::hostMode() const +{ + if (!isValid()) { + qCWarning(QT_BT_WINDOWS) << "The local device is not initialized correctly"; + return HostPoweredOff; + } + + if (::BluetoothIsDiscoverable(NULL)) + return HostDiscoverable; + if (::BluetoothIsConnectable(NULL)) + return HostConnectable; + return HostPoweredOff; +} + +QList<QBluetoothAddress> QBluetoothLocalDevice::connectedDevices() const +{ + return QList<QBluetoothAddress>(); +} + +QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices() +{ + return QBluetoothLocalDevicePrivate::localAdapters(); +} + +void QBluetoothLocalDevice::requestPairing(const QBluetoothAddress &address, Pairing pairing) +{ + Q_UNUSED(address); + Q_UNUSED(pairing); +} + +QBluetoothLocalDevice::Pairing QBluetoothLocalDevice::pairingStatus( + const QBluetoothAddress &address) const +{ + Q_UNUSED(address); + return Unpaired; +} + +void QBluetoothLocalDevice::pairingConfirmation(bool confirmation) +{ + Q_UNUSED(confirmation); +} + +QBluetoothLocalDevicePrivate::QBluetoothLocalDevicePrivate( + QBluetoothLocalDevice *q, const QBluetoothAddress &address) + : deviceValid(false) + , q_ptr(q) +{ + initialize(address); +} + +QBluetoothLocalDevicePrivate::~QBluetoothLocalDevicePrivate() +{ +} + +bool QBluetoothLocalDevicePrivate::isValid() const +{ + return deviceValid; +} + +void QBluetoothLocalDevicePrivate::initialize(const QBluetoothAddress &address) +{ + Q_Q(QBluetoothLocalDevice); + + const QList<QBluetoothHostInfo> adapterInfos = QBluetoothLocalDevicePrivate::localAdapters(); + for (const QBluetoothHostInfo &adapterInfo : adapterInfos) { + if (address == QBluetoothAddress() + || address == adapterInfo.address()) { + deviceAddress = adapterInfo.address(); + deviceName = adapterInfo.name(); + deviceValid = true; + return; + } + } + + qCWarning(QT_BT_WINDOWS) << "Unable to find classic local radio: " << address; + QMetaObject::invokeMethod(q, "error", Qt::QueuedConnection, + Q_ARG(QBluetoothLocalDevice::Error, + QBluetoothLocalDevice::UnknownError)); +} + +QList<QBluetoothHostInfo> QBluetoothLocalDevicePrivate::localAdapters() +{ + BLUETOOTH_FIND_RADIO_PARAMS params; + ::ZeroMemory(¶ms, sizeof(params)); + params.dwSize = sizeof(params); + + QList<QBluetoothHostInfo> foundAdapters; + + HANDLE hRadio = 0; + if (const HBLUETOOTH_RADIO_FIND hSearch = ::BluetoothFindFirstRadio(¶ms, &hRadio)) { + for (;;) { + BLUETOOTH_RADIO_INFO radio; + ::ZeroMemory(&radio, sizeof(radio)); + radio.dwSize = sizeof(radio); + + const DWORD retval = ::BluetoothGetRadioInfo(hRadio, &radio); + ::CloseHandle(hRadio); + + if (retval != ERROR_SUCCESS) + break; + + QBluetoothHostInfo adapterInfo; + adapterInfo.setAddress(QBluetoothAddress(radio.address.ullLong)); + adapterInfo.setName(QString::fromWCharArray(radio.szName)); + + foundAdapters << adapterInfo; + + if (!::BluetoothFindNextRadio(hSearch, &hRadio)) + break; + } + + ::BluetoothFindRadioClose(hSearch); + } + + return foundAdapters; +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothserver_win.cpp b/src/bluetooth/qbluetoothserver_win.cpp new file mode 100644 index 00000000..a9f8659e --- /dev/null +++ b/src/bluetooth/qbluetoothserver_win.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "qbluetoothserver.h" +#include "qbluetoothserver_p.h" +#include "qbluetoothsocket.h" + +QT_BEGIN_NAMESPACE + +QBluetoothServerPrivate::QBluetoothServerPrivate(QBluetoothServiceInfo::Protocol sType) + : maxPendingConnections(1), serverType(sType), m_lastError(QBluetoothServer::NoError) +{ + if (sType == QBluetoothServiceInfo::RfcommProtocol) + socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol); + else + socket = new QBluetoothSocket(QBluetoothServiceInfo::L2capProtocol); +} + +QBluetoothServerPrivate::~QBluetoothServerPrivate() +{ + delete socket; +} + +void QBluetoothServer::close() +{ +} + +bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port) +{ + Q_UNUSED(address); + Q_UNUSED(port); + Q_D(QBluetoothServer); + d->m_lastError = UnsupportedProtocolError; + emit error(d->m_lastError); + return false; +} + +void QBluetoothServer::setMaxPendingConnections(int numConnections) +{ + Q_UNUSED(numConnections); +} + +bool QBluetoothServer::hasPendingConnections() const +{ + return false; +} + +QBluetoothSocket *QBluetoothServer::nextPendingConnection() +{ + return 0; +} + +QBluetoothAddress QBluetoothServer::serverAddress() const +{ + return QBluetoothAddress(); +} + +quint16 QBluetoothServer::serverPort() const +{ + return 0; +} + +void QBluetoothServer::setSecurityFlags(QBluetooth::SecurityFlags security) +{ + Q_UNUSED(security); +} + +QBluetooth::SecurityFlags QBluetoothServer::securityFlags() const +{ + return QBluetooth::NoSecurity; +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothservicediscoveryagent.h b/src/bluetooth/qbluetoothservicediscoveryagent.h index 1db05f55..8533ab72 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent.h +++ b/src/bluetooth/qbluetoothservicediscoveryagent.h @@ -43,6 +43,7 @@ #include <QtBluetooth/qtbluetoothglobal.h> #include <QtCore/QObject> +#include <QtCore/QVariant> #include <QtBluetooth/QBluetoothServiceInfo> #include <QtBluetooth/QBluetoothUuid> @@ -110,6 +111,10 @@ Q_SIGNALS: private: QBluetoothServiceDiscoveryAgentPrivate *d_ptr; + +#ifdef QT_WIN_BLUETOOTH + Q_PRIVATE_SLOT(d_func(), void _q_nextSdpScan(QVariant input)) +#endif }; QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_p.h b/src/bluetooth/qbluetoothservicediscoveryagent_p.h index dbf8b1d4..4a522826 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_p.h +++ b/src/bluetooth/qbluetoothservicediscoveryagent_p.h @@ -72,7 +72,21 @@ class QXmlStreamReader; QT_END_NAMESPACE #endif -#ifdef QT_WINRT_BLUETOOTH +#ifdef QT_WIN_BLUETOOTH +#include <QtCore/QVariant> + +QT_BEGIN_NAMESPACE +class QThread; + +class ThreadWorkerFind : public QObject +{ + Q_OBJECT +signals: + void findFinished(QVariant result); +}; +QT_END_NAMESPACE + +#elif defined(QT_WINRT_BLUETOOTH) #include <QtCore/QPointer> #endif @@ -91,7 +105,7 @@ class QWinRTBluetoothServiceDiscoveryWorker; #endif class QBluetoothServiceDiscoveryAgentPrivate -#if defined QT_WINRT_BLUETOOTH +#if defined QT_WINRT_BLUETOOTH || defined QT_WIN_BLUETOOTH : public QObject { Q_OBJECT @@ -149,6 +163,9 @@ public: void _q_fetchUuidsTimeout(); void _q_hostModeStateChanged(QBluetoothLocalDevice::HostMode state); #endif +#ifdef QT_WIN_BLUETOOTH + void _q_nextSdpScan(const QVariant &input); +#endif private: void start(const QBluetoothAddress &address); @@ -200,6 +217,15 @@ private: QMap<QBluetoothAddress,QPair<QBluetoothDeviceInfo,QList<QBluetoothUuid> > > sdpCache; #endif +#ifdef QT_WIN_BLUETOOTH +private: + bool pendingStop; + bool pendingFinish; + + QThread *threadFind = nullptr; + ThreadWorkerFind *threadWorkerFind = nullptr; +#endif + #ifdef QT_WINRT_BLUETOOTH private slots: void processFoundService(quint64 deviceAddress, const QBluetoothServiceInfo &info); diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_win.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_win.cpp new file mode 100644 index 00000000..e76dd7b6 --- /dev/null +++ b/src/bluetooth/qbluetoothservicediscoveryagent_win.cpp @@ -0,0 +1,452 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "qbluetoothservicediscoveryagent.h" +#include "qbluetoothservicediscoveryagent_p.h" + +#include <QtCore/QByteArray> +#include <QtCore/QLibrary> +#include <QtCore/QLoggingCategory> +#include <QtCore/QThread> +#include <QtCore/QUrl> + +#include <initguid.h> +#include <winsock2.h> +#include <qt_windows.h> +#include <bluetoothapis.h> +#include <ws2bth.h> + +Q_GLOBAL_STATIC(QLibrary, bluetoothapis) + +#define DEFINEFUNC(ret, func, ...) \ + typedef ret (WINAPI *fp_##func)(__VA_ARGS__); \ + static fp_##func p##func; + +#define RESOLVEFUNC(func) \ + p##func = (fp_##func)resolveFunction(library, #func); \ + if (!p##func) \ + return false; + +DEFINEFUNC(DWORD, BluetoothSdpGetElementData, LPBYTE, ULONG, PSDP_ELEMENT_DATA) + +QT_BEGIN_NAMESPACE + +struct FindServiceResult { + QBluetoothServiceInfo info; + Qt::HANDLE hSearch; + int systemError; +}; + +Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS) + +static inline QFunctionPointer resolveFunction(QLibrary *library, const char *func) +{ + const QFunctionPointer symbolFunctionPointer = library->resolve(func); + if (!symbolFunctionPointer) + qWarning("Cannot resolve '%s' in '%s'.", func, qPrintable(library->fileName())); + return symbolFunctionPointer; +} + +static inline bool resolveFunctions(QLibrary *library) +{ + if (!library->isLoaded()) { + library->setFileName(QStringLiteral("bluetoothapis")); + if (!library->load()) { + qWarning("Unable to load '%s' library.", qPrintable(library->fileName())); + return false; + } + } + + RESOLVEFUNC(BluetoothSdpGetElementData) + + return true; +} + +static QList<QVariant> spdContainerToVariantList(LPBYTE containerStream, ULONG containerLength); + +static QVariant spdElementToVariant(const SDP_ELEMENT_DATA &element) +{ + QVariant variant; + + switch (element.type) { + case SDP_TYPE_UINT: { + switch (element.specificType) { + case SDP_ST_UINT128: + //Not supported!!! + break; + case SDP_ST_UINT64: + variant = QVariant::fromValue<quint64>(element.data.uint64); + break; + case SDP_ST_UINT32: + variant = QVariant::fromValue<quint32>(element.data.uint32); + break; + case SDP_ST_UINT16: + variant = QVariant::fromValue<quint16>(element.data.uint16); + break; + case SDP_ST_UINT8: + variant = QVariant::fromValue<quint8>(element.data.uint8); + break; + default: + break; + } + break; + } + case SDP_TYPE_INT: { + switch (element.specificType) { + case SDP_ST_INT128: { + //Not supported!!! + break; + } + case SDP_ST_INT64: + variant = QVariant::fromValue<qint64>(element.data.int64); + break; + case SDP_ST_INT32: + variant = QVariant::fromValue<qint32>(element.data.int32); + break; + case SDP_ST_INT16: + variant = QVariant::fromValue<qint16>(element.data.int16); + break; + case SDP_ST_INT8: + variant = QVariant::fromValue<qint8>(element.data.int8); + break; + default: + break; + } + break; + } + case SDP_TYPE_UUID: { + switch (element.specificType) { + case SDP_ST_UUID128: { + //Not sure if this will work, might be evil + Q_ASSERT(sizeof(element.data.uuid128) == sizeof(quint128)); + + quint128 uuid128; + memcpy(&uuid128, &(element.data.uuid128), sizeof(quint128)); + + variant = QVariant::fromValue(QBluetoothUuid(uuid128)); + } + case SDP_ST_UUID32: + variant = QVariant::fromValue(QBluetoothUuid(quint32(element.data.uuid32))); + case SDP_ST_UUID16: + variant = QVariant::fromValue(QBluetoothUuid(quint16(element.data.uuid16))); + default: + break; + } + break; + } + case SDP_TYPE_STRING: { + const QByteArray stringBuffer(reinterpret_cast<const char*>(element.data.string.value), element.data.string.length); + variant = QVariant::fromValue<QString>(QString::fromLocal8Bit(stringBuffer)); + break; + } + case SDP_TYPE_URL: { + const QString urlString = QString::fromLocal8Bit(reinterpret_cast<const char*>(element.data.url.value), + (int)element.data.url.length); + const QUrl url(urlString); + if (url.isValid()) + variant = QVariant::fromValue<QUrl>(url); + break; + } + case SDP_TYPE_SEQUENCE: { + const QList<QVariant> sequenceList = spdContainerToVariantList(element.data.sequence.value, + element.data.sequence.length); + const QBluetoothServiceInfo::Sequence sequence(sequenceList); + variant = QVariant::fromValue(sequence); + break; + } + case SDP_TYPE_ALTERNATIVE: { + const QList<QVariant> alternativeList = spdContainerToVariantList(element.data.alternative.value, + element.data.alternative.length); + const QBluetoothServiceInfo::Alternative alternative(alternativeList); + variant = QVariant::fromValue(alternative); + break; + } + case SDP_TYPE_BOOLEAN: + variant = QVariant::fromValue<bool>((bool)element.data.booleanVal); + break; + case SDP_TYPE_NIL: + break; + default: + break; + } + + return variant; +} + +static QList<QVariant> spdContainerToVariantList(LPBYTE containerStream, ULONG containerLength) +{ + HBLUETOOTH_CONTAINER_ELEMENT iter = NULL; + SDP_ELEMENT_DATA element; + + QList<QVariant> sequence; + + for (;;) { + const DWORD result = BluetoothSdpGetContainerElementData(containerStream, + containerLength, + &iter, + &element); + + if (result == ERROR_SUCCESS) { + const QVariant variant = spdElementToVariant(element); + sequence.append(variant); + } else if (result == ERROR_NO_MORE_ITEMS) { + break; + } else if (result == ERROR_INVALID_PARAMETER) { + break; + } + } + + return sequence; +} + +#if defined(Q_CC_MINGW) +# define SDP_CALLBACK +#else +# define SDP_CALLBACK QT_WIN_CALLBACK +#endif +static BOOL SDP_CALLBACK bluetoothSdpCallback(ULONG attributeId, LPBYTE valueStream, ULONG streamSize, LPVOID param) +{ + QBluetoothServiceInfo *result = static_cast<QBluetoothServiceInfo*>(param); + + SDP_ELEMENT_DATA element; + + if (pBluetoothSdpGetElementData(valueStream, streamSize, &element) == ERROR_SUCCESS) + switch (element.type) { + case SDP_TYPE_UINT: + case SDP_TYPE_INT: + case SDP_TYPE_UUID: + case SDP_TYPE_STRING: + case SDP_TYPE_URL: + case SDP_TYPE_BOOLEAN: + case SDP_TYPE_SEQUENCE: + case SDP_TYPE_ALTERNATIVE: { + const QVariant variant = spdElementToVariant(element); + result->setAttribute(attributeId, variant); + break; + } + case SDP_TYPE_NIL: + break; + default: + break; + } + + return true; +} + +enum { + WSAControlFlags = LUP_FLUSHCACHE + | LUP_RETURN_NAME + | LUP_RETURN_TYPE + | LUP_RETURN_ADDR + | LUP_RETURN_BLOB + | LUP_RETURN_COMMENT +}; + +static FindServiceResult findNextService(HANDLE hSearch) +{ + FindServiceResult result; + result.systemError = NO_ERROR; + result.hSearch = hSearch; + + QByteArray resultBuffer(2048, 0); + WSAQUERYSET *resultQuery = reinterpret_cast<WSAQUERYSET*>(resultBuffer.data()); + DWORD resultBufferSize = DWORD(resultBuffer.size()); + const int resultCode = WSALookupServiceNext(hSearch, + WSAControlFlags, + &resultBufferSize, + resultQuery); + + if (resultCode == SOCKET_ERROR) { + result.systemError = ::WSAGetLastError(); + if (result.systemError == WSA_E_NO_MORE) + WSALookupServiceEnd(hSearch); + return result; + } + + if (resultQuery->lpBlob + && BluetoothSdpEnumAttributes(resultQuery->lpBlob->pBlobData, + resultQuery->lpBlob->cbSize, + bluetoothSdpCallback, + &result.info)) { + return result; + } else { + result.systemError = GetLastError(); + } + return result; +} + +static FindServiceResult findFirstService(const QBluetoothAddress &address) +{ + //### should we try for 2.2 on all platforms ?? + WSAData wsadata; + FindServiceResult result; + result.hSearch = INVALID_HANDLE_VALUE; + + // IPv6 requires Winsock v2.0 or better. + if (WSAStartup(MAKEWORD(2, 0), &wsadata) != 0) { + result.systemError = ::WSAGetLastError(); + return result; + } + + const QString addressAsString = QStringLiteral("(%1)").arg(address.toString()); + + QVector<WCHAR> addressAsWChar(addressAsString.size()); + addressAsString.toWCharArray(addressAsWChar.data()); + + GUID protocol = L2CAP_PROTOCOL_UUID; //Search for L2CAP and RFCOMM services + + WSAQUERYSET serviceQuery = {0}; + serviceQuery.dwSize = sizeof(WSAQUERYSET); //As specified by the windows documentation + serviceQuery.lpServiceClassId = &protocol; //The protocal of the service what is being queried + serviceQuery.dwNameSpace = NS_BTH; //As specified by the windows documentation + serviceQuery.dwNumberOfCsAddrs = 0; //As specified by the windows documentation + serviceQuery.lpszContext = addressAsWChar.data(); //The remote address that query will run on + + HANDLE hSearch; + const int resultCode = WSALookupServiceBegin(&serviceQuery, + WSAControlFlags, + &hSearch); + if (resultCode == SOCKET_ERROR) { + result.systemError = ::WSAGetLastError(); + WSALookupServiceEnd(hSearch); + return result; + } + return findNextService(hSearch); +} + +QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate( + QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter) + : error(QBluetoothServiceDiscoveryAgent::NoError), + state(Inactive), + deviceDiscoveryAgent(0), + mode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery), + singleDevice(false), + pendingStop(false), + pendingFinish(false), + q_ptr(qp) +{ + Q_UNUSED(deviceAdapter); + + resolveFunctions(bluetoothapis()); + + threadFind = new QThread; + threadWorkerFind = new ThreadWorkerFind; + threadWorkerFind->moveToThread(threadFind); + connect(threadWorkerFind, &ThreadWorkerFind::findFinished, this, &QBluetoothServiceDiscoveryAgentPrivate::_q_nextSdpScan); + connect(threadFind, &QThread::finished, threadWorkerFind, &ThreadWorkerFind::deleteLater); + connect(threadFind, &QThread::finished, threadFind, &QThread::deleteLater); + threadFind->start(); +} + +QBluetoothServiceDiscoveryAgentPrivate::~QBluetoothServiceDiscoveryAgentPrivate() +{ + if (pendingFinish) { + stop(); + } + if (threadFind) + threadFind->quit(); +} + +void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &address) +{ + if (!pendingFinish) { + pendingFinish = true; + pendingStop = false; + + const auto threadWorker = threadWorkerFind; + QMetaObject::invokeMethod(threadWorkerFind, [threadWorker, address]() + { + FindServiceResult result = findFirstService(address); + emit threadWorker->findFinished(QVariant::fromValue(result)); + }, Qt::QueuedConnection); + } +} + +void QBluetoothServiceDiscoveryAgentPrivate::stop() +{ + pendingStop = true; +} + +void QBluetoothServiceDiscoveryAgentPrivate::_q_nextSdpScan(const QVariant &input) +{ + Q_Q(QBluetoothServiceDiscoveryAgent); + auto result = input.value<FindServiceResult>(); + + if (pendingStop) { + WSALookupServiceEnd(result.hSearch); + pendingStop = false; + pendingFinish = false; + emit q->canceled(); + } else { + if (result.systemError == WSA_E_NO_MORE) { + result.systemError = NO_ERROR; + } else if (result.systemError != NO_ERROR) { + if (result.hSearch != INVALID_HANDLE_VALUE) + WSALookupServiceEnd(result.hSearch); + error = (result.systemError == ERROR_INVALID_HANDLE) ? + QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError + : QBluetoothServiceDiscoveryAgent::InputOutputError; + errorString = qt_error_string(result.systemError); + qCWarning(QT_BT_WINDOWS) << errorString; + emit q->error(this->error); + } else { + result.info.setDevice(discoveredDevices.at(0)); + if (result.info.isValid()) { + if (!isDuplicatedService(result.info)) { + discoveredServices.append(result.info); + emit q->serviceDiscovered(result.info); + } + } + const auto threadWorker = threadWorkerFind; + const auto hSearch = result.hSearch; + QMetaObject::invokeMethod(threadWorkerFind, [threadWorker, hSearch]() + { + FindServiceResult result = findNextService(hSearch); + emit threadWorker->findFinished(QVariant::fromValue(result)); + }, Qt::QueuedConnection); + return; + } + pendingFinish = false; + _q_serviceDiscoveryFinished(); + } +} + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(FindServiceResult) diff --git a/src/bluetooth/qbluetoothserviceinfo_win.cpp b/src/bluetooth/qbluetoothserviceinfo_win.cpp new file mode 100644 index 00000000..6629e610 --- /dev/null +++ b/src/bluetooth/qbluetoothserviceinfo_win.cpp @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "qbluetoothserviceinfo.h" +#include "qbluetoothserviceinfo_p.h" + +QT_BEGIN_NAMESPACE + +QBluetoothServiceInfoPrivate::QBluetoothServiceInfoPrivate() +{ +} + +QBluetoothServiceInfoPrivate::~QBluetoothServiceInfoPrivate() +{ +} + +bool QBluetoothServiceInfoPrivate::isRegistered() const +{ + return false; +} + +bool QBluetoothServiceInfoPrivate::registerService(const QBluetoothAddress &localAdapter) +{ + Q_UNUSED(localAdapter); + return false; +} + +bool QBluetoothServiceInfoPrivate::unregisterService() +{ + return false; +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothsocket.cpp b/src/bluetooth/qbluetoothsocket.cpp index e6e1d955..54eb6024 100644 --- a/src/bluetooth/qbluetoothsocket.cpp +++ b/src/bluetooth/qbluetoothsocket.cpp @@ -47,6 +47,8 @@ #include "qbluetoothsocket_android_p.h" #elif defined(QT_WINRT_BLUETOOTH) #include "qbluetoothsocket_winrt_p.h" +#elif defined(QT_WIN_BLUETOOTH) +#include "qbluetoothsocket_win_p.h" #else #include "qbluetoothsocket_dummy_p.h" #endif @@ -267,6 +269,8 @@ static QBluetoothSocketBasePrivate *createSocketPrivate() return new QBluetoothSocketPrivateAndroid(); #elif defined(QT_WINRT_BLUETOOTH) return new QBluetoothSocketPrivateWinRT(); +#elif defined(QT_WIN_BLUETOOTH) + return new QBluetoothSocketPrivateWin(); #else return new QBluetoothSocketPrivateDummy(); #endif diff --git a/src/bluetooth/qbluetoothsocket.h b/src/bluetooth/qbluetoothsocket.h index d2535544..eefcd2ad 100644 --- a/src/bluetooth/qbluetoothsocket.h +++ b/src/bluetooth/qbluetoothsocket.h @@ -75,6 +75,7 @@ class Q_BLUETOOTH_EXPORT QBluetoothSocket : public QIODevice friend class QBluetoothSocketPrivateBluez; friend class QBluetoothSocketPrivateBluezDBus; friend class QBluetoothSocketPrivateDummy; + friend class QBluetoothSocketPrivateWin; friend class QBluetoothSocketPrivateWinRT; public: diff --git a/src/bluetooth/qbluetoothsocket_win.cpp b/src/bluetooth/qbluetoothsocket_win.cpp new file mode 100644 index 00000000..14c780d1 --- /dev/null +++ b/src/bluetooth/qbluetoothsocket_win.cpp @@ -0,0 +1,587 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 "qbluetoothsocket.h" +#include "qbluetoothsocket_win_p.h" + +#include <QtCore/qloggingcategory.h> + +#include <QtBluetooth/qbluetoothdeviceinfo.h> +#include <QtBluetooth/QBluetoothLocalDevice> +#include <QtCore/QSocketNotifier> + +#include <winsock2.h> +#include <ws2bth.h> +#include <bluetoothapis.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS) + +QBluetoothSocketPrivateWin::QBluetoothSocketPrivateWin() + : QBluetoothSocketBasePrivate() +{ +} + +QBluetoothSocketPrivateWin::~QBluetoothSocketPrivateWin() +{ + abort(); +} + +bool QBluetoothSocketPrivateWin::ensureNativeSocket(QBluetoothServiceInfo::Protocol type) +{ + Q_Q(QBluetoothSocket); + + if (socket != INVALID_SOCKET) { + if (socketType == type) + return true; + abort(); + } + + socketType = type; + + switch (type) { + case QBluetoothServiceInfo::RfcommProtocol: + socket = ::socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM); + break; + default: + socket = INVALID_SOCKET; + errorString = QBluetoothSocket::tr("Unsupported protocol. Win32 only supports RFCOMM sockets"); + q->setSocketError(QBluetoothSocket::UnsupportedProtocolError); + return false; + } + + if (socket == INVALID_SOCKET) { + const int error = ::WSAGetLastError(); + qCWarning(QT_BT_WINDOWS) << "Failed to create socket:" << error << qt_error_string(error); + errorString = QBluetoothSocket::tr("Failed to create socket"); + q->setSocketError(QBluetoothSocket::UnknownSocketError); + return false; + } + + if (!createNotifiers()) + return false; + + return true; +} + +void QBluetoothSocketPrivateWin::connectToServiceHelper(const QBluetoothAddress &address, quint16 port, QIODevice::OpenMode openMode) +{ + Q_Q(QBluetoothSocket); + + if (socket == INVALID_SOCKET && !ensureNativeSocket(socketType)) + return; + + if (!configureSecurity()) + return; + + SOCKADDR_BTH addr = {0}; + addr.addressFamily = AF_BTH; + addr.port = port; + addr.btAddr = address.toUInt64(); + + switch (socketType) { + case QBluetoothServiceInfo::RfcommProtocol: + addr.serviceClassId = RFCOMM_PROTOCOL_UUID; + break; + default: + errorString = QBluetoothSocket::tr("Socket type not handled: %1").arg(socketType); + q->setSocketError(QBluetoothSocket::UnsupportedProtocolError); + return; + } + + connectWriteNotifier->setEnabled(true); + readNotifier->setEnabled(true); + exceptNotifier->setEnabled(true); + + const int result = ::connect(socket, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)); + + const int error = ::WSAGetLastError(); + if (result != SOCKET_ERROR || error == WSAEWOULDBLOCK) { + q->setSocketState(QBluetoothSocket::ConnectingState); + q->setOpenMode(openMode); + } else { + errorString = qt_error_string(error); + q->setSocketError(QBluetoothSocket::UnknownSocketError); + } +} + +void QBluetoothSocketPrivateWin::connectToService( + const QBluetoothServiceInfo &service, QIODevice::OpenMode openMode) +{ + Q_Q(QBluetoothSocket); + + if (q->state() != QBluetoothSocket::UnconnectedState + && q->state() != QBluetoothSocket::ServiceLookupState) { + //qCWarning(QT_BT_WINDOWS) << "QBluetoothSocketPrivateWIN::connectToService called on busy socket"; + errorString = QBluetoothSocket::tr("Trying to connect while connection is in progress"); + q->setSocketError(QBluetoothSocket::OperationError); + return; + } + + // we are checking the service protocol and not socketType() + // socketType will change in ensureNativeSocket() + if (service.socketProtocol() == QBluetoothServiceInfo::UnknownProtocol) { + qCWarning(QT_BT_WINDOWS) << "QBluetoothSocket::connectToService cannot " + "connect with 'UnknownProtocol' (type provided by given service)"; + errorString = QBluetoothSocket::tr("Socket type not supported"); + q->setSocketError(QBluetoothSocket::UnsupportedProtocolError); + return; + } + + if (service.protocolServiceMultiplexer() > 0) { + Q_ASSERT(service.socketProtocol() == QBluetoothServiceInfo::L2capProtocol); + + if (!ensureNativeSocket(QBluetoothServiceInfo::L2capProtocol)) { + errorString = QBluetoothSocket::tr("Unknown socket error"); + q->setSocketError(QBluetoothSocket::UnknownSocketError); + return; + } + connectToServiceHelper(service.device().address(), service.protocolServiceMultiplexer(), + openMode); + } else if (service.serverChannel() > 0) { + Q_ASSERT(service.socketProtocol() == QBluetoothServiceInfo::RfcommProtocol); + + if (!ensureNativeSocket(QBluetoothServiceInfo::RfcommProtocol)) { + errorString = QBluetoothSocket::tr("Unknown socket error"); + q->setSocketError(QBluetoothSocket::UnknownSocketError); + return; + } + connectToServiceHelper(service.device().address(), 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)) { + qCWarning(QT_BT_WINDOWS) << "No port, no PSM, and no UUID provided. Unable to connect"; + return; + } + qCDebug(QT_BT_WINDOWS) << "Need a port/psm, doing discovery"; + q->doDeviceDiscovery(service, openMode); + } +} +void QBluetoothSocketPrivateWin::_q_writeNotify() +{ + Q_Q(QBluetoothSocket); + + if (state == QBluetoothSocket::ConnectingState) { + updateAddressesAndPorts(); + q->setSocketState(QBluetoothSocket::ConnectedState); + emit q->connected(); + connectWriteNotifier->setEnabled(false); + } else { + if (txBuffer.isEmpty()) { + connectWriteNotifier->setEnabled(false); + return; + } + + char buf[1024]; + const int size = txBuffer.read(&buf[0], sizeof(buf)); + const int writtenBytes = ::send(socket, &buf[0], size, 0); + if (writtenBytes == SOCKET_ERROR) { + // every other case returns error + const int error = ::WSAGetLastError(); + errorString = QBluetoothSocket::tr("Network Error: %1").arg(qt_error_string(error)); + q->setSocketError(QBluetoothSocket::NetworkError); + } else if (writtenBytes <= size) { + // add remainder back to buffer + char *remainder = &buf[writtenBytes]; + txBuffer.ungetBlock(remainder, size - writtenBytes); + if (writtenBytes > 0) + emit q->bytesWritten(writtenBytes); + } else { + errorString = QBluetoothSocket::tr("Logic error: more bytes sent than passed to ::send"); + q->setSocketError(QBluetoothSocket::NetworkError); + } + + if (!txBuffer.isEmpty()) { + connectWriteNotifier->setEnabled(true); + } else if (state == QBluetoothSocket::ClosingState) { + connectWriteNotifier->setEnabled(false); + this->close(); + } + } +} + +void QBluetoothSocketPrivateWin::_q_readNotify() +{ + Q_Q(QBluetoothSocket); + + char *writePointer = buffer.reserve(QPRIVATELINEARBUFFER_BUFFERSIZE); + const int bytesRead = ::recv(socket, writePointer, QPRIVATELINEARBUFFER_BUFFERSIZE, 0); + if (bytesRead == SOCKET_ERROR) { + const int error = ::WSAGetLastError(); + buffer.chop(QPRIVATELINEARBUFFER_BUFFERSIZE); + readNotifier->setEnabled(false); + connectWriteNotifier->setEnabled(false); + errorString = qt_error_string(error); + qCWarning(QT_BT_WINDOWS) << Q_FUNC_INFO << socket << "error:" << error << errorString; + switch (error) { + case WSAEHOSTDOWN: + q->setSocketError(QBluetoothSocket::HostNotFoundError); + break; + case WSAECONNRESET: + q->setSocketError(QBluetoothSocket::RemoteHostClosedError); + break; + default: + q->setSocketError(QBluetoothSocket::UnknownSocketError); + break; + } + + q->disconnectFromService(); + } else { + const int unusedBytes = QPRIVATELINEARBUFFER_BUFFERSIZE - bytesRead; + buffer.chop(unusedBytes); + if (bytesRead > 0) + emit q->readyRead(); + } +} + +void QBluetoothSocketPrivateWin::_q_exceptNotify() +{ + Q_Q(QBluetoothSocket); + + const int error = ::WSAGetLastError(); + errorString = qt_error_string(error); + q->setSocketError(QBluetoothSocket::UnknownSocketError); + + if (state == QBluetoothSocket::ConnectingState) { + abort(); + q->setSocketState(QBluetoothSocket::UnconnectedState); + emit q->disconnected(); + } +} + +void QBluetoothSocketPrivateWin::connectToService( + const QBluetoothAddress &address, const QBluetoothUuid &uuid, + QIODevice::OpenMode openMode) +{ + Q_Q(QBluetoothSocket); + + if (q->state() != QBluetoothSocket::UnconnectedState) { + qCWarning(QT_BT_WINDOWS) << "QBluetoothSocketPrivateWin::connectToService called on busy socket"; + errorString = QBluetoothSocket::tr("Trying to connect while connection is in progress"); + q->setSocketError(QBluetoothSocket::OperationError); + return; + } + + if (q->socketType() == QBluetoothServiceInfo::UnknownProtocol) { + qCWarning(QT_BT_WINDOWS) << "QBluetoothSocketPrivateWin::connectToService cannot " + "connect with 'UnknownProtocol' (type provided by given service)"; + errorString = QBluetoothSocket::tr("Socket type not supported"); + q->setSocketError(QBluetoothSocket::UnsupportedProtocolError); + return; + } + + QBluetoothServiceInfo service; + QBluetoothDeviceInfo device(address, QString(), QBluetoothDeviceInfo::MiscellaneousDevice); + service.setDevice(device); + service.setServiceUuid(uuid); + q->doDeviceDiscovery(service, openMode); +} + +void QBluetoothSocketPrivateWin::connectToService( + const QBluetoothAddress &address, quint16 port, QIODevice::OpenMode openMode) +{ + Q_Q(QBluetoothSocket); + + if (q->socketType() == QBluetoothServiceInfo::UnknownProtocol) { + qCWarning(QT_BT_WINDOWS) << "QBluetoothSocketPrivateWin::connectToService cannot " + "connect with 'UnknownProtocol' (type provided by given service)"; + errorString = QBluetoothSocket::tr("Socket type not supported"); + q->setSocketError(QBluetoothSocket::UnsupportedProtocolError); + return; + } + + if (q->state() != QBluetoothSocket::UnconnectedState) { + qCWarning(QT_BT_WINDOWS) << "QBluetoothSocketPrivateWin::connectToService called on busy socket"; + errorString = QBluetoothSocket::tr("Trying to connect while connection is in progress"); + q->setSocketError(QBluetoothSocket::OperationError); + return; + } + + q->setOpenMode(openMode); + connectToServiceHelper(address, port, openMode); +} + +void QBluetoothSocketPrivateWin::abort() +{ + delete readNotifier; + readNotifier = nullptr; + delete connectWriteNotifier; + connectWriteNotifier = nullptr; + delete exceptNotifier; + exceptNotifier = nullptr; + + m_localAddress.clear(); + m_localPort = 0; + m_peerAddress.clear(); + m_peerPort = 0; + + // We don't transition through Closing for abort, so + // we don't call disconnectFromService or QBluetoothSocket::close + ::closesocket(socket); + socket = INVALID_SOCKET; +} + +QString QBluetoothSocketPrivateWin::localName() const +{ + const QBluetoothLocalDevice device(m_localAddress); + return device.name(); +} + +QBluetoothAddress QBluetoothSocketPrivateWin::localAddress() const +{ + return m_localAddress; +} + +quint16 QBluetoothSocketPrivateWin::localPort() const +{ + return m_localPort; +} + +QString QBluetoothSocketPrivateWin::peerName() const +{ + if (socket == INVALID_SOCKET) + return {}; + BLUETOOTH_DEVICE_INFO bdi = {0}; + bdi.dwSize = sizeof(bdi); + bdi.Address.ullLong = m_peerAddress.toUInt64(); + const DWORD res = ::BluetoothGetDeviceInfo(nullptr, &bdi); + if (res == ERROR_SUCCESS) + return QString::fromWCharArray(&bdi.szName[0]); + qCWarning(QT_BT_WINDOWS) << "Error calling BluetoothGetDeviceInfo" << res << qt_error_string(res); + return {}; +} + +QBluetoothAddress QBluetoothSocketPrivateWin::peerAddress() const +{ + return m_peerAddress; +} + +quint16 QBluetoothSocketPrivateWin::peerPort() const +{ + return m_peerPort; +} + +qint64 QBluetoothSocketPrivateWin::writeData(const char *data, qint64 maxSize) +{ + Q_Q(QBluetoothSocket); + + if (state != QBluetoothSocket::ConnectedState) { + errorString = QBluetoothSocket::tr("Cannot write while not connected"); + q->setSocketError(QBluetoothSocket::OperationError); + return -1; + } + + if (q->openMode() & QIODevice::Unbuffered) { + const int bytesWritten = ::send(socket, data, maxSize, 0); + + if (bytesWritten == SOCKET_ERROR) { + const int error = ::WSAGetLastError(); + errorString = QBluetoothSocket::tr("Network Error: %1").arg(qt_error_string(error)); + q->setSocketError(QBluetoothSocket::NetworkError); + } + + if (bytesWritten > 0) + emit q->bytesWritten(bytesWritten); + + return bytesWritten; + } else { + + if (!connectWriteNotifier) + return -1; + + if (txBuffer.isEmpty()) + connectWriteNotifier->setEnabled(true); + + char *txbuf = txBuffer.reserve(maxSize); + ::memcpy(txbuf, data, maxSize); + + return maxSize; + } +} + +qint64 QBluetoothSocketPrivateWin::readData(char *data, qint64 maxSize) +{ + Q_Q(QBluetoothSocket); + + if (state != QBluetoothSocket::ConnectedState) { + errorString = QBluetoothSocket::tr("Cannot read while not connected"); + q->setSocketError(QBluetoothSocket::OperationError); + return -1; + } + + const int bytesRead = buffer.read(data, maxSize); + return bytesRead; +} + +void QBluetoothSocketPrivateWin::close() +{ + if (txBuffer.isEmpty()) + abort(); + else + connectWriteNotifier->setEnabled(true); +} + +bool QBluetoothSocketPrivateWin::setSocketDescriptor(int socketDescriptor, QBluetoothServiceInfo::Protocol socketType_, + QBluetoothSocket::SocketState socketState, QBluetoothSocket::OpenMode openMode) +{ + Q_Q(QBluetoothSocket); + + abort(); + + socketType = socketType_; + socket = socketDescriptor; + + if (!createNotifiers()) + return false; + updateAddressesAndPorts(); + q->setSocketState(socketState); + q->setOpenMode(openMode); + if (socketState == QBluetoothSocket::ConnectedState) { + connectWriteNotifier->setEnabled(true); + readNotifier->setEnabled(true); + exceptNotifier->setEnabled(true); + } + + return true; +} + +qint64 QBluetoothSocketPrivateWin::bytesAvailable() const +{ + return buffer.size(); +} + +bool QBluetoothSocketPrivateWin::canReadLine() const +{ + return buffer.canReadLine(); +} + +qint64 QBluetoothSocketPrivateWin::bytesToWrite() const +{ + return txBuffer.size(); +} + +bool QBluetoothSocketPrivateWin::createNotifiers() +{ + Q_Q(QBluetoothSocket); + + ULONG mode = 1; // 1 to enable non-blocking socket + const int result = ::ioctlsocket(socket, FIONBIO, &mode); + + if (result == SOCKET_ERROR) { + const int error = ::WSAGetLastError(); + errorString = qt_error_string(error); + q->setSocketError(QBluetoothSocket::UnknownSocketError); + qCWarning(QT_BT_WINDOWS) << "Error setting socket to non-blocking" << error << errorString; + abort(); + return false; + } + readNotifier = new QSocketNotifier(socket, QSocketNotifier::Read); + QObject::connect(readNotifier, &QSocketNotifier::activated, this, &QBluetoothSocketPrivateWin::_q_readNotify); + connectWriteNotifier = new QSocketNotifier(socket, QSocketNotifier::Write, q); + QObject::connect(connectWriteNotifier, &QSocketNotifier::activated, this, &QBluetoothSocketPrivateWin::_q_writeNotify); + exceptNotifier = new QSocketNotifier(socket, QSocketNotifier::Exception, q); + QObject::connect(exceptNotifier, &QSocketNotifier::activated, this, &QBluetoothSocketPrivateWin::_q_exceptNotify); + + connectWriteNotifier->setEnabled(false); + readNotifier->setEnabled(false); + exceptNotifier->setEnabled(false); + return true; +} + +void QBluetoothSocketPrivateWin::updateAddressesAndPorts() +{ + SOCKADDR_BTH localAddr = {0}; + int localAddrLength = sizeof(localAddr); + const int localResult = ::getsockname(socket, reinterpret_cast<sockaddr *>(&localAddr), &localAddrLength); + if (localResult != SOCKET_ERROR) { + m_localAddress = QBluetoothAddress(localAddr.btAddr); + m_localPort = localAddr.port; + } else { + const int error = ::WSAGetLastError(); + qCWarning(QT_BT_WINDOWS) << "Error getting local address and port" << error << qt_error_string(error); + errorString = QBluetoothSocket::tr("Cannot get socket's local address and port"); + } + + SOCKADDR_BTH peerAddr = {0}; + int peerAddrLength = sizeof(peerAddr); + const int peerResult = ::getpeername(socket, reinterpret_cast<sockaddr *>(&peerAddr), &peerAddrLength); + if (peerResult != SOCKET_ERROR) { + m_peerAddress = QBluetoothAddress(peerAddr.btAddr); + m_peerPort = peerAddr.port; + } else { + const int error = ::WSAGetLastError(); + qCWarning(QT_BT_WINDOWS) << "Error getting peer address and port" << error << qt_error_string(error); + errorString = QBluetoothSocket::tr("Cannot get socket's peer address and port"); + } +} + +bool QBluetoothSocketPrivateWin::configureSecurity() +{ + Q_Q(QBluetoothSocket); + + if (secFlags & QBluetooth::Authorization) { + ULONG authenticate = TRUE; + const int result = ::setsockopt(socket, SOL_RFCOMM, SO_BTH_AUTHENTICATE, reinterpret_cast<const char*>(&authenticate), sizeof(authenticate)); + if (result == SOCKET_ERROR) { + const int error = ::WSAGetLastError(); + qCWarning(QT_BT_WINDOWS) << "Failed to set socket option, closing socket for safety" << error; + qCWarning(QT_BT_WINDOWS) << "Error: " << qt_error_string(error); + errorString = QBluetoothSocket::tr("Cannot set connection security level"); + q->setSocketError(QBluetoothSocket::UnknownSocketError); + return false; + } + } + + if (secFlags & QBluetooth::Encryption) { + ULONG encrypt = TRUE; + const int result = ::setsockopt(socket, SOL_RFCOMM, SO_BTH_ENCRYPT, reinterpret_cast<const char*>(&encrypt), sizeof(encrypt)); + if (result == SOCKET_ERROR) { + const int error = ::WSAGetLastError(); + qCWarning(QT_BT_WINDOWS) << "Failed to set socket option, closing socket for safety" << error; + qCWarning(QT_BT_WINDOWS) << "Error: " << qt_error_string(error); + errorString = QBluetoothSocket::tr("Cannot set connection security level"); + q->setSocketError(QBluetoothSocket::UnknownSocketError); + return false; + } + } + return true; +} +QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothsocket_win_p.h b/src/bluetooth/qbluetoothsocket_win_p.h new file mode 100644 index 00000000..fe0fc99f --- /dev/null +++ b/src/bluetooth/qbluetoothsocket_win_p.h @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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 QBLUETOOTHSOCKET_WIN_H +#define QBLUETOOTHSOCKET_WIN_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 "qbluetoothsocket.h" +#include "qbluetoothsocketbase_p.h" +#include <QtGlobal> + +QT_BEGIN_NAMESPACE + +class QBluetoothSocketPrivateWin final : public QBluetoothSocketBasePrivate +{ + Q_OBJECT + friend class QBluetoothServerPrivate; + +public: + QBluetoothSocketPrivateWin(); + ~QBluetoothSocketPrivateWin() 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; + + 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 abort() override; + void close() override; + + qint64 writeData(const char *data, qint64 maxSize) override; + qint64 readData(char *data, qint64 maxSize) override; + + bool setSocketDescriptor(int socketDescriptor, QBluetoothServiceInfo::Protocol socketType, + QBluetoothSocket::SocketState socketState = QBluetoothSocket::ConnectedState, + QBluetoothSocket::OpenMode openMode = QBluetoothSocket::ReadWrite) override; + + qint64 bytesAvailable() const override; + bool canReadLine() const override; + qint64 bytesToWrite() const override; + +private slots: + void _q_readNotify(); + void _q_writeNotify(); + void _q_exceptNotify(); + +private: + bool createNotifiers(); + void updateAddressesAndPorts(); + bool configureSecurity(); + + QSocketNotifier *exceptNotifier = nullptr; + QBluetoothAddress m_localAddress; + quint16 m_localPort = 0; + QBluetoothAddress m_peerAddress; + quint16 m_peerPort = 0; +}; + +QT_END_NAMESPACE + +#endif // QBLUETOOTHSOCKET_WIN_H diff --git a/src/bluetooth/qbluetoothutils_win.cpp b/src/bluetooth/qbluetoothutils_win.cpp index fa3127cb..a5151d82 100644 --- a/src/bluetooth/qbluetoothutils_win.cpp +++ b/src/bluetooth/qbluetoothutils_win.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtBluetooth module of the Qt Toolkit. diff --git a/src/bluetooth/qlowenergycharacteristic.h b/src/bluetooth/qlowenergycharacteristic.h index 9b27d621..bb6487c4 100644 --- a/src/bluetooth/qlowenergycharacteristic.h +++ b/src/bluetooth/qlowenergycharacteristic.h @@ -102,6 +102,7 @@ protected: friend class QLowEnergyControllerPrivateBluezDBus; friend class QLowEnergyControllerPrivateCommon; friend class QLowEnergyControllerPrivateOSX; + friend class QLowEnergyControllerPrivateWin32; friend class QLowEnergyControllerPrivateWinRT; friend class QLowEnergyControllerPrivateWinRTNew; QLowEnergyCharacteristicPrivate *data = nullptr; diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp index 8fc044fb..c3742b76 100644 --- a/src/bluetooth/qlowenergycontroller.cpp +++ b/src/bluetooth/qlowenergycontroller.cpp @@ -60,6 +60,8 @@ #if QT_CONFIG(winrt_btle_no_pairing) #include "qlowenergycontroller_winrt_new_p.h" #endif +#elif defined(QT_WIN_BLUETOOTH) +#include "qlowenergycontroller_win_p.h" #else #include "qlowenergycontroller_p.h" #endif @@ -321,6 +323,8 @@ static QLowEnergyControllerPrivate *privateController(QLowEnergyController::Role qCDebug(QT_BT_WINRT) << "Using pre 15063 low energy controller"; return new QLowEnergyControllerPrivateWinRT(); #endif +#elif defined(QT_WIN_BLUETOOTH) + return new QLowEnergyControllerPrivateWin32(); #else Q_UNUSED(role); return new QLowEnergyControllerPrivateCommon(); diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h index 54a49b53..fe1da852 100644 --- a/src/bluetooth/qlowenergycontroller_p.h +++ b/src/bluetooth/qlowenergycontroller_p.h @@ -104,6 +104,7 @@ public: const QByteArray &newValue) override; void addToGenericAttributeList(const QLowEnergyServiceData &service, + QLowEnergyHandle startHandle) override; }; diff --git a/src/bluetooth/qlowenergycontroller_win.cpp b/src/bluetooth/qlowenergycontroller_win.cpp new file mode 100644 index 00000000..cc848d1e --- /dev/null +++ b/src/bluetooth/qlowenergycontroller_win.cpp @@ -0,0 +1,1346 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2014 Denis Shienkov <denis.shienkov@gmail.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_win_p.h" +#include "qbluetoothdevicediscoveryagent_p.h" + +#include <QtCore/QLoggingCategory> +#include <QtCore/QIODevice> // for open modes +#include <QtCore/QEvent> +#include <QtCore/QMutex> +#include <QtCore/QThread> +#include <QtCore/QDataStream> +#include <QtCore/QCoreApplication> + +#include <algorithm> // for std::max + +#include <setupapi.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS) + +Q_GLOBAL_STATIC(QLibrary, bluetoothapis) + +Q_GLOBAL_STATIC(QVector<QLowEnergyControllerPrivateWin32 *>, qControllers) +static QMutex controllersGuard(QMutex::NonRecursive); + +const QEvent::Type CharactericticValueEventType = static_cast<QEvent::Type>(QEvent::User + 1); + +class CharactericticValueEvent : public QEvent +{ +public: + explicit CharactericticValueEvent(const PBLUETOOTH_GATT_VALUE_CHANGED_EVENT gattValueChangedEvent) + : QEvent(CharactericticValueEventType) + , m_handle(0) + { + if (!gattValueChangedEvent || gattValueChangedEvent->CharacteristicValueDataSize == 0) + return; + + m_handle = gattValueChangedEvent->ChangedAttributeHandle; + + const PBTH_LE_GATT_CHARACTERISTIC_VALUE gattValue = gattValueChangedEvent->CharacteristicValue; + if (!gattValue) + return; + + m_value = QByteArray(reinterpret_cast<const char *>(&gattValue->Data[0]), + gattValue->DataSize); + } + + QByteArray m_value; + QLowEnergyHandle m_handle; +}; + +// Bit masks of ClientCharacteristicConfiguration value, see btle spec. +namespace ClientCharacteristicConfigurationValue { +enum { UseNotifications = 0x1, UseIndications = 0x2 }; +} + +static bool gattFunctionsResolved = false; + +static QBluetoothAddress getDeviceAddress(const QString &servicePath) +{ + const int firstbound = servicePath.lastIndexOf(QStringLiteral("_")); + const int lastbound = servicePath.indexOf(QLatin1Char('#'), firstbound); + const QString hex = servicePath.mid(firstbound + 1, lastbound - firstbound - 1); + bool ok = false; + return QBluetoothAddress(hex.toULongLong(&ok, 16)); +} + +static QString getServiceSystemPath(const QBluetoothAddress &deviceAddress, + const QBluetoothUuid &serviceUuid, int *systemErrorCode) +{ + const HDEVINFO deviceInfoSet = ::SetupDiGetClassDevs( + reinterpret_cast<const GUID *>(&serviceUuid), + NULL, + 0, + DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + if (deviceInfoSet == INVALID_HANDLE_VALUE) { + *systemErrorCode = ::GetLastError(); + return QString(); + } + + QString foundSystemPath; + DWORD index = 0; + + for (;;) { + SP_DEVICE_INTERFACE_DATA deviceInterfaceData; + ::ZeroMemory(&deviceInterfaceData, sizeof(deviceInterfaceData)); + deviceInterfaceData.cbSize = sizeof(deviceInterfaceData); + + if (!::SetupDiEnumDeviceInterfaces( + deviceInfoSet, + NULL, + reinterpret_cast<const GUID *>(&serviceUuid), + index++, + &deviceInterfaceData)) { + *systemErrorCode = ::GetLastError(); + break; + } + + DWORD deviceInterfaceDetailDataSize = 0; + if (!::SetupDiGetDeviceInterfaceDetail( + deviceInfoSet, + &deviceInterfaceData, + NULL, + deviceInterfaceDetailDataSize, + &deviceInterfaceDetailDataSize, + NULL)) { + const DWORD error = ::GetLastError(); + if (error != ERROR_INSUFFICIENT_BUFFER) { + *systemErrorCode = error; + break; + } + } + + SP_DEVINFO_DATA deviceInfoData; + ::ZeroMemory(&deviceInfoData, sizeof(deviceInfoData)); + deviceInfoData.cbSize = sizeof(deviceInfoData); + + QByteArray deviceInterfaceDetailDataBuffer( + deviceInterfaceDetailDataSize, 0); + + PSP_INTERFACE_DEVICE_DETAIL_DATA deviceInterfaceDetailData = + reinterpret_cast<PSP_INTERFACE_DEVICE_DETAIL_DATA> + (deviceInterfaceDetailDataBuffer.data()); + + deviceInterfaceDetailData->cbSize = + sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA); + + if (!::SetupDiGetDeviceInterfaceDetail( + deviceInfoSet, + &deviceInterfaceData, + deviceInterfaceDetailData, + deviceInterfaceDetailDataBuffer.size(), + &deviceInterfaceDetailDataSize, + &deviceInfoData)) { + *systemErrorCode = ::GetLastError(); + break; + } + + // We need to check on required device address which contains in a + // system path. As it is not enough to use only service UUID for this. + const auto candidateSystemPath = QString::fromWCharArray(deviceInterfaceDetailData->DevicePath); + const auto candidateDeviceAddress = getDeviceAddress(candidateSystemPath); + if (candidateDeviceAddress == deviceAddress) { + foundSystemPath = candidateSystemPath; + *systemErrorCode = NO_ERROR; + break; + } + } + + ::SetupDiDestroyDeviceInfoList(deviceInfoSet); + return foundSystemPath; +} + +static HANDLE openSystemDevice( + const QString &systemPath, QIODevice::OpenMode openMode, int *systemErrorCode) +{ + DWORD desiredAccess = 0; + DWORD shareMode = 0; + + if (openMode & QIODevice::ReadOnly) { + desiredAccess |= GENERIC_READ; + shareMode |= FILE_SHARE_READ; + } + + if (openMode & QIODevice::WriteOnly) { + desiredAccess |= GENERIC_WRITE; + shareMode |= FILE_SHARE_WRITE; + } + + const HANDLE hDevice = ::CreateFile( + reinterpret_cast<const wchar_t *>(systemPath.utf16()), + desiredAccess, + shareMode, + NULL, + OPEN_EXISTING, + 0, + NULL); + + *systemErrorCode = (INVALID_HANDLE_VALUE == hDevice) + ? ::GetLastError() : NO_ERROR; + return hDevice; +} + +static HANDLE openSystemService(const QBluetoothAddress &deviceAddress, + const QBluetoothUuid &service, QIODevice::OpenMode openMode, int *systemErrorCode) +{ + const QString serviceSystemPath = getServiceSystemPath( + deviceAddress, service, systemErrorCode); + + if (*systemErrorCode != NO_ERROR) + return INVALID_HANDLE_VALUE; + + const HANDLE hService = openSystemDevice( + serviceSystemPath, openMode, systemErrorCode); + + if (*systemErrorCode != NO_ERROR) + return INVALID_HANDLE_VALUE; + + return hService; +} + +static void closeSystemDevice(HANDLE hDevice) +{ + if (hDevice && hDevice != INVALID_HANDLE_VALUE) + ::CloseHandle(hDevice); +} + +static QVector<BTH_LE_GATT_SERVICE> enumeratePrimaryGattServices( + HANDLE hDevice, int *systemErrorCode) +{ + if (!gattFunctionsResolved) { + *systemErrorCode = ERROR_NOT_SUPPORTED; + return QVector<BTH_LE_GATT_SERVICE>(); + } + + QVector<BTH_LE_GATT_SERVICE> foundServices; + USHORT servicesCount = 0; + for (;;) { + const HRESULT hr = ::BluetoothGATTGetServices( + hDevice, + servicesCount, + foundServices.isEmpty() ? NULL : &foundServices[0], + &servicesCount, + BLUETOOTH_GATT_FLAG_NONE); + + if (SUCCEEDED(hr)) { + *systemErrorCode = NO_ERROR; + return foundServices; + } else { + const DWORD error = WIN32_FROM_HRESULT(hr); + if (error == ERROR_MORE_DATA) { + foundServices.resize(servicesCount); + } else { + *systemErrorCode = error; + return QVector<BTH_LE_GATT_SERVICE>(); + } + } + } +} + +static QVector<BTH_LE_GATT_CHARACTERISTIC> enumerateGattCharacteristics( + HANDLE hService, PBTH_LE_GATT_SERVICE gattService, int *systemErrorCode) +{ + if (!gattFunctionsResolved) { + *systemErrorCode = ERROR_NOT_SUPPORTED; + return QVector<BTH_LE_GATT_CHARACTERISTIC>(); + } + + QVector<BTH_LE_GATT_CHARACTERISTIC> foundCharacteristics; + USHORT characteristicsCount = 0; + for (;;) { + const HRESULT hr = ::BluetoothGATTGetCharacteristics( + hService, + gattService, + characteristicsCount, + foundCharacteristics.isEmpty() ? NULL : &foundCharacteristics[0], + &characteristicsCount, + BLUETOOTH_GATT_FLAG_NONE); + + if (SUCCEEDED(hr)) { + *systemErrorCode = NO_ERROR; + return foundCharacteristics; + } else { + const DWORD error = WIN32_FROM_HRESULT(hr); + if (error == ERROR_MORE_DATA) { + foundCharacteristics.resize(characteristicsCount); + } else { + *systemErrorCode = error; + return QVector<BTH_LE_GATT_CHARACTERISTIC>(); + } + } + } +} + +static QByteArray getGattCharacteristicValue( + HANDLE hService, PBTH_LE_GATT_CHARACTERISTIC gattCharacteristic, int *systemErrorCode) +{ + if (!gattFunctionsResolved) { + *systemErrorCode = ERROR_NOT_SUPPORTED; + return QByteArray(); + } + + QByteArray valueBuffer; + USHORT valueBufferSize = 0; + for (;;) { + const auto valuePtr = valueBuffer.isEmpty() + ? NULL + : reinterpret_cast<PBTH_LE_GATT_CHARACTERISTIC_VALUE>(valueBuffer.data()); + + const HRESULT hr = ::BluetoothGATTGetCharacteristicValue( + hService, + gattCharacteristic, + valueBufferSize, + valuePtr, + &valueBufferSize, + BLUETOOTH_GATT_FLAG_NONE); + + if (SUCCEEDED(hr)) { + *systemErrorCode = NO_ERROR; + return QByteArray(reinterpret_cast<const char *>(&valuePtr->Data[0]), + valuePtr->DataSize); + } else { + const DWORD error = WIN32_FROM_HRESULT(hr); + if (error == ERROR_MORE_DATA) { + valueBuffer.resize(valueBufferSize); + } else { + *systemErrorCode = error; + return QByteArray(); + } + } + } +} + +static void setGattCharacteristicValue( + HANDLE hService, PBTH_LE_GATT_CHARACTERISTIC gattCharacteristic, + const QByteArray &value, DWORD flags, int *systemErrorCode) +{ + if (!gattFunctionsResolved) { + *systemErrorCode = ERROR_NOT_SUPPORTED; + return; + } + + QByteArray valueBuffer; + QDataStream out(&valueBuffer, QIODevice::WriteOnly); + ULONG dataSize = value.size(); + out.writeRawData(reinterpret_cast<const char *>(&dataSize), sizeof(dataSize)); + out.writeRawData(value.constData(), value.size()); + + BTH_LE_GATT_RELIABLE_WRITE_CONTEXT reliableWriteContext = 0; + + const HRESULT hr = ::BluetoothGATTSetCharacteristicValue( + hService, + gattCharacteristic, + reinterpret_cast<PBTH_LE_GATT_CHARACTERISTIC_VALUE>(valueBuffer.data()), + reliableWriteContext, + flags); + + if (SUCCEEDED(hr)) + *systemErrorCode = NO_ERROR; + else + *systemErrorCode = WIN32_FROM_HRESULT(hr); +} + +static QVector<BTH_LE_GATT_DESCRIPTOR> enumerateGattDescriptors( + HANDLE hService, PBTH_LE_GATT_CHARACTERISTIC gattCharacteristic, int *systemErrorCode) +{ + if (!gattFunctionsResolved) { + *systemErrorCode = ERROR_NOT_SUPPORTED; + return QVector<BTH_LE_GATT_DESCRIPTOR>(); + } + + QVector<BTH_LE_GATT_DESCRIPTOR> foundDescriptors; + USHORT descriptorsCount = 0; + for (;;) { + const HRESULT hr = ::BluetoothGATTGetDescriptors( + hService, + gattCharacteristic, + descriptorsCount, + foundDescriptors.isEmpty() ? NULL : &foundDescriptors[0], + &descriptorsCount, + BLUETOOTH_GATT_FLAG_NONE); + + if (SUCCEEDED(hr)) { + *systemErrorCode = NO_ERROR; + return foundDescriptors; + } else { + const DWORD error = WIN32_FROM_HRESULT(hr); + if (error == ERROR_MORE_DATA) { + foundDescriptors.resize(descriptorsCount); + } else { + *systemErrorCode = error; + return QVector<BTH_LE_GATT_DESCRIPTOR>(); + } + } + } +} + +static QByteArray getGattDescriptorValue( + HANDLE hService, PBTH_LE_GATT_DESCRIPTOR gattDescriptor, int *systemErrorCode) +{ + if (!gattFunctionsResolved) { + *systemErrorCode = ERROR_NOT_SUPPORTED; + return QByteArray(); + } + + QByteArray valueBuffer; + USHORT valueBufferSize = 0; + for (;;) { + const auto valuePtr = valueBuffer.isEmpty() + ? NULL + : reinterpret_cast<PBTH_LE_GATT_DESCRIPTOR_VALUE>(valueBuffer.data()); + + const HRESULT hr = ::BluetoothGATTGetDescriptorValue( + hService, + gattDescriptor, + valueBufferSize, + valuePtr, + &valueBufferSize, + BLUETOOTH_GATT_FLAG_NONE); + + if (SUCCEEDED(hr)) { + *systemErrorCode = NO_ERROR; + return QByteArray(reinterpret_cast<const char *>(&valuePtr->Data[0]), + valuePtr->DataSize); + } else { + const DWORD error = WIN32_FROM_HRESULT(hr); + if (error == ERROR_MORE_DATA) { + valueBuffer.resize(valueBufferSize); + } else { + *systemErrorCode = error; + return QByteArray(); + } + } + } +} + +static void setGattDescriptorValue( + HANDLE hService, PBTH_LE_GATT_DESCRIPTOR gattDescriptor, + QByteArray value, int *systemErrorCode) +{ + if (!gattFunctionsResolved) { + *systemErrorCode = ERROR_NOT_SUPPORTED; + return; + } + + const int requiredValueBufferSize = sizeof(BTH_LE_GATT_DESCRIPTOR_VALUE) + + value.size(); + + QByteArray valueBuffer(requiredValueBufferSize, 0); + + PBTH_LE_GATT_DESCRIPTOR_VALUE gattValue = reinterpret_cast< + PBTH_LE_GATT_DESCRIPTOR_VALUE>(valueBuffer.data()); + + gattValue->DescriptorType = gattDescriptor->DescriptorType; + + if (gattValue->DescriptorType == ClientCharacteristicConfiguration) { + QDataStream in(value); + quint8 u; + in >> u; + + // We need to setup appropriate fields that allow to subscribe for events. + gattValue->ClientCharacteristicConfiguration.IsSubscribeToNotification = + bool(u & ClientCharacteristicConfigurationValue::UseNotifications); + gattValue->ClientCharacteristicConfiguration.IsSubscribeToIndication = + bool(u & ClientCharacteristicConfigurationValue::UseIndications); + } + + gattValue->DataSize = ULONG(value.size()); + ::memcpy(gattValue->Data, value.constData(), value.size()); + + const HRESULT hr = ::BluetoothGATTSetDescriptorValue( + hService, + gattDescriptor, + gattValue, + BLUETOOTH_GATT_FLAG_NONE); + + if (SUCCEEDED(hr)) + *systemErrorCode = NO_ERROR; + else + *systemErrorCode = WIN32_FROM_HRESULT(hr); +} + +static void WINAPI eventChangedCallbackEntry( + BTH_LE_GATT_EVENT_TYPE eventType, PVOID eventOutParameter, PVOID context) +{ + if ((eventType != CharacteristicValueChangedEvent) || !eventOutParameter || !context) + return; + + QMutexLocker locker(&controllersGuard); + const auto target = static_cast<QLowEnergyControllerPrivateWin32 *>(context); + if (!qControllers->contains(target)) + return; + + CharactericticValueEvent *e = new CharactericticValueEvent( + reinterpret_cast<const PBLUETOOTH_GATT_VALUE_CHANGED_EVENT>(eventOutParameter)); + + QCoreApplication::postEvent(target, e); +} + +static HANDLE registerEvent( + HANDLE hService, BTH_LE_GATT_CHARACTERISTIC gattCharacteristic, + PVOID context, int *systemErrorCode) +{ + if (!gattFunctionsResolved) { + *systemErrorCode = ERROR_NOT_SUPPORTED; + return INVALID_HANDLE_VALUE; + } + + HANDLE hEvent = INVALID_HANDLE_VALUE; + + BLUETOOTH_GATT_VALUE_CHANGED_EVENT_REGISTRATION registration; + ::ZeroMemory(®istration, sizeof(registration)); + registration.NumCharacteristics = 1; + registration.Characteristics[0] = gattCharacteristic; + + const HRESULT hr = ::BluetoothGATTRegisterEvent( + hService, + CharacteristicValueChangedEvent, + ®istration, + eventChangedCallbackEntry, + context, + &hEvent, + BLUETOOTH_GATT_FLAG_NONE); + + if (SUCCEEDED(hr)) + *systemErrorCode = NO_ERROR; + else + *systemErrorCode = WIN32_FROM_HRESULT(hr); + + return hEvent; +} + +static void unregisterEvent(HANDLE hEvent, int *systemErrorCode) +{ + if (!gattFunctionsResolved) { + *systemErrorCode = ERROR_NOT_SUPPORTED; + return; + } + + const HRESULT hr = ::BluetoothGATTUnregisterEvent( + hEvent, + BLUETOOTH_GATT_FLAG_NONE); + + if (SUCCEEDED(hr)) + *systemErrorCode = NO_ERROR; + else + *systemErrorCode = WIN32_FROM_HRESULT(hr); +} + +static QBluetoothUuid qtBluetoothUuidFromNativeLeUuid(const BTH_LE_UUID &uuid) +{ + return uuid.IsShortUuid ? QBluetoothUuid(uuid.Value.ShortUuid) + : QBluetoothUuid(uuid.Value.LongUuid); +} + +static BTH_LE_UUID nativeLeUuidFromQtBluetoothUuid(const QBluetoothUuid &uuid) +{ + BTH_LE_UUID gattUuid; + ::ZeroMemory(&gattUuid, sizeof(gattUuid)); + if (uuid.minimumSize() == 2) { + gattUuid.IsShortUuid = TRUE; + gattUuid.Value.ShortUuid = uuid.data1; // other fields should be empty! + } else { + gattUuid.Value.LongUuid = uuid; + } + return gattUuid; +} + +static BTH_LE_GATT_CHARACTERISTIC recoverNativeLeGattCharacteristic( + QLowEnergyHandle serviceHandle, QLowEnergyHandle characteristicHandle, + const QLowEnergyServicePrivate::CharData &characteristicData) +{ + BTH_LE_GATT_CHARACTERISTIC gattCharacteristic; + + gattCharacteristic.ServiceHandle = serviceHandle; + gattCharacteristic.AttributeHandle = characteristicHandle; + gattCharacteristic.CharacteristicValueHandle = characteristicData.valueHandle; + + gattCharacteristic.CharacteristicUuid = nativeLeUuidFromQtBluetoothUuid( + characteristicData.uuid); + + gattCharacteristic.HasExtendedProperties = bool(characteristicData.properties + & QLowEnergyCharacteristic::ExtendedProperty); + gattCharacteristic.IsBroadcastable = bool(characteristicData.properties + & QLowEnergyCharacteristic::Broadcasting); + gattCharacteristic.IsIndicatable = bool(characteristicData.properties + & QLowEnergyCharacteristic::Indicate); + gattCharacteristic.IsNotifiable = bool(characteristicData.properties + & QLowEnergyCharacteristic::Notify); + gattCharacteristic.IsReadable = bool(characteristicData.properties + & QLowEnergyCharacteristic::Read); + gattCharacteristic.IsSignedWritable = bool(characteristicData.properties + & QLowEnergyCharacteristic::WriteSigned); + gattCharacteristic.IsWritable = bool(characteristicData.properties + & QLowEnergyCharacteristic::Write); + gattCharacteristic.IsWritableWithoutResponse = bool(characteristicData.properties + & QLowEnergyCharacteristic::WriteNoResponse); + + return gattCharacteristic; +} + +static BTH_LE_GATT_DESCRIPTOR_TYPE nativeLeGattDescriptorTypeFromUuid( + const QBluetoothUuid &uuid) +{ + switch (uuid.toUInt16()) { + case QBluetoothUuid::CharacteristicExtendedProperties: + return CharacteristicExtendedProperties; + case QBluetoothUuid::CharacteristicUserDescription: + return CharacteristicUserDescription; + case QBluetoothUuid::ClientCharacteristicConfiguration: + return ClientCharacteristicConfiguration; + case QBluetoothUuid::ServerCharacteristicConfiguration: + return ServerCharacteristicConfiguration; + case QBluetoothUuid::CharacteristicPresentationFormat: + return CharacteristicFormat; + case QBluetoothUuid::CharacteristicAggregateFormat: + return CharacteristicAggregateFormat; + default: + return CustomDescriptor; + } +} + +static BTH_LE_GATT_DESCRIPTOR recoverNativeLeGattDescriptor( + QLowEnergyHandle serviceHandle, QLowEnergyHandle characteristicHandle, + QLowEnergyHandle descriptorHandle, + const QLowEnergyServicePrivate::DescData &descriptorData) +{ + BTH_LE_GATT_DESCRIPTOR gattDescriptor; + + gattDescriptor.ServiceHandle = serviceHandle; + gattDescriptor.CharacteristicHandle = characteristicHandle; + gattDescriptor.AttributeHandle = descriptorHandle; + + gattDescriptor.DescriptorUuid = nativeLeUuidFromQtBluetoothUuid( + descriptorData.uuid); + + gattDescriptor.DescriptorType = nativeLeGattDescriptorTypeFromUuid + (descriptorData.uuid); + + return gattDescriptor; +} + +void QLowEnergyControllerPrivateWin32::customEvent(QEvent *e) +{ + if (e->type() != CharactericticValueEventType) + return; + + const CharactericticValueEvent *characteristicEvent + = static_cast<CharactericticValueEvent *>(e); + + updateValueOfCharacteristic(characteristicEvent->m_handle, + characteristicEvent->m_value, false); + + const QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle( + characteristicEvent->m_handle); + if (service.isNull()) + return; + + const QLowEnergyCharacteristic ch(service, characteristicEvent->m_handle); + emit service->characteristicChanged(ch, characteristicEvent->m_value); +} + +QLowEnergyControllerPrivateWin32::QLowEnergyControllerPrivateWin32() + : QLowEnergyControllerPrivate() +{ + QMutexLocker locker(&controllersGuard); + qControllers()->append(this); + + gattFunctionsResolved = resolveFunctions(bluetoothapis()); + if (!gattFunctionsResolved) { + qCWarning(QT_BT_WINDOWS) << "LE is not supported on this OS"; + return; + } +} + +QLowEnergyControllerPrivateWin32::~QLowEnergyControllerPrivateWin32() +{ + QMutexLocker locker(&controllersGuard); + qControllers()->removeAll(this); +} + +void QLowEnergyControllerPrivateWin32::init() +{ + Q_UNIMPLEMENTED(); +} + +void QLowEnergyControllerPrivateWin32::connectToDevice() +{ + // required to pass unit test on default backend + if (remoteDevice.isNull()) { + qWarning() << "Invalid/null remote device address"; + setError(QLowEnergyController::UnknownRemoteDeviceError); + return; + } + + if (!deviceSystemPath.isEmpty()) { + qCDebug(QT_BT_WINDOWS) << "Already is connected"; + return; + } + + setState(QLowEnergyController::ConnectingState); + + deviceSystemPath = + QBluetoothDeviceDiscoveryAgentPrivate::discoveredLeDeviceSystemPath( + remoteDevice); + + if (deviceSystemPath.isEmpty()) { + qCWarning(QT_BT_WINDOWS) << qt_error_string(ERROR_PATH_NOT_FOUND); + setError(QLowEnergyController::UnknownRemoteDeviceError); + setState(QLowEnergyController::UnconnectedState); + return; + } + + setState(QLowEnergyController::ConnectedState); + + thread = new QThread; + threadWorker = new ThreadWorker; + threadWorker->moveToThread(thread); + connect(threadWorker, &ThreadWorker::jobFinished, this, &QLowEnergyControllerPrivateWin32::jobFinished); + connect(thread, &QThread::finished, threadWorker, &ThreadWorker::deleteLater); + connect(thread, &QThread::finished, thread, &QThread::deleteLater); + thread->start(); + + Q_Q(QLowEnergyController); + emit q->connected(); +} + +void QLowEnergyControllerPrivateWin32::disconnectFromDevice() +{ + if (deviceSystemPath.isEmpty()) { + qCDebug(QT_BT_WINDOWS) << "Already is disconnected"; + return; + } + + setState(QLowEnergyController::ClosingState); + deviceSystemPath.clear(); + setState(QLowEnergyController::UnconnectedState); + + if (thread) { + disconnect(threadWorker, &ThreadWorker::jobFinished, this, &QLowEnergyControllerPrivateWin32::jobFinished); + thread->quit(); + thread = nullptr; + } + + for (const QSharedPointer<QLowEnergyServicePrivate> servicePrivate: serviceList) + closeSystemDevice(servicePrivate->hService); + + Q_Q(QLowEnergyController); + emit q->disconnected(); +} + +void QLowEnergyControllerPrivateWin32::discoverServices() +{ + int systemErrorCode = NO_ERROR; + + const HANDLE hDevice = openSystemDevice( + deviceSystemPath, QIODevice::ReadOnly, &systemErrorCode); + + if (systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << qt_error_string(systemErrorCode); + setError(QLowEnergyController::NetworkError); + setState(QLowEnergyController::ConnectedState); + return; + } + + const QVector<BTH_LE_GATT_SERVICE> foundServices = + enumeratePrimaryGattServices(hDevice, &systemErrorCode); + + closeSystemDevice(hDevice); + + if (systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << qt_error_string(systemErrorCode); + setError(QLowEnergyController::NetworkError); + setState(QLowEnergyController::ConnectedState); + return; + } + + setState(QLowEnergyController::DiscoveringState); + + Q_Q(QLowEnergyController); + + for (const BTH_LE_GATT_SERVICE &service : foundServices) { + const QBluetoothUuid uuid = qtBluetoothUuidFromNativeLeUuid( + service.ServiceUuid); + qCDebug(QT_BT_WINDOWS) << "Found uuid:" << uuid; + + QLowEnergyServicePrivate *priv = new QLowEnergyServicePrivate(); + priv->uuid = uuid; + priv->type = QLowEnergyService::PrimaryService; + priv->startHandle = service.AttributeHandle; + priv->setController(this); + + QSharedPointer<QLowEnergyServicePrivate> pointer(priv); + serviceList.insert(uuid, pointer); + + emit q->serviceDiscovered(uuid); + } + + setState(QLowEnergyController::DiscoveredState); + emit q->discoveryFinished(); +} + +void QLowEnergyControllerPrivateWin32::discoverServiceDetails( + const QBluetoothUuid &service) +{ + if (!serviceList.contains(service)) { + qCWarning(QT_BT_WINDOWS) << "Discovery of unknown service" << service.toString() + << "not possible"; + return; + } + + const QSharedPointer<QLowEnergyServicePrivate> servicePrivate = + serviceList.value(service); + + int systemErrorCode = NO_ERROR; + + // Only open a service once and close it in the QLowEnergyServicePrivate destructor + if (!servicePrivate->hService || servicePrivate->hService == INVALID_HANDLE_VALUE) { + servicePrivate->hService = openSystemService(remoteDevice, service, + QIODevice::ReadOnly | QIODevice::WriteOnly, + &systemErrorCode); + } + + if (systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service.toString() + << ":" << qt_error_string(systemErrorCode); + servicePrivate->setError(QLowEnergyService::UnknownError); + servicePrivate->setState(QLowEnergyService::DiscoveryRequired); + return; + } + + // We assume that the service does not have any characteristics with descriptors. + servicePrivate->endHandle = servicePrivate->startHandle; + + const QVector<BTH_LE_GATT_CHARACTERISTIC> foundCharacteristics = + enumerateGattCharacteristics(servicePrivate->hService, NULL, &systemErrorCode); + + if (systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << "Unable to get characteristics for service" << service.toString() + << ":" << qt_error_string(systemErrorCode); + servicePrivate->setError(QLowEnergyService::CharacteristicReadError); + servicePrivate->setState(QLowEnergyService::DiscoveryRequired); + return; + } + + for (const BTH_LE_GATT_CHARACTERISTIC &gattCharacteristic : foundCharacteristics) { + const QLowEnergyHandle characteristicHandle = gattCharacteristic.AttributeHandle; + + QLowEnergyServicePrivate::CharData detailsData; + + detailsData.hValueChangeEvent = NULL; + + detailsData.uuid = qtBluetoothUuidFromNativeLeUuid( + gattCharacteristic.CharacteristicUuid); + detailsData.valueHandle = gattCharacteristic.CharacteristicValueHandle; + + QLowEnergyCharacteristic::PropertyTypes properties = QLowEnergyCharacteristic::Unknown; + if (gattCharacteristic.HasExtendedProperties) + properties |= QLowEnergyCharacteristic::ExtendedProperty; + if (gattCharacteristic.IsBroadcastable) + properties |= QLowEnergyCharacteristic::Broadcasting; + if (gattCharacteristic.IsIndicatable) + properties |= QLowEnergyCharacteristic::Indicate; + if (gattCharacteristic.IsNotifiable) + properties |= QLowEnergyCharacteristic::Notify; + if (gattCharacteristic.IsReadable) + properties |= QLowEnergyCharacteristic::Read; + if (gattCharacteristic.IsSignedWritable) + properties |= QLowEnergyCharacteristic::WriteSigned; + if (gattCharacteristic.IsWritable) + properties |= QLowEnergyCharacteristic::Write; + if (gattCharacteristic.IsWritableWithoutResponse) + properties |= QLowEnergyCharacteristic::WriteNoResponse; + + detailsData.properties = properties; + detailsData.value = getGattCharacteristicValue( + servicePrivate->hService, const_cast<PBTH_LE_GATT_CHARACTERISTIC>( + &gattCharacteristic), &systemErrorCode); + + if (systemErrorCode != NO_ERROR) { + // We do not interrupt enumerating of characteristics + // if value can not be read + qCWarning(QT_BT_WINDOWS) << "Unable to get value for characteristic" + << detailsData.uuid.toString() + << "of the service" << service.toString() + << ":" << qt_error_string(systemErrorCode); + } + + // We assume that the characteristic has no any descriptors. So, the + // biggest characteristic + 1 will indicate an end handle of service. + servicePrivate->endHandle = std::max( + servicePrivate->endHandle, + QLowEnergyHandle(gattCharacteristic.AttributeHandle + 1)); + + const QVector<BTH_LE_GATT_DESCRIPTOR> foundDescriptors = enumerateGattDescriptors( + servicePrivate->hService, const_cast<PBTH_LE_GATT_CHARACTERISTIC>( + &gattCharacteristic), &systemErrorCode); + + if (systemErrorCode != NO_ERROR) { + if (systemErrorCode != ERROR_NOT_FOUND) { + qCWarning(QT_BT_WINDOWS) << "Unable to get descriptor for characteristic" + << detailsData.uuid.toString() + << "of the service" << service.toString() + << ":" << qt_error_string(systemErrorCode); + servicePrivate->setError(QLowEnergyService::DescriptorReadError); + servicePrivate->setState(QLowEnergyService::DiscoveryRequired); + return; + } + } + + for (const BTH_LE_GATT_DESCRIPTOR &gattDescriptor : foundDescriptors) { + const QLowEnergyHandle descriptorHandle = gattDescriptor.AttributeHandle; + + QLowEnergyServicePrivate::DescData data; + data.uuid = qtBluetoothUuidFromNativeLeUuid( + gattDescriptor.DescriptorUuid); + + data.value = getGattDescriptorValue(servicePrivate->hService, const_cast<PBTH_LE_GATT_DESCRIPTOR>( + &gattDescriptor), &systemErrorCode); + + if (systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << "Unable to get value for descriptor" + << data.uuid.toString() + << "for characteristic" + << detailsData.uuid.toString() + << "of the service" << service.toString() + << ":" << qt_error_string(systemErrorCode); + servicePrivate->setError(QLowEnergyService::DescriptorReadError); + servicePrivate->setState(QLowEnergyService::DiscoveryRequired); + return; + } + + // Biggest descriptor will contain an end handle of service. + servicePrivate->endHandle = std::max( + servicePrivate->endHandle, + QLowEnergyHandle(gattDescriptor.AttributeHandle)); + + detailsData.descriptorList.insert(descriptorHandle, data); + } + + servicePrivate->characteristicList.insert(characteristicHandle, detailsData); + } + + servicePrivate->setState(QLowEnergyService::ServiceDiscovered); +} + +void QLowEnergyControllerPrivateWin32::startAdvertising(const QLowEnergyAdvertisingParameters &, const QLowEnergyAdvertisingData &, const QLowEnergyAdvertisingData &) +{ + Q_UNIMPLEMENTED(); +} + +void QLowEnergyControllerPrivateWin32::stopAdvertising() +{ + Q_UNIMPLEMENTED(); +} + +void QLowEnergyControllerPrivateWin32::requestConnectionUpdate(const QLowEnergyConnectionParameters &) +{ + Q_UNIMPLEMENTED(); +} + +void QLowEnergyControllerPrivateWin32::readCharacteristic( + const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle) +{ + Q_ASSERT(!service.isNull()); + if (!service->characteristicList.contains(charHandle)) + return; + + const QLowEnergyServicePrivate::CharData &charDetails + = service->characteristicList[charHandle]; + if (!(charDetails.properties & QLowEnergyCharacteristic::Read)) { + // if this succeeds the device has a bug, char is advertised as + // non-readable. We try to be permissive and let the remote + // device answer to the read attempt + qCWarning(QT_BT_WINDOWS) << "Reading non-readable char" << charHandle; + } + + ReadCharData data; + data.systemErrorCode = NO_ERROR; + data.hService = service->hService; + + if (data.systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service->uuid.toString() + << ":" << qt_error_string(data.systemErrorCode); + service->setError(QLowEnergyService::CharacteristicReadError); + return; + } + + data.gattCharacteristic = recoverNativeLeGattCharacteristic( + service->startHandle, charHandle, charDetails); + + ThreadWorkerJob job; + job.operation = ThreadWorkerJob::ReadChar; + job.data = QVariant::fromValue(data); + + QMetaObject::invokeMethod(threadWorker, "putJob", Qt::QueuedConnection, + Q_ARG(ThreadWorkerJob, job)); +} + +void QLowEnergyControllerPrivateWin32::writeCharacteristic( + const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QByteArray &newValue, + QLowEnergyService::WriteMode mode) +{ + Q_ASSERT(!service.isNull()); + + if (!service->characteristicList.contains(charHandle)) { + service->setError(QLowEnergyService::CharacteristicWriteError); + return; + } + + WriteCharData data; + data.systemErrorCode = NO_ERROR; + data.hService = service->hService; + + if (data.systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service->uuid.toString() + << ":" << qt_error_string(data.systemErrorCode); + service->setError(QLowEnergyService::CharacteristicWriteError); + return; + } + + const QLowEnergyServicePrivate::CharData &charDetails + = service->characteristicList[charHandle]; + + data.gattCharacteristic = recoverNativeLeGattCharacteristic( + service->startHandle, charHandle, charDetails); + + data.flags = (mode == QLowEnergyService::WriteWithResponse) + ? BLUETOOTH_GATT_FLAG_NONE + : BLUETOOTH_GATT_FLAG_WRITE_WITHOUT_RESPONSE; + + ThreadWorkerJob job; + job.operation = ThreadWorkerJob::WriteChar; + data.newValue = newValue; + data.mode = mode; + job.data = QVariant::fromValue(data); + + QMetaObject::invokeMethod(threadWorker, "putJob", Qt::QueuedConnection, + Q_ARG(ThreadWorkerJob, job)); +} + +void QLowEnergyControllerPrivateWin32::jobFinished(const ThreadWorkerJob &job) +{ + switch (job.operation) { + case ThreadWorkerJob::WriteChar: + { + const WriteCharData data = job.data.value<WriteCharData>(); + const QLowEnergyHandle charHandle = static_cast<QLowEnergyHandle>(data.gattCharacteristic.AttributeHandle); + const QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); + + if (data.systemErrorCode != NO_ERROR) { + const QLowEnergyServicePrivate::CharData &charDetails = service->characteristicList[charHandle]; + qCWarning(QT_BT_WINDOWS) << "Unable to set value for characteristic" + << charDetails.uuid.toString() + << "of the service" << service->uuid.toString() + << ":" << qt_error_string(data.systemErrorCode); + service->setError(QLowEnergyService::CharacteristicWriteError); + return; + } + + updateValueOfCharacteristic(charHandle, data.newValue, false); + + if (data.mode == QLowEnergyService::WriteWithResponse) { + const QLowEnergyCharacteristic ch = characteristicForHandle(charHandle); + emit service->characteristicWritten(ch, data.newValue); + } + } + break; + case ThreadWorkerJob::ReadChar: + { + const ReadCharData data = job.data.value<ReadCharData>(); + const QLowEnergyHandle charHandle = static_cast<QLowEnergyHandle>(data.gattCharacteristic.AttributeHandle); + const QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); + + if (data.systemErrorCode != NO_ERROR) { + const QLowEnergyServicePrivate::CharData &charDetails = service->characteristicList[charHandle]; + qCWarning(QT_BT_WINDOWS) << "Unable to get value for characteristic" + << charDetails.uuid.toString() + << "of the service" << service->uuid.toString() + << ":" << qt_error_string(data.systemErrorCode); + service->setError(QLowEnergyService::CharacteristicReadError); + return; + } + + updateValueOfCharacteristic(charHandle, data.value, false); + + const QLowEnergyCharacteristic ch(service, charHandle); + emit service->characteristicRead(ch, data.value); + } + break; + case ThreadWorkerJob::WriteDescr: + { + WriteDescData data = job.data.value<WriteDescData>(); + const QLowEnergyHandle descriptorHandle = static_cast<QLowEnergyHandle>(data.gattDescriptor.AttributeHandle); + const QLowEnergyHandle charHandle = static_cast<QLowEnergyHandle>(data.gattDescriptor.CharacteristicHandle); + const QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); + QLowEnergyServicePrivate::CharData &charDetails = service->characteristicList[charHandle]; + const QLowEnergyServicePrivate::DescData &dscrDetails = charDetails.descriptorList[descriptorHandle]; + + if (data.systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << "Unable to set value for descriptor" + << dscrDetails.uuid.toString() + << "for characteristic" + << charDetails.uuid.toString() + << "of the service" << service->uuid.toString() + << ":" << qt_error_string(data.systemErrorCode); + service->setError(QLowEnergyService::DescriptorWriteError); + return; + } + + if (data.gattDescriptor.DescriptorType == ClientCharacteristicConfiguration) { + + QDataStream in(data.newValue); + quint8 u; + in >> u; + + if (u & ClientCharacteristicConfigurationValue::UseNotifications + || u & ClientCharacteristicConfigurationValue::UseIndications) { + if (!charDetails.hValueChangeEvent) { + BTH_LE_GATT_CHARACTERISTIC gattCharacteristic = recoverNativeLeGattCharacteristic( + service->startHandle, charHandle, charDetails); + + // note: if the service handle is closed the event registration is no longer valid. + charDetails.hValueChangeEvent = registerEvent( + data.hService, gattCharacteristic, this, &data.systemErrorCode); + } + } else { + if (charDetails.hValueChangeEvent) { + unregisterEvent(charDetails.hValueChangeEvent, &data.systemErrorCode); + charDetails.hValueChangeEvent = NULL; + } + } + + if (data.systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << "Unable to subscribe events for descriptor" + << dscrDetails.uuid.toString() + << "for characteristic" + << charDetails.uuid.toString() + << "of the service" << service->uuid.toString() + << ":" << qt_error_string(data.systemErrorCode); + service->setError(QLowEnergyService::DescriptorWriteError); + return; + } + } + + updateValueOfDescriptor(charHandle, descriptorHandle, data.newValue, false); + + const QLowEnergyDescriptor dscr(service, charHandle, descriptorHandle); + emit service->descriptorWritten(dscr, data.newValue); + } + break; + case ThreadWorkerJob::ReadDescr: + { + ReadDescData data = job.data.value<ReadDescData>(); + const QLowEnergyHandle descriptorHandle = static_cast<QLowEnergyHandle>(data.gattDescriptor.AttributeHandle); + const QLowEnergyHandle charHandle = static_cast<QLowEnergyHandle>(data.gattDescriptor.CharacteristicHandle); + const QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); + QLowEnergyServicePrivate::CharData &charDetails = service->characteristicList[charHandle]; + const QLowEnergyServicePrivate::DescData &dscrDetails = charDetails.descriptorList[descriptorHandle]; + + if (data.systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << "Unable to get value for descriptor" + << dscrDetails.uuid.toString() + << "for characteristic" + << charDetails.uuid.toString() + << "of the service" << service->uuid.toString() + << ":" << qt_error_string(data.systemErrorCode); + service->setError(QLowEnergyService::DescriptorReadError); + return; + } + + updateValueOfDescriptor(charHandle, descriptorHandle, data.value, false); + + QLowEnergyDescriptor dscr(service, charHandle, descriptorHandle); + emit service->descriptorRead(dscr, data.value); + } + break; + } + + QMetaObject::invokeMethod(threadWorker, "runPendingJob", Qt::QueuedConnection); +} + +void QLowEnergyControllerPrivateWin32::readDescriptor( + const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descriptorHandle) +{ + Q_ASSERT(!service.isNull()); + if (!service->characteristicList.contains(charHandle)) + return; + + const QLowEnergyServicePrivate::CharData &charDetails + = service->characteristicList[charHandle]; + if (!charDetails.descriptorList.contains(descriptorHandle)) + return; + + ReadDescData data; + data.systemErrorCode = NO_ERROR; + data.hService = service->hService; + + if (data.systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service->uuid.toString() + << ":" << qt_error_string(data.systemErrorCode); + service->setError(QLowEnergyService::DescriptorReadError); + return; + } + + const QLowEnergyServicePrivate::DescData &dscrDetails + = charDetails.descriptorList[descriptorHandle]; + + data.gattDescriptor = recoverNativeLeGattDescriptor( + service->startHandle, charHandle, descriptorHandle, dscrDetails); + + ThreadWorkerJob job; + job.operation = ThreadWorkerJob::ReadDescr; + job.data = QVariant::fromValue(data); + + QMetaObject::invokeMethod(threadWorker, "putJob", Qt::QueuedConnection, + Q_ARG(ThreadWorkerJob, job)); +} + +void QLowEnergyControllerPrivateWin32::writeDescriptor( + const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descriptorHandle, + const QByteArray &newValue) +{ + Q_ASSERT(!service.isNull()); + if (!service->characteristicList.contains(charHandle)) + return; + + QLowEnergyServicePrivate::CharData &charDetails + = service->characteristicList[charHandle]; + if (!charDetails.descriptorList.contains(descriptorHandle)) + return; + + WriteDescData data; + data.systemErrorCode = NO_ERROR; + data.newValue = newValue; + data.hService = service->hService; + + if (data.systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service->uuid.toString() + << ":" << qt_error_string(data.systemErrorCode); + service->setError(QLowEnergyService::DescriptorWriteError); + return; + } + + const QLowEnergyServicePrivate::DescData &dscrDetails + = charDetails.descriptorList[descriptorHandle]; + + data.gattDescriptor = recoverNativeLeGattDescriptor( + service->startHandle, charHandle, descriptorHandle, dscrDetails); + + ThreadWorkerJob job; + job.operation = ThreadWorkerJob::WriteDescr; + job.data = QVariant::fromValue(data); + + QMetaObject::invokeMethod(threadWorker, "putJob", Qt::QueuedConnection, + Q_ARG(ThreadWorkerJob, job)); +} + +void QLowEnergyControllerPrivateWin32::addToGenericAttributeList(const QLowEnergyServiceData &, QLowEnergyHandle) +{ + Q_UNIMPLEMENTED(); +} + +void ThreadWorker::putJob(const ThreadWorkerJob &job) +{ + m_jobs.append(job); + if (m_jobs.count() == 1) + runPendingJob(); +} + +void ThreadWorker::runPendingJob() +{ + if (!m_jobs.count()) + return; + + ThreadWorkerJob job = m_jobs.first(); + + switch (job.operation) { + case ThreadWorkerJob::WriteChar: + { + WriteCharData data = job.data.value<WriteCharData>(); + setGattCharacteristicValue(data.hService, &data.gattCharacteristic, + data.newValue, data.flags, &data.systemErrorCode); + job.data = QVariant::fromValue(data); + } + break; + case ThreadWorkerJob::ReadChar: + { + ReadCharData data = job.data.value<ReadCharData>(); + data.value = getGattCharacteristicValue( + data.hService, &data.gattCharacteristic, &data.systemErrorCode); + job.data = QVariant::fromValue(data); + } + break; + case ThreadWorkerJob::WriteDescr: + { + WriteDescData data = job.data.value<WriteDescData>(); + setGattDescriptorValue(data.hService, &data.gattDescriptor, + data.newValue, &data.systemErrorCode); + job.data = QVariant::fromValue(data); + } + break; + case ThreadWorkerJob::ReadDescr: + { + ReadDescData data = job.data.value<ReadDescData>(); + data.value = getGattDescriptorValue( + data.hService, + const_cast<PBTH_LE_GATT_DESCRIPTOR>(&data.gattDescriptor), + &data.systemErrorCode); + job.data = QVariant::fromValue(data); + } + break; + } + + m_jobs.removeFirst(); + emit jobFinished(job); +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycontroller_win_p.h b/src/bluetooth/qlowenergycontroller_win_p.h new file mode 100644 index 00000000..f8e3b10a --- /dev/null +++ b/src/bluetooth/qlowenergycontroller_win_p.h @@ -0,0 +1,185 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 QLOWENERGYCONTROLLERPRIVATE_WIN32_P_H +#define QLOWENERGYCONTROLLERPRIVATE_WIN32_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 <qglobal.h> +#include <QtCore/QQueue> +#include <QtCore/QVector> +#include <QtBluetooth/qbluetooth.h> +#include <QtBluetooth/qlowenergycharacteristic.h> +#include "qlowenergycontroller.h" +#include "qlowenergycontrollerbase_p.h" + +#include <windows/qwinlowenergybluetooth_p.h> + +QT_BEGIN_NAMESPACE + +class QThread; +class QLowEnergyControllerPrivateWin32; + +class ThreadWorkerJob +{ +public: + enum Operation { WriteChar, ReadChar, WriteDescr, ReadDescr }; + Operation operation; + QVariant data; +}; + +struct WriteCharData +{ + QByteArray newValue; + QLowEnergyService::WriteMode mode; + HANDLE hService; + DWORD flags; + BTH_LE_GATT_CHARACTERISTIC gattCharacteristic; + int systemErrorCode; +}; + +struct ReadCharData +{ + QByteArray value; + HANDLE hService; + BTH_LE_GATT_CHARACTERISTIC gattCharacteristic; + int systemErrorCode; +}; + +struct WriteDescData +{ + QByteArray newValue; + HANDLE hService; + BTH_LE_GATT_DESCRIPTOR gattDescriptor; + int systemErrorCode; +}; + +struct ReadDescData +{ + QByteArray value; + HANDLE hService; + BTH_LE_GATT_DESCRIPTOR gattDescriptor; + int systemErrorCode; +}; + +class ThreadWorker : public QObject +{ + Q_OBJECT +public: + Q_INVOKABLE void putJob(const ThreadWorkerJob &job); + Q_INVOKABLE void runPendingJob(); +signals: + void jobFinished(const ThreadWorkerJob &job); +private: + QVector<ThreadWorkerJob> m_jobs; +}; + +class QLowEnergyServiceData; + +extern void registerQLowEnergyControllerMetaType(); + +class QLowEnergyControllerPrivateWin32 : public QLowEnergyControllerPrivate +{ + Q_OBJECT +public: + QLowEnergyControllerPrivateWin32(); + ~QLowEnergyControllerPrivateWin32(); + + void init() override; + + void connectToDevice() override; + void disconnectFromDevice() override; + + void discoverServices() override; + void discoverServiceDetails(const QBluetoothUuid &service) override; + + void startAdvertising(const QLowEnergyAdvertisingParameters ¶ms, + const QLowEnergyAdvertisingData &advertisingData, + const QLowEnergyAdvertisingData &scanResponseData) override; + void stopAdvertising() override; + + void requestConnectionUpdate(const QLowEnergyConnectionParameters ¶ms) override; + + // read data + void readCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle) override; + void readDescriptor(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descriptorHandle) override; + + // write data + 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 addToGenericAttributeList(const QLowEnergyServiceData &service, + QLowEnergyHandle startHandle) override; +public slots: + void jobFinished(const ThreadWorkerJob &job); +protected: + void customEvent(QEvent *e); +private: + QThread *thread = nullptr; + ThreadWorker *threadWorker = nullptr; + QString deviceSystemPath; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(ThreadWorkerJob) +Q_DECLARE_METATYPE(WriteCharData) +Q_DECLARE_METATYPE(ReadCharData) +Q_DECLARE_METATYPE(WriteDescData) +Q_DECLARE_METATYPE(ReadDescData) + +#endif // QLOWENERGYCONTROLLERPRIVATE_WIN32__P_H diff --git a/src/bluetooth/qlowenergydescriptor.h b/src/bluetooth/qlowenergydescriptor.h index 62ca5fd3..adfe1203 100644 --- a/src/bluetooth/qlowenergydescriptor.h +++ b/src/bluetooth/qlowenergydescriptor.h @@ -84,6 +84,7 @@ protected: friend class QLowEnergyControllerPrivateBluezDBus; friend class QLowEnergyControllerPrivateCommon; friend class QLowEnergyControllerPrivateOSX; + friend class QLowEnergyControllerPrivateWin32; friend class QLowEnergyControllerPrivateWinRT; friend class QLowEnergyControllerPrivateWinRTNew; QLowEnergyDescriptorPrivate *data = nullptr; diff --git a/src/bluetooth/qlowenergyserviceprivate_p.h b/src/bluetooth/qlowenergyserviceprivate_p.h index fb4163a0..226af145 100644 --- a/src/bluetooth/qlowenergyserviceprivate_p.h +++ b/src/bluetooth/qlowenergyserviceprivate_p.h @@ -60,6 +60,9 @@ #if defined(QT_ANDROID_BLUETOOTH) #include <QtAndroidExtras/QAndroidJniObject> #endif +#if defined(QT_WIN_BLUETOOTH) +#include <qt_windows.h> +#endif QT_BEGIN_NAMESPACE @@ -83,6 +86,9 @@ public: QLowEnergyCharacteristic::PropertyTypes properties; QByteArray value; QHash<QLowEnergyHandle, DescData> descriptorList; +#ifdef QT_WIN_BLUETOOTH + Qt::HANDLE hValueChangeEvent; +#endif }; enum GattAttributeTypes { @@ -128,6 +134,9 @@ public: // reference to the BluetoothGattService object QAndroidJniObject androidService; #endif +#if defined(QT_WIN_BLUETOOTH) + Qt::HANDLE hService = nullptr; +#endif }; diff --git a/src/bluetooth/windows/qwinlowenergybluetooth_p.h b/src/bluetooth/windows/qwinlowenergybluetooth_p.h new file mode 100644 index 00000000..39b88a5f --- /dev/null +++ b/src/bluetooth/windows/qwinlowenergybluetooth_p.h @@ -0,0 +1,231 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Copyright (C) 2014 Denis Shienkov <denis.shienkov@gmail.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$ +** +****************************************************************************/ + +#ifndef QWINLOWENERGYBLUETOOTH_P_H +#define QWINLOWENERGYBLUETOOTH_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 <QtCore/qlibrary.h> + +#include <qt_windows.h> + +#define WIN32_FROM_HRESULT(hr) \ + (SUCCEEDED(hr) ? ERROR_SUCCESS : \ + (HRESULT_FACILITY(hr) == FACILITY_WIN32 ? HRESULT_CODE(hr) : (hr))) + +#define BLUETOOTH_GATT_FLAG_NONE 0x00000000 +#define BLUETOOTH_GATT_FLAG_CONNECTION_ENCRYPTED 0x00000001 +#define BLUETOOTH_GATT_FLAG_CONNECTION_AUTHENTICATED 0x00000002 +#define BLUETOOTH_GATT_FLAG_FORCE_READ_FROM_DEVICE 0x00000004 +#define BLUETOOTH_GATT_FLAG_FORCE_READ_FROM_CACHE 0x00000008 +#define BLUETOOTH_GATT_FLAG_SIGNED_WRITE 0x00000010 +#define BLUETOOTH_GATT_FLAG_WRITE_WITHOUT_RESPONSE 0x00000020 +#define BLUETOOTH_GATT_FLAG_RETURN_ALL 0x00000040 + +typedef enum _BTH_LE_GATT_DESCRIPTOR_TYPE { + CharacteristicExtendedProperties, + CharacteristicUserDescription, + ClientCharacteristicConfiguration, + ServerCharacteristicConfiguration, + CharacteristicFormat, + CharacteristicAggregateFormat, + CustomDescriptor +} BTH_LE_GATT_DESCRIPTOR_TYPE, *PBTH_LE_GATT_DESCRIPTOR_TYPE; + +typedef enum _BTH_LE_GATT_EVENT_TYPE { + CharacteristicValueChangedEvent +} BTH_LE_GATT_EVENT_TYPE; + +typedef struct _BTH_LE_UUID { + BOOLEAN IsShortUuid; + union { + USHORT ShortUuid; + GUID LongUuid; + } Value; +} BTH_LE_UUID, *PBTH_LE_UUID; + +typedef struct _BTH_LE_GATT_SERVICE { + BTH_LE_UUID ServiceUuid; + USHORT AttributeHandle; +} BTH_LE_GATT_SERVICE, *PBTH_LE_GATT_SERVICE; + +typedef struct _BTH_LE_GATT_CHARACTERISTIC { + USHORT ServiceHandle; + BTH_LE_UUID CharacteristicUuid; + USHORT AttributeHandle; + USHORT CharacteristicValueHandle; + BOOLEAN IsBroadcastable; + BOOLEAN IsReadable; + BOOLEAN IsWritable; + BOOLEAN IsWritableWithoutResponse; + BOOLEAN IsSignedWritable; + BOOLEAN IsNotifiable; + BOOLEAN IsIndicatable; + BOOLEAN HasExtendedProperties; +} BTH_LE_GATT_CHARACTERISTIC, *PBTH_LE_GATT_CHARACTERISTIC; + +typedef struct _BTH_LE_GATT_CHARACTERISTIC_VALUE { + ULONG DataSize; + UCHAR Data[1]; +} BTH_LE_GATT_CHARACTERISTIC_VALUE, *PBTH_LE_GATT_CHARACTERISTIC_VALUE; + +typedef struct _BTH_LE_GATT_DESCRIPTOR { + USHORT ServiceHandle; + USHORT CharacteristicHandle; + BTH_LE_GATT_DESCRIPTOR_TYPE DescriptorType; + BTH_LE_UUID DescriptorUuid; + USHORT AttributeHandle; +} BTH_LE_GATT_DESCRIPTOR, *PBTH_LE_GATT_DESCRIPTOR; + +typedef struct _BTH_LE_GATT_DESCRIPTOR_VALUE { + BTH_LE_GATT_DESCRIPTOR_TYPE DescriptorType; + BTH_LE_UUID DescriptorUuid; + union { + struct { + BOOLEAN IsReliableWriteEnabled; + BOOLEAN IsAuxiliariesWritable; + } CharacteristicExtendedProperties; + struct { + BOOLEAN IsSubscribeToNotification; + BOOLEAN IsSubscribeToIndication; + } ClientCharacteristicConfiguration; + struct { + BOOLEAN IsBroadcast; + } ServerCharacteristicConfiguration; + struct { + UCHAR Format; + UCHAR Exponent; + BTH_LE_UUID Unit; + UCHAR NameSpace; + BTH_LE_UUID Description; + } CharacteristicFormat; + }; + ULONG DataSize; + UCHAR Data[1]; +} BTH_LE_GATT_DESCRIPTOR_VALUE, *PBTH_LE_GATT_DESCRIPTOR_VALUE; + +typedef struct _BLUETOOTH_GATT_VALUE_CHANGED_EVENT { + USHORT ChangedAttributeHandle; + size_t CharacteristicValueDataSize; + PBTH_LE_GATT_CHARACTERISTIC_VALUE CharacteristicValue; +} BLUETOOTH_GATT_VALUE_CHANGED_EVENT, *PBLUETOOTH_GATT_VALUE_CHANGED_EVENT; + +typedef struct _BLUETOOTH_GATT_VALUE_CHANGED_EVENT_REGISTRATION { + USHORT NumCharacteristics; + BTH_LE_GATT_CHARACTERISTIC Characteristics[1]; +} BLUETOOTH_GATT_VALUE_CHANGED_EVENT_REGISTRATION, *PBLUETOOTH_GATT_VALUE_CHANGED_EVENT_REGISTRATION; + +typedef VOID (CALLBACK *PFNBLUETOOTH_GATT_EVENT_CALLBACK)( + BTH_LE_GATT_EVENT_TYPE EventType, + PVOID EventOutParameter, + PVOID Context + ); + +typedef ULONG64 BTH_LE_GATT_RELIABLE_WRITE_CONTEXT, *PBTH_LE_GATT_RELIABLE_WRITE_CONTEXT; + +#define DEFINEFUNC(ret, func, ...) \ + typedef ret (WINAPI *fp_##func)(__VA_ARGS__); \ + static fp_##func func; + +#define RESOLVEFUNC(func) \ + func = (fp_##func)resolveFunction(library, #func); \ + if (!func) \ + return false; + +DEFINEFUNC(HRESULT, BluetoothGATTGetServices, HANDLE, USHORT, PBTH_LE_GATT_SERVICE, PUSHORT, ULONG) +DEFINEFUNC(HRESULT, BluetoothGATTGetIncludedServices, HANDLE, PBTH_LE_GATT_SERVICE, USHORT, PBTH_LE_GATT_SERVICE, PUSHORT, ULONG) +DEFINEFUNC(HRESULT, BluetoothGATTGetCharacteristics, HANDLE, PBTH_LE_GATT_SERVICE, USHORT, PBTH_LE_GATT_CHARACTERISTIC, PUSHORT, ULONG) +DEFINEFUNC(HRESULT, BluetoothGATTGetDescriptors, HANDLE, PBTH_LE_GATT_CHARACTERISTIC, USHORT, PBTH_LE_GATT_DESCRIPTOR, PUSHORT, ULONG) +DEFINEFUNC(HRESULT, BluetoothGATTGetCharacteristicValue, HANDLE, PBTH_LE_GATT_CHARACTERISTIC, ULONG, PBTH_LE_GATT_CHARACTERISTIC_VALUE, PUSHORT, ULONG) +DEFINEFUNC(HRESULT, BluetoothGATTGetDescriptorValue, HANDLE, PBTH_LE_GATT_DESCRIPTOR, ULONG, PBTH_LE_GATT_DESCRIPTOR_VALUE, PUSHORT, ULONG) +DEFINEFUNC(HRESULT, BluetoothGATTBeginReliableWrite, HANDLE, PBTH_LE_GATT_RELIABLE_WRITE_CONTEXT, ULONG) +DEFINEFUNC(HRESULT, BluetoothGATTEndReliableWrite, HANDLE, BTH_LE_GATT_RELIABLE_WRITE_CONTEXT, ULONG) +DEFINEFUNC(HRESULT, BluetoothGATTAbortReliableWrite, HANDLE, BTH_LE_GATT_RELIABLE_WRITE_CONTEXT, ULONG) +DEFINEFUNC(HRESULT, BluetoothGATTSetCharacteristicValue, HANDLE, PBTH_LE_GATT_CHARACTERISTIC, PBTH_LE_GATT_CHARACTERISTIC_VALUE, BTH_LE_GATT_RELIABLE_WRITE_CONTEXT, ULONG) +DEFINEFUNC(HRESULT, BluetoothGATTSetDescriptorValue, HANDLE, PBTH_LE_GATT_DESCRIPTOR, PBTH_LE_GATT_DESCRIPTOR_VALUE, ULONG) +DEFINEFUNC(HRESULT, BluetoothGATTRegisterEvent, HANDLE, BTH_LE_GATT_EVENT_TYPE, PVOID, PFNBLUETOOTH_GATT_EVENT_CALLBACK, PVOID, PHANDLE, ULONG) +DEFINEFUNC(HRESULT, BluetoothGATTUnregisterEvent, HANDLE, ULONG) + +static inline QFunctionPointer resolveFunction(QLibrary *library, const char *func) +{ + QFunctionPointer symbolFunctionPointer = library->resolve(func); + if (!symbolFunctionPointer) + qWarning("Cannot resolve '%s' in '%s'.", func, qPrintable(library->fileName())); + return symbolFunctionPointer; +} + +static inline bool resolveFunctions(QLibrary *library) +{ + if (!library->isLoaded()) { + library->setFileName(QStringLiteral("bluetoothapis")); + if (!library->load()) { + qWarning("Unable to load '%s' library.", qPrintable(library->fileName())); + return false; + } + } + + RESOLVEFUNC(BluetoothGATTGetServices) + RESOLVEFUNC(BluetoothGATTGetIncludedServices) + RESOLVEFUNC(BluetoothGATTGetCharacteristics) + RESOLVEFUNC(BluetoothGATTGetDescriptors) + RESOLVEFUNC(BluetoothGATTGetCharacteristicValue) + RESOLVEFUNC(BluetoothGATTGetDescriptorValue) + RESOLVEFUNC(BluetoothGATTBeginReliableWrite) + RESOLVEFUNC(BluetoothGATTEndReliableWrite) + RESOLVEFUNC(BluetoothGATTAbortReliableWrite) + RESOLVEFUNC(BluetoothGATTSetCharacteristicValue) + RESOLVEFUNC(BluetoothGATTSetDescriptorValue) + RESOLVEFUNC(BluetoothGATTRegisterEvent) + RESOLVEFUNC(BluetoothGATTUnregisterEvent) + + return true; +} + +#endif // QWINLOWENERGYBLUETOOTH_P_H diff --git a/src/bluetooth/windows/windows.pri b/src/bluetooth/windows/windows.pri new file mode 100644 index 00000000..bf35eaa4 --- /dev/null +++ b/src/bluetooth/windows/windows.pri @@ -0,0 +1,2 @@ +PRIVATE_HEADERS += \ + windows/qwinlowenergybluetooth_p.h |