Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qbluetoothsocket_macos.mm
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5// The order is important (the first header contains
6// the base class for a private socket) - workaround for
7// dependencies problem.
10
15#include "darwin/btutility_p.h"
16#include "darwin/uistrings_p.h"
17#include "qbluetoothsocket.h"
18
19#include <QtCore/qloggingcategory.h>
20#include <QtCore/qcoreapplication.h>
21#include <QtCore/qmetaobject.h>
22
23#include <algorithm>
24#include <limits>
25
27
28namespace {
29
31
32} // unnamed namespace
33
34QBluetoothSocketPrivateDarwin::QBluetoothSocketPrivateDarwin()
35 : writeChunk(std::numeric_limits<UInt16>::max())
36{
37 q_ptr = nullptr;
38}
39
40QBluetoothSocketPrivateDarwin::~QBluetoothSocketPrivateDarwin()
41{
42}
43
44bool QBluetoothSocketPrivateDarwin::ensureNativeSocket(QBluetoothServiceInfo::Protocol type)
45{
46 // For now - very simplistic, we don't call it in this file, public class
47 // only calls it in a ctor, setting the protocol RFCOMM (in case of Android)
48 // or, indeed, doing, socket-related initialization in BlueZ backend.
52}
53
54QString QBluetoothSocketPrivateDarwin::localName() const
55{
57 return device.name();
58}
59
60QBluetoothAddress QBluetoothSocketPrivateDarwin::localAddress() const
61{
63 return device.address();
64}
65
66quint16 QBluetoothSocketPrivateDarwin::localPort() const
67{
68 return 0;
69}
70
71QString QBluetoothSocketPrivateDarwin::peerName() const
72{
74
75 NSString *nsName = nil;
77 if (rfcommChannel)
78 nsName = [rfcommChannel.getAs<DarwinBTRFCOMMChannel>() peerName];
80 if (l2capChannel)
81 nsName = [l2capChannel.getAs<DarwinBTL2CAPChannel>() peerName];
82 }
83
84 if (nsName)
85 return QString::fromNSString(nsName);
86
87 return QString();
88}
89
90QBluetoothAddress QBluetoothSocketPrivateDarwin::peerAddress() const
91{
92 BluetoothDeviceAddress addr = {};
94 if (rfcommChannel)
95 addr = [rfcommChannel.getAs<DarwinBTRFCOMMChannel>() peerAddress];
97 if (l2capChannel)
98 addr = [l2capChannel.getAs<DarwinBTL2CAPChannel>() peerAddress];
99 }
100
102}
103
104quint16 QBluetoothSocketPrivateDarwin::peerPort() const
105{
107 if (rfcommChannel)
108 return [rfcommChannel.getAs<DarwinBTRFCOMMChannel>() getChannelID];
110 if (l2capChannel)
111 return [l2capChannel.getAs<DarwinBTL2CAPChannel>() getPSM];
112 }
113
114 return 0;
115}
116
117void QBluetoothSocketPrivateDarwin::abort()
118{
119 // Can never be called while we're in connectToService:
120 Q_ASSERT_X(!isConnecting, Q_FUNC_INFO, "internal inconsistency - "
121 "still in connectToService()");
122
124 rfcommChannel.reset();
126 l2capChannel.reset();
127
128 Q_ASSERT(q_ptr);
129
131 emit q_ptr->readChannelFinished();
132
133}
134
135void QBluetoothSocketPrivateDarwin::close()
136{
137 // Can never be called while we're in connectToService:
138 Q_ASSERT_X(!isConnecting, Q_FUNC_INFO, "internal inconsistency - "
139 "still in connectToService()");
140
141 if (!txBuffer.size())
142 abort();
143}
144
145
146qint64 QBluetoothSocketPrivateDarwin::writeData(const char *data, qint64 maxSize)
147{
148 Q_ASSERT_X(data, Q_FUNC_INFO, "invalid data (null)");
149 Q_ASSERT_X(maxSize > 0, Q_FUNC_INFO, "invalid data size");
150
154 return -1;
155 }
156
157 // We do not have a real socket API under the hood,
158 // IOBluetoothL2CAPChannel is buffered (writeAsync).
159
160 if (!txBuffer.size())
161 QMetaObject::invokeMethod(this, [this](){_q_writeNotify();}, Qt::QueuedConnection);
162
163 char *dst = txBuffer.reserve(int(maxSize));
164 std::copy(data, data + maxSize, dst);
165
166 return maxSize;
167}
168
169qint64 QBluetoothSocketPrivateDarwin::readData(char *data, qint64 maxSize)
170{
171 if (!data)
172 return 0;
173
177 return -1;
178 }
179
180 if (!rxBuffer.isEmpty())
181 return rxBuffer.read(data, int(maxSize));
182
183 return 0;
184}
185
186qint64 QBluetoothSocketPrivateDarwin::bytesAvailable() const
187{
188 return rxBuffer.size();
189}
190
191bool QBluetoothSocketPrivateDarwin::canReadLine() const
192{
193 return rxBuffer.canReadLine();
194}
195
196qint64 QBluetoothSocketPrivateDarwin::bytesToWrite() const
197{
198 return txBuffer.size();
199}
200
201bool QBluetoothSocketPrivateDarwin::setSocketDescriptor(int socketDescriptor, QBluetoothServiceInfo::Protocol socketType,
202 QBluetoothSocket::SocketState socketState, QIODevice::OpenMode openMode)
203{
204 Q_UNUSED(socketDescriptor);
206 Q_UNUSED(socketState);
207 Q_UNUSED(openMode);
208
209 qCWarning(QT_BT_DARWIN) << "setting a socket descriptor is not supported by IOBluetooth";
210 // Noop on macOS.
211 return false;
212}
213
214void QBluetoothSocketPrivateDarwin::connectToServiceHelper(const QBluetoothAddress &address, quint16 port,
215 QIODevice::OpenMode openMode)
216{
218 Q_UNUSED(port);
219 Q_UNUSED(openMode);
220}
221
222void QBluetoothSocketPrivateDarwin::connectToService(const QBluetoothServiceInfo &service, QIODevice::OpenMode openMode)
223{
224 Q_ASSERT(q_ptr);
225
227
229 qCWarning(QT_BT_DARWIN) << "called on a busy socket";
232 return;
233 }
234
235 // Report this problem early, potentially avoid device discovery:
236 if (service.socketProtocol() == QBluetoothServiceInfo::UnknownProtocol) {
237 qCWarning(QT_BT_DARWIN) << Q_FUNC_INFO << "cannot connect with 'UnknownProtocol' type";
240 return;
241 }
242
243 socketType = service.socketProtocol();
244
245 if (service.protocolServiceMultiplexer() > 0) {
246 connectToService(service.device().address(),
247 quint16(service.protocolServiceMultiplexer()),
248 openMode);
249 } else if (service.serverChannel() > 0) {
250 connectToService(service.device().address(),
251 quint16(service.serverChannel()),
252 openMode);
253 } else {
254 // Try service discovery.
255 if (service.serviceUuid().isNull()) {
256 qCWarning(QT_BT_DARWIN) << "No port, no PSM, and no "
257 "UUID provided, unable to connect";
258 return;
259 }
260
261 q_ptr->doDeviceDiscovery(service, openMode);
262 }
263}
264
265void QBluetoothSocketPrivateDarwin::connectToService(const QBluetoothAddress &address, const QBluetoothUuid &uuid,
266 QIODevice::OpenMode openMode)
267{
268 Q_ASSERT(q_ptr);
269
271
272 // Report this problem early, avoid device discovery:
274 qCWarning(QT_BT_DARWIN) << Q_FUNC_INFO << "cannot connect with 'UnknownProtocol' type";
277 return;
278 }
279
281 qCWarning(QT_BT_DARWIN) << "called on a busy socket";
284 return;
285 }
286
289 service.setDevice(device);
290 service.setServiceUuid(uuid);
291 q_ptr->doDeviceDiscovery(service, openMode);
292}
293
294void QBluetoothSocketPrivateDarwin::connectToService(const QBluetoothAddress &address, quint16 port,
295 QIODevice::OpenMode mode)
296{
297 Q_ASSERT(q_ptr);
298
300
302 qCWarning(QT_BT_DARWIN) << Q_FUNC_INFO << "cannot connect with 'UnknownProtocol' type";
305 return;
306 }
307
309 Q_FUNC_INFO, "invalid state");
310
311 q_ptr->setOpenMode(mode);
312
314 errorString.clear();
315 rxBuffer.clear();
316 txBuffer.clear();
317
318 IOReturn status = kIOReturnError;
319 // Setting socket state on q_ptr will emit a signal,
320 // we'd like to avoid any signal until this function completes.
321 const QBluetoothSocket::SocketState oldState = state;
322 // To prevent other connectToService calls (from QBluetoothSocket):
323 // and also avoid signals in delegate callbacks.
325 // We're still inside this function:
326 isConnecting = true;
327
328 // We'll later (or now) have to set an open mode on QBluetoothSocket.
329 openMode = mode;
330
332 rfcommChannel.reset([[DarwinBTRFCOMMChannel alloc] initWithDelegate:this], RetainPolicy::noInitialRetain);
333 if (rfcommChannel)
334 status = [rfcommChannel.getAs<DarwinBTRFCOMMChannel>() connectAsyncToDevice:address withChannelID:port];
335 else
336 status = kIOReturnNoMemory;
338 l2capChannel.reset([[DarwinBTL2CAPChannel alloc] initWithDelegate:this], RetainPolicy::noInitialRetain);
339 if (l2capChannel)
340 status = [l2capChannel.getAs<DarwinBTL2CAPChannel>() connectAsyncToDevice:address withPSM:port];
341 else
342 status = kIOReturnNoMemory;
343 }
344
345 // We're probably still connecting, but at least are leaving this function:
346 isConnecting = false;
347
348 // QBluetoothSocket will change the state and also emit
349 // a signal later if required.
350 if (status == kIOReturnSuccess && socketError == QBluetoothSocket::SocketError::NoSocketError) {
352 // Callback 'channelOpenComplete' fired before
353 // connectToService finished:
354 state = oldState;
355 // Connected, setOpenMode on a QBluetoothSocket.
356 q_ptr->setOpenMode(openMode);
358 if (rxBuffer.size()) // We also have some data already ...
359 emit q_ptr->readyRead();
361 // Even if we have some data, we can not read it if
362 // state != ConnectedState.
363 rxBuffer.clear();
364 state = oldState;
366 } else {
367 // No error and we're connecting ...
368 state = oldState;
370 }
371 } else {
372 state = oldState;
373 if (status != kIOReturnSuccess)
376 }
377}
378
379void QBluetoothSocketPrivateDarwin::_q_writeNotify()
380{
383 Q_FUNC_INFO, "invalid socket type");
384 Q_ASSERT_X(l2capChannel || rfcommChannel, Q_FUNC_INFO,
385 "invalid socket (no open channel)");
386 Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)");
387
388 if (txBuffer.size()) {
389 const bool isL2CAP = socketType == QBluetoothServiceInfo::L2capProtocol;
390 writeChunk.resize(isL2CAP ? std::numeric_limits<UInt16>::max() :
391 [rfcommChannel.getAs<DarwinBTRFCOMMChannel>() getMTU]);
392
393 const auto size = txBuffer.read(writeChunk.data(), writeChunk.size());
394 IOReturn status = kIOReturnError;
395 if (!isL2CAP)
396 status = [rfcommChannel.getAs<DarwinBTRFCOMMChannel>() writeAsync:writeChunk.data() length:UInt16(size)];
397 else
398 status = [l2capChannel.getAs<DarwinBTL2CAPChannel>() writeAsync:writeChunk.data() length:UInt16(size)];
399
400 if (status != kIOReturnSuccess) {
402 q_ptr->setSocketError(QBluetoothSocket::SocketError::NetworkError);
403 return;
404 } else {
405 emit q_ptr->bytesWritten(size);
406 }
407 }
408
409 if (!txBuffer.size() && state == QBluetoothSocket::SocketState::ClosingState)
410 close();
411}
412
413bool QBluetoothSocketPrivateDarwin::setRFCOMChannel(void *generic)
414{
415 // A special case "constructor": on OS X we do not have a real listening socket,
416 // instead a bluetooth server "listens" for channel open notifications and
417 // creates (if asked by a user later) a "socket" object
418 // for this connection. This function initializes
419 // a "socket" from such an external channel (reported by a notification).
420 auto channel = static_cast<IOBluetoothRFCOMMChannel *>(generic);
421 // It must be a newborn socket!
423 && state == QBluetoothSocket::SocketState::UnconnectedState && !rfcommChannel && !l2capChannel,
424 Q_FUNC_INFO, "unexpected socket state");
425
426 openMode = QIODevice::ReadWrite;
427 rfcommChannel.reset([[DarwinBTRFCOMMChannel alloc] initWithDelegate:this channel:channel],
428 RetainPolicy::noInitialRetain);
429 if (rfcommChannel) {// We do not handle errors, up to an external user.
430 q_ptr->setOpenMode(QIODevice::ReadWrite);
433 }
434
435 return rfcommChannel;
436}
437
438bool QBluetoothSocketPrivateDarwin::setL2CAPChannel(void *generic)
439{
440 // A special case "constructor": on OS X we do not have a real listening socket,
441 // instead a bluetooth server "listens" for channel open notifications and
442 // creates (if asked by a user later) a "socket" object
443 // for this connection. This function initializes
444 // a "socket" from such an external channel (reported by a notification).
445 auto channel = static_cast<IOBluetoothL2CAPChannel *>(generic);
446
447 // It must be a newborn socket!
449 && state == QBluetoothSocket::SocketState::UnconnectedState && !l2capChannel && !rfcommChannel,
450 Q_FUNC_INFO, "unexpected socket state");
451
452 openMode = QIODevice::ReadWrite;
453 l2capChannel.reset([[DarwinBTL2CAPChannel alloc] initWithDelegate:this channel:channel], RetainPolicy::noInitialRetain);
454 if (l2capChannel) {// We do not handle errors, up to an external user.
455 q_ptr->setOpenMode(QIODevice::ReadWrite);
458 }
459
460 return l2capChannel;
461}
462
463void QBluetoothSocketPrivateDarwin::setChannelError(IOReturn errorCode)
464{
465 Q_UNUSED(errorCode);
466
467 Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)");
468
469 if (isConnecting) {
470 // The delegate's method was called while we are still in
471 // connectToService ... will emit a moment later.
473 } else {
475 }
476}
477
478void QBluetoothSocketPrivateDarwin::channelOpenComplete()
479{
480 Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)");
481
482 if (!isConnecting) {
483 q_ptr->setOpenMode(openMode);
485 } else {
487 // We are still in connectToService, it'll care
488 // about signals!
489 }
490}
491
492void QBluetoothSocketPrivateDarwin::channelClosed()
493{
494 Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)");
495
496 // Channel was closed by IOBluetooth and we can not write any data
497 // (thus close/abort probably will not work).
498
499 if (!isConnecting) {
500 q_ptr->setOpenMode(QIODevice::NotOpen);
502 emit q_ptr->readChannelFinished();
503 } else {
505 // We are still in connectToService and do not want
506 // to emit any signals yet.
507 }
508}
509
510void QBluetoothSocketPrivateDarwin::readChannelData(void *data, std::size_t size)
511{
512 Q_ASSERT_X(data, Q_FUNC_INFO, "invalid data (null)");
513 Q_ASSERT_X(size, Q_FUNC_INFO, "invalid data size (0)");
514 Q_ASSERT_X(q_ptr, Q_FUNC_INFO, "invalid q_ptr (null)");
515
516 const char *src = static_cast<char *>(data);
517 char *dst = rxBuffer.reserve(int(size));
518 std::copy(src, src + size, dst);
519
520 if (!isConnecting) {
521 // If we're still in connectToService, do not emit.
522 emit q_ptr->readyRead();
523 } // else connectToService must check and emit readyRead!
524}
525
526void QBluetoothSocketPrivateDarwin::writeComplete()
527{
528 _q_writeNotify();
529}
530
IOBluetoothL2CAPChannel * channel
IOBluetoothDevice * device
#define QT_BT_MAC_AUTORELEASEPOOL
Definition btutility_p.h:78
\inmodule QtBluetooth
\inmodule QtBluetooth
\inmodule QtBluetooth
\inmodule QtBluetooth
Protocol
This enum describes the socket protocol used by the service.
SocketState
This enum describes the state of the Bluetooth socket.
\inmodule QtBluetooth
static QString translate(const char *context, const char *key, const char *disambiguation=nullptr, int n=-1)
\threadsafe
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
#define this
Definition dialogs.cpp:9
else opt state
[0]
QString qt_address(NSString *address)
Definition btutility.mm:36
void qt_test_iobluetooth_runloop()
Definition btutility.mm:125
QString qt_error_string(IOReturn errorCode)
Definition btutility.mm:104
QAbstractSocket::SocketError socketError(QIODevice *device)
Combined button and popup list for selecting options.
Q_MULTIMEDIA_EXPORT QString errorString(HRESULT hr)
Q_CORE_EXPORT QtJniTypes::Service service()
@ QueuedConnection
#define Q_FUNC_INFO
EGLOutputPortEXT port
static QT_BEGIN_NAMESPACE const char * socketType(QSocketNotifier::Type type)
#define qCWarning(category,...)
GLenum mode
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLuint GLenum GLsizei length
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum src
GLenum type
GLenum GLenum dst
GLenum const void * addr
GLuint GLuint64EXT address
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_ASSERT_X(cond, x, msg)
Definition qrandom.cpp:48
const char SOC_CONNECT_IN_PROGRESS[]
Definition uistrings.cpp:32
const char SOC_NETWORK_ERROR[]
Definition uistrings.cpp:30
const char SOC_NOREAD[]
Definition uistrings.cpp:35
const char SOCKET[]
Definition uistrings.cpp:29
const char SOC_NOWRITE[]
Definition uistrings.cpp:31
static T getAs(QTextDocument *doc, const QTextImageFormat &format, const qreal devicePixelRatio=1.0)
#define emit
#define Q_UNUSED(x)
unsigned short quint16
Definition qtypes.h:48
long long qint64
Definition qtypes.h:60
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...