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