1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include <QtCore/QGlobalStatic> |
5 | #include <QtCore/QLoggingCategory> |
6 | #include <QtCore/QMap> |
7 | #include <QtCore/QVersionNumber> |
8 | #include <QtNetwork/private/qnet_unix_p.h> |
9 | #include "bluez5_helper_p.h" |
10 | #include "bluez_data_p.h" |
11 | #include "objectmanager_p.h" |
12 | #include "properties_p.h" |
13 | #include "adapter1_bluez5_p.h" |
14 | |
15 | QT_BEGIN_NAMESPACE |
16 | |
17 | QT_IMPL_METATYPE_EXTERN(InterfaceList) |
18 | QT_IMPL_METATYPE_EXTERN(ManufacturerDataList) |
19 | QT_IMPL_METATYPE_EXTERN(ServiceDataList) |
20 | QT_IMPL_METATYPE_EXTERN(ManagedObjectList) |
21 | |
22 | |
23 | Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) |
24 | |
25 | typedef enum Bluez5TestResultType |
26 | { |
27 | BluezVersionUnknown, |
28 | BluezVersion5, |
29 | BluezNotAvailable |
30 | } Bluez5TestResult; |
31 | |
32 | Q_GLOBAL_STATIC_WITH_ARGS(Bluez5TestResult, bluezVersion, (BluezVersionUnknown)) |
33 | Q_GLOBAL_STATIC_WITH_ARGS(QVersionNumber, bluezDaemonVersion, (QVersionNumber())) |
34 | |
35 | /* |
36 | Ensures that the DBus types are registered |
37 | */ |
38 | |
39 | void initializeBluez5() |
40 | { |
41 | if (*bluezVersion() == BluezVersionUnknown) { |
42 | OrgFreedesktopDBusObjectManagerInterface manager(QStringLiteral("org.bluez" ), |
43 | QStringLiteral("/" ), |
44 | QDBusConnection::systemBus()); |
45 | qDBusRegisterMetaType<InterfaceList>(); |
46 | qDBusRegisterMetaType<ManagedObjectList>(); |
47 | qDBusRegisterMetaType<ManufacturerDataList>(); |
48 | qDBusRegisterMetaType<ServiceDataList>(); |
49 | |
50 | QDBusPendingReply<ManagedObjectList> reply = manager.GetManagedObjects(); |
51 | reply.waitForFinished(); |
52 | if (reply.isError()) { |
53 | *bluezVersion() = BluezNotAvailable; |
54 | qWarning() << "Cannot find a compatible running Bluez. " |
55 | "Please check the Bluez installation. " |
56 | "QtBluetooth requires at least BlueZ version 5." ; |
57 | } else { |
58 | *bluezVersion() = BluezVersion5; |
59 | qCDebug(QT_BT_BLUEZ) << "Bluez 5 detected." ; |
60 | } |
61 | } |
62 | } |
63 | |
64 | /* |
65 | Checks that the mandatory Bluetooth HCI ioctls are offered |
66 | by Linux kernel. Returns \c true if the ictls are available; otherwise \c false. |
67 | |
68 | Mandatory ioctls: |
69 | - HCIGETCONNLIST |
70 | - HCIGETDEVINFO |
71 | - HCIGETDEVLIST |
72 | */ |
73 | bool mandatoryHciIoctlsAvailable() |
74 | { |
75 | // open hci socket |
76 | int hciSocket = ::qt_safe_socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); |
77 | if (hciSocket < 0) { |
78 | qCWarning(QT_BT_BLUEZ) << "Cannot open HCI socket:" << qt_error_string(errno); |
79 | return false; |
80 | } |
81 | |
82 | // check HCIGETDEVLIST & HCIGETDEVLIST |
83 | struct hci_dev_req *devRequest = nullptr; |
84 | struct hci_dev_list_req *devRequestList = nullptr; |
85 | struct hci_dev_info devInfo; |
86 | const int devListSize = sizeof(struct hci_dev_list_req) |
87 | + HCI_MAX_DEV * sizeof(struct hci_dev_req); |
88 | |
89 | devRequestList = (hci_dev_list_req *) malloc(size: devListSize); |
90 | if (!devRequestList) { |
91 | qt_safe_close(fd: hciSocket); |
92 | return false; // if we cannot malloc nothing will help anyway |
93 | } |
94 | |
95 | QScopedPointer<hci_dev_list_req, QScopedPointerPodDeleter> pDevList(devRequestList); |
96 | memset(s: pDevList.data(), c: 0, n: devListSize); |
97 | pDevList->dev_num = HCI_MAX_DEV; |
98 | devRequest = pDevList->dev_req; |
99 | |
100 | if (qt_safe_ioctl(sockfd: hciSocket, HCIGETDEVLIST, arg: devRequestList) < 0) { |
101 | qt_safe_close(fd: hciSocket); |
102 | qCWarning(QT_BT_BLUEZ) << "HCI icotl HCIGETDEVLIST:" << qt_error_string(errno); |
103 | return false; |
104 | } |
105 | |
106 | if (devRequestList->dev_num > 0) { |
107 | devInfo.dev_id = devRequest->dev_id; |
108 | if (qt_safe_ioctl(sockfd: hciSocket, HCIGETDEVINFO, arg: &devInfo) < 0) { |
109 | qt_safe_close(fd: hciSocket); |
110 | qCWarning(QT_BT_BLUEZ) << "HCI icotl HCIGETDEVINFO:" << qt_error_string(errno); |
111 | return false; |
112 | } |
113 | } |
114 | |
115 | // check HCIGETCONNLIST |
116 | const int maxNoOfConnections = 20; |
117 | hci_conn_list_req *infoList = nullptr; |
118 | infoList = (hci_conn_list_req *) |
119 | malloc(size: sizeof(hci_conn_list_req) + maxNoOfConnections * sizeof(hci_conn_info)); |
120 | |
121 | if (!infoList) { |
122 | qt_safe_close(fd: hciSocket); |
123 | return false; |
124 | } |
125 | |
126 | QScopedPointer<hci_conn_list_req, QScopedPointerPodDeleter> pInfoList(infoList); |
127 | pInfoList->conn_num = maxNoOfConnections; |
128 | pInfoList->dev_id = devInfo.dev_id; |
129 | |
130 | if (qt_safe_ioctl(sockfd: hciSocket, HCIGETCONNLIST, arg: (void *) infoList) < 0) { |
131 | qCWarning(QT_BT_BLUEZ) << "HCI icotl HCIGETCONNLIST:" << qt_error_string(errno); |
132 | qt_safe_close(fd: hciSocket); |
133 | return false; |
134 | } |
135 | |
136 | qt_safe_close(fd: hciSocket); |
137 | return true; |
138 | } |
139 | |
140 | /* |
141 | * This function returns the version of bluetoothd in use on the system. |
142 | * This is required to determine which QLEControllerPrivate implementation |
143 | * is required. The following version tags are of significance: |
144 | * |
145 | * Version < 4.0 -> QLEControllerPrivateCommon |
146 | * Version < 5.42 -> QLEControllerPrivateBluez |
147 | * Version >= 5.42 -> QLEControllerPrivateBluezDBus |
148 | * |
149 | * This function utilizes a singleton pattern. It always returns a cached |
150 | * version tag which is determined on first call. This is necessary to |
151 | * avoid continuesly running the somewhat expensive tests. |
152 | * |
153 | * The function must never return a null QVersionNumber. |
154 | */ |
155 | QVersionNumber bluetoothdVersion() |
156 | { |
157 | if (bluezDaemonVersion()->isNull()) { |
158 | // Register DBus specific meta types (copied from isBluez5()) |
159 | // Not all code paths run through isBluez5() but still need the |
160 | // registration. |
161 | qDBusRegisterMetaType<InterfaceList>(); |
162 | qDBusRegisterMetaType<ManagedObjectList>(); |
163 | qDBusRegisterMetaType<ManufacturerDataList>(); |
164 | qDBusRegisterMetaType<ServiceDataList>(); |
165 | |
166 | qCDebug(QT_BT_BLUEZ) << "Detecting bluetoothd version" ; |
167 | //Order of matching |
168 | // 1. Pick whatever the user decides via BLUETOOTH_FORCE_DBUS_LE_VERSION |
169 | // Set version to below version 5.42 to use custom/old GATT stack implementation |
170 | const QString version = qEnvironmentVariable(varName: "BLUETOOTH_FORCE_DBUS_LE_VERSION" ); |
171 | if (!version.isNull()) { |
172 | const QVersionNumber vn = QVersionNumber::fromString(string: version); |
173 | if (!vn.isNull()) { |
174 | *bluezDaemonVersion() = vn; |
175 | qCDebug(QT_BT_BLUEZ) << "Forcing Bluez LE API selection:" |
176 | << bluezDaemonVersion()->toString(); |
177 | } |
178 | } |
179 | |
180 | // 2. Find bluetoothd binary and check "bluetoothd --version" |
181 | if (bluezDaemonVersion()->isNull() && qt_haveLinuxProcfs()) { |
182 | QDBusConnection session = QDBusConnection::systemBus(); |
183 | qint64 pid = session.interface()->servicePid(QStringLiteral("org.bluez" )).value(); |
184 | QByteArray buffer; |
185 | |
186 | auto determineBinaryVersion = [](const QString &binary) -> QVersionNumber { |
187 | QProcess process; |
188 | process.start(program: binary, arguments: {QStringLiteral("--version" )}); |
189 | process.waitForFinished(); |
190 | |
191 | const QString version = QString::fromLocal8Bit(ba: process.readAll()); |
192 | const QVersionNumber vn = QVersionNumber::fromString(string: version); |
193 | if (!vn.isNull()) |
194 | qCDebug(QT_BT_BLUEZ) << "Detected bluetoothd version" << vn; |
195 | return vn; |
196 | }; |
197 | |
198 | //try reading /proc/<pid>/exe first -> requires process owner |
199 | qCDebug(QT_BT_BLUEZ) << "Using /proc/<pid>/exe" ; |
200 | const QString procExe = QStringLiteral("/proc/%1/exe" ).arg(a: pid); |
201 | const QVersionNumber vn = determineBinaryVersion(procExe); |
202 | if (!vn.isNull()) |
203 | *bluezDaemonVersion() = vn; |
204 | |
205 | if (bluezDaemonVersion()->isNull()) { |
206 | qCDebug(QT_BT_BLUEZ) << "Using /proc/<pid>/cmdline" ; |
207 | //try to reading /proc/<pid>/cmdline (does not require additional process rights) |
208 | QFile procFile(QStringLiteral("/proc/%1/cmdline" ).arg(a: pid)); |
209 | if (procFile.open(flags: QIODevice::ReadOnly|QIODevice::Text)) { |
210 | buffer = procFile.readAll(); |
211 | procFile.close(); |
212 | |
213 | // cmdline params separated by character \0 -> first is bluetoothd binary |
214 | const QString binary = QString::fromLocal8Bit(ba: buffer.split(sep: '\0').at(i: 0)); |
215 | QFileInfo info(binary); |
216 | if (info.isExecutable()) |
217 | *bluezDaemonVersion() = determineBinaryVersion(binary); |
218 | else |
219 | qCDebug(QT_BT_BLUEZ) << "Cannot determine bluetoothd version via cmdline:" |
220 | << binary; |
221 | } |
222 | } |
223 | } |
224 | |
225 | // 3. Fall back to custom ATT backend, if possible? |
226 | if (bluezDaemonVersion()->isNull()) { |
227 | // Check mandatory HCI ioctls are available |
228 | if (mandatoryHciIoctlsAvailable()) { |
229 | // default 4.0 for now -> implies custom (G)ATT implementation |
230 | *bluezDaemonVersion() = QVersionNumber(4, 0); |
231 | } |
232 | } |
233 | |
234 | // 4. Ultimate fallback -> enable dummy backend |
235 | if (bluezDaemonVersion()->isNull()) { |
236 | // version 3 represents disabled BTLE |
237 | // bluezDaemonVersion should not be null to avoid repeated version tests |
238 | *bluezDaemonVersion() = QVersionNumber(3, 0); |
239 | qCWarning(QT_BT_BLUEZ) << "Cannot determine bluetoothd version and required Bluetooth HCI ioctols" ; |
240 | qCWarning(QT_BT_BLUEZ) << "Disabling Qt Bluetooth LE feature" ; |
241 | } |
242 | |
243 | qCDebug(QT_BT_BLUEZ) << "Bluetoothd:" << bluezDaemonVersion()->toString(); |
244 | } |
245 | |
246 | Q_ASSERT(!bluezDaemonVersion()->isNull()); |
247 | return *bluezDaemonVersion(); |
248 | } |
249 | |
250 | struct AdapterData |
251 | { |
252 | public: |
253 | AdapterData() : reference(1), wasListeningAlready(false) {} |
254 | |
255 | int reference; |
256 | bool wasListeningAlready; |
257 | OrgFreedesktopDBusPropertiesInterface *propteryListener = nullptr; |
258 | }; |
259 | |
260 | class QtBluezDiscoveryManagerPrivate |
261 | { |
262 | public: |
263 | QMap<QString, AdapterData *> references; |
264 | OrgFreedesktopDBusObjectManagerInterface *manager = nullptr; |
265 | }; |
266 | |
267 | Q_GLOBAL_STATIC(QtBluezDiscoveryManager, discoveryManager) |
268 | |
269 | /*! |
270 | \internal |
271 | \class QtBluezDiscoveryManager |
272 | |
273 | This class manages the access to "org.bluez.Adapter1::Start/StopDiscovery. |
274 | |
275 | The flag is a system flag. We want to ensure that the various Qt classes don't turn |
276 | the flag on and off and thereby get into their way. If some other system component |
277 | changes the flag (e.g. adapter removed) we notify all Qt classes about the change by emitting |
278 | \l discoveryInterrupted(QString). Classes should indicate this via an appropriate |
279 | error message to the user. |
280 | |
281 | Once the signal was emitted, all existing requests for discovery mode on the same adapter |
282 | have to be renewed via \l registerDiscoveryInterest(QString). |
283 | */ |
284 | |
285 | QtBluezDiscoveryManager::QtBluezDiscoveryManager(QObject *parent) : |
286 | QObject(parent) |
287 | { |
288 | qCDebug(QT_BT_BLUEZ) << "Creating QtBluezDiscoveryManager" ; |
289 | d = new QtBluezDiscoveryManagerPrivate(); |
290 | |
291 | d->manager = new OrgFreedesktopDBusObjectManagerInterface( |
292 | QStringLiteral("org.bluez" ), QStringLiteral("/" ), |
293 | QDBusConnection::systemBus(), this); |
294 | connect(asender: d->manager, SIGNAL(InterfacesRemoved(QDBusObjectPath,QStringList)), |
295 | SLOT(InterfacesRemoved(QDBusObjectPath,QStringList))); |
296 | } |
297 | |
298 | QtBluezDiscoveryManager::~QtBluezDiscoveryManager() |
299 | { |
300 | qCDebug(QT_BT_BLUEZ) << "Destroying QtBluezDiscoveryManager" ; |
301 | |
302 | const QList<QString> adapterPaths = d->references.keys(); |
303 | for (const QString &adapterPath : adapterPaths) { |
304 | AdapterData *data = d->references.take(key: adapterPath); |
305 | delete data->propteryListener; |
306 | |
307 | // turn discovery off if it wasn't on already |
308 | if (!data->wasListeningAlready) { |
309 | OrgBluezAdapter1Interface iface(QStringLiteral("org.bluez" ), adapterPath, |
310 | QDBusConnection::systemBus()); |
311 | iface.StopDiscovery(); |
312 | } |
313 | |
314 | delete data; |
315 | } |
316 | |
317 | delete d; |
318 | } |
319 | |
320 | QtBluezDiscoveryManager *QtBluezDiscoveryManager::instance() |
321 | { |
322 | return discoveryManager(); |
323 | } |
324 | |
325 | bool QtBluezDiscoveryManager::registerDiscoveryInterest(const QString &adapterPath) |
326 | { |
327 | if (adapterPath.isEmpty()) |
328 | return false; |
329 | |
330 | // already monitored adapter? -> increase ref count -> done |
331 | if (d->references.contains(key: adapterPath)) { |
332 | d->references[adapterPath]->reference++; |
333 | return true; |
334 | } |
335 | |
336 | AdapterData *data = new AdapterData(); |
337 | |
338 | OrgFreedesktopDBusPropertiesInterface *propIface = new OrgFreedesktopDBusPropertiesInterface( |
339 | QStringLiteral("org.bluez" ), adapterPath, QDBusConnection::systemBus()); |
340 | connect(sender: propIface, signal: &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, |
341 | context: this, slot: &QtBluezDiscoveryManager::PropertiesChanged); |
342 | data->propteryListener = propIface; |
343 | |
344 | OrgBluezAdapter1Interface iface(QStringLiteral("org.bluez" ), adapterPath, |
345 | QDBusConnection::systemBus()); |
346 | data->wasListeningAlready = iface.discovering(); |
347 | |
348 | d->references[adapterPath] = data; |
349 | |
350 | if (!data->wasListeningAlready) |
351 | iface.StartDiscovery(); |
352 | |
353 | return true; |
354 | } |
355 | |
356 | void QtBluezDiscoveryManager::unregisterDiscoveryInterest(const QString &adapterPath) |
357 | { |
358 | if (!d->references.contains(key: adapterPath)) |
359 | return; |
360 | |
361 | AdapterData *data = d->references[adapterPath]; |
362 | data->reference--; |
363 | |
364 | if (data->reference > 0) // more than one client requested discovery mode |
365 | return; |
366 | |
367 | d->references.remove(key: adapterPath); |
368 | if (!data->wasListeningAlready) { // Qt turned discovery mode on, Qt has to turn it off again |
369 | OrgBluezAdapter1Interface iface(QStringLiteral("org.bluez" ), adapterPath, |
370 | QDBusConnection::systemBus()); |
371 | iface.StopDiscovery(); |
372 | } |
373 | |
374 | delete data->propteryListener; |
375 | delete data; |
376 | } |
377 | |
378 | //void QtBluezDiscoveryManager::dumpState() const |
379 | //{ |
380 | // qCDebug(QT_BT_BLUEZ) << "-------------------------"; |
381 | // if (d->references.isEmpty()) { |
382 | // qCDebug(QT_BT_BLUEZ) << "No running registration"; |
383 | // } else { |
384 | // const QList<QString> paths = d->references.keys(); |
385 | // for (const QString &path : paths) { |
386 | // qCDebug(QT_BT_BLUEZ) << path << "->" << d->references[path]->reference; |
387 | // } |
388 | // } |
389 | // qCDebug(QT_BT_BLUEZ) << "-------------------------"; |
390 | //} |
391 | |
392 | void QtBluezDiscoveryManager::InterfacesRemoved(const QDBusObjectPath &object_path, |
393 | const QStringList &interfaces) |
394 | { |
395 | if (!d->references.contains(key: object_path.path()) |
396 | || !interfaces.contains(QStringLiteral("org.bluez.Adapter1" ))) |
397 | return; |
398 | |
399 | removeAdapterFromMonitoring(dbusPath: object_path.path()); |
400 | } |
401 | |
402 | void QtBluezDiscoveryManager::PropertiesChanged(const QString &interface, |
403 | const QVariantMap &changed_properties, |
404 | const QStringList &invalidated_properties, |
405 | const QDBusMessage &) |
406 | { |
407 | Q_UNUSED(invalidated_properties); |
408 | |
409 | OrgFreedesktopDBusPropertiesInterface *propIface = |
410 | qobject_cast<OrgFreedesktopDBusPropertiesInterface *>(object: sender()); |
411 | |
412 | if (!propIface) |
413 | return; |
414 | |
415 | if (interface == QStringLiteral("org.bluez.Adapter1" ) |
416 | && d->references.contains(key: propIface->path()) |
417 | && changed_properties.contains(QStringLiteral("Discovering" ))) { |
418 | bool isDiscovering = changed_properties.value(QStringLiteral("Discovering" )).toBool(); |
419 | if (!isDiscovering) { |
420 | |
421 | /* |
422 | Once we stop the Discovering flag will switch a few ms later. This comes through this code |
423 | path. If a new device discovery is started while we are still |
424 | waiting for the flag change signal, then the new device discovery will be aborted prematurely. |
425 | To compensate we check whether there was renewed interest. |
426 | */ |
427 | |
428 | AdapterData *data = d->references[propIface->path()]; |
429 | if (!data) { |
430 | removeAdapterFromMonitoring(dbusPath: propIface->path()); |
431 | } else { |
432 | OrgBluezAdapter1Interface iface(QStringLiteral("org.bluez" ), propIface->path(), |
433 | QDBusConnection::systemBus()); |
434 | iface.StartDiscovery(); |
435 | } |
436 | } |
437 | } |
438 | } |
439 | |
440 | void QtBluezDiscoveryManager::removeAdapterFromMonitoring(const QString &dbusPath) |
441 | { |
442 | // remove adapter from monitoring |
443 | AdapterData *data = d->references.take(key: dbusPath); |
444 | delete data->propteryListener; |
445 | delete data; |
446 | |
447 | emit discoveryInterrupted(adapterPath: dbusPath); |
448 | } |
449 | |
450 | /* |
451 | Finds the path for the local adapter with \a wantedAddress or an empty string |
452 | if no local adapter with the given address can be found. |
453 | If \a wantedAddress is \c null it returns the first/default adapter or an empty |
454 | string if none is available. |
455 | |
456 | If \a ok is false the lookup was aborted due to a dbus error and this function |
457 | returns an empty string. |
458 | */ |
459 | QString findAdapterForAddress(const QBluetoothAddress &wantedAddress, bool *ok = nullptr) |
460 | { |
461 | OrgFreedesktopDBusObjectManagerInterface manager(QStringLiteral("org.bluez" ), |
462 | QStringLiteral("/" ), |
463 | QDBusConnection::systemBus()); |
464 | |
465 | QDBusPendingReply<ManagedObjectList> reply = manager.GetManagedObjects(); |
466 | reply.waitForFinished(); |
467 | if (reply.isError()) { |
468 | if (ok) |
469 | *ok = false; |
470 | |
471 | return QString(); |
472 | } |
473 | |
474 | typedef QPair<QString, QBluetoothAddress> AddressForPathType; |
475 | QList<AddressForPathType> localAdapters; |
476 | |
477 | ManagedObjectList managedObjectList = reply.value(); |
478 | for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) { |
479 | const QDBusObjectPath &path = it.key(); |
480 | const InterfaceList &ifaceList = it.value(); |
481 | |
482 | for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) { |
483 | const QString &iface = jt.key(); |
484 | |
485 | if (iface == QStringLiteral("org.bluez.Adapter1" )) { |
486 | AddressForPathType pair; |
487 | pair.first = path.path(); |
488 | pair.second = QBluetoothAddress(ifaceList.value(key: iface).value( |
489 | QStringLiteral("Address" )).toString()); |
490 | if (!pair.second.isNull()) |
491 | localAdapters.append(t: pair); |
492 | break; |
493 | } |
494 | } |
495 | } |
496 | |
497 | if (ok) |
498 | *ok = true; |
499 | |
500 | if (localAdapters.isEmpty()) |
501 | return QString(); // -> no local adapter found |
502 | |
503 | if (wantedAddress.isNull()) |
504 | return localAdapters.front().first; // -> return first found adapter |
505 | |
506 | for (const AddressForPathType &pair : std::as_const(t&: localAdapters)) { |
507 | if (pair.second == wantedAddress) |
508 | return pair.first; // -> found local adapter with wanted address |
509 | } |
510 | |
511 | return QString(); // nothing matching found |
512 | } |
513 | |
514 | /* |
515 | |
516 | Checks the presence of peripheral interface and returns path to the adapter |
517 | |
518 | */ |
519 | |
520 | QString adapterWithDBusPeripheralInterface(const QBluetoothAddress &localAddress) |
521 | { |
522 | initializeBluez5(); |
523 | |
524 | // First find the object path to the desired adapter |
525 | bool ok = false; |
526 | const QString hostAdapterPath = findAdapterForAddress(wantedAddress: localAddress, ok: &ok); |
527 | if (!ok || hostAdapterPath.isEmpty()) |
528 | return {}; |
529 | |
530 | // Then check if that adapter provides peripheral dbus interface |
531 | OrgFreedesktopDBusObjectManagerInterface manager(QStringLiteral("org.bluez" ), |
532 | QStringLiteral("/" ), |
533 | QDBusConnection::systemBus()); |
534 | QDBusPendingReply<ManagedObjectList> reply = manager.GetManagedObjects(); |
535 | reply.waitForFinished(); |
536 | if (reply.isError()) |
537 | return {}; |
538 | |
539 | using namespace Qt::StringLiterals; |
540 | // For example /org/bluez/hci0 contains org.bluezLEAdvertisingManager1 |
541 | const bool peripheralSupported = reply.value() |
542 | .value(key: QDBusObjectPath(hostAdapterPath)) |
543 | .contains(key: "org.bluez.LEAdvertisingManager1"_L1 ); |
544 | |
545 | qCDebug(QT_BT_BLUEZ) << "Peripheral role" |
546 | << (peripheralSupported ? "" : "not" ) |
547 | << "supported on" << hostAdapterPath; |
548 | return peripheralSupported ? hostAdapterPath : QString{}; |
549 | } |
550 | |
551 | /* |
552 | Removes every character that cannot be used in QDbusObjectPath |
553 | |
554 | See QDbusUtil::isValidObjectPath(QString) for more details. |
555 | */ |
556 | QString sanitizeNameForDBus(const QString &text) |
557 | { |
558 | QString appName = text; |
559 | for (qsizetype i = 0; i < appName.size(); ++i) { |
560 | ushort us = appName[i].unicode(); |
561 | bool valid = (us >= 'a' && us <= 'z') |
562 | || (us >= 'A' && us <= 'Z') |
563 | || (us >= '0' && us <= '9') |
564 | || (us == '_'); |
565 | |
566 | if (!valid) |
567 | appName[i] = QLatin1Char('_'); |
568 | } |
569 | |
570 | return appName; |
571 | } |
572 | |
573 | QT_END_NAMESPACE |
574 | |
575 | #include "moc_bluez5_helper_p.cpp" |
576 | |