| 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 |     OrgFreedesktopDBusPropertiesInterfaceBluetooth *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 |     OrgFreedesktopDBusPropertiesInterfaceBluetooth *propIface = new OrgFreedesktopDBusPropertiesInterfaceBluetooth( | 
| 339 |                 QStringLiteral("org.bluez" ), adapterPath, QDBusConnection::systemBus()); | 
| 340 |     connect(sender: propIface, signal: &OrgFreedesktopDBusPropertiesInterfaceBluetooth::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 |     OrgFreedesktopDBusPropertiesInterfaceBluetooth *propIface = | 
| 410 |             qobject_cast<OrgFreedesktopDBusPropertiesInterfaceBluetooth *>(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 returns an | 
| 452 |     empty string if no local adapter with the given address can be found. | 
| 453 |  | 
| 454 |     If \a wantedAddress is \c null it returns the first powered-on adapter, or | 
| 455 |     the first adapter if all of them are powered off, or an empty string if | 
| 456 |     none is available. | 
| 457 |  | 
| 458 |     If \a ok is false the lookup was aborted due to a dbus error and this function | 
| 459 |     returns an empty string. | 
| 460 |  */ | 
| 461 | QString findAdapterForAddress(const QBluetoothAddress &wantedAddress, bool *ok = nullptr) | 
| 462 | { | 
| 463 |     OrgFreedesktopDBusObjectManagerInterface manager(QStringLiteral("org.bluez" ), | 
| 464 |                                                      QStringLiteral("/" ), | 
| 465 |                                                      QDBusConnection::systemBus()); | 
| 466 |  | 
| 467 |     QDBusPendingReply<ManagedObjectList> reply = manager.GetManagedObjects(); | 
| 468 |     reply.waitForFinished(); | 
| 469 |     if (reply.isError()) { | 
| 470 |         if (ok) | 
| 471 |             *ok = false; | 
| 472 |  | 
| 473 |         return QString(); | 
| 474 |     } | 
| 475 |  | 
| 476 |     struct AdapterInfo | 
| 477 |     { | 
| 478 |         QString path; | 
| 479 |         QBluetoothAddress address; | 
| 480 |         bool powered; | 
| 481 |     }; | 
| 482 |     QList<AdapterInfo> localAdapters; | 
| 483 |  | 
| 484 |     ManagedObjectList managedObjectList = reply.value(); | 
| 485 |     for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) { | 
| 486 |         const QDBusObjectPath &path = it.key(); | 
| 487 |         const InterfaceList &ifaceList = it.value(); | 
| 488 |  | 
| 489 |         for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) { | 
| 490 |             const QString &iface = jt.key(); | 
| 491 |  | 
| 492 |             if (iface == QStringLiteral("org.bluez.Adapter1" )) { | 
| 493 |                 AdapterInfo info; | 
| 494 |                 info.path = path.path(); | 
| 495 |                 info.address = QBluetoothAddress(ifaceList.value(key: iface).value( | 
| 496 |                                                     QStringLiteral("Address" )).toString()); | 
| 497 |                 info.powered = ifaceList.value(key: iface).value(QStringLiteral("Powered" )).toBool(); | 
| 498 |                 if (!info.address.isNull()) | 
| 499 |                     localAdapters.append(t: info); | 
| 500 |                 break; | 
| 501 |             } | 
| 502 |         } | 
| 503 |     } | 
| 504 |  | 
| 505 |     if (ok) | 
| 506 |         *ok = true; | 
| 507 |  | 
| 508 |     if (localAdapters.isEmpty()) | 
| 509 |         return QString(); // -> no local adapter found | 
| 510 |  | 
| 511 |     auto findFirstPowered = [&localAdapters]() { | 
| 512 |         Q_ASSERT(!localAdapters.isEmpty()); | 
| 513 |         auto it = std::find_if(first: localAdapters.cbegin(), last: localAdapters.cend(), | 
| 514 |                                pred: [](const AdapterInfo &info) { | 
| 515 |                                    return info.powered == true; | 
| 516 |                                }); | 
| 517 |         if (it != localAdapters.cend()) | 
| 518 |             return it->path; | 
| 519 |         // simply return first if none is powered | 
| 520 |         return localAdapters.cbegin()->path; | 
| 521 |     }; | 
| 522 |  | 
| 523 |     if (wantedAddress.isNull()) | 
| 524 |         return findFirstPowered(); | 
| 525 |  | 
| 526 |     for (const AdapterInfo &info : std::as_const(t&: localAdapters)) { | 
| 527 |         if (info.address == wantedAddress) | 
| 528 |             return info.path; // -> found local adapter with wanted address | 
| 529 |     } | 
| 530 |  | 
| 531 |     return QString(); // nothing matching found | 
| 532 | } | 
| 533 |  | 
| 534 | /* | 
| 535 |  | 
| 536 |   Checks the presence of peripheral interface and returns path to the adapter | 
| 537 |  | 
| 538 | */ | 
| 539 |  | 
| 540 | QString adapterWithDBusPeripheralInterface(const QBluetoothAddress &localAddress) | 
| 541 | { | 
| 542 |     initializeBluez5(); | 
| 543 |  | 
| 544 |     // First find the object path to the desired adapter | 
| 545 |     bool ok = false; | 
| 546 |     const QString hostAdapterPath = findAdapterForAddress(wantedAddress: localAddress, ok: &ok); | 
| 547 |     if (!ok || hostAdapterPath.isEmpty()) | 
| 548 |         return {}; | 
| 549 |  | 
| 550 |     // Then check if that adapter provides peripheral dbus interface | 
| 551 |     OrgFreedesktopDBusObjectManagerInterface manager(QStringLiteral("org.bluez" ), | 
| 552 |                                                      QStringLiteral("/" ), | 
| 553 |                                                      QDBusConnection::systemBus()); | 
| 554 |     QDBusPendingReply<ManagedObjectList> reply = manager.GetManagedObjects(); | 
| 555 |     reply.waitForFinished(); | 
| 556 |     if (reply.isError()) | 
| 557 |         return {}; | 
| 558 |  | 
| 559 |     using namespace Qt::StringLiterals; | 
| 560 |     // For example /org/bluez/hci0 contains org.bluezLEAdvertisingManager1 | 
| 561 |     const bool peripheralSupported = reply.value() | 
| 562 |                    .value(key: QDBusObjectPath(hostAdapterPath)) | 
| 563 |                    .contains(key: "org.bluez.LEAdvertisingManager1"_L1 ); | 
| 564 |  | 
| 565 |     qCDebug(QT_BT_BLUEZ) << "Peripheral role"  | 
| 566 |                          << (peripheralSupported ? ""  : "not" ) | 
| 567 |                          << "supported on"  << hostAdapterPath; | 
| 568 |     return peripheralSupported ? hostAdapterPath : QString{}; | 
| 569 | } | 
| 570 |  | 
| 571 | /* | 
| 572 |     Removes every character that cannot be used in QDbusObjectPath | 
| 573 |  | 
| 574 |     See QDbusUtil::isValidObjectPath(QString) for more details. | 
| 575 |  */ | 
| 576 | QString sanitizeNameForDBus(const QString &text) | 
| 577 | { | 
| 578 |     QString appName = text; | 
| 579 |     for (qsizetype i = 0; i < appName.size(); ++i) { | 
| 580 |         ushort us = appName[i].unicode(); | 
| 581 |         bool valid = (us >= 'a' && us <= 'z') | 
| 582 |                       || (us >= 'A' && us <= 'Z') | 
| 583 |                       || (us >= '0' && us <= '9') | 
| 584 |                       || (us == '_'); | 
| 585 |  | 
| 586 |         if (!valid) | 
| 587 |             appName[i] = QLatin1Char('_'); | 
| 588 |     } | 
| 589 |  | 
| 590 |     return appName; | 
| 591 | } | 
| 592 |  | 
| 593 | QT_END_NAMESPACE | 
| 594 |  | 
| 595 | #include "moc_bluez5_helper_p.cpp" | 
| 596 |  |