| 1 | /* |
| 2 | * BluezQt - Asynchronous Bluez wrapper library |
| 3 | * |
| 4 | * SPDX-FileCopyrightText: 2014 David Rosca <nowrep@gmail.com> |
| 5 | * |
| 6 | * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL |
| 7 | */ |
| 8 | |
| 9 | #include "device_p.h" |
| 10 | #include "adapter.h" |
| 11 | #include "battery.h" |
| 12 | #include "battery_p.h" |
| 13 | #include "device.h" |
| 14 | #include "gattserviceremote.h" |
| 15 | #include "gattserviceremote_p.h" |
| 16 | #include "input.h" |
| 17 | #include "input_p.h" |
| 18 | #include "macros.h" |
| 19 | #include "mediaplayer.h" |
| 20 | #include "mediaplayer_p.h" |
| 21 | #include "mediatransport.h" |
| 22 | #include "mediatransport_p.h" |
| 23 | #include "utils.h" |
| 24 | |
| 25 | namespace BluezQt |
| 26 | { |
| 27 | static const qint16 = -32768; // qint16 minimum |
| 28 | |
| 29 | DevicePrivate::DevicePrivate(const QString &path, const QVariantMap &properties, const AdapterPtr &adapter) |
| 30 | : QObject() |
| 31 | , m_dbusProperties(nullptr) |
| 32 | , m_deviceClass(0) |
| 33 | , m_appearance(0) |
| 34 | , m_paired(false) |
| 35 | , m_trusted(false) |
| 36 | , m_blocked(false) |
| 37 | , m_legacyPairing(false) |
| 38 | , m_rssi(INVALID_RSSI) |
| 39 | , m_manufacturerData(ManData()) |
| 40 | , m_servicesResolved(false) |
| 41 | , m_connected(false) |
| 42 | , m_adapter(adapter) |
| 43 | { |
| 44 | m_bluezDevice = new BluezDevice(Strings::orgBluez(), path, DBusConnection::orgBluez(), this); |
| 45 | |
| 46 | init(properties); |
| 47 | } |
| 48 | |
| 49 | static QHash<QString, QByteArray> toByteArrayHash(const QDBusArgument &arg) |
| 50 | { |
| 51 | if (arg.currentType() != QDBusArgument::MapType) { |
| 52 | return {}; |
| 53 | } |
| 54 | |
| 55 | QHash<QString, QByteArray> result; |
| 56 | arg.beginMap(); |
| 57 | while (!arg.atEnd()) { |
| 58 | arg.beginMapEntry(); |
| 59 | QString key; |
| 60 | QDBusVariant value; |
| 61 | arg >> key >> value; |
| 62 | result.insert(key: key.toUpper(), value: value.variant().toByteArray()); |
| 63 | arg.endMapEntry(); |
| 64 | } |
| 65 | arg.endMap(); |
| 66 | return result; |
| 67 | } |
| 68 | |
| 69 | void DevicePrivate::init(const QVariantMap &properties) |
| 70 | { |
| 71 | m_dbusProperties = new DBusProperties(Strings::orgBluez(), m_bluezDevice->path(), DBusConnection::orgBluez(), this); |
| 72 | |
| 73 | // Init properties |
| 74 | m_address = properties.value(QStringLiteral("Address" )).toString(); |
| 75 | m_name = properties.value(QStringLiteral("Name" )).toString(); |
| 76 | m_alias = properties.value(QStringLiteral("Alias" )).toString(); |
| 77 | m_deviceClass = properties.value(QStringLiteral("Class" )).toUInt(); |
| 78 | m_appearance = properties.value(QStringLiteral("Appearance" )).toUInt(); |
| 79 | m_icon = properties.value(QStringLiteral("Icon" )).toString(); |
| 80 | m_paired = properties.value(QStringLiteral("Paired" )).toBool(); |
| 81 | m_trusted = properties.value(QStringLiteral("Trusted" )).toBool(); |
| 82 | m_blocked = properties.value(QStringLiteral("Blocked" )).toBool(); |
| 83 | m_legacyPairing = properties.value(QStringLiteral("LegacyPairing" )).toBool(); |
| 84 | m_rssi = properties.value(QStringLiteral("RSSI" )).toInt(); |
| 85 | m_manufacturerData = variantToManData(value: properties.value(QStringLiteral("ManufacturerData" ))); |
| 86 | m_servicesResolved = properties.value(QStringLiteral("ServicesResolved" )).toBool(); |
| 87 | m_connected = properties.value(QStringLiteral("Connected" )).toBool(); |
| 88 | m_uuids = stringListToUpper(list: properties.value(QStringLiteral("UUIDs" )).toStringList()); |
| 89 | m_modalias = properties.value(QStringLiteral("Modalias" )).toString(); |
| 90 | m_serviceData = toByteArrayHash(arg: properties.value(QStringLiteral("ServiceData" )).value<QDBusArgument>()); |
| 91 | |
| 92 | if (!m_rssi) { |
| 93 | m_rssi = INVALID_RSSI; |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | void DevicePrivate::interfacesAdded(const QString &path, const QVariantMapMap &interfaces) |
| 98 | { |
| 99 | bool changed = false; |
| 100 | QVariantMapMap::const_iterator it; |
| 101 | |
| 102 | for (it = interfaces.constBegin(); it != interfaces.constEnd(); ++it) { |
| 103 | if (it.key() == Strings::orgBluezBattery1()) { |
| 104 | m_battery = BatteryPtr(new Battery(path, it.value())); |
| 105 | m_battery->d->q = m_battery.toWeakRef(); |
| 106 | Q_EMIT q.lock()->batteryChanged(battery: m_battery); |
| 107 | changed = true; |
| 108 | } else if (it.key() == Strings::orgBluezInput1()) { |
| 109 | m_input = InputPtr(new Input(path, it.value())); |
| 110 | m_input->d->q = m_input.toWeakRef(); |
| 111 | Q_EMIT q.lock()->inputChanged(input: m_input); |
| 112 | changed = true; |
| 113 | } else if (it.key() == Strings::orgBluezMediaPlayer1()) { |
| 114 | m_mediaPlayer = MediaPlayerPtr(new MediaPlayer(path, it.value())); |
| 115 | m_mediaPlayer->d->q = m_mediaPlayer.toWeakRef(); |
| 116 | Q_EMIT q.lock()->mediaPlayerChanged(mediaPlayer: m_mediaPlayer); |
| 117 | changed = true; |
| 118 | } else if (it.key() == Strings::orgBluezMediaTransport1()) { |
| 119 | m_mediaTransport = MediaTransportPtr(new MediaTransport(path, it.value())); |
| 120 | m_mediaTransport->d->q = m_mediaTransport.toWeakRef(); |
| 121 | Q_EMIT q.lock()->mediaTransportChanged(mediaTransport: m_mediaTransport); |
| 122 | changed = true; |
| 123 | } else if (it.key() == Strings::orgBluezGattService1()) { |
| 124 | addGattService(gattServicePath: path,properties: it.value()); |
| 125 | changed = true; |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | for (auto& service : m_services) { |
| 130 | if (path.startsWith(s: service->ubi())) { |
| 131 | service->d->interfacesAdded(path, interfaces); |
| 132 | changed = true; |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | if (changed) { |
| 137 | Q_EMIT q.lock()->deviceChanged(device: q.toStrongRef()); |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | void DevicePrivate::interfacesRemoved(const QString &path, const QStringList &interfaces) |
| 142 | { |
| 143 | bool changed = false; |
| 144 | |
| 145 | for (const QString &interface : interfaces) { |
| 146 | if (interface == Strings::orgBluezBattery1() && m_battery && m_battery->d->m_path == path) { |
| 147 | m_battery.clear(); |
| 148 | Q_EMIT q.lock()->batteryChanged(battery: m_battery); |
| 149 | changed = true; |
| 150 | } else if (interface == Strings::orgBluezInput1() && m_input && m_input->d->m_path == path) { |
| 151 | m_input.clear(); |
| 152 | Q_EMIT q.lock()->inputChanged(input: m_input); |
| 153 | changed = true; |
| 154 | } else if (interface == Strings::orgBluezMediaPlayer1() && m_mediaPlayer && m_mediaPlayer->d->m_path == path) { |
| 155 | m_mediaPlayer.clear(); |
| 156 | Q_EMIT q.lock()->mediaPlayerChanged(mediaPlayer: m_mediaPlayer); |
| 157 | changed = true; |
| 158 | } else if (interface == Strings::orgBluezMediaTransport1() && m_mediaTransport && m_mediaTransport->d->m_path == path) { |
| 159 | m_mediaTransport.clear(); |
| 160 | Q_EMIT q.lock()->mediaTransportChanged(mediaTransport: m_mediaTransport); |
| 161 | changed = true; |
| 162 | } else if (interface == Strings::orgBluezGattService1()) { |
| 163 | removeGattService(gattServicePath: path); |
| 164 | changed = true; |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | for (auto& service : m_services) { |
| 169 | if (path.startsWith(s: service->ubi())) { |
| 170 | service->d->interfacesRemoved(path,interfaces); |
| 171 | changed = true; |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | if (changed) { |
| 176 | Q_EMIT q.lock()->deviceChanged(device: q.toStrongRef()); |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | void DevicePrivate::addGattService(const QString &gattServicePath, const QVariantMap &properties) |
| 181 | { |
| 182 | // Check if we have the right path |
| 183 | if (m_bluezDevice->path() != properties.value(QStringLiteral("Device" )).value<QDBusObjectPath>().path()) { |
| 184 | return; |
| 185 | } |
| 186 | |
| 187 | DevicePtr device = DevicePtr(this->q); |
| 188 | |
| 189 | if (!device) { |
| 190 | return; |
| 191 | } |
| 192 | |
| 193 | GattServiceRemotePtr gattService = GattServiceRemotePtr(new GattServiceRemote(gattServicePath, properties, device)); |
| 194 | gattService->d->q = gattService.toWeakRef(); |
| 195 | m_services.append(t: gattService); |
| 196 | |
| 197 | Q_EMIT device->gattServiceAdded(service: gattService); |
| 198 | Q_EMIT device->gattServicesChanged(services: m_services); |
| 199 | |
| 200 | // Connections |
| 201 | connect(sender: gattService.data(),signal: &GattServiceRemote::serviceChanged,context: q.lock().data(),slot: &Device::gattServiceChanged); |
| 202 | } |
| 203 | |
| 204 | void DevicePrivate::removeGattService(const QString &gattServicePath) |
| 205 | { |
| 206 | DevicePtr device = DevicePtr(this->q); |
| 207 | |
| 208 | if (!device) { |
| 209 | return; |
| 210 | } |
| 211 | |
| 212 | GattServiceRemotePtr gattService = nullptr; |
| 213 | for (int i=0; i < device->gattServices().size(); ++i) { |
| 214 | if (device->gattServices().at(i)->ubi() == gattServicePath) { |
| 215 | gattService = device->gattServices().at(i); |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | if (gattService == nullptr) { |
| 220 | return; |
| 221 | } |
| 222 | |
| 223 | m_services.removeOne(t: gattService); |
| 224 | |
| 225 | Q_EMIT device->gattServiceRemoved(service: gattService); |
| 226 | Q_EMIT device->gattServicesChanged(services: m_services); |
| 227 | |
| 228 | // Connections |
| 229 | disconnect(sender: gattService.data(),signal: &GattServiceRemote::serviceChanged,receiver: q.lock().data(),slot: &Device::gattServiceChanged); |
| 230 | } |
| 231 | |
| 232 | QDBusPendingReply<> DevicePrivate::setDBusProperty(const QString &name, const QVariant &value) |
| 233 | { |
| 234 | return m_dbusProperties->Set(interface_name: Strings::orgBluezDevice1(), property_name: name, value: QDBusVariant(value)); |
| 235 | } |
| 236 | |
| 237 | void DevicePrivate::propertiesChanged(const QString &path, const QString &interface, const QVariantMap &changed, const QStringList &invalidated) |
| 238 | { |
| 239 | if (interface == Strings::orgBluezBattery1() && m_battery) { |
| 240 | m_battery->d->propertiesChanged(interface, changed, invalidated); |
| 241 | } else if (interface == Strings::orgBluezInput1() && m_input) { |
| 242 | m_input->d->propertiesChanged(interface, changed, invalidated); |
| 243 | } else if (interface == Strings::orgBluezMediaPlayer1() && m_mediaPlayer) { |
| 244 | m_mediaPlayer->d->propertiesChanged(interface, changed, invalidated); |
| 245 | } else if ((interface == Strings::orgBluezGattService1()) || (interface == Strings::orgBluezGattCharacteristic1()) || (interface == Strings::orgBluezGattDescriptor1())) { |
| 246 | for (GattServiceRemotePtr service : m_services) { |
| 247 | if (path.startsWith(s: service->ubi())) { |
| 248 | service->d->propertiesChanged(path, interface, changed, invalidated); |
| 249 | return; |
| 250 | } |
| 251 | } |
| 252 | } else if (interface != Strings::orgBluezDevice1()) { |
| 253 | return; |
| 254 | } |
| 255 | |
| 256 | QVariantMap::const_iterator i; |
| 257 | for (i = changed.constBegin(); i != changed.constEnd(); ++i) { |
| 258 | const QVariant &value = i.value(); |
| 259 | const QString &property = i.key(); |
| 260 | |
| 261 | if (property == QLatin1String("Name" )) { |
| 262 | namePropertyChanged(value: value.toString()); |
| 263 | } else if (property == QLatin1String("Address" )) { |
| 264 | addressPropertyChanged(value: value.toString()); |
| 265 | } else if (property == QLatin1String("Alias" )) { |
| 266 | aliasPropertyChanged(value: value.toString()); |
| 267 | } else if (property == QLatin1String("Class" )) { |
| 268 | classPropertyChanged(value: value.toUInt()); |
| 269 | } else if (property == QLatin1String("Appearance" )) { |
| 270 | PROPERTY_CHANGED(m_appearance, toUInt, appearanceChanged); |
| 271 | } else if (property == QLatin1String("Icon" )) { |
| 272 | PROPERTY_CHANGED(m_icon, toString, iconChanged); |
| 273 | } else if (property == QLatin1String("Paired" )) { |
| 274 | PROPERTY_CHANGED(m_paired, toBool, pairedChanged); |
| 275 | } else if (property == QLatin1String("Trusted" )) { |
| 276 | PROPERTY_CHANGED(m_trusted, toBool, trustedChanged); |
| 277 | } else if (property == QLatin1String("Blocked" )) { |
| 278 | PROPERTY_CHANGED(m_blocked, toBool, blockedChanged); |
| 279 | } else if (property == QLatin1String("LegacyPairing" )) { |
| 280 | PROPERTY_CHANGED(m_legacyPairing, toBool, legacyPairingChanged); |
| 281 | } else if (property == QLatin1String("RSSI" )) { |
| 282 | PROPERTY_CHANGED(m_rssi, toInt, rssiChanged); |
| 283 | } else if (property == QLatin1String("ManufacturerData" )) { |
| 284 | PROPERTY_CHANGED2(m_manufacturerData, variantToManData(value), manufacturerDataChanged); |
| 285 | } else if (property == QLatin1String("ServicesResolved" )) { |
| 286 | PROPERTY_CHANGED(m_servicesResolved, toBool, servicesResolvedChanged); |
| 287 | } else if (property == QLatin1String("Connected" )) { |
| 288 | PROPERTY_CHANGED(m_connected, toBool, connectedChanged); |
| 289 | } else if (property == QLatin1String("Modalias" )) { |
| 290 | PROPERTY_CHANGED(m_modalias, toString, modaliasChanged); |
| 291 | } else if (property == QLatin1String("UUIDs" )) { |
| 292 | PROPERTY_CHANGED2(m_uuids, stringListToUpper(value.toStringList()), uuidsChanged); |
| 293 | } else if (property == QLatin1String("ServiceData" )) { |
| 294 | PROPERTY_CHANGED2(m_serviceData, toByteArrayHash(value.value<QDBusArgument>()), serviceDataChanged); |
| 295 | } |
| 296 | } |
| 297 | |
| 298 | for (const QString &property : invalidated) { |
| 299 | if (property == QLatin1String("Name" )) { |
| 300 | namePropertyChanged(value: QString()); |
| 301 | } else if (property == QLatin1String("Class" )) { |
| 302 | classPropertyChanged(value: 0); |
| 303 | } else if (property == QLatin1String("Appearance" )) { |
| 304 | PROPERTY_INVALIDATED(m_appearance, 0, appearanceChanged); |
| 305 | } else if (property == QLatin1String("Icon" )) { |
| 306 | PROPERTY_INVALIDATED(m_icon, QString(), iconChanged); |
| 307 | } else if (property == QLatin1String("RSSI" )) { |
| 308 | PROPERTY_INVALIDATED(m_rssi, INVALID_RSSI, rssiChanged); |
| 309 | } else if (property == QLatin1String("ManufacturerData" )) { |
| 310 | QMap<uint16_t,QByteArray> map; |
| 311 | PROPERTY_INVALIDATED(m_manufacturerData, map, manufacturerDataChanged); |
| 312 | } else if (property == QLatin1String("ServicesResolved" )) { |
| 313 | PROPERTY_INVALIDATED(m_servicesResolved, false, servicesResolvedChanged); |
| 314 | } else if (property == QLatin1String("Modalias" )) { |
| 315 | PROPERTY_INVALIDATED(m_modalias, QString(), modaliasChanged); |
| 316 | } else if (property == QLatin1String("UUIDs" )) { |
| 317 | PROPERTY_INVALIDATED(m_uuids, QStringList(), uuidsChanged); |
| 318 | } else if (property == QLatin1String("ServiceData" )) { |
| 319 | PROPERTY_INVALIDATED(m_serviceData, (QHash<QString, QByteArray>()), serviceDataChanged); |
| 320 | } |
| 321 | } |
| 322 | |
| 323 | Q_EMIT q.lock()->deviceChanged(device: q.toStrongRef()); |
| 324 | } |
| 325 | |
| 326 | void DevicePrivate::namePropertyChanged(const QString &value) |
| 327 | { |
| 328 | if (m_name != value) { |
| 329 | m_name = value; |
| 330 | Q_EMIT q.lock()->remoteNameChanged(remoteName: m_name); |
| 331 | Q_EMIT q.lock()->friendlyNameChanged(friendlyName: q.lock()->friendlyName()); |
| 332 | } |
| 333 | } |
| 334 | |
| 335 | void DevicePrivate::addressPropertyChanged(const QString &value) |
| 336 | { |
| 337 | if (m_address != value) { |
| 338 | m_address = value; |
| 339 | Q_EMIT q.lock()->addressChanged(address: m_address); |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | void DevicePrivate::aliasPropertyChanged(const QString &value) |
| 344 | { |
| 345 | if (m_alias != value) { |
| 346 | m_alias = value; |
| 347 | Q_EMIT q.lock()->nameChanged(name: m_alias); |
| 348 | Q_EMIT q.lock()->friendlyNameChanged(friendlyName: q.lock()->friendlyName()); |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | void DevicePrivate::classPropertyChanged(quint32 value) |
| 353 | { |
| 354 | if (m_deviceClass != value) { |
| 355 | m_deviceClass = value; |
| 356 | Q_EMIT q.lock()->deviceClassChanged(deviceClass: m_deviceClass); |
| 357 | Q_EMIT q.lock()->typeChanged(type: q.lock()->type()); |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | void DevicePrivate::serviceDataChanged(const QHash<QString, QByteArray> &value) |
| 362 | { |
| 363 | if (m_serviceData != value) { |
| 364 | m_serviceData = value; |
| 365 | Q_EMIT q.lock()->serviceDataChanged(serviceData: m_serviceData); |
| 366 | } |
| 367 | } |
| 368 | |
| 369 | } // namespace BluezQt |
| 370 | |
| 371 | #include "moc_device_p.cpp" |
| 372 | |