5#include <QtCore/QGlobalStatic>
6#include <QtCore/QLoggingCategory>
8#include <QtCore/QVersionNumber>
9#include <QtNetwork/private/qnet_unix_p.h>
18QT_IMPL_METATYPE_EXTERN(InterfaceList)
19QT_IMPL_METATYPE_EXTERN(ManufacturerDataList)
20QT_IMPL_METATYPE_EXTERN(ServiceDataList)
21QT_IMPL_METATYPE_EXTERN(ManagedObjectList)
24Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)
26using namespace QtBluetoothPrivate;
35Q_GLOBAL_STATIC_WITH_ARGS(Bluez5TestResult, bluezVersion, (BluezVersionUnknown))
36Q_GLOBAL_STATIC_WITH_ARGS(QVersionNumber, bluezDaemonVersion, (QVersionNumber()))
39
40
42void initializeBluez5()
44 if (*bluezVersion() == BluezVersionUnknown) {
45 OrgFreedesktopDBusObjectManagerInterface manager(QStringLiteral(
"org.bluez"),
47 QDBusConnection::systemBus());
48 qDBusRegisterMetaType<InterfaceList>();
49 qDBusRegisterMetaType<ManagedObjectList>();
50 qDBusRegisterMetaType<ManufacturerDataList>();
51 qDBusRegisterMetaType<ServiceDataList>();
53 QDBusPendingReply<ManagedObjectList> reply = manager.GetManagedObjects();
54 reply.waitForFinished();
55 if (reply.isError()) {
56 *bluezVersion() = BluezNotAvailable;
57 qWarning() <<
"Cannot find a compatible running Bluez. "
58 "Please check the Bluez installation. "
59 "QtBluetooth requires at least BlueZ version 5.";
61 *bluezVersion() = BluezVersion5;
62 qCDebug(QT_BT_BLUEZ) <<
"Bluez 5 detected.";
68
69
70
71
72
73
74
75
76bool mandatoryHciIoctlsAvailable()
79 int hciSocket = ::qt_safe_socket(AF_BLUETOOTH, SOCK_RAW,
BTPROTO_HCI);
81 qCWarning(QT_BT_BLUEZ) <<
"Cannot open HCI socket:" << qt_error_string(errno);
86 struct hci_dev_req *devRequest =
nullptr;
87 struct hci_dev_list_req *devRequestList =
nullptr;
88 struct hci_dev_info devInfo;
89 const int devListSize =
sizeof(
struct hci_dev_list_req)
92 devRequestList = (hci_dev_list_req *) malloc(devListSize);
93 if (!devRequestList) {
94 qt_safe_close(hciSocket);
98 QScopedPointer<hci_dev_list_req, QScopedPointerPodDeleter> pDevList(devRequestList);
99 memset(pDevList.data(), 0, devListSize);
101 devRequest = pDevList->dev_req;
103 if (qt_safe_ioctl(hciSocket,
HCIGETDEVLIST, devRequestList) < 0) {
104 qt_safe_close(hciSocket);
105 qCWarning(QT_BT_BLUEZ) <<
"HCI icotl HCIGETDEVLIST:" << qt_error_string(errno);
109 if (devRequestList->dev_num > 0) {
110 devInfo.dev_id = devRequest->dev_id;
112 qt_safe_close(hciSocket);
113 qCWarning(QT_BT_BLUEZ) <<
"HCI icotl HCIGETDEVINFO:" << qt_error_string(errno);
119 const int maxNoOfConnections = 20;
120 hci_conn_list_req *infoList =
nullptr;
121 infoList = (hci_conn_list_req *)
122 malloc(
sizeof(hci_conn_list_req) + maxNoOfConnections *
sizeof(hci_conn_info));
125 qt_safe_close(hciSocket);
129 QScopedPointer<hci_conn_list_req, QScopedPointerPodDeleter> pInfoList(infoList);
130 pInfoList->conn_num = maxNoOfConnections;
131 pInfoList->dev_id = devInfo.dev_id;
133 if (qt_safe_ioctl(hciSocket,
HCIGETCONNLIST, (
void *) infoList) < 0) {
134 qCWarning(QT_BT_BLUEZ) <<
"HCI icotl HCIGETCONNLIST:" << qt_error_string(errno);
135 qt_safe_close(hciSocket);
139 qt_safe_close(hciSocket);
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158QVersionNumber bluetoothdVersion()
160 if (bluezDaemonVersion()->isNull()) {
164 qDBusRegisterMetaType<InterfaceList>();
165 qDBusRegisterMetaType<ManagedObjectList>();
166 qDBusRegisterMetaType<ManufacturerDataList>();
167 qDBusRegisterMetaType<ServiceDataList>();
169 qCDebug(QT_BT_BLUEZ) <<
"Detecting bluetoothd version";
173 const QString version = qEnvironmentVariable(
"BLUETOOTH_FORCE_DBUS_LE_VERSION");
174 if (!version.isNull()) {
175 const QVersionNumber vn = QVersionNumber::fromString(version);
177 *bluezDaemonVersion() = vn;
178 qCDebug(QT_BT_BLUEZ) <<
"Forcing Bluez LE API selection:"
179 << bluezDaemonVersion()->toString();
184 if (bluezDaemonVersion()->isNull() && qt_haveLinuxProcfs()) {
185 QDBusConnection session = QDBusConnection::systemBus();
186 qint64 pid = session.interface()->servicePid(QStringLiteral(
"org.bluez")).value();
189 auto determineBinaryVersion = [](
const QString &binary) -> QVersionNumber {
192 process.start(binary, {QStringLiteral(
"--version")});
193 process.waitForFinished();
195 const QString version = QString::fromLocal8Bit(process.readAll());
196 const QVersionNumber vn = QVersionNumber::fromString(version);
198 qCDebug(QT_BT_BLUEZ) <<
"Detected bluetoothd version" << vn;
203 qCDebug(QT_BT_BLUEZ) <<
"Using /proc/<pid>/exe";
204 const QString procExe = QStringLiteral(
"/proc/%1/exe").arg(pid);
205 const QVersionNumber vn = determineBinaryVersion(procExe);
207 *bluezDaemonVersion() = vn;
209 if (bluezDaemonVersion()->isNull()) {
210 qCDebug(QT_BT_BLUEZ) <<
"Using /proc/<pid>/cmdline";
212 QFile procFile(QStringLiteral(
"/proc/%1/cmdline").arg(pid));
213 if (procFile.open(QIODevice::ReadOnly|QIODevice::Text)) {
214 buffer = procFile.readAll();
218 const QString binary = QString::fromLocal8Bit(buffer.split(
'\0').at(0));
219 QFileInfo info(binary);
220 if (info.isExecutable())
221 *bluezDaemonVersion() = determineBinaryVersion(binary);
223 qCDebug(QT_BT_BLUEZ) <<
"Cannot determine bluetoothd version via cmdline:"
230 if (bluezDaemonVersion()->isNull()) {
232 if (mandatoryHciIoctlsAvailable()) {
234 *bluezDaemonVersion() = QVersionNumber(4, 0);
239 if (bluezDaemonVersion()->isNull()) {
242 *bluezDaemonVersion() = QVersionNumber(3, 0);
243 qCWarning(QT_BT_BLUEZ) <<
"Cannot determine bluetoothd version and required Bluetooth HCI ioctols";
244 qCWarning(QT_BT_BLUEZ) <<
"Disabling Qt Bluetooth LE feature";
247 qCDebug(QT_BT_BLUEZ) <<
"Bluetoothd:" << bluezDaemonVersion()->toString();
250 Q_ASSERT(!bluezDaemonVersion()->isNull());
251 return *bluezDaemonVersion();
257 AdapterData() : reference(1), wasListeningAlready(
false) {}
260 bool wasListeningAlready;
261 OrgFreedesktopDBusPropertiesInterface *propteryListener =
nullptr;
274
275
276
277
278
279
280
281
282
283
284
285
286
287
292 qCDebug(QT_BT_BLUEZ) <<
"Creating QtBluezDiscoveryManager";
295 d->manager =
new OrgFreedesktopDBusObjectManagerInterface(
296 QStringLiteral(
"org.bluez"), QStringLiteral(
"/"),
297 QDBusConnection::systemBus(),
this);
298 connect(d->manager, SIGNAL(InterfacesRemoved(QDBusObjectPath,QStringList)),
299 SLOT(InterfacesRemoved(QDBusObjectPath,QStringList)));
304 qCDebug(QT_BT_BLUEZ) <<
"Destroying QtBluezDiscoveryManager";
306 const QList<QString> adapterPaths = d->references.keys();
307 for (
const QString &adapterPath : adapterPaths) {
308 AdapterData *data = d->references.take(adapterPath);
309 delete data->propteryListener;
312 if (!data->wasListeningAlready) {
313 OrgBluezAdapter1Interface iface(QStringLiteral(
"org.bluez"), adapterPath,
314 QDBusConnection::systemBus());
315 iface.StopDiscovery();
326 return discoveryManager();
331 if (adapterPath.isEmpty())
335 if (d->references.contains(adapterPath)) {
336 d->references[adapterPath]->reference++;
340 AdapterData *data =
new AdapterData();
342 OrgFreedesktopDBusPropertiesInterface *propIface =
new OrgFreedesktopDBusPropertiesInterface(
343 QStringLiteral(
"org.bluez"), adapterPath, QDBusConnection::systemBus());
344 connect(propIface, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged,
345 this, &QtBluezDiscoveryManager::PropertiesChanged);
346 data->propteryListener = propIface;
348 OrgBluezAdapter1Interface iface(QStringLiteral(
"org.bluez"), adapterPath,
349 QDBusConnection::systemBus());
350 data->wasListeningAlready = iface.discovering();
352 d->references[adapterPath] = data;
354 if (!data->wasListeningAlready)
355 iface.StartDiscovery();
362 if (!d->references.contains(adapterPath))
365 AdapterData *data = d->references[adapterPath];
368 if (data->reference > 0)
371 d->references.remove(adapterPath);
372 if (!data->wasListeningAlready) {
373 OrgBluezAdapter1Interface iface(QStringLiteral(
"org.bluez"), adapterPath,
374 QDBusConnection::systemBus());
375 iface.StopDiscovery();
378 delete data->propteryListener;
397 const QStringList &interfaces)
399 if (!d->references.contains(object_path.path())
400 || !interfaces.contains(QStringLiteral(
"org.bluez.Adapter1")))
403 removeAdapterFromMonitoring(object_path.path());
407 const QVariantMap &changed_properties,
408 const QStringList &invalidated_properties,
409 const QDBusMessage &)
411 Q_UNUSED(invalidated_properties);
413 OrgFreedesktopDBusPropertiesInterface *propIface =
414 qobject_cast<OrgFreedesktopDBusPropertiesInterface *>(sender());
419 if (interface == QStringLiteral(
"org.bluez.Adapter1")
420 && d->references.contains(propIface->path())
421 && changed_properties.contains(QStringLiteral(
"Discovering"))) {
422 bool isDiscovering = changed_properties.value(QStringLiteral(
"Discovering")).toBool();
423 if (!isDiscovering) {
426
427
428
429
430
432 AdapterData *data = d->references[propIface->path()];
434 removeAdapterFromMonitoring(propIface->path());
436 OrgBluezAdapter1Interface iface(QStringLiteral(
"org.bluez"), propIface->path(),
437 QDBusConnection::systemBus());
438 iface.StartDiscovery();
447 AdapterData *data = d->references.take(dbusPath);
448 delete data->propteryListener;
451 emit discoveryInterrupted(dbusPath);
455
456
457
458
459
460
461
462
463
464
467 OrgFreedesktopDBusObjectManagerInterface manager(QStringLiteral(
"org.bluez"),
469 QDBusConnection::systemBus());
471 QDBusPendingReply<ManagedObjectList> reply = manager.GetManagedObjects();
472 reply.waitForFinished();
473 if (reply.isError()) {
483 QBluetoothAddress address;
486 QList<AdapterInfo> localAdapters;
489 for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
490 const QDBusObjectPath &path = it.key();
493 for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
494 const QString &iface = jt.key();
496 if (iface == QStringLiteral(
"org.bluez.Adapter1")) {
498 info.path = path.path();
499 info.address = QBluetoothAddress(ifaceList.value(iface).value(
500 QStringLiteral(
"Address")).toString());
501 info.powered = ifaceList.value(iface).value(QStringLiteral(
"Powered")).toBool();
502 if (!info.address.isNull())
503 localAdapters.append(info);
512 if (localAdapters.isEmpty())
515 auto findFirstPowered = [&localAdapters]() {
516 Q_ASSERT(!localAdapters.isEmpty());
517 auto it = std::find_if(localAdapters.cbegin(), localAdapters.cend(),
518 [](
const AdapterInfo &info) {
519 return info.powered ==
true;
521 if (it != localAdapters.cend())
524 return localAdapters.cbegin()->path;
527 if (wantedAddress.isNull())
528 return findFirstPowered();
530 for (
const AdapterInfo &info : std::as_const(localAdapters)) {
531 if (info.address == wantedAddress)
539
540
541
542
550 const QString hostAdapterPath = findAdapterForAddress(localAddress, &ok);
551 if (!ok || hostAdapterPath.isEmpty())
555 OrgFreedesktopDBusObjectManagerInterface manager(QStringLiteral(
"org.bluez"),
557 QDBusConnection::systemBus());
558 QDBusPendingReply<ManagedObjectList> reply = manager.GetManagedObjects();
559 reply.waitForFinished();
563 using namespace Qt::StringLiterals;
565 const bool peripheralSupported = reply.value()
566 .value(QDBusObjectPath(hostAdapterPath))
567 .contains(
"org.bluez.LEAdvertisingManager1"_L1);
569 qCDebug(QT_BT_BLUEZ) <<
"Peripheral role"
570 << (peripheralSupported ?
"" :
"not")
571 <<
"supported on" << hostAdapterPath;
572 return peripheralSupported ? hostAdapterPath : QString{};
576
577
578
579
582 QString appName = text;
583 for (qsizetype i = 0; i < appName.size(); ++i) {
584 ushort us = appName[i].unicode();
585 bool valid = (us >=
'a' && us <=
'z')
586 || (us >=
'A' && us <=
'Z')
587 || (us >=
'0' && us <=
'9')
591 appName[i] = QLatin1Char(
'_');
599#include "moc_bluez5_helper_p.cpp"
QString adapterWithDBusPeripheralInterface(const QBluetoothAddress &localAddress)
QString sanitizeNameForDBus(const QString &text)
QString findAdapterForAddress(const QBluetoothAddress &wantedAddress, bool *ok=nullptr)
QMap< QDBusObjectPath, InterfaceList > ManagedObjectList
QMap< QString, QVariantMap > InterfaceList
QMap< QString, AdapterData * > references
OrgFreedesktopDBusObjectManagerInterface * manager
~QtBluezDiscoveryManager()
static QtBluezDiscoveryManager * instance()
bool registerDiscoveryInterest(const QString &adapterPath)
void unregisterDiscoveryInterest(const QString &adapterPath)
connect(manager, &QNetworkAccessManager::finished, this, &MyClass::replyFinished)