diff options
-rw-r--r-- | src/bluetooth/bluetooth.pro | 8 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbt.pri | 6 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtsocketlistener.mm | 142 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtsocketlistener_p.h | 92 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothserver_osx.mm | 501 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothserver_osx_p.h | 110 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothserver_p.h | 4 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothserviceinfo_osx.mm | 72 |
8 files changed, 897 insertions, 38 deletions
diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro index 428afa87..16ae481f 100644 --- a/src/bluetooth/bluetooth.pro +++ b/src/bluetooth/bluetooth.pro @@ -152,18 +152,20 @@ config_bluez:qtHaveModule(dbus) { qbluetoothdevicediscoveryagent_osx.mm \ qbluetoothserviceinfo_osx.mm \ qbluetoothservicediscoveryagent_osx.mm \ - qbluetoothsocket_osx.mm + qbluetoothsocket_osx.mm \ + qbluetoothserver_osx.mm SOURCES += \ - qbluetoothserver_p.cpp \ qlowenergycontroller_p.cpp - PRIVATE_HEADERS += qbluetoothsocket_osx_p.h + PRIVATE_HEADERS += qbluetoothsocket_osx_p.h \ + qbluetoothserver_osx_p.h SOURCES -= qbluetoothdevicediscoveryagent.cpp SOURCES -= qbluetoothserviceinfo.cpp SOURCES -= qbluetoothservicediscoveryagent.cpp SOURCES -= qbluetoothsocket.cpp + SOURCES -= qbluetoothserver.cpp } else { message("Unsupported Bluetooth platform, will not build a working QtBluetooth library.") message("Either no Qt D-Bus found or no BlueZ headers.") diff --git a/src/bluetooth/osx/osxbt.pri b/src/bluetooth/osx/osxbt.pri index 59b928db..c1382484 100644 --- a/src/bluetooth/osx/osxbt.pri +++ b/src/bluetooth/osx/osxbt.pri @@ -6,7 +6,8 @@ PRIVATE_HEADERS += osx/osxbtutility_p.h \ osx/osxbtrfcommchannel_p.h \ osx/osxbtl2capchannel_p.h \ osx/osxbtchanneldelegate_p.h \ - osx/osxbtservicerecord_p.h + osx/osxbtservicerecord_p.h \ + osx/osxbtsocketlistener_p.h OBJECTIVE_SOURCES += osx/osxbtutility.mm \ osx/osxbtdevicepair.mm \ @@ -16,4 +17,5 @@ OBJECTIVE_SOURCES += osx/osxbtutility.mm \ osx/osxbtrfcommchannel.mm \ osx/osxbtl2capchannel.mm \ osx/osxbtchanneldelegate.mm \ - osx/osxbtservicerecord.mm + osx/osxbtservicerecord.mm \ + osx/osxbtsocketlistener.mm diff --git a/src/bluetooth/osx/osxbtsocketlistener.mm b/src/bluetooth/osx/osxbtsocketlistener.mm new file mode 100644 index 00000000..125f910a --- /dev/null +++ b/src/bluetooth/osx/osxbtsocketlistener.mm @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "osxbtsocketlistener_p.h" +#include "osxbtutility_p.h" + +#include <QtCore/qloggingcategory.h> +#include <QtCore/qdebug.h> + +// Imports, since these are Objective-C headers and +// they do not have inclusion guards. +#import <IOBluetooth/objc/IOBluetoothRFCOMMChannel.h> +#import <IOBluetooth/objc/IOBluetoothL2CAPChannel.h> + +QT_BEGIN_NAMESPACE + +namespace OSXBluetooth { + +SocketListener::~SocketListener() +{ +} + +} + +QT_END_NAMESPACE + +#ifdef QT_NAMESPACE + +using namespace QT_NAMESPACE; + +#endif + +@implementation QT_MANGLE_NAMESPACE(OSXBTSocketListener) + +- (id)initWithListener:(OSXBluetooth::SocketListener *)aDelegate +{ + Q_ASSERT_X(aDelegate, "-initWithListener:", "invalid delegate (null)"); + if (self = [super init]) { + connectionNotification = nil; + delegate = aDelegate; + port = 0; + } + + return self; +} + +- (void)dealloc +{ + [connectionNotification unregister]; + [connectionNotification release]; + + [super dealloc]; +} + +- (bool)listenRFCOMMConnectionsWithChannelID:(BluetoothRFCOMMChannelID)channelID +{ + Q_ASSERT_X(!connectionNotification, "-listenRFCOMMConnectionsWithChannelID", + "already listening"); + + connectionNotification = [IOBluetoothRFCOMMChannel registerForChannelOpenNotifications:self + selector:@selector(rfcommOpenNotification:channel:) + withChannelID:channelID + direction:kIOBluetoothUserNotificationChannelDirectionIncoming]; + connectionNotification = [connectionNotification retain]; + if (connectionNotification) + port = channelID; + + return connectionNotification; +} + +- (bool)listenL2CAPConnectionsWithPSM:(BluetoothL2CAPPSM)psm +{ + Q_ASSERT_X(!connectionNotification, "-listenL2CAPConnectionsWithPSM:", + "already listening"); + + connectionNotification = [IOBluetoothL2CAPChannel registerForChannelOpenNotifications:self + selector:@selector(l2capOpenNotification:channel:) + withPSM:psm + direction:kIOBluetoothUserNotificationChannelDirectionIncoming]; + connectionNotification = [connectionNotification retain]; + if (connectionNotification) + port = psm; + + return connectionNotification; +} + +- (void)rfcommOpenNotification:(IOBluetoothUserNotification *)notification + channel:(IOBluetoothRFCOMMChannel *)newChannel +{ + Q_UNUSED(notification) + + Q_ASSERT_X(delegate, "-rfcommOpenNotification:channel:", + "invalid delegate (null)"); + delegate->openNotify(newChannel); +} + +- (void)l2capOpenNotification:(IOBluetoothUserNotification *)notification + channel:(IOBluetoothL2CAPChannel *)newChannel +{ + Q_UNUSED(notification) + + Q_ASSERT_X(delegate, "-l2capOpenNotification:channel:", + "invalid delegate (null)"); + delegate->openNotify(newChannel); +} + +- (quint16)port +{ + return port; +} + +@end diff --git a/src/bluetooth/osx/osxbtsocketlistener_p.h b/src/bluetooth/osx/osxbtsocketlistener_p.h new file mode 100644 index 00000000..0f82ba37 --- /dev/null +++ b/src/bluetooth/osx/osxbtsocketlistener_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef OSXBTSOCKETLISTENER_P_H +#define OSXBTSOCKETLISTENER_P_H + +#include <QtCore/qglobal.h> + +#include <Foundation/Foundation.h> +#include <IOBluetooth/Bluetooth.h> + +@class IOBluetoothUserNotification; +@class IOBluetoothRFCOMMChannel; +@class IOBluetoothL2CAPChannel; +@class QT_MANGLE_NAMESPACE(OSXBTSocketListener); + +QT_BEGIN_NAMESPACE + +namespace OSXBluetooth { + +class SocketListener +{ +public: + typedef QT_MANGLE_NAMESPACE(OSXBTSocketListener) ObjCListener; + + virtual ~SocketListener(); + + virtual void openNotify(IOBluetoothRFCOMMChannel *channel) = 0; + virtual void openNotify(IOBluetoothL2CAPChannel *channel) = 0; +}; + +} + +QT_END_NAMESPACE + +// A single OSXBTSocketListener can be started only once with +// RFCOMM or L2CAP protocol. It must be deleted to stop listening. + +@interface QT_MANGLE_NAMESPACE(OSXBTSocketListener) : NSObject +{ + IOBluetoothUserNotification *connectionNotification; + QT_PREPEND_NAMESPACE(OSXBluetooth::SocketListener) *delegate; + quint16 port; +} + +- (id)initWithListener:(QT_PREPEND_NAMESPACE(OSXBluetooth::SocketListener) *)aDelegate; +- (void)dealloc; + +- (bool)listenRFCOMMConnectionsWithChannelID:(BluetoothRFCOMMChannelID)channelID; +- (bool)listenL2CAPConnectionsWithPSM:(BluetoothL2CAPPSM)psm; + +- (void)rfcommOpenNotification:(IOBluetoothUserNotification *)notification + channel:(IOBluetoothRFCOMMChannel *)newChannel; + +- (void)l2capOpenNotification:(IOBluetoothUserNotification *)notification + channel:(IOBluetoothL2CAPChannel *)newChannel; + +- (quint16)port; + +@end + +#endif diff --git a/src/bluetooth/qbluetoothserver_osx.mm b/src/bluetooth/qbluetoothserver_osx.mm new file mode 100644 index 00000000..412fb28c --- /dev/null +++ b/src/bluetooth/qbluetoothserver_osx.mm @@ -0,0 +1,501 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "osx/osxbtsocketlistener_p.h" +#include "qbluetoothserver_osx_p.h" +#include "qbluetoothsocket_osx_p.h" +#include "qbluetoothlocaldevice.h" +#include "osx/osxbtutility_p.h" +#include "qbluetoothserver.h" +#include "qbluetoothsocket.h" + +#include <QtCore/qloggingcategory.h> +#include <QtCore/qscopedpointer.h> +#include <QtCore/qvariant.h> +#include <QtCore/qglobal.h> +#include <QtCore/qmutex.h> + +// Import, since Obj-C headers do not have inclusion guards. +#import <IOBluetooth/objc/IOBluetoothRFCOMMChannel.h> +#import <IOBluetooth/objc/IOBluetoothL2CAPChannel.h> + +#include <limits> + +QT_BEGIN_NAMESPACE + +namespace { + +typedef QBluetoothServiceInfo QSInfo; + +QMap<quint16, QBluetoothServerPrivate *> &busyPSMs() +{ + static QMap<quint16, QBluetoothServerPrivate *> psms; + return psms; +} + +QMap<quint16, QBluetoothServerPrivate *> &busyChannels() +{ + static QMap<quint16, QBluetoothServerPrivate *> channels; + return channels; +} + +typedef QMap<quint16, QBluetoothServerPrivate *>::iterator ServerMapIterator; + +} + + +QBluetoothServerPrivate::QBluetoothServerPrivate(QSInfo::Protocol type, QBluetoothServer *q) + : serverType(type), + q_ptr(q), + lastError(QBluetoothServer::NoError), + port(0), + maxPendingConnections(1) +{ + Q_ASSERT_X(q_ptr, "QBluetoothServerPrivate", "invalid q_ptr (null)"); + if (serverType == QSInfo::UnknownProtocol) { + qCWarning(QT_BT_OSX) << "QBluetoothServerPrivate::QBluetoothServerPrivate(), " + "unknown protocol"; + } +} + +QBluetoothServerPrivate::~QBluetoothServerPrivate() +{ + // Actually, not good, but lock must be acquired. + // TODO: test this. + const QMutexLocker lock(&channelMapMutex()); + unregisterServer(this); +} + +void QBluetoothServerPrivate::_q_newConnection() +{ + // Noop, we have openNotify for this. +} + +bool QBluetoothServerPrivate::startListener(quint16 realPort) +{ + Q_ASSERT_X(realPort, "startListener", "invalid port"); + + if (serverType == QSInfo::UnknownProtocol) { + qCWarning(QT_BT_OSX) << "QBluetoothServerPrivate::startListener(), " + "invalid protocol"; + return false; + } + + if (!listener) + listener.reset([[ObjCListener alloc] initWithListener:this]); + + bool result = false; + if (serverType == QSInfo::RfcommProtocol) + result = [listener listenRFCOMMConnectionsWithChannelID:realPort]; + else + result = [listener listenL2CAPConnectionsWithPSM:realPort]; + + if (!result) + listener.reset(nil); + + return result; +} + +void QBluetoothServerPrivate::stopListener() +{ + listener.reset(nil); +} + +void QBluetoothServerPrivate::openNotify(IOBluetoothRFCOMMChannel *channel) +{ + Q_ASSERT_X(listener, "openNotify", "invalid listener (nil)"); + Q_ASSERT_X(channel, "openNotify", "invalid channel (nil)"); + Q_ASSERT_X(q_ptr, "openNotify", "invalid q_ptr (null)"); + + PendingConnection newConnection(channel, true); + pendingConnections.append(newConnection); + + emit q_ptr->newConnection(); +} + +void QBluetoothServerPrivate::openNotify(IOBluetoothL2CAPChannel *channel) +{ + Q_ASSERT_X(listener, "openNotify", "invalid listener (nil)"); + Q_ASSERT_X(channel, "openNotify", "invalid channel (nil)"); + Q_ASSERT_X(q_ptr, "openNotify", "invalid q_ptr (null)"); + + PendingConnection newConnection(channel, true); + pendingConnections.append(newConnection); + + emit q_ptr->newConnection(); +} + +QMutex &QBluetoothServerPrivate::channelMapMutex() +{ + static QMutex mutex; + return mutex; +} + +bool QBluetoothServerPrivate::channelIsBusy(quint16 channelID) +{ + // External lock is required. + return busyChannels().contains(channelID); +} + +quint16 QBluetoothServerPrivate::findFreeChannel() +{ + // External lock is required. + for (quint16 i = 1; i <= 30; ++i) { + if (!busyChannels().contains(i)) + return i; + } + + return 0; //Invalid port. +} + +bool QBluetoothServerPrivate::psmIsBusy(quint16 psm) +{ + // External lock is required. + return busyPSMs().contains(psm); +} + +quint16 QBluetoothServerPrivate::findFreePSM() +{ + // External lock is required. + for (quint16 i = 1, e = std::numeric_limits<qint16>::max(); i < e; i += 2) { + if (!psmIsBusy(i)) + return i; + } + + return 0; // Invalid PSM. +} + +void QBluetoothServerPrivate::registerServer(QBluetoothServerPrivate *server, quint16 port) +{ + // External lock is required + port must be free. + Q_ASSERT_X(server, "registerServer", "invalid server (null)"); + + const QSInfo::Protocol type = server->serverType; + if (type == QSInfo::RfcommProtocol) { + Q_ASSERT_X(!channelIsBusy(port), "registerServer", + "port is busy"); + busyChannels()[port] = server; + } else if (type == QSInfo::L2capProtocol) { + Q_ASSERT_X(!psmIsBusy(port), "registerServer", + "port is busy"); + busyPSMs()[port] = server; + } else { + qCWarning(QT_BT_OSX) << "can not register a server with unknown " + "protocol type"; + } +} + +QBluetoothServerPrivate *QBluetoothServerPrivate::registeredServer(quint16 port, QBluetoothServiceInfo::Protocol protocol) +{ + // Eternal lock is required. + if (protocol == QSInfo::RfcommProtocol) { + ServerMapIterator it = busyChannels().find(port); + if (it != busyChannels().end()) + return it.value(); + } else if (protocol == QSInfo::L2capProtocol) { + ServerMapIterator it = busyPSMs().find(port); + if (it != busyPSMs().end()) + return it.value(); + } else { + qCWarning(QT_BT_OSX) << "QBluetoothServerPrivate::registeredServer(), " + "invalid protocol"; + } + + return Q_NULLPTR; +} + +void QBluetoothServerPrivate::unregisterServer(QBluetoothServerPrivate *server) +{ + // External lock is required. + const QSInfo::Protocol type = server->serverType; + const quint16 port = server->port; + + if (type == QSInfo::RfcommProtocol) { + ServerMapIterator it = busyChannels().find(port); + if (it != busyChannels().end()) { + busyChannels().erase(it); + } else { + qCWarning(QT_BT_OSX) << "QBluetoothServerPrivate::unregisterServer(), " + "server is not registered"; + } + } else if (type == QSInfo::L2capProtocol) { + ServerMapIterator it = busyPSMs().find(port); + if (it != busyPSMs().end()) { + busyPSMs().erase(it); + } else { + qCWarning(QT_BT_OSX) << "QBluetoothServerPrivate::unregisterServer(), " + "server is not registered"; + } + } else { + qCWarning(QT_BT_OSX) << "QBluetoothServerPrivate::unregisterServer(), " + "invalid protocol"; + } +} + + +QBluetoothServer::QBluetoothServer(QSInfo::Protocol serverType, QObject *parent) + : QObject(parent), + d_ptr(new QBluetoothServerPrivate(serverType, this)) +{ +} + +QBluetoothServer::~QBluetoothServer() +{ + delete d_ptr; +} + +void QBluetoothServer::close() +{ + d_ptr->listener.reset(nil); + + // Needs a lock :( + const QMutexLocker lock(&d_ptr->channelMapMutex()); + d_ptr->unregisterServer(d_ptr); +} + +bool QBluetoothServer::listen(const QBluetoothAddress &address, quint16 port) +{ + typedef QBluetoothServerPrivate::ObjCListener ObjCListener; + + if (d_ptr->listener) { + qCWarning(QT_BT_OSX) << "QBluetoothServer::listen() ", + "already in listen mode, " + "close server first"; + return false; + } + + const QBluetoothLocalDevice device(address); + if (!device.isValid()) { + qCWarning(QT_BT_OSX) << "QBluetoothServer::listen(), device does not support Bluetooth or " + << address.toString() + << " is not a valid local adapter"; + d_ptr->lastError = UnknownError; + emit error(UnknownError); + return false; + } + + const QBluetoothLocalDevice::HostMode hostMode = device.hostMode(); + if (hostMode == QBluetoothLocalDevice::HostPoweredOff) { + qCWarning(QT_BT_OSX) << "QBluetoothServer::listen(), " + "bluetooth device is powered off"; + d_ptr->lastError = PoweredOffError; + emit error(PoweredOffError); + return false; + } + + const QSInfo::Protocol type = d_ptr->serverType; + + if (type == QSInfo::UnknownProtocol) { + qCWarning(QT_BT_OSX) << "QBluetoothServer::listen(), " + "invalid protocol"; + d_ptr->lastError = UnsupportedProtocolError; + emit error(d_ptr->lastError); + return false; + } + + d_ptr->lastError = QBluetoothServer::NoError; + + // Now we have to register a (fake) port, doing a proper (?) lock. + const QMutexLocker lock(&d_ptr->channelMapMutex()); + + if (port) { + if (type == QSInfo::RfcommProtocol) { + if (d_ptr->channelIsBusy(port)) { + qCWarning(QT_BT_OSX) << "QBluetoothServer::listen(), server port: " + << port << "already registered"; + d_ptr->lastError = ServiceAlreadyRegisteredError; + } + } else { + if (d_ptr->psmIsBusy(port)) { + qCWarning(QT_BT_OSX) << "QBluetoothServer::listen(), server port: " + << port << "already registered"; + d_ptr->lastError = ServiceAlreadyRegisteredError; + } + } + } else { + type == QSInfo::RfcommProtocol ? port = d_ptr->findFreeChannel() + : port = d_ptr->findFreePSM(); + } + + if (d_ptr->lastError != QBluetoothServer::NoError) { + emit error(d_ptr->lastError); + return false; + } + + if (!port) { + qCWarning(QT_BT_OSX) << "QBluetoothServer::listen(), all ports are busy"; + d_ptr->lastError = ServiceAlreadyRegisteredError; + emit error(d_ptr->lastError); + return false; + } + + // It's a fake port, the real one will be different + // (provided after a service was registered). + d_ptr->port = port; + d_ptr->registerServer(d_ptr, port); + d_ptr->listener.reset([[ObjCListener alloc] initWithListener:d_ptr]); + + return true; +} + +QBluetoothServiceInfo QBluetoothServer::listen(const QBluetoothUuid &uuid, const QString &serviceName) +{ + if (!listen()) + return QBluetoothServiceInfo(); + + QBluetoothServiceInfo serviceInfo; + serviceInfo.setAttribute(QSInfo::ServiceName, serviceName); + serviceInfo.setAttribute(QSInfo::BrowseGroupList, + QBluetoothUuid(QBluetoothUuid::PublicBrowseGroup)); + + QSInfo::Sequence classId; + classId << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::SerialPort)); + serviceInfo.setAttribute(QSInfo::BluetoothProfileDescriptorList, classId); + + classId.prepend(QVariant::fromValue(uuid)); + serviceInfo.setAttribute(QSInfo::ServiceClassIds, classId); + serviceInfo.setServiceUuid(uuid); + + QSInfo::Sequence protocolDescriptorList; + QSInfo::Sequence protocol; + protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap)); + if (d_ptr->serverType == QSInfo::L2capProtocol) + protocol << QVariant::fromValue(serverPort()); + protocolDescriptorList.append(QVariant::fromValue(protocol)); + protocol.clear(); + + if (d_ptr->serverType == QBluetoothServiceInfo::RfcommProtocol) { + protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Rfcomm)) + << QVariant::fromValue(quint8(serverPort())); + protocolDescriptorList.append(QVariant::fromValue(protocol)); + } + + serviceInfo.setAttribute(QSInfo::ProtocolDescriptorList, + protocolDescriptorList); + + + // It's now up to a service info to acquire a real PSM/channel ID + // (provided by IOBluetooth) and start a listener. + if (!serviceInfo.registerService()) + return QBluetoothServiceInfo(); + + return serviceInfo; +} + +bool QBluetoothServer::isListening() const +{ + return d_ptr->listener; +} + +void QBluetoothServer::setMaxPendingConnections(int numConnections) +{ + // That's a 'fake' limit, it affects nothing. + d_ptr->maxPendingConnections = numConnections; +} + +int QBluetoothServer::maxPendingConnections() const +{ + // That's a 'fake' limit, it affects nothing. + return d_ptr->maxPendingConnections; +} + +bool QBluetoothServer::hasPendingConnections() const +{ + return d_ptr->pendingConnections.size(); +} + +QBluetoothSocket *QBluetoothServer::nextPendingConnection() +{ + if (!d_ptr->pendingConnections.size()) + return Q_NULLPTR; + + QScopedPointer<QBluetoothSocket> newSocket(new QBluetoothSocket); + QBluetoothServerPrivate::PendingConnection channel(d_ptr->pendingConnections.front()); + + // Remove it even if we have some errors below. + d_ptr->pendingConnections.pop_front(); + + if (d_ptr->serverType == QSInfo::RfcommProtocol) { + if (!newSocket->d_ptr->setChannel(static_cast<IOBluetoothRFCOMMChannel *>(channel))) + return Q_NULLPTR; + } else { + if (!newSocket->d_ptr->setChannel(static_cast<IOBluetoothL2CAPChannel *>(channel))) + return Q_NULLPTR; + } + + return newSocket.take(); +} + +QBluetoothAddress QBluetoothServer::serverAddress() const +{ + return QBluetoothLocalDevice().address(); +} + +quint16 QBluetoothServer::serverPort() const +{ + return d_ptr->port; +} + +void QBluetoothServer::setSecurityFlags(QBluetooth::SecurityFlags security) +{ + Q_UNUSED(security) + // Not implemented (yet?) +} + +QBluetooth::SecurityFlags QBluetoothServer::securityFlags() const +{ + // Not implemented (yet?) + return QBluetooth::NoSecurity; +} + +QSInfo::Protocol QBluetoothServer::serverType() const +{ + return d_ptr->serverType; +} + +QBluetoothServer::Error QBluetoothServer::error() const +{ + return d_ptr->lastError; +} + +#include "moc_qbluetoothserver.cpp" + +QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothserver_osx_p.h b/src/bluetooth/qbluetoothserver_osx_p.h new file mode 100644 index 00000000..d501743c --- /dev/null +++ b/src/bluetooth/qbluetoothserver_osx_p.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QBLUETOOTHSERVER_OSX_P_H +#define QBLUETOOTHSERVER_OSX_P_H + +#ifdef QT_OSX_BLUETOOTH + +#include "osx/osxbtsocketlistener_p.h" +#include "qbluetoothserviceinfo.h" +#include "osx/osxbtutility_p.h" +#include "qbluetoothserver.h" + +#include <QtCore/qglobal.h> +#include <QtCore/qlist.h> + +QT_BEGIN_NAMESPACE + +class QMutex; + +class QBluetoothServerPrivate : public OSXBluetooth::SocketListener +{ + friend class QBluetoothServer; + friend class QBluetoothServiceInfoPrivate; + +public: + QBluetoothServerPrivate(QBluetoothServiceInfo::Protocol type, QBluetoothServer *q); + ~QBluetoothServerPrivate(); + + void _q_newConnection(); +private: + bool startListener(quint16 realPort); + void stopListener(); + + // SocketListener (delegate): + void openNotify(IOBluetoothRFCOMMChannel *channel) Q_DECL_OVERRIDE; + void openNotify(IOBluetoothL2CAPChannel *channel) Q_DECL_OVERRIDE; + + QBluetoothServiceInfo::Protocol serverType; + QBluetoothServer *q_ptr; + QBluetoothServer::Error lastError; + + // Either a "temporary" channelID/PSM assigned by QBluetoothServer::listen, + // or a real channelID/PSM returned by IOBluetooth after we've registered + // a service. + quint16 port; + + typedef OSXBluetooth::ObjCScopedPointer<ObjCListener> Listener; + Listener listener; + + int maxPendingConnections; + + // These static functions below + // deal with differences between bluetooth sockets + // (bluez and QtBluetooth's API) and IOBluetooth, where it's not possible + // to have a real PSM/channelID _before_ a service is registered, + // the solution - "fake" ports. + // These functions require external locking - using channelMapMutex. + static QMutex &channelMapMutex(); + + static bool channelIsBusy(quint16 channelID); + static quint16 findFreeChannel(); + + static bool psmIsBusy(quint16 psm); + static quint16 findFreePSM(); + + static void registerServer(QBluetoothServerPrivate *server, quint16 port); + static QBluetoothServerPrivate *registeredServer(quint16 port, QBluetoothServiceInfo::Protocol protocol); + static void unregisterServer(QBluetoothServerPrivate *server); + + typedef OSXBluetooth::ObjCStrongReference<NSObject> PendingConnection; + QList<PendingConnection> pendingConnections; + +}; + +QT_END_NAMESPACE + +#endif //QT_OSX_BLUETOOTH + +#endif diff --git a/src/bluetooth/qbluetoothserver_p.h b/src/bluetooth/qbluetoothserver_p.h index 75f1bde6..ee65994b 100644 --- a/src/bluetooth/qbluetoothserver_p.h +++ b/src/bluetooth/qbluetoothserver_p.h @@ -74,6 +74,8 @@ class QBluetoothSocket; class QBluetoothServer; +#ifndef QT_OSX_BLUETOOTH + class QBluetoothServerPrivate #ifdef QT_QNX_BLUETOOTH : public QObject @@ -137,6 +139,8 @@ public: #endif }; +#endif //QT_OSX_BLUETOOTH + QT_END_NAMESPACE #endif diff --git a/src/bluetooth/qbluetoothserviceinfo_osx.mm b/src/bluetooth/qbluetoothserviceinfo_osx.mm index 621aab35..61f68f7f 100644 --- a/src/bluetooth/qbluetoothserviceinfo_osx.mm +++ b/src/bluetooth/qbluetoothserviceinfo_osx.mm @@ -40,6 +40,7 @@ ****************************************************************************/ #include "osx/osxbtservicerecord_p.h" +#include "qbluetoothserver_osx_p.h" #include "qbluetoothserviceinfo.h" #include "qbluetoothdeviceinfo.h" #include "osx/osxbtutility_p.h" @@ -47,6 +48,7 @@ #include <QtCore/qloggingcategory.h> #include <QtCore/qvariant.h> #include <QtCore/qglobal.h> +#include <QtCore/qmutex.h> #include <QtCore/qmap.h> #include <QtCore/qurl.h> @@ -58,6 +60,7 @@ QT_BEGIN_NAMESPACE class QBluetoothServiceInfoPrivate { public: + typedef QBluetoothServiceInfo QSInfo; QBluetoothServiceInfoPrivate(QBluetoothServiceInfo *q); ~QBluetoothServiceInfoPrivate(); @@ -116,54 +119,45 @@ bool QBluetoothServiceInfoPrivate::registerService(const QBluetoothAddress &loca return false; } - serviceRecord.reset([[IOBluetoothSDPServiceRecord + SDPRecord newRecord([[IOBluetoothSDPServiceRecord publishedServiceRecordWithDictionary:serviceDict] retain]); - if (!serviceRecord) { + if (!newRecord) { qCWarning(QT_BT_OSX) << "QBluetoothServiceInfoPrivate::registerService(), " "failed to create register a service record"; return false; } - QBluetoothServiceInfo::Sequence protocolDescriptorList; - bool updatePDL = false; + const QSInfo::Protocol type = q_ptr->socketProtocol(); + quint16 realPort = 0; + QBluetoothServerPrivate *server = Q_NULLPTR; - if (q_ptr->socketProtocol() == QBluetoothServiceInfo::L2capProtocol) { - // + if (type == QBluetoothServiceInfo::L2capProtocol) { BluetoothL2CAPPSM psm = 0; - if ([serviceRecord getL2CAPPSM:&psm] == kIOReturnSuccess) { - if (psm != q_ptr->protocolServiceMultiplexer()) { - // Update with a real PSM assigned by IOBluetooth! - updatePDL = true; - QBluetoothServiceInfo::Sequence protocol; - protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap)); - protocol << QVariant::fromValue(qint16(psm)); - protocolDescriptorList.append(QVariant::fromValue(protocol)); - } + server = QBluetoothServerPrivate::registeredServer(q_ptr->protocolServiceMultiplexer(), type); + if ([newRecord getL2CAPPSM:&psm] != kIOReturnSuccess) { + [newRecord removeServiceRecord]; + return false; } - } else if (q_ptr->socketProtocol() == QBluetoothServiceInfo::RfcommProtocol) { - // + realPort = psm; + } else if (type == QBluetoothServiceInfo::RfcommProtocol) { BluetoothRFCOMMChannelID channelID = 0; - if ([serviceRecord getRFCOMMChannelID:&channelID] == kIOReturnSuccess) { - if (channelID != q_ptr->serverChannel()) { - updatePDL = true; - QBluetoothServiceInfo::Sequence protocol; - protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::L2cap)); - protocolDescriptorList.append(QVariant::fromValue(protocol)); - protocol.clear(); - protocol << QVariant::fromValue(QBluetoothUuid(QBluetoothUuid::Rfcomm)) - << QVariant::fromValue(quint8(channelID)); - protocolDescriptorList.append(QVariant::fromValue(protocol)); - } + server = QBluetoothServerPrivate::registeredServer(q_ptr->serverChannel(), type); + if ([newRecord getRFCOMMChannelID:&channelID] != kIOReturnSuccess) { + [newRecord removeServiceRecord]; + return false; } + realPort = channelID; } - if (updatePDL) - q_ptr->setAttribute(QBluetoothServiceInfo::ProtocolDescriptorList, protocolDescriptorList); - - // TODO - check ServiceRecordHandle + error handling - if we failed to obtain a port. + if (!server) { + [newRecord removeServiceRecord]; + return false; + } registered = true; + serviceRecord.reset(newRecord.take()); + server->startListener(realPort); return true; } @@ -184,7 +178,19 @@ bool QBluetoothServiceInfoPrivate::unregisterService() [serviceRecord removeServiceRecord]; serviceRecord.reset(nil); - return false; + const QSInfo::Protocol type = q_ptr->socketProtocol(); + QBluetoothServerPrivate *server = Q_NULLPTR; + + const QMutexLocker lock(&QBluetoothServerPrivate::channelMapMutex()); + if (type == QSInfo::RfcommProtocol) + server = QBluetoothServerPrivate::registeredServer(q_ptr->serverChannel(), type); + else if (type == QSInfo::L2capProtocol) + server = QBluetoothServerPrivate::registeredServer(q_ptr->protocolServiceMultiplexer(), type); + + if (server) + server->stopListener(); + + return true; } bool QBluetoothServiceInfo::isRegistered() const |