diff options
author | Timur Pocheptsov <Timur.Pocheptsov@digia.com> | 2014-09-04 10:23:05 +0200 |
---|---|---|
committer | Timur Pocheptsov <Timur.Pocheptsov@digia.com> | 2014-09-16 15:27:09 +0200 |
commit | dc34c7aae7d4d641f4d06990141c7915542363ee (patch) | |
tree | c0245f807242b904ba44f5268d3551938cb4e5f8 /src/bluetooth | |
parent | 982eeb3547f85dc76e5864559ee56db74a7dd86f (diff) |
Port QBluetoothServiceInfo and QBluetoothServiceDiscoveryAgent to OS X.
QBluetoothServiceInfo and QBluetoothServiceDiscoveryAgent - version for
OS X (IOBluetooth-based).
Update 0: initial dummy version + mods to enable this new classes to be built
with qtconnectivity.
Update 1: SDP query + initial implementation of a services discovery agent.
Update 2: aux functions to "parse" a service records once I got it.
Update 3: extract services UUIDs on a discovered device, if any.
Update 4: refactor
Update 5: "fix" asserts
Update 6: more asserts fixed.
Update 7: add the ability to stop SDP query (to be tested!!!)
Update 8: mods as suggested in review.
Update 9: no reason to check the size of discoveredDevices after 'clear' call.
Update 10: set an error and error description only if it's a 'singleDevice'.
Update 11: fix private header (_p suffix).
Update 12: on 10.7 (with quite old clang) there is no 'subscript operator'
syntax for NSDictionary.
Change-Id: Ib3b07b49e3ed6381af75fb8b1e29cdf1e7a11237
Reviewed-by: Timur Pocheptsov <Timur.Pocheptsov@digia.com>
Reviewed-by: Alex Blasche <alexander.blasche@digia.com>
Diffstat (limited to 'src/bluetooth')
-rw-r--r-- | src/bluetooth/bluetooth.pro | 8 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbt.pri | 6 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtconnectionmonitor.mm | 6 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtdeviceinquiry.mm | 10 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtdevicepair.mm | 11 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtdevicepair_p.h | 4 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtsdpinquiry.mm | 277 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtsdpinquiry_p.h | 103 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtutility.mm | 33 | ||||
-rw-r--r-- | src/bluetooth/osx/osxbtutility_p.h | 23 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm | 9 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothlocaldevice_osx.mm | 4 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothservicediscoveryagent.h | 2 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothservicediscoveryagent_osx.mm | 570 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothserviceinfo_osx.mm | 332 | ||||
-rw-r--r-- | src/bluetooth/qbluetoothserviceinfo_p.h | 4 |
16 files changed, 1367 insertions, 35 deletions
diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro index 49febab8..2bc559d1 100644 --- a/src/bluetooth/bluetooth.pro +++ b/src/bluetooth/bluetooth.pro @@ -149,16 +149,18 @@ config_bluez:qtHaveModule(dbus) { include(osx/osxbt.pri) OBJECTIVE_SOURCES += \ qbluetoothlocaldevice_osx.mm \ - qbluetoothdevicediscoveryagent_osx.mm + qbluetoothdevicediscoveryagent_osx.mm \ + qbluetoothserviceinfo_osx.mm \ + qbluetoothservicediscoveryagent_osx.mm SOURCES += \ - qbluetoothservicediscoveryagent_p.cpp \ - qbluetoothserviceinfo_p.cpp \ qbluetoothsocket_p.cpp \ qbluetoothserver_p.cpp \ qlowenergycontroller_p.cpp SOURCES -= qbluetoothdevicediscoveryagent.cpp + SOURCES -= qbluetoothserviceinfo.cpp + SOURCES -= qbluetoothservicediscoveryagent.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 fd25d17e..0b5fff85 100644 --- a/src/bluetooth/osx/osxbt.pri +++ b/src/bluetooth/osx/osxbt.pri @@ -1,9 +1,11 @@ HEADERS += osx/osxbtutility_p.h \ osx/osxbtdevicepair_p.h \ osx/osxbtdeviceinquiry_p.h \ - osx/osxbtconnectionmonitor_p.h + osx/osxbtconnectionmonitor_p.h \ + osx/osxbtsdpinquiry_p.h OBJECTIVE_SOURCES += osx/osxbtutility.mm \ osx/osxbtdevicepair.mm \ osx/osxbtdeviceinquiry.mm \ - osx/osxbtconnectionmonitor.mm + osx/osxbtconnectionmonitor.mm \ + osx/osxbtsdpinquiry.mm diff --git a/src/bluetooth/osx/osxbtconnectionmonitor.mm b/src/bluetooth/osx/osxbtconnectionmonitor.mm index 7f9b08a2..b6f3f4e7 100644 --- a/src/bluetooth/osx/osxbtconnectionmonitor.mm +++ b/src/bluetooth/osx/osxbtconnectionmonitor.mm @@ -107,7 +107,7 @@ using namespace QT_NAMESPACE; // All Obj-C objects are autoreleased. - const QBluetoothAddress deviceAddress(OSXBluetooth::qt_bt_address([device getAddress])); + const QBluetoothAddress deviceAddress(OSXBluetooth::qt_address([device getAddress])); if (deviceAddress.isNull()) return; @@ -128,11 +128,9 @@ using namespace QT_NAMESPACE; QT_BT_MAC_AUTORELEASEPOOL; [notification unregister];//? - - // Does IOBluetoothUserNotification override isEqual? [foundConnections removeObject:notification]; - const QBluetoothAddress deviceAddress(OSXBluetooth::qt_bt_address([device getAddress])); + const QBluetoothAddress deviceAddress(OSXBluetooth::qt_address([device getAddress])); if (deviceAddress.isNull()) return; diff --git a/src/bluetooth/osx/osxbtdeviceinquiry.mm b/src/bluetooth/osx/osxbtdeviceinquiry.mm index a15554d8..8bfdc943 100644 --- a/src/bluetooth/osx/osxbtdeviceinquiry.mm +++ b/src/bluetooth/osx/osxbtdeviceinquiry.mm @@ -71,8 +71,7 @@ using namespace QT_NAMESPACE; - (id)initWithDelegate:(OSXBluetooth::DeviceInquiryDelegate *)delegate { if (self = [super init]) { - Q_ASSERT_X(delegate != Q_NULLPTR, "-initWithDelegate:", - "invalid device inquiry delegate (null)"); + Q_ASSERT_X(delegate, "-initWithDelegate:", "invalid device inquiry delegate (null)"); m_inquiry = [[IOBluetoothDeviceInquiry inquiryWithDelegate:self] retain]; @@ -127,8 +126,7 @@ using namespace QT_NAMESPACE; - (IOReturn)stop { if (m_active) { - Q_ASSERT_X(m_inquiry != nil, "-stop", - "active but nil inquiry"); + Q_ASSERT_X(m_inquiry, "-stop", "active but nil inquiry"); m_active = false; const IOReturn res = [m_inquiry stop]; @@ -151,7 +149,7 @@ using namespace QT_NAMESPACE; m_active = false; - Q_ASSERT_X(m_delegate != Q_NULLPTR, "-deviceInquiryComplete:error:aborted", + Q_ASSERT_X(m_delegate, "-deviceInquiryComplete:error:aborted", "invalid device inquiry delegate (null)"); if (error != kIOReturnSuccess) @@ -166,7 +164,7 @@ using namespace QT_NAMESPACE; if (sender != m_inquiry) // Can never happen in the current version. return; - Q_ASSERT_X(m_delegate != Q_NULLPTR, "-deviceInquiryDeviceFound:device:", + Q_ASSERT_X(m_delegate, "-deviceInquiryDeviceFound:device:", "invalid device inquiry delegate (null)"); m_delegate->deviceFound(sender, device); diff --git a/src/bluetooth/osx/osxbtdevicepair.mm b/src/bluetooth/osx/osxbtdevicepair.mm index 726df9a8..8edc30a6 100644 --- a/src/bluetooth/osx/osxbtdevicepair.mm +++ b/src/bluetooth/osx/osxbtdevicepair.mm @@ -82,9 +82,9 @@ using namespace QT_NAMESPACE; delegate:(OSXBluetooth::PairingDelegate *)object { if (self = [super init]) { - Q_ASSERT_X(address.isNull() == false, "-initWithTarget:delegate", + Q_ASSERT_X(!address.isNull(), "-initWithTarget:delegate", "invalid target address"); - Q_ASSERT_X(object != Q_NULLPTR, "-initWithTarget:delegate:", + Q_ASSERT_X(object, "-initWithTarget:delegate:", "invalid delegate (null)"); m_targetAddress = address; @@ -108,8 +108,7 @@ using namespace QT_NAMESPACE; if (m_active) return kIOReturnBusy; - Q_ASSERT_X(m_targetAddress.isNull() == false, "-start", - "invalid target address"); + Q_ASSERT_X(!m_targetAddress.isNull(), "-start", "invalid target address"); QT_BT_MAC_AUTORELEASEPOOL; @@ -196,7 +195,7 @@ using namespace QT_NAMESPACE; if (sender != m_pairing) // Can never happen. return; - Q_ASSERT_X(m_object != Q_NULLPTR, "-devicePairingUserConfirmationRequest:numericValue:", + Q_ASSERT_X(m_object, "-devicePairingUserConfirmationRequest:numericValue:", "invalid delegate (null)"); m_object->requestUserConfirmation(self, numericValue); @@ -213,7 +212,7 @@ using namespace QT_NAMESPACE; - (void)devicePairingFinished:(id)sender error:(IOReturn)error { - Q_ASSERT_X(m_object != Q_NULLPTR, "-devicePairingFinished:", + Q_ASSERT_X(m_object, "-devicePairingFinished:", "invalid delegate (null)"); if (sender != m_pairing) // Can never happen though. diff --git a/src/bluetooth/osx/osxbtdevicepair_p.h b/src/bluetooth/osx/osxbtdevicepair_p.h index a3b8d77a..843c58f4 100644 --- a/src/bluetooth/osx/osxbtdevicepair_p.h +++ b/src/bluetooth/osx/osxbtdevicepair_p.h @@ -39,8 +39,8 @@ ** ****************************************************************************/ -#ifndef OSXBTPAIRINGDELEGATE_P_H -#define OSXBTPAIRINGDELEGATE_P_H +#ifndef OSXBTDEVICEPAIR_P_H +#define OSXBTDEVICEPAIR_P_H #include "qbluetoothaddress.h" #include "osxbtutility_p.h" diff --git a/src/bluetooth/osx/osxbtsdpinquiry.mm b/src/bluetooth/osx/osxbtsdpinquiry.mm new file mode 100644 index 00000000..66a0524b --- /dev/null +++ b/src/bluetooth/osx/osxbtsdpinquiry.mm @@ -0,0 +1,277 @@ +/**************************************************************************** +** +** 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 "qbluetoothserviceinfo.h" +#include "osxbtsdpinquiry_p.h" +#include "qbluetoothuuid.h" +#include "osxbtutility_p.h" + + +#include <QtCore/qvariant.h> +#include <QtCore/qstring.h> + +// We have to import - objc header files, no inclusion guards. +#import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h> +#import <IOBluetooth/objc/IOBluetoothSDPDataElement.h> +#import <IOBluetooth/objc/IOBluetoothSDPUUID.h> +#import <IOBluetooth/objc/IOBluetoothDevice.h> + +QT_BEGIN_NAMESPACE + +namespace OSXBluetooth { + +SDPInquiryDelegate::~SDPInquiryDelegate() +{ +} + +QVariant extract_attribute_value(IOBluetoothSDPDataElement *dataElement) +{ + Q_ASSERT_X(dataElement, "extractAttributeValue()", "invalid data element (nil)"); + + // TODO: error handling and diagnostic messages. + + // All "temporary" obj-c objects are autoreleased. + QT_BT_MAC_AUTORELEASEPOOL; + + const BluetoothSDPDataElementTypeDescriptor typeDescriptor = [dataElement getTypeDescriptor]; + + switch (typeDescriptor) { + case kBluetoothSDPDataElementTypeNil: + break; + case kBluetoothSDPDataElementTypeUnsignedInt: + return [[dataElement getNumberValue] unsignedIntValue]; + case kBluetoothSDPDataElementTypeSignedInt: + return [[dataElement getNumberValue] intValue]; + case kBluetoothSDPDataElementTypeUUID: + return QVariant::fromValue(qt_uuid([[dataElement getUUIDValue] getUUIDWithLength:16])); + case kBluetoothSDPDataElementTypeString: + case kBluetoothSDPDataElementTypeURL: + return QString::fromNSString([dataElement getStringValue]); + case kBluetoothSDPDataElementTypeBoolean: + return [[dataElement getNumberValue] boolValue]; + case kBluetoothSDPDataElementTypeDataElementSequence: + case kBluetoothSDPDataElementTypeDataElementAlternative: // TODO: check this! + { + QBluetoothServiceInfo::Sequence sequence; + NSArray *const arr = [dataElement getArrayValue]; + for (IOBluetoothSDPDataElement *element in arr) + sequence.append(extract_attribute_value(element)); + + return QVariant::fromValue(sequence); + } + break;// Coding style. + default:; + } + + return QVariant(); +} + +void extract_service_record(IOBluetoothSDPServiceRecord *record, QBluetoothServiceInfo &serviceInfo) +{ + QT_BT_MAC_AUTORELEASEPOOL; + + if (!record) + return; + + NSDictionary *const attributes = record.attributes; + NSEnumerator *const keys = attributes.keyEnumerator; + for (NSNumber *key in keys) { + const quint16 attributeID = [key unsignedShortValue]; + IOBluetoothSDPDataElement *const element = [attributes objectForKey:key]; + const QVariant attributeValue = OSXBluetooth::extract_attribute_value(element); + serviceInfo.setAttribute(attributeID, attributeValue); + } +} + +QList<QBluetoothUuid> extract_services_uuids(IOBluetoothDevice *device) +{ + QList<QBluetoothUuid> uuids; + + // All "temporary" obj-c objects are autoreleased. + QT_BT_MAC_AUTORELEASEPOOL; + + if (!device || !device.services) + return uuids; + + NSArray * const records = device.services; + for (IOBluetoothSDPServiceRecord *record in records) { + IOBluetoothSDPDataElement *const element = + [record getAttributeDataElement:kBluetoothSDPAttributeIdentifierServiceClassIDList]; + + if (element && [element getTypeDescriptor] == kBluetoothSDPDataElementTypeUUID) + uuids.append(qt_uuid([[element getUUIDValue] getUUIDWithLength:16])); + } + + return uuids; +} + +} + +QT_END_NAMESPACE + + +#ifdef QT_NAMESPACE +using namespace QT_NAMESPACE; +#endif + +using namespace OSXBluetooth; + +@implementation QT_MANGLE_NAMESPACE(OSXBTSDPInquiry) + +- (id)initWithDelegate:(SDPInquiryDelegate *)aDelegate +{ + Q_ASSERT_X(aDelegate, "-initWithDelegate:", "invalid delegate (null)"); + + if (self = [super init]) { + delegate = aDelegate; + isActive = false; + } + + return self; +} + +- (void)dealloc +{ + //[device closeConnection]; //??? - synchronous, "In the future this API will be changed to allow asynchronous operation." + [device release]; + [super dealloc]; +} + +- (IOReturn)performSDPQueryWithDevice:(const QBluetoothAddress &)address +{ + Q_ASSERT_X(!isActive, "-performSDPQueryWithDevice", + "SDP query in process"); + + QList<QBluetoothUuid> emptyFilter; + return [self performSDPQueryWithDevice:address filters:emptyFilter]; +} + +- (IOReturn)performSDPQueryWithDevice:(const QBluetoothAddress &)address + filters:(const QList<QBluetoothUuid> &)qtFilters +{ + Q_ASSERT_X(!isActive, "-performSDPQueryWithDevice:filters:", + "SDP query in progress"); + Q_ASSERT_X(!address.isNull(), "-performSDPQueryWithDevice:filters:", + "invalid target device address"); + + QT_BT_MAC_AUTORELEASEPOOL; + + // We first try to allocate "filters": + ObjCScopedPointer<NSMutableArray> array; + if (qtFilters.size()) { + array.reset([[NSMutableArray alloc] init]); + if (!array) { + qCCritical(QT_BT_OSX) << "-performSDPQueryWithDevices:filters:, " + "failed to allocate an uuid filter"; + return kIOReturnError; + } + + foreach (const QBluetoothUuid &qUuid, qtFilters) { + ObjCStrongReference<IOBluetoothSDPUUID> uuid(iobluetooth_uuid(qUuid)); + if (uuid) + [array addObject:uuid]; + } + + if (int([array count]) != qtFilters.size()) { + qCCritical(QT_BT_OSX) << "-performSDPQueryWithDevices:filters:, " + << "failed to create an uuid filter"; + return kIOReturnError; + } + } + + const BluetoothDeviceAddress iobtAddress(iobluetooth_address(address)); + ObjCScopedPointer<IOBluetoothDevice> newDevice([[IOBluetoothDevice deviceWithAddress:&iobtAddress] retain]); + if (!newDevice) { + qCCritical(QT_BT_OSX) << "-performSDPQueryWithDevices:filters:, " + << "failed to create an IOBluetoothDevice object"; + return kIOReturnError; + } + + ObjCScopedPointer<IOBluetoothDevice> oldDevice(device); + device = newDevice.data(); + + IOReturn result = kIOReturnSuccess; + if (qtFilters.size()) + result = [device performSDPQuery:self uuids:array]; + else + result = [device performSDPQuery:self]; + + if (result != kIOReturnSuccess) { + qCCritical(QT_BT_OSX) << "-preformSDPQueryWithDevices:filters:, " + "failed to start an SDP query"; + + device = oldDevice.take(); + } else { + isActive = true; + newDevice.take(); + } + + return result; +} + +- (void)stopSDPQuery +{ + // There is no API to stop it, + // but there is a 'stop' member-function in Qt and + // after it's called sdpQueryComplete must be somehow ignored. + + [device release]; + device = nil; +} + +- (void)sdpQueryComplete:(IOBluetoothDevice *)aDevice status:(IOReturn)status +{ + // Can happen - there is no legal way to cancel an SDP query, + // after the 'reset' device can never be + // the same as the cancelled one. + if (device != aDevice) + return; + + Q_ASSERT_X(delegate, "-sdpQueryComplete:status:", + "invalid delegate (null)"); + + if (status != kIOReturnSuccess) + delegate->SDPInquiryError(aDevice, status); + else + delegate->SDPInquiryFinished(aDevice); +} + +@end diff --git a/src/bluetooth/osx/osxbtsdpinquiry_p.h b/src/bluetooth/osx/osxbtsdpinquiry_p.h new file mode 100644 index 00000000..b96de620 --- /dev/null +++ b/src/bluetooth/osx/osxbtsdpinquiry_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef OSXBTSDPINQUIRY_H +#define OSXBTSDPINQUIRY_H + +#include "qbluetoothaddress.h" +#include "qbluetoothuuid.h" + +#include <QtCore/qglobal.h> +#include <QtCore/qlist.h> + +#include <Foundation/Foundation.h> + +@class QT_MANGLE_NAMESPACE(OSXBTSDPInquiry); +@class IOBluetoothSDPServiceRecord; +@class IOBluetoothSDPDataElement; +@class IOBluetoothDevice; + +QT_BEGIN_NAMESPACE + +class QBluetoothServiceInfo; +class QVariant; + +namespace OSXBluetooth { + +class SDPInquiryDelegate { +public: + typedef QT_MANGLE_NAMESPACE(OSXBTSDPInquiry) ObjCServiceInquiry; + + virtual ~SDPInquiryDelegate(); + + virtual void SDPInquiryFinished(IOBluetoothDevice *device) = 0; + virtual void SDPInquiryError(IOBluetoothDevice *device, IOReturn errorCode) = 0; +}; + +void extract_service_record(IOBluetoothSDPServiceRecord *record, QBluetoothServiceInfo &serviceInfo); +QVariant extract_attribute_value(IOBluetoothSDPDataElement *dataElement); +QList<QBluetoothUuid> extract_services_uuids(IOBluetoothDevice *device); + +} + +QT_END_NAMESPACE + +@interface QT_MANGLE_NAMESPACE(OSXBTSDPInquiry) : NSObject +{ + QT_PREPEND_NAMESPACE(OSXBluetooth::SDPInquiryDelegate) *delegate; + IOBluetoothDevice *device; + bool isActive; +} + +- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(OSXBluetooth::SDPInquiryDelegate) *)aDelegate; +- (void)dealloc; + +- (IOReturn)performSDPQueryWithDevice:(const QBluetoothAddress &)address; +- (IOReturn)performSDPQueryWithDevice:(const QBluetoothAddress &)address + filters:(const QList<QBluetoothUuid> &)filters; + +- (void)stopSDPQuery; + +- (void)sdpQueryComplete:(IOBluetoothDevice *)aDevice status:(IOReturn)status; + +@end + +#endif diff --git a/src/bluetooth/osx/osxbtutility.mm b/src/bluetooth/osx/osxbtutility.mm index 14e8a947..7ddca95c 100644 --- a/src/bluetooth/osx/osxbtutility.mm +++ b/src/bluetooth/osx/osxbtutility.mm @@ -41,16 +41,22 @@ #include "qbluetoothaddress.h" #include "osxbtutility_p.h" +#include "qbluetoothuuid.h" #include <QtCore/qstring.h> +#import <IOBluetooth/objc/IOBluetoothSDPUUID.h> + +#include <algorithm> +#include <limits> + QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(QT_BT_OSX, "qt.bluetooth.osx") namespace OSXBluetooth { -QString qt_bt_address(NSString *address) +QString qt_address(NSString *address) { if (address && address.length) { NSString *const fixed = [address stringByReplacingOccurrencesOfString:@"-" withString:@":"]; @@ -60,7 +66,7 @@ QString qt_bt_address(NSString *address) return QString(); } -QBluetoothAddress qt_bt_address(const BluetoothDeviceAddress *a) +QBluetoothAddress qt_address(const BluetoothDeviceAddress *a) { if (a) { // TODO: can a byte order be different in BluetoothDeviceAddress? @@ -92,6 +98,29 @@ BluetoothDeviceAddress iobluetooth_address(const QBluetoothAddress &qAddress) return a; } +ObjCStrongReference<IOBluetoothSDPUUID> iobluetooth_uuid(const QBluetoothUuid &uuid) +{ + const unsigned nBytes = 128 / std::numeric_limits<unsigned char>::digits; + const quint128 intVal(uuid.toUInt128()); + + const ObjCStrongReference<IOBluetoothSDPUUID> iobtUUID([IOBluetoothSDPUUID uuidWithBytes:intVal.data + length:nBytes], true); + return iobtUUID; +} + +QBluetoothUuid qt_uuid(IOBluetoothSDPUUID *uuid) +{ + QBluetoothUuid qtUuid; + if (!uuid || [uuid length] != 16) // TODO: issue any diagnostic? + return qtUuid; + + // TODO: insure the correct byte-order!!! + quint128 uuidVal = {}; + const quint8 *const source = static_cast<const quint8 *>([uuid bytes]); + std::copy(source, source + 16, uuidVal.data); + return QBluetoothUuid(uuidVal); +} + } QT_END_NAMESPACE diff --git a/src/bluetooth/osx/osxbtutility_p.h b/src/bluetooth/osx/osxbtutility_p.h index cf267fc9..126b53d8 100644 --- a/src/bluetooth/osx/osxbtutility_p.h +++ b/src/bluetooth/osx/osxbtutility_p.h @@ -49,8 +49,12 @@ #include <Foundation/Foundation.h> #include <IOBluetooth/Bluetooth.h> +@class IOBluetoothSDPUUID; + QT_BEGIN_NAMESPACE +class QBluetoothUuid; + namespace OSXBluetooth { struct NSObjectDeleter { @@ -67,7 +71,17 @@ public: explicit ObjCScopedPointer(T *ptr = Q_NULLPTR) : QScopedPointer(ptr){} operator T*() const { - return static_cast<T *>(this->data()); + return data(); + } + + T *data()const + { + return static_cast<T *>(QScopedPointer::data()); + } + + T *take() + { + return static_cast<T *>(QScopedPointer::take()); } }; @@ -135,10 +149,13 @@ private: T *m_ptr; }; -QString qt_bt_address(NSString *address); -class QBluetoothAddress qt_bt_address(const BluetoothDeviceAddress *address); +QString qt_address(NSString *address); +class QBluetoothAddress qt_address(const BluetoothDeviceAddress *address); BluetoothDeviceAddress iobluetooth_address(const QBluetoothAddress &address); +ObjCStrongReference<IOBluetoothSDPUUID> iobluetooth_uuid(const QBluetoothUuid &uuid); +QBluetoothUuid qt_uuid(IOBluetoothSDPUUID *uuid); + } // namespace OSXBluetooth Q_DECLARE_LOGGING_CATEGORY(QT_BT_OSX) diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm index e7eb3c60..2f808038 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm @@ -42,6 +42,7 @@ #include "qbluetoothdevicediscoveryagent.h" #include "osx/osxbtdeviceinquiry_p.h" #include "qbluetoothlocaldevice.h" +#include "osx/osxbtsdpinquiry_p.h" #include "qbluetoothdeviceinfo.h" #include "osx/osxbtutility_p.h" #include "qbluetoothhostinfo.h" @@ -256,7 +257,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(IOBluetoothDeviceInquiry QT_BT_MAC_AUTORELEASEPOOL; // Let's collect some info about this device: - const QBluetoothAddress deviceAddress(OSXBluetooth::qt_bt_address([device getAddress])); + const QBluetoothAddress deviceAddress(OSXBluetooth::qt_address([device getAddress])); if (deviceAddress.isNull()) { qCWarning(QT_BT_OSX) << "QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(), " "invalid Bluetooth address"; @@ -274,10 +275,8 @@ void QBluetoothDeviceDiscoveryAgentPrivate::deviceFound(IOBluetoothDeviceInquiry QBluetoothDeviceInfo::LowEnergyCoreConfiguration); deviceInfo.setRssi(device.RSSI); - // TODO: check if I can extract services' uuids from device.services - // and use them. - deviceInfo.setServiceUuids(QList<QBluetoothUuid>(), - QBluetoothDeviceInfo::DataIncomplete); + const QList<QBluetoothUuid> uuids =OSXBluetooth::extract_services_uuids(device); + deviceInfo.setServiceUuids(uuids, QBluetoothDeviceInfo::DataIncomplete); for (int i = 0, e = discoveredDevices.size(); i < e; ++i) { if (discoveredDevices[i].address() == deviceInfo.address()) { diff --git a/src/bluetooth/qbluetoothlocaldevice_osx.mm b/src/bluetooth/qbluetoothlocaldevice_osx.mm index 6a222ae6..56a7fb7e 100644 --- a/src/bluetooth/qbluetoothlocaldevice_osx.mm +++ b/src/bluetooth/qbluetoothlocaldevice_osx.mm @@ -381,7 +381,7 @@ QBluetoothAddress QBluetoothLocalDevice::address() const if (isValid()) { if (NSString *const nsa = [d_ptr->hostController addressAsString]) - return QBluetoothAddress(OSXBluetooth::qt_bt_address(nsa)); + return QBluetoothAddress(OSXBluetooth::qt_address(nsa)); qCCritical(QT_BT_OSX) << "QBluetoothLocalDevice::address(), " "failed to obtain an address"; @@ -429,7 +429,7 @@ QList<QBluetoothAddress> QBluetoothLocalDevice::connectedDevices() const NSArray *const pairedDevices = [IOBluetoothDevice pairedDevices]; for (IOBluetoothDevice *device in pairedDevices) { if ([device isConnected]) { - const QBluetoothAddress address(OSXBluetooth::qt_bt_address([device getAddress])); + const QBluetoothAddress address(OSXBluetooth::qt_address([device getAddress])); if (!address.isNull()) connectedDevices.append(address); } diff --git a/src/bluetooth/qbluetoothservicediscoveryagent.h b/src/bluetooth/qbluetoothservicediscoveryagent.h index 5e47ada0..02be2f5b 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent.h +++ b/src/bluetooth/qbluetoothservicediscoveryagent.h @@ -99,10 +99,12 @@ Q_SIGNALS: private: QBluetoothServiceDiscoveryAgentPrivate *d_ptr; + Q_PRIVATE_SLOT(d_func(), void _q_deviceDiscovered(const QBluetoothDeviceInfo &info)) Q_PRIVATE_SLOT(d_func(), void _q_deviceDiscoveryFinished()) Q_PRIVATE_SLOT(d_func(), void _q_deviceDiscoveryError(QBluetoothDeviceDiscoveryAgent::Error)) Q_PRIVATE_SLOT(d_func(), void _q_serviceDiscoveryFinished()) + #ifdef QT_BLUEZ_BLUETOOTH Q_PRIVATE_SLOT(d_func(), void _q_discoveredServices(QDBusPendingCallWatcher*)) Q_PRIVATE_SLOT(d_func(), void _q_createdDevice(QDBusPendingCallWatcher*)) diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm new file mode 100644 index 00000000..46e0c471 --- /dev/null +++ b/src/bluetooth/qbluetoothservicediscoveryagent_osx.mm @@ -0,0 +1,570 @@ +/**************************************************************************** +** +** 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 "qbluetoothservicediscoveryagent.h" +#include "qbluetoothdevicediscoveryagent.h" +#include "qbluetoothlocaldevice.h" +#include "osx/osxbtsdpinquiry_p.h" +#include "qbluetoothhostinfo.h" +#include "osx/osxbtutility_p.h" + +#include <QtCore/qloggingcategory.h> +#include <QtCore/qscopedpointer.h> +#include <QtCore/qstring.h> +#include <QtCore/qdebug.h> +#include <QtCore/qlist.h> + +// We have to import obj-C headers, they are not guarded against a multiple inclusion. +#import <IOBluetooth/objc/IOBluetoothSDPServiceRecord.h> +#import <IOBluetooth/objc/IOBluetoothHostController.h> +#import <IOBluetooth/objc/IOBluetoothDevice.h> + +QT_BEGIN_NAMESPACE + +class QBluetoothServiceDiscoveryAgentPrivate : public QObject, public OSXBluetooth::SDPInquiryDelegate +{ + friend class QBluetoothServiceDiscoveryAgent; +public: + enum DiscoveryState { + Inactive, + DeviceDiscovery, + ServiceDiscovery, + }; + + QBluetoothServiceDiscoveryAgentPrivate(const QBluetoothAddress &localAddress); + + void startDeviceDiscovery(); + void stopDeviceDiscovery(); + + void startServiceDiscovery(); + void stopServiceDiscovery(); + + DiscoveryState discoveryState(); + void setDiscoveryMode(QBluetoothServiceDiscoveryAgent::DiscoveryMode m); + QBluetoothServiceDiscoveryAgent::DiscoveryMode DiscoveryMode(); + + void _q_deviceDiscovered(const QBluetoothDeviceInfo &info); + void _q_deviceDiscoveryError(QBluetoothDeviceDiscoveryAgent::Error); + void _q_deviceDiscoveryFinished(); + void _q_serviceDiscoveryFinished(); + + +private: + // SDPInquiryDelegate: + void SDPInquiryFinished(IOBluetoothDevice *device) Q_DECL_OVERRIDE; + void SDPInquiryError(IOBluetoothDevice *device, IOReturn errorCode) Q_DECL_OVERRIDE; + + void performMinimalServiceDiscovery(const QBluetoothAddress &deviceAddress); + void setupDeviceDiscoveryAgent(); + bool isDuplicatedService(const QBluetoothServiceInfo &serviceInfo) const; + void serviceDiscoveryFinished(); + + QBluetoothServiceDiscoveryAgent *q_ptr; + + QBluetoothServiceDiscoveryAgent::Error error; + QString errorString; + + QList<QBluetoothDeviceInfo> discoveredDevices; + QList<QBluetoothServiceInfo> discoveredServices; + QList<QBluetoothUuid> uuidFilter; + + bool singleDevice; + QBluetoothAddress deviceAddress; + QBluetoothAddress localAdapterAddress; + + DiscoveryState state; + QBluetoothServiceDiscoveryAgent::DiscoveryMode discoveryMode; + + QScopedPointer<QBluetoothDeviceDiscoveryAgent> deviceDiscoveryAgent; + OSXBluetooth::ObjCScopedPointer<ObjCServiceInquiry> serviceInquiry; +}; + +QBluetoothServiceDiscoveryAgentPrivate::QBluetoothServiceDiscoveryAgentPrivate(const QBluetoothAddress &localAddress) : + q_ptr(0), + error(QBluetoothServiceDiscoveryAgent::NoError), + singleDevice(false), + localAdapterAddress(localAddress), + state(Inactive), + discoveryMode(QBluetoothServiceDiscoveryAgent::MinimalDiscovery) +{ + serviceInquiry.reset([[ObjCServiceInquiry alloc] initWithDelegate:this]); +} + +void QBluetoothServiceDiscoveryAgentPrivate::startDeviceDiscovery() +{ + Q_ASSERT_X(q_ptr, "startDeviceDiscovery()", "invalid q_ptr (null)"); + Q_ASSERT_X(state == Inactive, "startDeviceDiscovery()", "invalid state"); + Q_ASSERT_X(error != QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError, + "startDeviceDiscovery()", "invalid bluetooth adapter"); + + Q_ASSERT_X(deviceDiscoveryAgent.isNull(), "startDeviceDiscovery()", + "discovery agent already exists"); + + state = DeviceDiscovery; + + setupDeviceDiscoveryAgent(); + deviceDiscoveryAgent->start(); +} + +void QBluetoothServiceDiscoveryAgentPrivate::stopDeviceDiscovery() +{ + Q_ASSERT_X(q_ptr, "stopDeviceDiscovery()", "invalid q_ptr (null)"); + Q_ASSERT_X(!deviceDiscoveryAgent.isNull(), "stopDeviceDiscovery()", + "invalid device discovery agent (null)"); + Q_ASSERT_X(state == DeviceDiscovery, "stopDeviceDiscovery()", + "invalid state"); + + deviceDiscoveryAgent->stop(); + deviceDiscoveryAgent.reset(Q_NULLPTR); + state = Inactive; + + emit q_ptr->canceled(); +} + +void QBluetoothServiceDiscoveryAgentPrivate::startServiceDiscovery() +{ + Q_ASSERT_X(state == Inactive, "startServiceDiscovery()", "invalid state"); + Q_ASSERT_X(q_ptr, "startServiceDiscovery()", "invalid q_ptr (null)"); + Q_ASSERT_X(error != QBluetoothServiceDiscoveryAgent::InvalidBluetoothAdapterError, + "startServiceDiscovery()", "invalid bluetooth adapter"); + + if (discoveredDevices.isEmpty()) { + emit q_ptr->finished(); + return; + } + + QT_BT_MAC_AUTORELEASEPOOL; + + state = ServiceDiscovery; + const QBluetoothAddress &address(discoveredDevices.at(0).address()); + + // Autoreleased object. + IOBluetoothHostController *const hc = [IOBluetoothHostController defaultController]; + if (![hc powerState]) { + discoveredDevices.clear(); + if (singleDevice) { + error = QBluetoothServiceDiscoveryAgent::PoweredOffError; + errorString = QBluetoothServiceDiscoveryAgent::tr("Local device is powered off"); + emit q_ptr->error(error); + } + + return serviceDiscoveryFinished(); + } + + if (DiscoveryMode() == QBluetoothServiceDiscoveryAgent::MinimalDiscovery) { + performMinimalServiceDiscovery(address); + } else { + uuidFilter.size() ? [serviceInquiry performSDPQueryWithDevice:address filters:uuidFilter] + : [serviceInquiry performSDPQueryWithDevice:address]; + } +} + +void QBluetoothServiceDiscoveryAgentPrivate::stopServiceDiscovery() +{ + Q_ASSERT_X(state != Inactive, "stopServiceDiscovery()", "invalid state"); + Q_ASSERT_X(q_ptr, "stopServiceDiscovery()", "invalid q_ptr (null)"); + + discoveredDevices.clear(); + state = Inactive; + + // "Stops" immediately. + [serviceInquiry stopSDPQuery]; + + emit q_ptr->canceled(); +} + +QBluetoothServiceDiscoveryAgentPrivate::DiscoveryState + QBluetoothServiceDiscoveryAgentPrivate::discoveryState() +{ + return state; +} + +void QBluetoothServiceDiscoveryAgentPrivate::setDiscoveryMode( + QBluetoothServiceDiscoveryAgent::DiscoveryMode m) +{ + discoveryMode = m; + +} + +QBluetoothServiceDiscoveryAgent::DiscoveryMode + QBluetoothServiceDiscoveryAgentPrivate::DiscoveryMode() +{ + return discoveryMode; +} + +void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscovered(const QBluetoothDeviceInfo &info) +{ + // Look for duplicates, and cached entries + for (int i = 0; i < discoveredDevices.count(); i++) { + if (discoveredDevices.at(i).address() == info.address()) { + discoveredDevices.removeAt(i); + break; + } + } + + discoveredDevices.prepend(info); +} + +void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscoveryError(QBluetoothDeviceDiscoveryAgent::Error) +{ + Q_ASSERT_X(q_ptr, "_q_deviceDiscoveryError()", "invalid q_ptr (null)"); + + error = QBluetoothServiceDiscoveryAgent::UnknownError; + errorString = tr("Unknown error while scanning for devices"); + + deviceDiscoveryAgent->stop(); + deviceDiscoveryAgent.reset(Q_NULLPTR); + + state = QBluetoothServiceDiscoveryAgentPrivate::Inactive; + emit q_ptr->error(error); + emit q_ptr->finished(); +} + +void QBluetoothServiceDiscoveryAgentPrivate::_q_deviceDiscoveryFinished() +{ + Q_ASSERT_X(q_ptr, "_q_deviceDiscoveryFinished()", + "invalid q_ptr (null)"); + + if (deviceDiscoveryAgent->error() != QBluetoothDeviceDiscoveryAgent::NoError) { + //Forward the device discovery error + error = static_cast<QBluetoothServiceDiscoveryAgent::Error>(deviceDiscoveryAgent->error()); + errorString = deviceDiscoveryAgent->errorString(); + deviceDiscoveryAgent.reset(Q_NULLPTR); + state = Inactive; + emit q_ptr->error(error); + emit q_ptr->finished(); + } else { + deviceDiscoveryAgent.reset(Q_NULLPTR); + startServiceDiscovery(); + } +} + +void QBluetoothServiceDiscoveryAgentPrivate::_q_serviceDiscoveryFinished() +{ + // See SDPInquiryFinished. +} + +void QBluetoothServiceDiscoveryAgentPrivate::SDPInquiryFinished(IOBluetoothDevice *device) +{ + Q_ASSERT_X(device, "SDPInquiryFinished()", "invalid IOBluetoothDevice (nil)"); + + if (state == Inactive) + return; + + QT_BT_MAC_AUTORELEASEPOOL; + + NSArray *const records = device.services; + for (IOBluetoothSDPServiceRecord *record in records) { + QBluetoothServiceInfo serviceInfo; + Q_ASSERT_X(discoveredDevices.size() >= 1, "SDPInquiryFinished()", + "invalid number of devices"); + + serviceInfo.setDevice(discoveredDevices.at(0)); + OSXBluetooth::extract_service_record(record, serviceInfo); + + if (!serviceInfo.isValid()) + continue; + + if (!isDuplicatedService(serviceInfo)) { + discoveredServices.append(serviceInfo); + emit q_ptr->serviceDiscovered(serviceInfo); + } + } + + serviceDiscoveryFinished(); +} + +void QBluetoothServiceDiscoveryAgentPrivate::SDPInquiryError(IOBluetoothDevice *device, IOReturn errorCode) +{ + Q_UNUSED(device) + Q_UNUSED(errorCode) + + discoveredDevices.clear(); + // TODO: find a better mapping from IOReturn to QBluetoothServiceDiscoveryAgent::Error. + if (singleDevice) { + error = QBluetoothServiceDiscoveryAgent::UnknownError; + errorString = QObject::tr("service discovery agent: unknown error"); + emit q_ptr->error(error); + } + + serviceDiscoveryFinished(); +} + +void QBluetoothServiceDiscoveryAgentPrivate::performMinimalServiceDiscovery(const QBluetoothAddress &deviceAddress) +{ + Q_ASSERT_X(!deviceAddress.isNull(), "performMinimalServiceDiscovery()", + "invalid device address"); + + QT_BT_MAC_AUTORELEASEPOOL; + + const BluetoothDeviceAddress iobtAddress = OSXBluetooth::iobluetooth_address(deviceAddress); + IOBluetoothDevice *const device = [IOBluetoothDevice deviceWithAddress:&iobtAddress]; + if (!device || !device.services) { + if (singleDevice) { + error = QBluetoothServiceDiscoveryAgent::UnknownError; + errorString = tr("service discovery agent: minimal service discovery failed"); + emit q_ptr->error(error); + } + } else { + + NSArray *const records = device.services; + for (IOBluetoothSDPServiceRecord *record in records) { + QBluetoothServiceInfo serviceInfo; + Q_ASSERT_X(discoveredDevices.size() >= 1, "SDPInquiryFinished()", + "invalid number of devices"); + + serviceInfo.setDevice(discoveredDevices.at(0)); + OSXBluetooth::extract_service_record(record, serviceInfo); + + if (!serviceInfo.isValid()) + continue; + + if (!isDuplicatedService(serviceInfo)) { + discoveredServices.append(serviceInfo); + emit q_ptr->serviceDiscovered(serviceInfo); + } + } + } + + serviceDiscoveryFinished(); +} + +void QBluetoothServiceDiscoveryAgentPrivate::setupDeviceDiscoveryAgent() +{ + Q_ASSERT_X(q_ptr, "setupDeviceDiscoveryAgent()", + "invalid q_ptr (null)"); + Q_ASSERT_X(deviceDiscoveryAgent.isNull() || !deviceDiscoveryAgent->isActive(), + "setupDeviceDiscoveryAgent()", + "device discovery agent is active"); + + deviceDiscoveryAgent.reset(new QBluetoothDeviceDiscoveryAgent(localAdapterAddress, q_ptr)); + + QObject::connect(deviceDiscoveryAgent.data(), SIGNAL(deviceDiscovered(const QBluetoothDeviceInfo &)), + q_ptr, SLOT(_q_deviceDiscovered(const QBluetoothDeviceInfo &))); + QObject::connect(deviceDiscoveryAgent.data(), SIGNAL(finished()), + q_ptr, SLOT(_q_deviceDiscoveryFinished())); + QObject::connect(deviceDiscoveryAgent.data(), SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)), + q_ptr, SLOT(_q_deviceDiscoveryError(QBluetoothDeviceDiscoveryAgent::Error))); +} + +bool QBluetoothServiceDiscoveryAgentPrivate::isDuplicatedService(const QBluetoothServiceInfo &serviceInfo) const +{ + //check the service is not already part of our known list + for (int j = 0; j < discoveredServices.count(); j++) { + const QBluetoothServiceInfo &info = discoveredServices.at(j); + if (info.device() == serviceInfo.device() + && info.serviceClassUuids() == serviceInfo.serviceClassUuids() + && info.serviceUuid() == serviceInfo.serviceUuid()) { + return true; + } + } + + return false; +} + +void QBluetoothServiceDiscoveryAgentPrivate::serviceDiscoveryFinished() +{ + if (!discoveredDevices.isEmpty()) + discoveredDevices.removeFirst(); + + if (state == ServiceDiscovery) + startServiceDiscovery(); +} + +QBluetoothServiceDiscoveryAgent::QBluetoothServiceDiscoveryAgent(QObject *parent) +: QObject(parent), d_ptr(new QBluetoothServiceDiscoveryAgentPrivate(QBluetoothAddress())) +{ + d_ptr->q_ptr = this; +} + +QBluetoothServiceDiscoveryAgent::QBluetoothServiceDiscoveryAgent(const QBluetoothAddress &deviceAdapter, QObject *parent) +: QObject(parent), d_ptr(new QBluetoothServiceDiscoveryAgentPrivate(deviceAdapter)) +{ + d_ptr->q_ptr = this; + if (!deviceAdapter.isNull()) { + const QList<QBluetoothHostInfo> localDevices = QBluetoothLocalDevice::allDevices(); + foreach (const QBluetoothHostInfo &hostInfo, localDevices) { + if (hostInfo.address() == deviceAdapter) + return; + } + d_ptr->error = InvalidBluetoothAdapterError; + d_ptr->errorString = tr("Invalid Bluetooth adapter address"); + } +} + +QBluetoothServiceDiscoveryAgent::~QBluetoothServiceDiscoveryAgent() +{ + delete d_ptr; +} + +QList<QBluetoothServiceInfo> QBluetoothServiceDiscoveryAgent::discoveredServices() const +{ + return d_ptr->discoveredServices; +} + +/*! + Sets the UUID filter to \a uuids. Only services matching the UUIDs in \a uuids will be + returned. + + An empty UUID list is equivalent to a list containing only QBluetoothUuid::PublicBrowseGroup. + + \sa uuidFilter() +*/ +void QBluetoothServiceDiscoveryAgent::setUuidFilter(const QList<QBluetoothUuid> &uuids) +{ + d_ptr->uuidFilter = uuids; +} + +/*! + This is an overloaded member function, provided for convenience. + + Sets the UUID filter to a list containing the single element \a uuid. + + \sa uuidFilter() +*/ +void QBluetoothServiceDiscoveryAgent::setUuidFilter(const QBluetoothUuid &uuid) +{ + d_ptr->uuidFilter.clear(); + d_ptr->uuidFilter.append(uuid); +} + +/*! + Returns the UUID filter. + + \sa setUuidFilter() +*/ +QList<QBluetoothUuid> QBluetoothServiceDiscoveryAgent::uuidFilter() const +{ + return d_ptr->uuidFilter; +} + +/*! + Sets the remote device address to \a address. If \a address is default constructed, + services will be discovered on all contactable Bluetooth devices. A new remote + address can only be set while there is no service discovery in progress; otherwise + this function returns false. + + On some platforms such as Blackberry the service discovery might lead to pairing requests. + Therefore it is not recommended to do service discoveries on all devices. + + \sa remoteAddress() +*/ +bool QBluetoothServiceDiscoveryAgent::setRemoteAddress(const QBluetoothAddress &address) +{ + if (isActive()) + return false; + + if (!address.isNull()) + d_ptr->singleDevice = true; + + d_ptr->deviceAddress = address; + return true; +} + +QBluetoothAddress QBluetoothServiceDiscoveryAgent::remoteAddress() const +{ + if (d_ptr->singleDevice) + return d_ptr->deviceAddress; + + return QBluetoothAddress(); +} + +void QBluetoothServiceDiscoveryAgent::start(DiscoveryMode mode) +{ + if (d_ptr->discoveryState() == QBluetoothServiceDiscoveryAgentPrivate::Inactive + && d_ptr->error != InvalidBluetoothAdapterError) + { + d_ptr->setDiscoveryMode(mode); + if (d_ptr->deviceAddress.isNull()) { + d_ptr->startDeviceDiscovery(); + } else { + d_ptr->discoveredDevices.append(QBluetoothDeviceInfo(d_ptr->deviceAddress, QString(), 0)); + d_ptr->startServiceDiscovery(); + } + } +} + +void QBluetoothServiceDiscoveryAgent::stop() +{ + if (d_ptr->error == InvalidBluetoothAdapterError || !isActive()) + return; + + switch (d_ptr->discoveryState()) { + case QBluetoothServiceDiscoveryAgentPrivate::DeviceDiscovery: + d_ptr->stopDeviceDiscovery(); + break; + case QBluetoothServiceDiscoveryAgentPrivate::ServiceDiscovery: + d_ptr->stopServiceDiscovery(); + default:; + } + + d_ptr->discoveredDevices.clear(); +} + +void QBluetoothServiceDiscoveryAgent::clear() +{ + // Don't clear the list while the search is ongoing + if (isActive()) + return; + + d_ptr->discoveredDevices.clear(); + d_ptr->discoveredServices.clear(); + d_ptr->uuidFilter.clear(); +} + +bool QBluetoothServiceDiscoveryAgent::isActive() const +{ + return d_ptr->state != QBluetoothServiceDiscoveryAgentPrivate::Inactive; +} + +QBluetoothServiceDiscoveryAgent::Error QBluetoothServiceDiscoveryAgent::error() const +{ + return d_ptr->error; +} + +QString QBluetoothServiceDiscoveryAgent::errorString() const +{ + return d_ptr->errorString; +} + +#include "moc_qbluetoothservicediscoveryagent.cpp" + +QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothserviceinfo_osx.mm b/src/bluetooth/qbluetoothserviceinfo_osx.mm new file mode 100644 index 00000000..b8463a52 --- /dev/null +++ b/src/bluetooth/qbluetoothserviceinfo_osx.mm @@ -0,0 +1,332 @@ +/**************************************************************************** +** +** 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 "qbluetoothserviceinfo.h" +#include "qbluetoothdeviceinfo.h" + +#include <QtCore/qglobal.h> +#include <QtCore/qmap.h> +#include <QtCore/qurl.h> + +QT_BEGIN_NAMESPACE + +class QBluetoothServiceInfoPrivate +{ +public: + QBluetoothServiceInfoPrivate(); + ~QBluetoothServiceInfoPrivate(); + + bool registerService(const QBluetoothAddress &localAdapter = QBluetoothAddress()); + + bool isRegistered() const; + + bool unregisterService(); + + QBluetoothDeviceInfo deviceInfo; + QMap<quint16, QVariant> attributes; + + QBluetoothServiceInfo::Sequence protocolDescriptor(QBluetoothUuid::ProtocolUuid protocol) const; + int serverChannel() const; +}; + +QBluetoothServiceInfoPrivate::QBluetoothServiceInfoPrivate() +{ +} + +QBluetoothServiceInfoPrivate::~QBluetoothServiceInfoPrivate() +{ +} + +bool QBluetoothServiceInfoPrivate::registerService(const QBluetoothAddress &localAdapter) +{ + Q_UNUSED(localAdapter) + return false; +} + +bool QBluetoothServiceInfoPrivate::isRegistered() const +{ + return false; +} + +bool QBluetoothServiceInfoPrivate::unregisterService() +{ + return false; +} + +bool QBluetoothServiceInfo::isRegistered() const +{ + return d_ptr->isRegistered(); +} + +bool QBluetoothServiceInfo::registerService(const QBluetoothAddress &localAdapter) +{ + return d_ptr->registerService(localAdapter); +} + +bool QBluetoothServiceInfo::unregisterService() +{ + return d_ptr->unregisterService(); +} + +QBluetoothServiceInfo::QBluetoothServiceInfo() + : d_ptr(QSharedPointer<QBluetoothServiceInfoPrivate>(new QBluetoothServiceInfoPrivate)) +{ +} + +QBluetoothServiceInfo::QBluetoothServiceInfo(const QBluetoothServiceInfo &other) + : d_ptr(other.d_ptr) +{ +} + +QBluetoothServiceInfo::~QBluetoothServiceInfo() +{ +} + +bool QBluetoothServiceInfo::isValid() const +{ + return !d_ptr->attributes.isEmpty(); +} + +bool QBluetoothServiceInfo::isComplete() const +{ + return d_ptr->attributes.keys().contains(ProtocolDescriptorList); +} + +QBluetoothDeviceInfo QBluetoothServiceInfo::device() const +{ + return d_ptr->deviceInfo; +} + +void QBluetoothServiceInfo::setDevice(const QBluetoothDeviceInfo &device) +{ + d_ptr->deviceInfo = device; +} + +void QBluetoothServiceInfo::setAttribute(quint16 attributeId, const QVariant &value) +{ + d_ptr->attributes[attributeId] = value; +} + +QVariant QBluetoothServiceInfo::attribute(quint16 attributeId) const +{ + return d_ptr->attributes.value(attributeId); +} + +QList<quint16> QBluetoothServiceInfo::attributes() const +{ + return d_ptr->attributes.keys(); +} + +bool QBluetoothServiceInfo::contains(quint16 attributeId) const +{ + return d_ptr->attributes.contains(attributeId); +} + +void QBluetoothServiceInfo::removeAttribute(quint16 attributeId) +{ + d_ptr->attributes.remove(attributeId); +} + +QBluetoothServiceInfo::Protocol QBluetoothServiceInfo::socketProtocol() const +{ + QBluetoothServiceInfo::Sequence parameters = protocolDescriptor(QBluetoothUuid::Rfcomm); + if (!parameters.isEmpty()) + return RfcommProtocol; + + parameters = protocolDescriptor(QBluetoothUuid::L2cap); + if (!parameters.isEmpty()) + return L2capProtocol; + + return UnknownProtocol; +} + +int QBluetoothServiceInfo::protocolServiceMultiplexer() const +{ + QBluetoothServiceInfo::Sequence parameters = protocolDescriptor(QBluetoothUuid::L2cap); + + if (parameters.isEmpty()) + return -1; + else if (parameters.count() == 1) + return 0; + else + return parameters.at(1).toUInt(); +} + +int QBluetoothServiceInfo::serverChannel() const +{ + return d_ptr->serverChannel(); +} + +QBluetoothServiceInfo::Sequence QBluetoothServiceInfo::protocolDescriptor(QBluetoothUuid::ProtocolUuid protocol) const +{ + return d_ptr->protocolDescriptor(protocol); +} + +QList<QBluetoothUuid> QBluetoothServiceInfo::serviceClassUuids() const +{ + QList<QBluetoothUuid> results; + + const QVariant var = attribute(QBluetoothServiceInfo::ServiceClassIds); + if (!var.isValid()) + return results; + + const QBluetoothServiceInfo::Sequence seq = var.value<QBluetoothServiceInfo::Sequence>(); + for (int i = 0; i < seq.count(); i++) + results.append(seq.at(i).value<QBluetoothUuid>()); + + return results; +} + +QBluetoothServiceInfo &QBluetoothServiceInfo::operator=(const QBluetoothServiceInfo &other) +{ + d_ptr = other.d_ptr; + + return *this; +} + +static void dumpAttributeVariant(const QVariant &var, const QString indent) +{ + switch (int(var.type())) { + case QMetaType::Void: + qDebug("%sEmpty", indent.toLocal8Bit().constData()); + break; + case QMetaType::UChar: + qDebug("%suchar %u", indent.toLocal8Bit().constData(), var.toUInt()); + break; + case QMetaType::UShort: + qDebug("%sushort %u", indent.toLocal8Bit().constData(), var.toUInt()); + case QMetaType::UInt: + qDebug("%suint %u", indent.toLocal8Bit().constData(), var.toUInt()); + break; + case QMetaType::Char: + qDebug("%schar %d", indent.toLocal8Bit().constData(), var.toInt()); + break; + case QMetaType::Short: + qDebug("%sshort %d", indent.toLocal8Bit().constData(), var.toInt()); + break; + case QMetaType::Int: + qDebug("%sint %d", indent.toLocal8Bit().constData(), var.toInt()); + break; + case QMetaType::QString: + qDebug("%sstring %s", indent.toLocal8Bit().constData(), var.toString().toLocal8Bit().constData()); + break; + case QMetaType::Bool: + qDebug("%sbool %d", indent.toLocal8Bit().constData(), var.toBool()); + break; + case QMetaType::QUrl: + qDebug("%surl %s", indent.toLocal8Bit().constData(), var.toUrl().toString().toLocal8Bit().constData()); + break; + case QVariant::UserType: + if (var.userType() == qMetaTypeId<QBluetoothUuid>()) { + QBluetoothUuid uuid = var.value<QBluetoothUuid>(); + switch (uuid.minimumSize()) { + case 0: + qDebug("%suuid NULL", indent.toLocal8Bit().constData()); + break; + case 2: + qDebug("%suuid %04x", indent.toLocal8Bit().constData(), uuid.toUInt16()); + break; + case 4: + qDebug("%suuid %08x", indent.toLocal8Bit().constData(), uuid.toUInt32()); + break; + case 16: + qDebug("%suuid %s", indent.toLocal8Bit().constData(), QByteArray(reinterpret_cast<const char *>(uuid.toUInt128().data), 16).toHex().constData()); + break; + default: + qDebug("%suuid ???", indent.toLocal8Bit().constData()); + ; + } + } else if (var.userType() == qMetaTypeId<QBluetoothServiceInfo::Sequence>()) { + qDebug("%sSequence", indent.toLocal8Bit().constData()); + const QBluetoothServiceInfo::Sequence *sequence = static_cast<const QBluetoothServiceInfo::Sequence *>(var.data()); + foreach (const QVariant &v, *sequence) + dumpAttributeVariant(v, indent + QLatin1Char('\t')); + } else if (var.userType() == qMetaTypeId<QBluetoothServiceInfo::Alternative>()) { + qDebug("%sAlternative", indent.toLocal8Bit().constData()); + const QBluetoothServiceInfo::Alternative *alternative = static_cast<const QBluetoothServiceInfo::Alternative *>(var.data()); + foreach (const QVariant &v, *alternative) + dumpAttributeVariant(v, indent + QLatin1Char('\t')); + } + break; + default: + qDebug("%sunknown variant type %d", indent.toLocal8Bit().constData(), var.userType()); + } +} + +QDebug operator << (QDebug dbg, const QBluetoothServiceInfo &info) +{ + foreach (quint16 id, info.attributes()) { + dumpAttributeVariant(info.attribute(id), QString::fromLatin1("(%1)\t").arg(id)); + } + return dbg; +} + +QBluetoothServiceInfo::Sequence QBluetoothServiceInfoPrivate::protocolDescriptor(QBluetoothUuid::ProtocolUuid protocol) const +{ + if (!attributes.contains(QBluetoothServiceInfo::ProtocolDescriptorList)) + return QBluetoothServiceInfo::Sequence(); + + foreach (const QVariant &v, attributes.value(QBluetoothServiceInfo::ProtocolDescriptorList).value<QBluetoothServiceInfo::Sequence>()) { + QBluetoothServiceInfo::Sequence parameters = v.value<QBluetoothServiceInfo::Sequence>(); + if (parameters.empty()) + continue; + if (parameters.at(0).userType() == qMetaTypeId<QBluetoothUuid>()) { + if (parameters.at(0).value<QBluetoothUuid>() == protocol) + return parameters; + } + } + + return QBluetoothServiceInfo::Sequence(); +} + +int QBluetoothServiceInfoPrivate::serverChannel() const +{ + QBluetoothServiceInfo::Sequence parameters = protocolDescriptor(QBluetoothUuid::Rfcomm); + + if (parameters.isEmpty()) + return -1; + else if (parameters.count() == 1) + return 0; + else + return parameters.at(1).toUInt(); +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothserviceinfo_p.h b/src/bluetooth/qbluetoothserviceinfo_p.h index 7ff7132c..ea6a122a 100644 --- a/src/bluetooth/qbluetoothserviceinfo_p.h +++ b/src/bluetooth/qbluetoothserviceinfo_p.h @@ -60,6 +60,8 @@ QT_BEGIN_NAMESPACE class QBluetoothServiceInfo; +#ifndef QT_OSX_BLUETOOTH + class QBluetoothServiceInfoPrivate : public QObject { @@ -93,6 +95,8 @@ private: mutable bool registered; }; +#endif + QT_END_NAMESPACE #endif |