| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtBluetooth module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qbluetoothsocket_winrt_p.h" |
| |
| #ifdef CLASSIC_APP_BUILD |
| #define Q_OS_WINRT |
| #endif |
| #include <qfunctions_winrt.h> |
| |
| #include <private/qeventdispatcher_winrt_p.h> |
| |
| #include <QtBluetooth/QBluetoothLocalDevice> |
| #include <QtBluetooth/qbluetoothdeviceinfo.h> |
| #include <QtBluetooth/qbluetoothserviceinfo.h> |
| #include <QtCore/qloggingcategory.h> |
| |
| #include <robuffer.h> |
| #include <windows.devices.bluetooth.h> |
| #include <windows.networking.sockets.h> |
| #include <windows.storage.streams.h> |
| #include <wrl.h> |
| |
| using namespace Microsoft::WRL; |
| using namespace Microsoft::WRL::Wrappers; |
| using namespace ABI::Windows::Devices::Bluetooth; |
| using namespace ABI::Windows::Devices::Bluetooth::Rfcomm; |
| using namespace ABI::Windows::Foundation; |
| using namespace ABI::Windows::Foundation::Collections; |
| using namespace ABI::Windows::Networking; |
| using namespace ABI::Windows::Networking::Sockets; |
| using namespace ABI::Windows::Storage::Streams; |
| |
| typedef IAsyncOperationWithProgressCompletedHandler<IBuffer *, UINT32> SocketReadCompletedHandler; |
| typedef IAsyncOperationWithProgress<IBuffer *, UINT32> IAsyncBufferOperation; |
| |
| QT_BEGIN_NAMESPACE |
| |
| Q_DECLARE_LOGGING_CATEGORY(QT_BT_WINRT) |
| |
| struct SocketGlobal |
| { |
| SocketGlobal() |
| { |
| HRESULT hr; |
| hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(), |
| &bufferFactory); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| |
| ComPtr<IBufferFactory> bufferFactory; |
| }; |
| Q_GLOBAL_STATIC(SocketGlobal, g) |
| |
| #define READ_BUFFER_SIZE 65536 |
| |
| static inline QString qt_QStringFromHString(const HString &string) |
| { |
| UINT32 length; |
| PCWSTR rawString = string.GetRawBuffer(&length); |
| if (length > INT_MAX) |
| length = INT_MAX; |
| return QString::fromWCharArray(rawString, int(length)); |
| } |
| |
| static qint64 writeIOStream(ComPtr<IOutputStream> stream, const char *data, qint64 len) |
| { |
| ComPtr<IBuffer> buffer; |
| if (len > UINT32_MAX) { |
| qCWarning(QT_BT_WINRT) << "writeIOStream can only write up to" << UINT32_MAX << "bytes."; |
| len = UINT32_MAX; |
| } |
| quint32 ulen = static_cast<quint32>(len); |
| HRESULT hr = g->bufferFactory->Create(ulen, &buffer); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = buffer->put_Length(ulen); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteArrayAccess; |
| hr = buffer.As(&byteArrayAccess); |
| Q_ASSERT_SUCCEEDED(hr); |
| byte *bytes; |
| hr = byteArrayAccess->Buffer(&bytes); |
| Q_ASSERT_SUCCEEDED(hr); |
| memcpy(bytes, data, ulen); |
| ComPtr<IAsyncOperationWithProgress<UINT32, UINT32>> op; |
| hr = stream->WriteAsync(buffer.Get(), &op); |
| RETURN_IF_FAILED("Failed to write to stream", return -1); |
| UINT32 bytesWritten; |
| hr = QWinRTFunctions::await(op, &bytesWritten); |
| RETURN_IF_FAILED("Failed to write to stream", return -1); |
| return bytesWritten; |
| } |
| |
| class SocketWorker : public QObject |
| { |
| Q_OBJECT |
| public: |
| SocketWorker() |
| { |
| } |
| |
| ~SocketWorker() |
| { |
| } |
| void close() |
| { |
| m_shuttingDown = true; |
| if (Q_UNLIKELY(m_initialReadOp)) { |
| onReadyRead(m_initialReadOp.Get(), Canceled); |
| ComPtr<IAsyncInfo> info; |
| HRESULT hr = m_initialReadOp.As(&info); |
| Q_ASSERT_SUCCEEDED(hr); |
| if (info) { |
| hr = info->Cancel(); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = info->Close(); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| m_initialReadOp.Reset(); |
| } |
| |
| if (m_readOp) { |
| onReadyRead(m_readOp.Get(), Canceled); |
| ComPtr<IAsyncInfo> info; |
| HRESULT hr = m_readOp.As(&info); |
| Q_ASSERT_SUCCEEDED(hr); |
| if (info) { |
| hr = info->Cancel(); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = info->Close(); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| m_readOp.Reset(); |
| } |
| } |
| |
| signals: |
| void newDataReceived(const QVector<QByteArray> &data); |
| void socketErrorOccured(QBluetoothSocket::SocketError error); |
| |
| public slots: |
| Q_INVOKABLE void notifyAboutNewData() |
| { |
| QMutexLocker locker(&m_mutex); |
| const QVector<QByteArray> newData = std::move(m_pendingData); |
| m_pendingData.clear(); |
| emit newDataReceived(newData); |
| } |
| |
| public: |
| void startReading() |
| { |
| HRESULT hr; |
| hr = QEventDispatcherWinRT::runOnXamlThread([this]() |
| { |
| ComPtr<IBuffer> buffer; |
| HRESULT hr = g->bufferFactory->Create(READ_BUFFER_SIZE, &buffer); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IInputStream> stream; |
| hr = m_socket->get_InputStream(&stream); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = stream->ReadAsync(buffer.Get(), READ_BUFFER_SIZE, InputStreamOptions_Partial, m_initialReadOp.GetAddressOf()); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = m_initialReadOp->put_Completed(Callback<SocketReadCompletedHandler>(this, &SocketWorker::onReadyRead).Get()); |
| Q_ASSERT_SUCCEEDED(hr); |
| return S_OK; |
| }); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| |
| HRESULT onReadyRead(IAsyncBufferOperation *asyncInfo, AsyncStatus status) |
| { |
| if (m_shuttingDown) |
| return S_OK; |
| |
| if (asyncInfo == m_initialReadOp.Get()) { |
| m_initialReadOp.Reset(); |
| } else if (asyncInfo == m_readOp.Get()) { |
| m_readOp.Reset(); |
| } else { |
| Q_ASSERT(false); |
| } |
| |
| // A read in UnconnectedState will close the socket and return -1 and thus tell the caller, |
| // that the connection was closed. The socket cannot be closed here, as the subsequent read |
| // might fail then. |
| if (status == Error || status == Canceled) { |
| emit socketErrorOccured(QBluetoothSocket::NetworkError); |
| return S_OK; |
| } |
| |
| ComPtr<IBuffer> buffer; |
| HRESULT hr = asyncInfo->GetResults(&buffer); |
| if (FAILED(hr)) { |
| qErrnoWarning(hr, "Failed to get read results buffer"); |
| emit socketErrorOccured(QBluetoothSocket::UnknownSocketError); |
| return S_OK; |
| } |
| |
| UINT32 bufferLength; |
| hr = buffer->get_Length(&bufferLength); |
| if (FAILED(hr)) { |
| qErrnoWarning(hr, "Failed to get buffer length"); |
| emit socketErrorOccured(QBluetoothSocket::UnknownSocketError); |
| return S_OK; |
| } |
| // A zero sized buffer length signals, that the remote host closed the connection. The socket |
| // cannot be closed though, as the following read might have socket descriptor -1 and thus and |
| // the closing of the socket won't be communicated to the caller. So only the error is set. The |
| // actual socket close happens inside of read. |
| if (!bufferLength) { |
| emit socketErrorOccured(QBluetoothSocket::RemoteHostClosedError); |
| return S_OK; |
| } |
| |
| ComPtr<Windows::Storage::Streams::IBufferByteAccess> byteArrayAccess; |
| hr = buffer.As(&byteArrayAccess); |
| if (FAILED(hr)) { |
| qErrnoWarning(hr, "Failed to get cast buffer"); |
| emit socketErrorOccured(QBluetoothSocket::UnknownSocketError); |
| return S_OK; |
| } |
| byte *data; |
| hr = byteArrayAccess->Buffer(&data); |
| if (FAILED(hr)) { |
| qErrnoWarning(hr, "Failed to access buffer data"); |
| emit socketErrorOccured(QBluetoothSocket::UnknownSocketError); |
| return S_OK; |
| } |
| |
| QByteArray newData(reinterpret_cast<const char*>(data), int(bufferLength)); |
| QMutexLocker readLocker(&m_mutex); |
| if (m_pendingData.isEmpty()) |
| QMetaObject::invokeMethod(this, "notifyAboutNewData", Qt::QueuedConnection); |
| m_pendingData << newData; |
| readLocker.unlock(); |
| |
| hr = QEventDispatcherWinRT::runOnXamlThread([buffer, this]() { |
| UINT32 readBufferLength; |
| ComPtr<IInputStream> stream; |
| HRESULT hr = m_socket->get_InputStream(&stream); |
| if (FAILED(hr)) { |
| qErrnoWarning(hr, "Failed to obtain input stream"); |
| emit socketErrorOccured(QBluetoothSocket::UnknownSocketError); |
| return S_OK; |
| } |
| |
| // Reuse the stream buffer |
| hr = buffer->get_Capacity(&readBufferLength); |
| if (FAILED(hr)) { |
| qErrnoWarning(hr, "Failed to get buffer capacity"); |
| emit socketErrorOccured(QBluetoothSocket::UnknownSocketError); |
| return S_OK; |
| } |
| hr = buffer->put_Length(0); |
| if (FAILED(hr)) { |
| qErrnoWarning(hr, "Failed to set buffer length"); |
| emit socketErrorOccured(QBluetoothSocket::UnknownSocketError); |
| return S_OK; |
| } |
| |
| hr = stream->ReadAsync(buffer.Get(), readBufferLength, InputStreamOptions_Partial, &m_readOp); |
| if (FAILED(hr)) { |
| qErrnoWarning(hr, "onReadyRead(): Could not read into socket stream buffer."); |
| emit socketErrorOccured(QBluetoothSocket::UnknownSocketError); |
| return S_OK; |
| } |
| hr = m_readOp->put_Completed(Callback<SocketReadCompletedHandler>(this, &SocketWorker::onReadyRead).Get()); |
| if (FAILED(hr)) { |
| qErrnoWarning(hr, "onReadyRead(): Failed to set socket read callback."); |
| emit socketErrorOccured(QBluetoothSocket::UnknownSocketError); |
| return S_OK; |
| } |
| return S_OK; |
| }); |
| Q_ASSERT_SUCCEEDED(hr); |
| return S_OK; |
| } |
| |
| void setSocket(ComPtr<IStreamSocket> socket) { m_socket = socket; } |
| |
| private: |
| ComPtr<IStreamSocket> m_socket; |
| QVector<QByteArray> m_pendingData; |
| bool m_shuttingDown = false; |
| |
| // Protects pendingData/pendingDatagrams which are accessed from native callbacks |
| QMutex m_mutex; |
| |
| ComPtr<IAsyncOperationWithProgress<IBuffer *, UINT32>> m_initialReadOp; |
| ComPtr<IAsyncOperationWithProgress<IBuffer *, UINT32>> m_readOp; |
| }; |
| |
| QBluetoothSocketPrivateWinRT::QBluetoothSocketPrivateWinRT() |
| : m_worker(new SocketWorker()) |
| { |
| secFlags = QBluetooth::NoSecurity; |
| connect(m_worker, &SocketWorker::newDataReceived, |
| this, &QBluetoothSocketPrivateWinRT::handleNewData, Qt::QueuedConnection); |
| connect(m_worker, &SocketWorker::socketErrorOccured, |
| this, &QBluetoothSocketPrivateWinRT::handleError, Qt::QueuedConnection); |
| } |
| |
| QBluetoothSocketPrivateWinRT::~QBluetoothSocketPrivateWinRT() |
| { |
| abort(); |
| } |
| |
| bool QBluetoothSocketPrivateWinRT::ensureNativeSocket(QBluetoothServiceInfo::Protocol type) |
| { |
| if (socket != -1) { |
| if (type == socketType) |
| return true; |
| m_socketObject = nullptr; |
| socket = -1; |
| } |
| socketType = type; |
| if (socketType != QBluetoothServiceInfo::RfcommProtocol) |
| return false; |
| |
| HRESULT hr; |
| hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Networking_Sockets_StreamSocket).Get(), &m_socketObject); |
| if (FAILED(hr) || !m_socketObject) { |
| qErrnoWarning(hr, "ensureNativeSocket: Could not create socket instance"); |
| return false; |
| } |
| socket = qintptr(m_socketObject.Get()); |
| m_worker->setSocket(m_socketObject); |
| |
| return true; |
| } |
| |
| void QBluetoothSocketPrivateWinRT::connectToService(Microsoft::WRL::ComPtr<IHostName> hostName, |
| const QString &serviceName, |
| QIODevice::OpenMode openMode) |
| { |
| Q_Q(QBluetoothSocket); |
| |
| if (socket == -1 && !ensureNativeSocket(socketType)) { |
| errorString = QBluetoothSocket::tr("Unknown socket error"); |
| q->setSocketError(QBluetoothSocket::UnknownSocketError); |
| return; |
| } |
| |
| HStringReference serviceNameReference(reinterpret_cast<LPCWSTR>(serviceName.utf16())); |
| |
| HRESULT hr = m_socketObject->ConnectAsync(hostName.Get(), serviceNameReference.Get(), &m_connectOp); |
| if (hr == E_ACCESSDENIED) { |
| qErrnoWarning(hr, "QBluetoothSocketPrivateWinRT::connectToService: Unable to connect to bluetooth socket." |
| "Please check your manifest capabilities."); |
| q->setSocketState(QBluetoothSocket::UnconnectedState); |
| return; |
| } |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| q->setSocketState(QBluetoothSocket::ConnectingState); |
| requestedOpenMode = openMode; |
| hr = QEventDispatcherWinRT::runOnXamlThread([this]() { |
| HRESULT hr; |
| hr = m_connectOp->put_Completed(Callback<IAsyncActionCompletedHandler>( |
| this, &QBluetoothSocketPrivateWinRT::handleConnectOpFinished).Get()); |
| RETURN_HR_IF_FAILED("connectToHostByName: Could not register \"connectOp\" callback"); |
| return S_OK; |
| }); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| |
| void QBluetoothSocketPrivateWinRT::connectToServiceHelper(const QBluetoothAddress &address, quint16 port, QIODevice::OpenMode openMode) |
| { |
| Q_Q(QBluetoothSocket); |
| |
| if (socket == -1 && !ensureNativeSocket(socketType)) { |
| errorString = QBluetoothSocket::tr("Unknown socket error"); |
| q->setSocketError(QBluetoothSocket::UnknownSocketError); |
| return; |
| } |
| |
| const QString addressString = address.toString(); |
| HStringReference hostNameRef(reinterpret_cast<LPCWSTR>(addressString.utf16())); |
| ComPtr<IHostNameFactory> hostNameFactory; |
| HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_HostName).Get(), |
| &hostNameFactory); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IHostName> remoteHost; |
| hr = hostNameFactory->CreateHostName(hostNameRef.Get(), &remoteHost); |
| RETURN_VOID_IF_FAILED("QBluetoothSocketPrivateWinRT::connectToService: Could not create hostname."); |
| const QString portString = QString::number(port); |
| connectToService(remoteHost, portString, openMode); |
| } |
| |
| void QBluetoothSocketPrivateWinRT::connectToService( |
| const QBluetoothServiceInfo &service, QIODevice::OpenMode openMode) |
| { |
| Q_Q(QBluetoothSocket); |
| |
| if (q->state() != QBluetoothSocket::UnconnectedState |
| && q->state() != QBluetoothSocket::ServiceLookupState) { |
| qCWarning(QT_BT_WINRT) << "QBluetoothSocket::connectToService called on busy socket"; |
| errorString = QBluetoothSocket::tr("Trying to connect while connection is in progress"); |
| q->setSocketError(QBluetoothSocket::OperationError); |
| return; |
| } |
| |
| // we are checking the service protocol and not socketType() |
| // socketType will change in ensureNativeSocket() |
| if (service.socketProtocol() != QBluetoothServiceInfo::RfcommProtocol) { |
| errorString = QBluetoothSocket::tr("Socket type not supported"); |
| q->setSocketError(QBluetoothSocket::UnsupportedProtocolError); |
| return; |
| } |
| |
| const QString connectionHostName = service.attribute(0xBEEF).toString(); |
| const QString connectionServiceName = service.attribute(0xBEF0).toString(); |
| if (service.protocolServiceMultiplexer() > 0) { |
| Q_ASSERT(service.socketProtocol() == QBluetoothServiceInfo::L2capProtocol); |
| |
| if (!ensureNativeSocket(QBluetoothServiceInfo::L2capProtocol)) { |
| errorString = QBluetoothSocket::tr("Unknown socket error"); |
| q->setSocketError(QBluetoothSocket::UnknownSocketError); |
| return; |
| } |
| connectToServiceHelper(service.device().address(), |
| quint16(service.protocolServiceMultiplexer()), openMode); |
| } else if (!connectionHostName.isEmpty() && !connectionServiceName.isEmpty()) { |
| Q_ASSERT(service.socketProtocol() == QBluetoothServiceInfo::RfcommProtocol); |
| if (!ensureNativeSocket(QBluetoothServiceInfo::RfcommProtocol)) { |
| errorString = QBluetoothSocket::tr("Unknown socket error"); |
| q->setSocketError(QBluetoothSocket::UnknownSocketError); |
| return; |
| } |
| HStringReference hostNameRef(reinterpret_cast<LPCWSTR>(connectionHostName.utf16())); |
| ComPtr<IHostNameFactory> hostNameFactory; |
| HRESULT hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Networking_HostName).Get(), |
| &hostNameFactory); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IHostName> remoteHost; |
| hr = hostNameFactory->CreateHostName(hostNameRef.Get(), &remoteHost); |
| connectToService(remoteHost, connectionServiceName, openMode); |
| } else if (service.serverChannel() > 0) { |
| Q_ASSERT(service.socketProtocol() == QBluetoothServiceInfo::RfcommProtocol); |
| |
| if (!ensureNativeSocket(QBluetoothServiceInfo::RfcommProtocol)) { |
| errorString = QBluetoothSocket::tr("Unknown socket error"); |
| q->setSocketError(QBluetoothSocket::UnknownSocketError); |
| return; |
| } |
| connectToServiceHelper(service.device().address(), quint16(service.serverChannel()), |
| openMode); |
| } else { |
| // try doing service discovery to see if we can find the socket |
| if (service.serviceUuid().isNull() |
| && !service.serviceClassUuids().contains(QBluetoothUuid::SerialPort)) { |
| qCWarning(QT_BT_WINRT) << "No port, no PSM, and no UUID provided. Unable to connect"; |
| return; |
| } |
| qCDebug(QT_BT_WINRT) << "Need a port/psm, doing discovery"; |
| q->doDeviceDiscovery(service, openMode); |
| } |
| } |
| |
| void QBluetoothSocketPrivateWinRT::connectToService( |
| const QBluetoothAddress &address, const QBluetoothUuid &uuid, QIODevice::OpenMode openMode) |
| { |
| Q_Q(QBluetoothSocket); |
| |
| if (q->state() != QBluetoothSocket::UnconnectedState) { |
| qCWarning(QT_BT_WINRT) << "QBluetoothSocketPrivateWinRT::connectToService called on busy socket"; |
| errorString = QBluetoothSocket::tr("Trying to connect while connection is in progress"); |
| q->setSocketError(QBluetoothSocket::OperationError); |
| return; |
| } |
| |
| if (q->socketType() != QBluetoothServiceInfo::RfcommProtocol) { |
| errorString = QBluetoothSocket::tr("Socket type not supported"); |
| q->setSocketError(QBluetoothSocket::UnsupportedProtocolError); |
| return; |
| } |
| |
| QBluetoothServiceInfo service; |
| QBluetoothDeviceInfo device(address, QString(), QBluetoothDeviceInfo::MiscellaneousDevice); |
| service.setDevice(device); |
| service.setServiceUuid(uuid); |
| q->doDeviceDiscovery(service, openMode); |
| } |
| |
| void QBluetoothSocketPrivateWinRT::connectToService( |
| const QBluetoothAddress &address, quint16 port, QIODevice::OpenMode openMode) |
| { |
| Q_Q(QBluetoothSocket); |
| |
| if (q->state() != QBluetoothSocket::UnconnectedState) { |
| qCWarning(QT_BT_WINRT) << "QBluetoothSocketPrivateWinRT::connectToService called on busy socket"; |
| errorString = QBluetoothSocket::tr("Trying to connect while connection is in progress"); |
| q->setSocketError(QBluetoothSocket::OperationError); |
| return; |
| } |
| |
| if (q->socketType() != QBluetoothServiceInfo::RfcommProtocol) { |
| errorString = QBluetoothSocket::tr("Socket type not supported"); |
| q->setSocketError(QBluetoothSocket::UnsupportedProtocolError); |
| return; |
| } |
| |
| connectToServiceHelper(address, port, openMode); |
| } |
| |
| void QBluetoothSocketPrivateWinRT::abort() |
| { |
| Q_Q(QBluetoothSocket); |
| if (state == QBluetoothSocket::UnconnectedState) |
| return; |
| |
| disconnect(m_worker, &SocketWorker::newDataReceived, |
| this, &QBluetoothSocketPrivateWinRT::handleNewData); |
| disconnect(m_worker, &SocketWorker::socketErrorOccured, |
| this, &QBluetoothSocketPrivateWinRT::handleError); |
| m_worker->close(); |
| m_worker->deleteLater(); |
| |
| if (socket != -1) { |
| m_socketObject = nullptr; |
| socket = -1; |
| } |
| |
| const bool wasConnected = q->state() == QBluetoothSocket::ConnectedState; |
| q->setSocketState(QBluetoothSocket::UnconnectedState); |
| if (wasConnected) { |
| q->setOpenMode(QIODevice::NotOpen); |
| emit q->readChannelFinished(); |
| } |
| } |
| |
| QString QBluetoothSocketPrivateWinRT::localName() const |
| { |
| const QBluetoothAddress address = localAddress(); |
| if (address.isNull()) |
| return QString(); |
| |
| QBluetoothLocalDevice device(address); |
| return device.name(); |
| } |
| |
| QBluetoothAddress QBluetoothSocketPrivateWinRT::localAddress() const |
| { |
| if (!m_socketObject) |
| return QBluetoothAddress(); |
| |
| HRESULT hr; |
| ComPtr<IStreamSocketInformation> info; |
| hr = m_socketObject->get_Information(&info); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IHostName> localHost; |
| hr = info->get_LocalAddress(&localHost); |
| Q_ASSERT_SUCCEEDED(hr); |
| HString localAddress; |
| hr = localHost->get_CanonicalName(localAddress.GetAddressOf()); |
| Q_ASSERT_SUCCEEDED(hr); |
| return QBluetoothAddress(qt_QStringFromHString(localAddress)); |
| } |
| |
| quint16 QBluetoothSocketPrivateWinRT::localPort() const |
| { |
| if (!m_socketObject) |
| return 0; |
| |
| HRESULT hr; |
| ComPtr<IStreamSocketInformation> info; |
| hr = m_socketObject->get_Information(&info); |
| Q_ASSERT_SUCCEEDED(hr); |
| HString localPortString; |
| hr = info->get_LocalPort(localPortString.GetAddressOf()); |
| Q_ASSERT_SUCCEEDED(hr); |
| bool ok = true; |
| const uint port = qt_QStringFromHString(localPortString).toUInt(&ok); |
| if (!ok || port > UINT16_MAX) { |
| qCWarning(QT_BT_WINRT) << "Unexpected local port"; |
| return 0; |
| } |
| return quint16(port); |
| } |
| |
| QString QBluetoothSocketPrivateWinRT::peerName() const |
| { |
| if (!m_socketObject) |
| return QString(); |
| |
| HRESULT hr; |
| ComPtr<IStreamSocketInformation> info; |
| hr = m_socketObject->get_Information(&info); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IHostName> remoteHost; |
| hr = info->get_RemoteHostName(&remoteHost); |
| Q_ASSERT_SUCCEEDED(hr); |
| HString remoteHostName; |
| hr = remoteHost->get_DisplayName(remoteHostName.GetAddressOf()); |
| Q_ASSERT_SUCCEEDED(hr); |
| return qt_QStringFromHString(remoteHostName); |
| } |
| |
| QBluetoothAddress QBluetoothSocketPrivateWinRT::peerAddress() const |
| { |
| if (!m_socketObject) |
| return QBluetoothAddress(); |
| |
| HRESULT hr; |
| ComPtr<IStreamSocketInformation> info; |
| hr = m_socketObject->get_Information(&info); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IHostName> remoteHost; |
| hr = info->get_RemoteAddress(&remoteHost); |
| Q_ASSERT_SUCCEEDED(hr); |
| HString remoteAddress; |
| hr = remoteHost->get_CanonicalName(remoteAddress.GetAddressOf()); |
| Q_ASSERT_SUCCEEDED(hr); |
| return QBluetoothAddress(qt_QStringFromHString(remoteAddress)); |
| } |
| |
| quint16 QBluetoothSocketPrivateWinRT::peerPort() const |
| { |
| if (!m_socketObject) |
| return 0; |
| |
| HRESULT hr; |
| ComPtr<IStreamSocketInformation> info; |
| hr = m_socketObject->get_Information(&info); |
| Q_ASSERT_SUCCEEDED(hr); |
| HString remotePortString; |
| hr = info->get_LocalPort(remotePortString.GetAddressOf()); |
| Q_ASSERT_SUCCEEDED(hr); |
| bool ok = true; |
| const uint port = qt_QStringFromHString(remotePortString).toUInt(&ok); |
| if (!ok || port > UINT16_MAX) { |
| qCWarning(QT_BT_WINRT) << "Unexpected remote port"; |
| return 0; |
| } |
| return quint16(port); |
| } |
| |
| qint64 QBluetoothSocketPrivateWinRT::writeData(const char *data, qint64 maxSize) |
| { |
| Q_Q(QBluetoothSocket); |
| |
| if (state != QBluetoothSocket::ConnectedState) { |
| errorString = QBluetoothSocket::tr("Cannot write while not connected"); |
| q->setSocketError(QBluetoothSocket::OperationError); |
| return -1; |
| } |
| |
| ComPtr<IOutputStream> stream; |
| HRESULT hr; |
| hr = m_socketObject->get_OutputStream(&stream); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| qint64 bytesWritten = writeIOStream(stream, data, maxSize); |
| if (bytesWritten < 0) { |
| qCWarning(QT_BT_WINRT) << "Socket::writeData: " << state; |
| errorString = QBluetoothSocket::tr("Cannot read while not connected"); |
| q->setSocketError(QBluetoothSocket::OperationError); |
| } |
| |
| emit q->bytesWritten(bytesWritten); |
| return bytesWritten; |
| } |
| |
| qint64 QBluetoothSocketPrivateWinRT::readData(char *data, qint64 maxSize) |
| { |
| Q_Q(QBluetoothSocket); |
| |
| if (state != QBluetoothSocket::ConnectedState) { |
| errorString = QBluetoothSocket::tr("Cannot read while not connected"); |
| q->setSocketError(QBluetoothSocket::OperationError); |
| return -1; |
| } |
| |
| if (!buffer.isEmpty()) { |
| if (maxSize > INT_MAX) |
| maxSize = INT_MAX; |
| return buffer.read(data, int(maxSize)); |
| } |
| |
| return 0; |
| } |
| |
| void QBluetoothSocketPrivateWinRT::close() |
| { |
| abort(); |
| } |
| |
| bool QBluetoothSocketPrivateWinRT::setSocketDescriptor(int socketDescriptor, QBluetoothServiceInfo::Protocol socketType, |
| QBluetoothSocket::SocketState socketState, QBluetoothSocket::OpenMode openMode) |
| { |
| Q_UNUSED(socketDescriptor); |
| Q_UNUSED(socketType) |
| Q_UNUSED(socketState); |
| Q_UNUSED(openMode); |
| qCWarning(QT_BT_WINRT) << "No socket descriptor support on WinRT."; |
| return false; |
| } |
| |
| bool QBluetoothSocketPrivateWinRT::setSocketDescriptor(ComPtr<IStreamSocket> socketPtr, QBluetoothServiceInfo::Protocol socketType, |
| QBluetoothSocket::SocketState socketState, QBluetoothSocket::OpenMode openMode) |
| { |
| Q_Q(QBluetoothSocket); |
| if (socketType != QBluetoothServiceInfo::RfcommProtocol || !socketPtr) |
| return false; |
| |
| m_socketObject = socketPtr; |
| socket = qintptr(m_socketObject.Get()); |
| m_worker->setSocket(m_socketObject); |
| q->setSocketState(socketState); |
| if (socketState == QBluetoothSocket::ConnectedState) |
| m_worker->startReading(); |
| q->setOpenMode(openMode); |
| return true; |
| } |
| |
| qint64 QBluetoothSocketPrivateWinRT::bytesAvailable() const |
| { |
| return buffer.size(); |
| } |
| |
| qint64 QBluetoothSocketPrivateWinRT::bytesToWrite() const |
| { |
| return 0; // nothing because always unbuffered |
| } |
| |
| bool QBluetoothSocketPrivateWinRT::canReadLine() const |
| { |
| return buffer.canReadLine(); |
| } |
| |
| void QBluetoothSocketPrivateWinRT::handleNewData(const QVector<QByteArray> &data) |
| { |
| // Defer putting the data into the list until the next event loop iteration |
| // (where the readyRead signal is emitted as well) |
| QMetaObject::invokeMethod(this, "addToPendingData", Qt::QueuedConnection, |
| Q_ARG(QVector<QByteArray>, data)); |
| } |
| |
| void QBluetoothSocketPrivateWinRT::handleError(QBluetoothSocket::SocketError error) |
| { |
| Q_Q(QBluetoothSocket); |
| switch (error) { |
| case QBluetoothSocket::NetworkError: |
| errorString = QBluetoothSocket::tr("Network error"); |
| break; |
| case QBluetoothSocket::RemoteHostClosedError: |
| errorString = QBluetoothSocket::tr("Remote host closed connection"); |
| break; |
| default: |
| errorString = QBluetoothSocket::tr("Unknown socket error"); |
| } |
| |
| q->setSocketError(error); |
| const bool wasConnected = q->state() == QBluetoothSocket::ConnectedState; |
| q->setSocketState(QBluetoothSocket::UnconnectedState); |
| if (wasConnected) { |
| q->setOpenMode(QIODevice::NotOpen); |
| emit q->readChannelFinished(); |
| } |
| } |
| |
| void QBluetoothSocketPrivateWinRT::addToPendingData(const QVector<QByteArray> &data) |
| { |
| Q_Q(QBluetoothSocket); |
| QMutexLocker locker(&m_readMutex); |
| m_pendingData.append(data); |
| for (const QByteArray &newData : data) { |
| char *writePointer = buffer.reserve(newData.length()); |
| memcpy(writePointer, newData.data(), size_t(newData.length())); |
| } |
| locker.unlock(); |
| emit q->readyRead(); |
| } |
| |
| HRESULT QBluetoothSocketPrivateWinRT::handleConnectOpFinished(ABI::Windows::Foundation::IAsyncAction *action, ABI::Windows::Foundation::AsyncStatus status) |
| { |
| Q_Q(QBluetoothSocket); |
| if (status != Completed || !m_connectOp) { // Protect against a late callback |
| errorString = QBluetoothSocket::tr("Unknown socket error"); |
| q->setSocketError(QBluetoothSocket::UnknownSocketError); |
| q->setSocketState(QBluetoothSocket::UnconnectedState); |
| return S_OK; |
| } |
| |
| HRESULT hr = action->GetResults(); |
| switch (hr) { |
| |
| // A connection attempt failed because the connected party did not properly respond after a |
| // period of time, or established connection failed because connected host has failed to respond. |
| case HRESULT_FROM_WIN32(WSAETIMEDOUT): |
| errorString = QBluetoothSocket::tr("Connection timed out"); |
| q->setSocketError(QBluetoothSocket::NetworkError); |
| q->setSocketState(QBluetoothSocket::UnconnectedState); |
| return S_OK; |
| // A socket operation was attempted to an unreachable host. |
| case HRESULT_FROM_WIN32(WSAEHOSTUNREACH): |
| errorString = QBluetoothSocket::tr("Host not reachable"); |
| q->setSocketError(QBluetoothSocket::HostNotFoundError); |
| q->setSocketState(QBluetoothSocket::UnconnectedState); |
| return S_OK; |
| // No connection could be made because the target machine actively refused it. |
| case HRESULT_FROM_WIN32(WSAECONNREFUSED): |
| errorString = QBluetoothSocket::tr("Host refused connection"); |
| q->setSocketError(QBluetoothSocket::HostNotFoundError); |
| q->setSocketState(QBluetoothSocket::UnconnectedState); |
| return S_OK; |
| default: |
| if (FAILED(hr)) { |
| errorString = QBluetoothSocket::tr("Unknown socket error"); |
| q->setSocketError(QBluetoothSocket::UnknownSocketError); |
| q->setSocketState(QBluetoothSocket::UnconnectedState); |
| return S_OK; |
| } |
| } |
| |
| // The callback might be triggered several times if we do not cancel/reset it here |
| if (m_connectOp) { |
| ComPtr<IAsyncInfo> info; |
| hr = m_connectOp.As(&info); |
| Q_ASSERT_SUCCEEDED(hr); |
| if (info) { |
| hr = info->Cancel(); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = info->Close(); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| m_connectOp.Reset(); |
| } |
| |
| q->setOpenMode(requestedOpenMode); |
| q->setSocketState(QBluetoothSocket::ConnectedState); |
| m_worker->startReading(); |
| |
| return S_OK; |
| } |
| |
| QT_END_NAMESPACE |
| |
| #include "qbluetoothsocket_winrt.moc" |