diff options
-rw-r--r-- | src/bluetooth/bluetooth.pro | 19 | ||||
-rw-r--r-- | src/bluetooth/qbluetooth.cpp | 2 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothdevicediscoveryagent.h | 1 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothdevicediscoveryagent_p.h | 32 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothdevicediscoveryagent_win.cpp | 559 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothlocaldevice_p.h | 25 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothlocaldevice_win.cpp | 256 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothserver_win.cpp | 103 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothservicediscoveryagent.h | 3 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothservicediscoveryagent_p.h | 18 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothservicediscoveryagent_win.cpp | 431 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothserviceinfo_win.cpp | 63 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothsocket_win.cpp | 148 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_p.h | 6 | ||||
-rw-r--r-- | src/bluetooth/qlowenergycontroller_win.cpp | 1194 | ||||
-rw-r--r-- | src/bluetooth/qlowenergyserviceprivate_p.h | 3 | ||||
-rw-r--r-- | src/bluetooth/windows/qwinlowenergybluetooth_p.h | 210 | ||||
-rw-r--r-- | src/bluetooth/windows/windows.pri | 2 |
18 files changed, 3074 insertions, 1 deletions
diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro index 1ef7406b..e096374f 100644 --- a/src/bluetooth/bluetooth.pro +++ b/src/bluetooth/bluetooth.pro @@ -215,6 +215,25 @@ 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 + } 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 d3e41265..feb25324 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 243d7fd2..a1135bf1 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,6 +79,10 @@ class QDBusVariant; QT_END_NAMESPACE #endif +#ifdef QT_WIN_BLUETOOTH +#include <QtConcurrent> +#endif + QT_BEGIN_NAMESPACE #ifdef QT_WINRT_BLUETOOTH @@ -85,7 +90,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 @@ -159,6 +164,31 @@ 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(); + void startClassicDevicesDiscovery(Qt::HANDLE hSearch = nullptr); + void completeClassicDevicesDiscovery(); + + void processDiscoveredDevice(const QBluetoothDeviceInfo &foundDevice); + + QBluetoothAddress adapterAddress; + bool pendingCancel; + bool pendingStart; + bool active; + + QFutureWatcher<QVariant> *classicScanWatcher; + QFutureWatcher<QVariant> *lowenergyScanWatcher; +#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..9f05ffa1 --- /dev/null +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_win.cpp @@ -0,0 +1,559 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2014 Denis Shienkov <denis.shienkov@gmail.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbluetoothdevicediscoveryagent.h" +#include "qbluetoothuuid.h" +#include "qbluetoothdevicediscoveryagent_p.h" +#include "qbluetoothlocaldevice_p.h" + +#include <QtCore/qmutex.h> +#include <QtConcurrent/QtConcurrent> + +#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() ? NULL : 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; + ::ZeroMemory(&searchParams, sizeof(searchParams)); + 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 = NULL; + + BLUETOOTH_DEVICE_INFO deviceInfo; + ::ZeroMemory(&deviceInfo, sizeof(deviceInfo)); + 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; + ::ZeroMemory(&deviceInfo, sizeof(deviceInfo)); + 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), + NULL, + 0, + 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; + ::ZeroMemory(&deviceInterfaceData, sizeof(deviceInterfaceData)); + deviceInterfaceData.cbSize = sizeof(deviceInterfaceData); + + if (!::SetupDiEnumDeviceInterfaces( + hDeviceInfo, + NULL, + reinterpret_cast<const GUID *>(&deviceInterfaceGuid), + index++, + &deviceInterfaceData)) { + *systemErrorCode = ::GetLastError(); + break; + } + + DWORD deviceInterfaceDetailDataSize = 0; + if (!::SetupDiGetDeviceInterfaceDetail( + hDeviceInfo, + &deviceInterfaceData, + NULL, + deviceInterfaceDetailDataSize, + &deviceInterfaceDetailDataSize, + NULL)) { + if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + *systemErrorCode = ::GetLastError(); + 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( + 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 +}; + +static QVariant discoverLeDevicesStatic() +{ + DiscoveryResult result; // Do not use hSearch here! + result.systemErrorCode = NO_ERROR; + result.devices = enumerateLeDevices(&result.systemErrorCode); + return QVariant::fromValue(result); +} + +static QVariant discoverClassicDevicesStatic(HBLUETOOTH_DEVICE_FIND 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); + return QVariant::fromValue(result); +} + +QString QBluetoothDeviceDiscoveryAgentPrivate::discoveredLeDeviceSystemPath( + const QBluetoothAddress &deviceAddress) +{ + // update LE devices cache + DWORD dummyErrorCode; + enumerateLeDevices(&dummyErrorCode); + + QMutexLocker locker(cachedLeDeviceEntriesGuard()); + for (int i = 0; i < cachedLeDeviceEntries()->count(); ++i) { + if (cachedLeDeviceEntries()->at(i).deviceAddress == deviceAddress) + return cachedLeDeviceEntries()->at(i).devicePath; + } + return QString(); +} + +QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate( + const QBluetoothAddress &deviceAdapter, + QBluetoothDeviceDiscoveryAgent *parent) + : inquiryType(QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry) + , lastError(QBluetoothDeviceDiscoveryAgent::NoError) + , adapterAddress(deviceAdapter) + , pendingCancel(false) + , pendingStart(false) + , active(false) + , classicScanWatcher(nullptr) + , lowenergyScanWatcher(nullptr) + , lowEnergySearchTimeout(-1) // remains -1 -> timeout not supported + , q_ptr(parent) +{ +} + +QBluetoothDeviceDiscoveryAgentPrivate::~QBluetoothDeviceDiscoveryAgentPrivate() +{ + if (active) + stop(); +} + +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() +{ + if (!lowenergyScanWatcher) { + lowenergyScanWatcher = new QFutureWatcher<QVariant>(this); + connect(lowenergyScanWatcher, &QFutureWatcher<QVariant>::finished, + this, &QBluetoothDeviceDiscoveryAgentPrivate::completeLeDevicesDiscovery); + } + const QFuture<QVariant> future = QtConcurrent::run(discoverLeDevicesStatic); + lowenergyScanWatcher->setFuture(future); +} + +void QBluetoothDeviceDiscoveryAgentPrivate::completeLeDevicesDiscovery() +{ + if (pendingCancel && !pendingStart) { + cancelDiscovery(); + } else if (pendingStart) { + restartDiscovery(); + } else { + const DiscoveryResult result = lowenergyScanWatcher->result().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) +{ + if (!classicScanWatcher) { + classicScanWatcher = new QFutureWatcher<QVariant>(this); + connect(classicScanWatcher, &QFutureWatcher<QVariant>::finished, + this, &QBluetoothDeviceDiscoveryAgentPrivate::completeClassicDevicesDiscovery); + } + const QFuture<QVariant> future = QtConcurrent::run(discoverClassicDevicesStatic, hSearch); + classicScanWatcher->setFuture(future); +} + +void QBluetoothDeviceDiscoveryAgentPrivate::completeClassicDevicesDiscovery() +{ + const DiscoveryResult result = classicScanWatcher->result().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, qt_error_string(NO_ERROR)); + } else if (result.systemErrorCode == NO_ERROR) { + if (result.hSearch) { + for (const QBluetoothDeviceInfo &device : result.devices) + processDiscoveredDevice(device); + + startClassicDevicesDiscovery(result.hSearch); + } else { + finishDiscovery(QBluetoothDeviceDiscoveryAgent::NoError, qt_error_string(NO_ERROR)); + } + } 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 a52b683c..d0db3a1f 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. @@ -207,6 +208,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_WINRT_BLUETOOTH) class QBluetoothLocalDevicePrivate : public QObject { @@ -231,6 +255,7 @@ public: private: QBluetoothLocalDevice *q_ptr; }; + #elif !defined(QT_OSX_BLUETOOTH) class QBluetoothLocalDevicePrivate : public QObject { diff --git a/src/bluetooth/qbluetoothlocaldevice_win.cpp b/src/bluetooth/qbluetoothlocaldevice_win.cpp new file mode 100644 index 00000000..78b82a97 --- /dev/null +++ b/src/bluetooth/qbluetoothlocaldevice_win.cpp @@ -0,0 +1,256 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2014 Denis Shienkov <denis.shienkov@gmail.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $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..86b520d6 --- /dev/null +++ b/src/bluetooth/qbluetoothserver_win.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $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 4a553c7d..4b1a72c1 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent.h +++ b/src/bluetooth/qbluetoothservicediscoveryagent.h @@ -129,6 +129,9 @@ private: Q_PRIVATE_SLOT(d_func(), void _q_fetchUuidsTimeout()) Q_PRIVATE_SLOT(d_func(), void _q_hostModeStateChanged(QBluetoothLocalDevice::HostMode state)) #endif +#ifdef QT_WIN_BLUETOOTH + Q_PRIVATE_SLOT(d_func(), void _q_nextSdpScan()) +#endif }; QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_p.h b/src/bluetooth/qbluetoothservicediscoveryagent_p.h index c4ea20a9..956ccabb 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_p.h +++ b/src/bluetooth/qbluetoothservicediscoveryagent_p.h @@ -72,6 +72,10 @@ class QXmlStreamReader; QT_END_NAMESPACE #endif +#ifdef QT_WIN_BLUETOOTH +#include <QFutureWatcher> +#endif + QT_BEGIN_NAMESPACE class QBluetoothDeviceDiscoveryAgent; @@ -145,6 +149,9 @@ public: void _q_fetchUuidsTimeout(); void _q_hostModeStateChanged(QBluetoothLocalDevice::HostMode state); #endif +#ifdef QT_WIN_BLUETOOTH + void _q_nextSdpScan(); +#endif private: void start(const QBluetoothAddress &address); @@ -196,6 +203,17 @@ private: QMap<QBluetoothAddress,QPair<QBluetoothDeviceInfo,QList<QBluetoothUuid> > > sdpCache; #endif +#ifdef QT_WIN_BLUETOOTH +private: + int systemError; + bool pendingStop; + bool pendingFinish; + + QFutureWatcher<QBluetoothServiceInfo> *searchWatcher; + + Qt::HANDLE hSearch; +#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..e8df3ebe --- /dev/null +++ b/src/bluetooth/qbluetoothservicediscoveryagent_win.cpp @@ -0,0 +1,431 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbluetoothservicediscoveryagent.h" +#include "qbluetoothservicediscoveryagent_p.h" + +#include <QtCore/QByteArray> +#include <QtConcurrent> + +#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 + +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; +} + +static void cleanupServiceDiscovery(HANDLE hSearch) +{ + if (hSearch != INVALID_HANDLE_VALUE) + WSALookupServiceEnd(hSearch); + WSACleanup(); +} + +enum { + WSAControlFlags = LUP_FLUSHCACHE + | LUP_RETURN_NAME + | LUP_RETURN_TYPE + | LUP_RETURN_ADDR + | LUP_RETURN_BLOB + | LUP_RETURN_COMMENT +}; + +static QBluetoothServiceInfo findNextService(HANDLE hSearch, int *systemError) +{ + QBluetoothServiceInfo result; + + 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) { + *systemError = ::WSAGetLastError(); + if (*systemError == WSA_E_NO_MORE) + cleanupServiceDiscovery(hSearch); + return QBluetoothServiceInfo(); + } + + if (resultQuery->lpBlob + && BluetoothSdpEnumAttributes(resultQuery->lpBlob->pBlobData, + resultQuery->lpBlob->cbSize, + bluetoothSdpCallback, + &result)) { + return result; + } else { + *systemError = GetLastError(); + } + + return result; +} + +static QBluetoothServiceInfo findFirstService(LPHANDLE hSearch, const QBluetoothAddress &address, int *systemError) +{ + //### should we try for 2.2 on all platforms ?? + WSAData wsadata; + + // IPv6 requires Winsock v2.0 or better. + if (WSAStartup(MAKEWORD(2, 0), &wsadata) != 0) { + *systemError = ::WSAGetLastError(); + return QBluetoothServiceInfo(); + } + + 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; + ::ZeroMemory(&serviceQuery, sizeof(serviceQuery)); + 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 + + const int resultCode = WSALookupServiceBegin(&serviceQuery, + WSAControlFlags, + hSearch); + if (resultCode == SOCKET_ERROR) { + *systemError = ::WSAGetLastError(); + cleanupServiceDiscovery(hSearch); + return QBluetoothServiceInfo(); + } + *systemError = NO_ERROR; + return findNextService(*hSearch, systemError); +} + +QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate( + QBluetoothServiceDiscoveryAgent *qp, const QBluetoothAddress &deviceAdapter) + : error(QBluetoothServiceDiscoveryAgent::NoError), + state(Inactive), + deviceDiscoveryAgent(0), + mode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery), + singleDevice(false), + systemError(NO_ERROR), + pendingStop(false), + pendingFinish(false), + searchWatcher(Q_NULLPTR), + hSearch(INVALID_HANDLE_VALUE), + q_ptr(qp) +{ + Q_UNUSED(deviceAdapter); + resolveFunctions(bluetoothapis()); + searchWatcher = new QFutureWatcher<QBluetoothServiceInfo>(); +} + +QBluetoothServiceDiscoveryAgentPrivate::~QBluetoothServiceDiscoveryAgentPrivate() +{ + if (pendingFinish) { + stop(); + searchWatcher->waitForFinished(); + } + delete searchWatcher; +} + +void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &address) +{ + Q_Q(QBluetoothServiceDiscoveryAgent); + if (!pendingFinish) { + pendingFinish = true; + pendingStop = false; + + QObject::connect(searchWatcher, SIGNAL(finished()), q, SLOT(_q_nextSdpScan()), Qt::UniqueConnection); + const QFuture<QBluetoothServiceInfo> future = + QtConcurrent::run(&findFirstService, + &hSearch, + address, + &systemError); + searchWatcher->setFuture(future); + } +} + +void QBluetoothServiceDiscoveryAgentPrivate::stop() +{ + pendingStop = true; +} + +void QBluetoothServiceDiscoveryAgentPrivate::_q_nextSdpScan() +{ + Q_Q(QBluetoothServiceDiscoveryAgent); + + if (pendingStop) { + pendingStop = false; + pendingFinish = false; + emit q->canceled(); + } else { + if (systemError == WSA_E_NO_MORE) { + systemError = NO_ERROR; + } else if (systemError != NO_ERROR) { + error = (systemError == ERROR_INVALID_HANDLE) ? + QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError + : QBluetoothServiceDiscoveryAgent::InputOutputError; + errorString = qt_error_string(systemError); + qCWarning(QT_BT_WINDOWS) << errorString; + emit q->error(this->error); + } else { + QBluetoothServiceInfo serviceInfo = searchWatcher->result(); + serviceInfo.setDevice(discoveredDevices.at(0)); + if (serviceInfo.isValid()) { + if (!isDuplicatedService(serviceInfo)) { + discoveredServices.append(serviceInfo); + emit q->serviceDiscovered(serviceInfo); + } + } + const QFuture<QBluetoothServiceInfo> future = + QtConcurrent::run(&findNextService, hSearch, &systemError); + searchWatcher->setFuture(future); + return; + } + hSearch = INVALID_HANDLE_VALUE; + pendingFinish = false; + _q_serviceDiscoveryFinished(); + } +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothserviceinfo_win.cpp b/src/bluetooth/qbluetoothserviceinfo_win.cpp new file mode 100644 index 00000000..8942149a --- /dev/null +++ b/src/bluetooth/qbluetoothserviceinfo_win.cpp @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $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_win.cpp b/src/bluetooth/qbluetoothsocket_win.cpp new file mode 100644 index 00000000..e18c985b --- /dev/null +++ b/src/bluetooth/qbluetoothsocket_win.cpp @@ -0,0 +1,148 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qbluetoothsocket.h" +#include "qbluetoothsocket_p.h" + +QT_BEGIN_NAMESPACE + +QBluetoothSocketPrivate::QBluetoothSocketPrivate() + : socket(-1), + socketType(QBluetoothServiceInfo::UnknownProtocol), + state(QBluetoothSocket::UnconnectedState), + socketError(QBluetoothSocket::NoSocketError) +{ +} + +QBluetoothSocketPrivate::~QBluetoothSocketPrivate() +{ +} + +bool QBluetoothSocketPrivate::ensureNativeSocket(QBluetoothServiceInfo::Protocol type) +{ + socketType = type; + return false; +} + +void QBluetoothSocketPrivate::connectToService(const QBluetoothAddress &address, quint16 port, QIODevice::OpenMode openMode) +{ + Q_UNUSED(openMode); + Q_UNUSED(address); + Q_UNUSED(port); +} + +void QBluetoothSocketPrivate::abort() +{ +} + +QString QBluetoothSocketPrivate::localName() const +{ + return QString(); +} + +QBluetoothAddress QBluetoothSocketPrivate::localAddress() const +{ + return QBluetoothAddress(); +} + +quint16 QBluetoothSocketPrivate::localPort() const +{ + return 0; +} + +QString QBluetoothSocketPrivate::peerName() const +{ + return QString(); +} + +QBluetoothAddress QBluetoothSocketPrivate::peerAddress() const +{ + return QBluetoothAddress(); +} + +quint16 QBluetoothSocketPrivate::peerPort() const +{ + return 0; +} + +qint64 QBluetoothSocketPrivate::writeData(const char *data, qint64 maxSize) +{ + Q_UNUSED(data); + Q_UNUSED(maxSize); + + Q_Q(QBluetoothSocket); + + if (state != QBluetoothSocket::ConnectedState) { + errorString = QBluetoothSocket::tr("Cannot write while not connected"); + q->setSocketError(QBluetoothSocket::OperationError); + return -1; + } + return -1; +} + +qint64 QBluetoothSocketPrivate::readData(char *data, qint64 maxSize) +{ + Q_UNUSED(data); + Q_UNUSED(maxSize); + + Q_Q(QBluetoothSocket); + + if (state != QBluetoothSocket::ConnectedState) { + errorString = QBluetoothSocket::tr("Cannot write while not connected"); + q->setSocketError(QBluetoothSocket::OperationError); + return -1; + } + + return -1; +} + +void QBluetoothSocketPrivate::close() +{ +} + +bool QBluetoothSocketPrivate::setSocketDescriptor(int socketDescriptor, QBluetoothServiceInfo::Protocol socketType, + QBluetoothSocket::SocketState socketState, QBluetoothSocket::OpenMode openMode) +{ + Q_UNUSED(socketDescriptor); + Q_UNUSED(socketType) + Q_UNUSED(socketState); + Q_UNUSED(openMode); + return false; +} + +qint64 QBluetoothSocketPrivate::bytesAvailable() const +{ + return 0; +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h index 6e866144..84d307c1 100644 --- a/src/bluetooth/qlowenergycontroller_p.h +++ b/src/bluetooth/qlowenergycontroller_p.h @@ -459,6 +459,12 @@ private: void centralConnectionUpdated(QLowEnergyController::ControllerState newState, QLowEnergyController::Error errorCode); +#elif defined(QT_WIN_BLUETOOTH) +protected: + void customEvent(QEvent *e); +private: + QString deviceSystemPath; + #elif defined(QT_WINRT_BLUETOOTH) private slots: void characteristicChanged(int charHandle, const QByteArray &data); diff --git a/src/bluetooth/qlowenergycontroller_win.cpp b/src/bluetooth/qlowenergycontroller_win.cpp new file mode 100644 index 00000000..89f46455 --- /dev/null +++ b/src/bluetooth/qlowenergycontroller_win.cpp @@ -0,0 +1,1194 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2014 Denis Shienkov <denis.shienkov@gmail.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlowenergycontroller_p.h" +#include "qbluetoothdevicediscoveryagent_p.h" + +#include <QtCore/QLoggingCategory> +#include <QtCore/QIODevice> // for open modes +#include <QtCore/QEvent> +#include <QtCore/QMutex> + +#include <algorithm> // for std::max + +#include <windows/qwinlowenergybluetooth_p.h> + +#include <setupapi.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINDOWS) + +Q_GLOBAL_STATIC(QLibrary, bluetoothapis) + +Q_GLOBAL_STATIC(QVector<QLowEnergyControllerPrivate *>, 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.indexOf(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)) { + if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + *systemErrorCode = ::GetLastError(); + 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 (hr == HRESULT_FROM_WIN32(ERROR_MORE_DATA)) { + foundServices.resize(servicesCount); + } else if (hr == S_OK) { + *systemErrorCode = NO_ERROR; + return foundServices; + } else { + *systemErrorCode = ::GetLastError(); + 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 (hr == HRESULT_FROM_WIN32(ERROR_MORE_DATA)) { + foundCharacteristics.resize(characteristicsCount); + } else if (hr == S_OK) { + *systemErrorCode = NO_ERROR; + return foundCharacteristics; + } else { + *systemErrorCode = ::GetLastError(); + 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 HRESULT hr = ::BluetoothGATTGetCharacteristicValue( + hService, + gattCharacteristic, + valueBufferSize, + valueBuffer.isEmpty() ? NULL : reinterpret_cast<PBTH_LE_GATT_CHARACTERISTIC_VALUE>(valueBuffer.data()), + &valueBufferSize, + BLUETOOTH_GATT_FLAG_NONE); + + if (hr == HRESULT_FROM_WIN32(ERROR_MORE_DATA)) { + valueBuffer.resize(valueBufferSize); + } else if (hr == S_OK) { + *systemErrorCode = NO_ERROR; + const PBTH_LE_GATT_CHARACTERISTIC_VALUE value = reinterpret_cast< + PBTH_LE_GATT_CHARACTERISTIC_VALUE>(valueBuffer.data()); + + return QByteArray(reinterpret_cast<const char *>(&value->Data[0]), value->DataSize); + } else { + *systemErrorCode = ::GetLastError(); + 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; + + if (::BluetoothGATTSetCharacteristicValue( + hService, + gattCharacteristic, + reinterpret_cast<PBTH_LE_GATT_CHARACTERISTIC_VALUE>(valueBuffer.data()), + reliableWriteContext, + flags) != S_OK) { + *systemErrorCode = ::GetLastError(); + } else { + *systemErrorCode = NO_ERROR; + } +} + +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 (hr == HRESULT_FROM_WIN32(ERROR_MORE_DATA)) { + foundDescriptors.resize(descriptorsCount); + } else if (hr == S_OK) { + *systemErrorCode = NO_ERROR; + return foundDescriptors; + } else { + *systemErrorCode = ::GetLastError(); + 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 HRESULT hr = ::BluetoothGATTGetDescriptorValue( + hService, + gattDescriptor, + valueBufferSize, + valueBuffer.isEmpty() ? NULL : reinterpret_cast<PBTH_LE_GATT_DESCRIPTOR_VALUE>(valueBuffer.data()), + &valueBufferSize, + BLUETOOTH_GATT_FLAG_NONE); + + if (hr == HRESULT_FROM_WIN32(ERROR_MORE_DATA)) { + valueBuffer.resize(valueBufferSize); + } else if (hr == S_OK) { + *systemErrorCode = NO_ERROR; + const PBTH_LE_GATT_DESCRIPTOR_VALUE value = reinterpret_cast< + PBTH_LE_GATT_DESCRIPTOR_VALUE>(valueBuffer.data()); + + return QByteArray(reinterpret_cast<const char *>(&value->Data[0]), value->DataSize); + } else { + *systemErrorCode = ::GetLastError(); + 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()); + + if (::BluetoothGATTSetDescriptorValue( + hService, + gattDescriptor, + gattValue, + BLUETOOTH_GATT_FLAG_NONE) != S_OK) { + *systemErrorCode = ::GetLastError(); + return; + } + + *systemErrorCode = NO_ERROR; +} + +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<QLowEnergyControllerPrivate *>(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; + if (::BluetoothGATTRegisterEvent( + hService, + CharacteristicValueChangedEvent, + ®istration, + eventChangedCallbackEntry, + context, + &hEvent, + BLUETOOTH_GATT_FLAG_NONE) != S_OK) { + *systemErrorCode = ::GetLastError(); + } else { + *systemErrorCode = NO_ERROR; + } + + return hEvent; +} + +static void unregisterEvent(HANDLE hEvent, int *systemErrorCode) +{ + if (!gattFunctionsResolved) { + *systemErrorCode = ERROR_NOT_SUPPORTED; + return; + } + + if (::BluetoothGATTUnregisterEvent( + hEvent, + BLUETOOTH_GATT_FLAG_NONE) != S_OK) { + *systemErrorCode = ::GetLastError(); + } else { + *systemErrorCode = NO_ERROR; + } +} + +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 QLowEnergyControllerPrivate::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); +} + +QLowEnergyControllerPrivate::QLowEnergyControllerPrivate() + : QObject() + , state(QLowEnergyController::UnconnectedState) + , error(QLowEnergyController::NoError) +{ + QMutexLocker locker(&controllersGuard); + qControllers()->append(this); + + gattFunctionsResolved = resolveFunctions(bluetoothapis()); + if (!gattFunctionsResolved) { + qCWarning(QT_BT_WINDOWS) << "LE is not supported on this OS"; + return; + } +} + +QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate() +{ + QMutexLocker locker(&controllersGuard); + qControllers()->removeAll(this); +} + +void QLowEnergyControllerPrivate::init() +{ + Q_UNIMPLEMENTED(); +} + +void QLowEnergyControllerPrivate::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); + + Q_Q(QLowEnergyController); + emit q->connected(); +} + +void QLowEnergyControllerPrivate::disconnectFromDevice() +{ + if (deviceSystemPath.isEmpty()) { + qCDebug(QT_BT_WINDOWS) << "Already is disconnected"; + return; + } + + setState(QLowEnergyController::ClosingState); + deviceSystemPath.clear(); + setState(QLowEnergyController::UnconnectedState); + + Q_Q(QLowEnergyController); + emit q->disconnected(); +} + +void QLowEnergyControllerPrivate::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 QLowEnergyControllerPrivate::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; + + const HANDLE hService = openSystemService( + remoteDevice, service, QIODevice::ReadOnly, &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(hService, NULL, &systemErrorCode); + + if (systemErrorCode != NO_ERROR) { + closeSystemDevice(hService); + 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( + 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( + hService, const_cast<PBTH_LE_GATT_CHARACTERISTIC>( + &gattCharacteristic), &systemErrorCode); + + if (systemErrorCode != NO_ERROR) { + if (systemErrorCode != ERROR_NOT_FOUND) { + closeSystemDevice(hService); + 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(hService, const_cast<PBTH_LE_GATT_DESCRIPTOR>( + &gattDescriptor), &systemErrorCode); + + if (systemErrorCode != NO_ERROR) { + closeSystemDevice(hService); + 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); + } + + closeSystemDevice(hService); + + servicePrivate->setState(QLowEnergyService::ServiceDiscovered); +} + +void QLowEnergyControllerPrivate::startAdvertising(const QLowEnergyAdvertisingParameters &, const QLowEnergyAdvertisingData &, const QLowEnergyAdvertisingData &) +{ + Q_UNIMPLEMENTED(); +} + +void QLowEnergyControllerPrivate::stopAdvertising() +{ + Q_UNIMPLEMENTED(); +} + +void QLowEnergyControllerPrivate::requestConnectionUpdate(const QLowEnergyConnectionParameters &) +{ + Q_UNIMPLEMENTED(); +} + +void QLowEnergyControllerPrivate::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; + } + + int systemErrorCode = NO_ERROR; + + const HANDLE hService = openSystemService( + remoteDevice, service->uuid, QIODevice::ReadOnly, &systemErrorCode); + + if (systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service->uuid.toString() + << ":" << qt_error_string(systemErrorCode); + service->setError(QLowEnergyService::CharacteristicReadError); + return; + } + + BTH_LE_GATT_CHARACTERISTIC gattCharacteristic = recoverNativeLeGattCharacteristic( + service->startHandle, charHandle, charDetails); + + const QByteArray characteristicValue = getGattCharacteristicValue( + hService, &gattCharacteristic, &systemErrorCode); + closeSystemDevice(hService); + + if (systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << "Unable to get value for characteristic" + << charDetails.uuid.toString() + << "of the service" << service->uuid.toString() + << ":" << qt_error_string(systemErrorCode); + service->setError(QLowEnergyService::CharacteristicReadError); + return; + } + + updateValueOfCharacteristic(charHandle, characteristicValue, false); + + const QLowEnergyCharacteristic ch(service, charHandle); + emit service->characteristicRead(ch, characteristicValue); +} + +void QLowEnergyControllerPrivate::writeCharacteristic( + const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QByteArray &newValue, + QLowEnergyService::WriteMode mode) +{ + Q_ASSERT(!service.isNull()); + if (!service->characteristicList.contains(charHandle)) + return; + + int systemErrorCode = NO_ERROR; + + const HANDLE hService = openSystemService( + remoteDevice, service->uuid, QIODevice::ReadWrite, &systemErrorCode); + + if (systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service->uuid.toString() + << ":" << qt_error_string(systemErrorCode); + service->setError(QLowEnergyService::CharacteristicWriteError); + return; + } + + const QLowEnergyServicePrivate::CharData &charDetails + = service->characteristicList[charHandle]; + + BTH_LE_GATT_CHARACTERISTIC gattCharacteristic = recoverNativeLeGattCharacteristic( + service->startHandle, charHandle, charDetails); + + const DWORD flags = (mode == QLowEnergyService::WriteWithResponse) + ? BLUETOOTH_GATT_FLAG_NONE + : BLUETOOTH_GATT_FLAG_WRITE_WITHOUT_RESPONSE; + + // TODO: If a device is not connected, this function will block + // for some time. So, need to re-implement of writeCharacteristic() + // with use QFutureWatcher. + setGattCharacteristicValue(hService, &gattCharacteristic, + newValue, flags, &systemErrorCode); + closeSystemDevice(hService); + + if (systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << "Unable to set value for characteristic" + << charDetails.uuid.toString() + << "of the service" << service->uuid.toString() + << ":" << qt_error_string(systemErrorCode); + service->setError(QLowEnergyService::CharacteristicWriteError); + return; + } + + updateValueOfCharacteristic(charHandle, newValue, false); + + if (mode == QLowEnergyService::WriteWithResponse) { + const QLowEnergyCharacteristic ch(service, charHandle); + emit service->characteristicWritten(ch, newValue); + } +} + +void QLowEnergyControllerPrivate::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; + + int systemErrorCode = NO_ERROR; + + const HANDLE hService = openSystemService( + remoteDevice, service->uuid, QIODevice::ReadOnly, &systemErrorCode); + + if (systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service->uuid.toString() + << ":" << qt_error_string(systemErrorCode); + service->setError(QLowEnergyService::DescriptorReadError); + return; + } + + const QLowEnergyServicePrivate::DescData &dscrDetails + = charDetails.descriptorList[descriptorHandle]; + + BTH_LE_GATT_DESCRIPTOR gattDescriptor = recoverNativeLeGattDescriptor( + service->startHandle, charHandle, descriptorHandle, dscrDetails); + + const QByteArray value = getGattDescriptorValue( + hService, const_cast<PBTH_LE_GATT_DESCRIPTOR>( + &gattDescriptor), &systemErrorCode); + closeSystemDevice(hService); + + if (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(systemErrorCode); + service->setError(QLowEnergyService::DescriptorReadError); + return; + } + + updateValueOfDescriptor(charHandle, descriptorHandle, value, false); + + QLowEnergyDescriptor dscr(service, charHandle, descriptorHandle); + emit service->descriptorRead(dscr, value); +} + +void QLowEnergyControllerPrivate::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; + + int systemErrorCode = NO_ERROR; + + const HANDLE hService = openSystemService( + remoteDevice, service->uuid, QIODevice::ReadWrite, &systemErrorCode); + + if (systemErrorCode != NO_ERROR) { + qCWarning(QT_BT_WINDOWS) << "Unable to open service" << service->uuid.toString() + << ":" << qt_error_string(systemErrorCode); + service->setError(QLowEnergyService::DescriptorWriteError); + return; + } + + const QLowEnergyServicePrivate::DescData &dscrDetails + = charDetails.descriptorList[descriptorHandle]; + + BTH_LE_GATT_DESCRIPTOR gattDescriptor = recoverNativeLeGattDescriptor( + service->startHandle, charHandle, descriptorHandle, dscrDetails); + + setGattDescriptorValue(hService, &gattDescriptor, newValue, &systemErrorCode); + + if (systemErrorCode != NO_ERROR) { + closeSystemDevice(hService); + 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(systemErrorCode); + service->setError(QLowEnergyService::DescriptorWriteError); + return; + } + + if (gattDescriptor.DescriptorType == ClientCharacteristicConfiguration) { + + QDataStream in(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); + + charDetails.hValueChangeEvent = registerEvent( + hService, gattCharacteristic, this, &systemErrorCode); + } + } else { + if (charDetails.hValueChangeEvent) { + unregisterEvent(charDetails.hValueChangeEvent, &systemErrorCode); + charDetails.hValueChangeEvent = NULL; + } + } + + closeSystemDevice(hService); + + if (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(systemErrorCode); + service->setError(QLowEnergyService::DescriptorWriteError); + return; + } + } else { + closeSystemDevice(hService); + } + + updateValueOfDescriptor(charHandle, descriptorHandle, newValue, false); + + const QLowEnergyDescriptor dscr(service, charHandle, descriptorHandle); + emit service->descriptorWritten(dscr, newValue); +} + +void QLowEnergyControllerPrivate::addToGenericAttributeList(const QLowEnergyServiceData &, QLowEnergyHandle) +{ + Q_UNIMPLEMENTED(); +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergyserviceprivate_p.h b/src/bluetooth/qlowenergyserviceprivate_p.h index 7727b583..26ca56f6 100644 --- a/src/bluetooth/qlowenergyserviceprivate_p.h +++ b/src/bluetooth/qlowenergyserviceprivate_p.h @@ -83,6 +83,9 @@ public: QLowEnergyCharacteristic::PropertyTypes properties; QByteArray value; QHash<QLowEnergyHandle, DescData> descriptorList; +#ifdef QT_WIN_BLUETOOTH + Qt::HANDLE hValueChangeEvent; +#endif }; enum GattAttributeTypes { diff --git a/src/bluetooth/windows/qwinlowenergybluetooth_p.h b/src/bluetooth/windows/qwinlowenergybluetooth_p.h new file mode 100644 index 00000000..c12ea4ae --- /dev/null +++ b/src/bluetooth/windows/qwinlowenergybluetooth_p.h @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Copyright (C) 2014 Denis Shienkov <denis.shienkov@gmail.com> +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINLOWENERGYBLUETOOTH_P_H +#define QWINLOWENERGYBLUETOOTH_P_H + +#include <QtCore/qlibrary.h> + +#include <qt_windows.h> + +#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 |