summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTimur Pocheptsov <Timur.Pocheptsov@digia.com>2014-09-04 10:23:05 +0200
committerTimur Pocheptsov <Timur.Pocheptsov@digia.com>2014-09-16 15:27:09 +0200
commitdc34c7aae7d4d641f4d06990141c7915542363ee (patch)
treec0245f807242b904ba44f5268d3551938cb4e5f8
parent982eeb3547f85dc76e5864559ee56db74a7dd86f (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>
-rw-r--r--src/bluetooth/bluetooth.pro8
-rw-r--r--src/bluetooth/osx/osxbt.pri6
-rw-r--r--src/bluetooth/osx/osxbtconnectionmonitor.mm6
-rw-r--r--src/bluetooth/osx/osxbtdeviceinquiry.mm10
-rw-r--r--src/bluetooth/osx/osxbtdevicepair.mm11
-rw-r--r--src/bluetooth/osx/osxbtdevicepair_p.h4
-rw-r--r--src/bluetooth/osx/osxbtsdpinquiry.mm277
-rw-r--r--src/bluetooth/osx/osxbtsdpinquiry_p.h103
-rw-r--r--src/bluetooth/osx/osxbtutility.mm33
-rw-r--r--src/bluetooth/osx/osxbtutility_p.h23
-rw-r--r--src/bluetooth/qbluetoothdevicediscoveryagent_osx.mm9
-rw-r--r--src/bluetooth/qbluetoothlocaldevice_osx.mm4
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent.h2
-rw-r--r--src/bluetooth/qbluetoothservicediscoveryagent_osx.mm570
-rw-r--r--src/bluetooth/qbluetoothserviceinfo_osx.mm332
-rw-r--r--src/bluetooth/qbluetoothserviceinfo_p.h4
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