1 | /* |
2 | * BluezQt - Asynchronous Bluez wrapper library |
3 | * |
4 | * SPDX-FileCopyrightText: 2014-2015 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 "manager_p.h" |
10 | #include "adapter.h" |
11 | #include "adapter_p.h" |
12 | #include "debug.h" |
13 | #include "device.h" |
14 | #include "device_p.h" |
15 | #include "manager.h" |
16 | #include "utils.h" |
17 | |
18 | #include <QDBusServiceWatcher> |
19 | |
20 | namespace BluezQt |
21 | { |
22 | ManagerPrivate::ManagerPrivate(Manager *parent) |
23 | : QObject(parent) |
24 | , q(parent) |
25 | , m_dbusObjectManager(nullptr) |
26 | , m_bluezAgentManager(nullptr) |
27 | , m_bluezProfileManager(nullptr) |
28 | , m_initialized(false) |
29 | , m_bluezRunning(false) |
30 | , m_loaded(false) |
31 | , m_adaptersLoaded(false) |
32 | { |
33 | qDBusRegisterMetaType<DBusManagerStruct>(); |
34 | qRegisterMetaType<QVariantMapMap>(typeName: "QVariantMapMap" ); |
35 | qDBusRegisterMetaType<QVariantMapMap>(); |
36 | |
37 | m_rfkill = new Rfkill(this); |
38 | m_bluetoothBlocked = rfkillBlocked(); |
39 | connect(sender: m_rfkill, signal: &Rfkill::stateChanged, context: this, slot: &ManagerPrivate::rfkillStateChanged); |
40 | |
41 | connect(sender: q, signal: &Manager::adapterRemoved, context: this, slot: &ManagerPrivate::adapterRemoved); |
42 | } |
43 | |
44 | void ManagerPrivate::init() |
45 | { |
46 | // Keep an eye on org.bluez service |
47 | QDBusServiceWatcher *serviceWatcher = new QDBusServiceWatcher(Strings::orgBluez(), |
48 | DBusConnection::orgBluez(), |
49 | QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration, |
50 | this); |
51 | |
52 | connect(sender: serviceWatcher, signal: &QDBusServiceWatcher::serviceRegistered, context: this, slot: &ManagerPrivate::serviceRegistered); |
53 | connect(sender: serviceWatcher, signal: &QDBusServiceWatcher::serviceUnregistered, context: this, slot: &ManagerPrivate::serviceUnregistered); |
54 | |
55 | // Update the current state of org.bluez service |
56 | if (!DBusConnection::orgBluez().isConnected()) { |
57 | Q_EMIT initError(QStringLiteral("DBus system bus is not connected!" )); |
58 | return; |
59 | } |
60 | |
61 | QDBusMessage call = |
62 | QDBusMessage::createMethodCall(destination: Strings::orgFreedesktopDBus(), QStringLiteral("/" ), interface: Strings::orgFreedesktopDBus(), QStringLiteral("NameHasOwner" )); |
63 | |
64 | call << Strings::orgBluez(); |
65 | |
66 | QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(DBusConnection::orgBluez().asyncCall(message: call)); |
67 | connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, context: this, slot: &ManagerPrivate::nameHasOwnerFinished); |
68 | |
69 | DBusConnection::orgBluez().connect(service: Strings::orgBluez(), |
70 | path: QString(), |
71 | interface: Strings::orgFreedesktopDBusProperties(), |
72 | QStringLiteral("PropertiesChanged" ), |
73 | receiver: this, |
74 | SLOT(propertiesChanged(QString, QVariantMap, QStringList))); |
75 | } |
76 | |
77 | void ManagerPrivate::nameHasOwnerFinished(QDBusPendingCallWatcher *watcher) |
78 | { |
79 | const QDBusPendingReply<bool> &reply = *watcher; |
80 | watcher->deleteLater(); |
81 | |
82 | if (reply.isError()) { |
83 | Q_EMIT initError(errorText: reply.error().message()); |
84 | return; |
85 | } |
86 | |
87 | m_bluezRunning = reply.value(); |
88 | |
89 | if (m_bluezRunning) { |
90 | load(); |
91 | } else { |
92 | m_initialized = true; |
93 | Q_EMIT initFinished(); |
94 | } |
95 | } |
96 | |
97 | void ManagerPrivate::load() |
98 | { |
99 | if (!m_bluezRunning || m_loaded) { |
100 | return; |
101 | } |
102 | |
103 | // Force QDBus to cache owner of org.bluez - this will be the only blocking call on system connection |
104 | DBusConnection::orgBluez().connect(service: Strings::orgBluez(), QStringLiteral("/" ), interface: Strings::orgFreedesktopDBus(), QStringLiteral("Dummy" ), receiver: this, SLOT(dummy())); |
105 | |
106 | m_dbusObjectManager = new DBusObjectManager(Strings::orgBluez(), QStringLiteral("/" ), DBusConnection::orgBluez(), this); |
107 | |
108 | QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(m_dbusObjectManager->GetManagedObjects(), this); |
109 | connect(sender: watcher, signal: &QDBusPendingCallWatcher::finished, context: this, slot: &ManagerPrivate::getManagedObjectsFinished); |
110 | } |
111 | |
112 | void ManagerPrivate::getManagedObjectsFinished(QDBusPendingCallWatcher *watcher) |
113 | { |
114 | const QDBusPendingReply<DBusManagerStruct> &reply = *watcher; |
115 | watcher->deleteLater(); |
116 | |
117 | if (reply.isError()) { |
118 | Q_EMIT initError(errorText: reply.error().message()); |
119 | return; |
120 | } |
121 | |
122 | DBusManagerStruct::const_iterator it; |
123 | const DBusManagerStruct &managedObjects = reply.value(); |
124 | |
125 | for (it = managedObjects.constBegin(); it != managedObjects.constEnd(); ++it) { |
126 | const QString &path = it.key().path(); |
127 | const QVariantMapMap &interfaces = it.value(); |
128 | |
129 | interfacesAdded(objectPath: it.key(), interfaces); |
130 | |
131 | if (interfaces.contains(key: Strings::orgBluezAgentManager1())) { |
132 | m_bluezAgentManager = new BluezAgentManager(Strings::orgBluez(), path, DBusConnection::orgBluez(), this); |
133 | } |
134 | if (interfaces.contains(key: Strings::orgBluezProfileManager1())) { |
135 | m_bluezProfileManager = new BluezProfileManager(Strings::orgBluez(), path, DBusConnection::orgBluez(), this); |
136 | } |
137 | } |
138 | |
139 | if (!m_bluezAgentManager) { |
140 | Q_EMIT initError(QStringLiteral("Cannot find org.bluez.AgentManager1 object!" )); |
141 | return; |
142 | } |
143 | |
144 | if (!m_bluezProfileManager) { |
145 | Q_EMIT initError(QStringLiteral("Cannot find org.bluez.ProfileManager1 object!" )); |
146 | return; |
147 | } |
148 | |
149 | connect(sender: m_dbusObjectManager, signal: &DBusObjectManager::InterfacesAdded, context: this, slot: &ManagerPrivate::interfacesAdded); |
150 | connect(sender: m_dbusObjectManager, signal: &DBusObjectManager::InterfacesRemoved, context: this, slot: &ManagerPrivate::interfacesRemoved); |
151 | |
152 | m_loaded = true; |
153 | m_initialized = true; |
154 | |
155 | Q_EMIT q->operationalChanged(operational: true); |
156 | |
157 | if (q->isBluetoothOperational()) { |
158 | Q_EMIT q->bluetoothOperationalChanged(operational: true); |
159 | } |
160 | |
161 | Q_EMIT initFinished(); |
162 | } |
163 | |
164 | void ManagerPrivate::clear() |
165 | { |
166 | m_loaded = false; |
167 | |
168 | // Delete all devices first |
169 | while (!m_devices.isEmpty()) { |
170 | DevicePtr device = m_devices.begin().value(); |
171 | m_devices.remove(key: m_devices.begin().key()); |
172 | device->adapter()->d->removeDevice(device); |
173 | } |
174 | |
175 | // Delete all adapters |
176 | while (!m_adapters.isEmpty()) { |
177 | AdapterPtr adapter = m_adapters.begin().value(); |
178 | m_adapters.remove(key: m_adapters.begin().key()); |
179 | Q_EMIT adapter->adapterRemoved(adapter); |
180 | |
181 | if (m_adapters.isEmpty()) { |
182 | Q_EMIT q->allAdaptersRemoved(); |
183 | } |
184 | } |
185 | |
186 | // Delete all other objects |
187 | m_usableAdapter.clear(); |
188 | |
189 | if (m_dbusObjectManager) { |
190 | m_dbusObjectManager->deleteLater(); |
191 | m_dbusObjectManager = nullptr; |
192 | } |
193 | |
194 | if (m_bluezAgentManager) { |
195 | m_bluezAgentManager->deleteLater(); |
196 | m_bluezAgentManager = nullptr; |
197 | } |
198 | } |
199 | |
200 | AdapterPtr ManagerPrivate::findUsableAdapter() const |
201 | { |
202 | for (AdapterPtr adapter : std::as_const(t: m_adapters)) { |
203 | if (adapter->isPowered()) { |
204 | return adapter; |
205 | } |
206 | } |
207 | return AdapterPtr(); |
208 | } |
209 | |
210 | void ManagerPrivate::serviceRegistered() |
211 | { |
212 | qCDebug(BLUEZQT) << "BlueZ service registered" ; |
213 | m_bluezRunning = true; |
214 | |
215 | load(); |
216 | } |
217 | |
218 | void ManagerPrivate::serviceUnregistered() |
219 | { |
220 | qCDebug(BLUEZQT) << "BlueZ service unregistered" ; |
221 | |
222 | bool wasBtOperational = q->isBluetoothOperational(); |
223 | m_bluezRunning = false; |
224 | |
225 | if (wasBtOperational) { |
226 | Q_EMIT q->bluetoothOperationalChanged(operational: false); |
227 | } |
228 | |
229 | clear(); |
230 | Q_EMIT q->operationalChanged(operational: false); |
231 | } |
232 | |
233 | void ManagerPrivate::interfacesAdded(const QDBusObjectPath &objectPath, const QVariantMapMap &interfaces) |
234 | { |
235 | const QString &path = objectPath.path(); |
236 | QVariantMapMap::const_iterator it; |
237 | |
238 | for (it = interfaces.constBegin(); it != interfaces.constEnd(); ++it) { |
239 | if (it.key() == Strings::orgBluezAdapter1()) { |
240 | addAdapter(adapterPath: path, properties: it.value()); |
241 | } else if (it.key() == Strings::orgBluezDevice1()) { |
242 | addDevice(devicePath: path, properties: it.value()); |
243 | } |
244 | } |
245 | |
246 | for (auto it = m_adapters.cbegin(); it != m_adapters.cend(); ++it) { |
247 | if (path.startsWith(s: it.value()->ubi())) { |
248 | it.value()->d->interfacesAdded(path, interfaces); |
249 | break; |
250 | } |
251 | } |
252 | |
253 | for (auto it = m_devices.cbegin(); it != m_devices.cend(); ++it) { |
254 | if (path.startsWith(s: it.value()->ubi())) { |
255 | it.value()->d->interfacesAdded(path, interfaces); |
256 | break; |
257 | } |
258 | } |
259 | } |
260 | |
261 | void ManagerPrivate::interfacesRemoved(const QDBusObjectPath &objectPath, const QStringList &interfaces) |
262 | { |
263 | const QString &path = objectPath.path(); |
264 | |
265 | for (const QString &interface : interfaces) { |
266 | if (interface == Strings::orgBluezAdapter1()) { |
267 | removeAdapter(adapterPath: path); |
268 | } else if (interface == Strings::orgBluezDevice1()) { |
269 | removeDevice(devicePath: path); |
270 | } |
271 | } |
272 | |
273 | for (auto it = m_adapters.cbegin(); it != m_adapters.cend(); ++it) { |
274 | if (path.startsWith(s: it.value()->ubi())) { |
275 | it.value()->d->interfacesRemoved(path, interfaces); |
276 | break; |
277 | } |
278 | } |
279 | |
280 | for (auto it = m_devices.cbegin(); it != m_devices.cend(); ++it) { |
281 | if (path.startsWith(s: it.value()->ubi())) { |
282 | it.value()->d->interfacesRemoved(path, interfaces); |
283 | break; |
284 | } |
285 | } |
286 | } |
287 | |
288 | void ManagerPrivate::adapterRemoved(const AdapterPtr &adapter) |
289 | { |
290 | disconnect(sender: adapter.data(), signal: &Adapter::poweredChanged, receiver: this, slot: &ManagerPrivate::adapterPoweredChanged); |
291 | |
292 | // Current usable adapter was removed |
293 | if (adapter == m_usableAdapter) { |
294 | setUsableAdapter(findUsableAdapter()); |
295 | } |
296 | } |
297 | |
298 | void ManagerPrivate::adapterPoweredChanged(bool powered) |
299 | { |
300 | Q_ASSERT(qobject_cast<Adapter *>(sender())); |
301 | AdapterPtr adapter = static_cast<Adapter *>(sender())->toSharedPtr(); |
302 | |
303 | // Current usable adapter was powered off |
304 | if (m_usableAdapter == adapter && !powered) { |
305 | setUsableAdapter(findUsableAdapter()); |
306 | } |
307 | |
308 | // Adapter was powered on, set it as usable |
309 | if (!m_usableAdapter && powered) { |
310 | setUsableAdapter(adapter); |
311 | } |
312 | } |
313 | |
314 | void ManagerPrivate::rfkillStateChanged(Rfkill::State state) |
315 | { |
316 | Q_UNUSED(state) |
317 | |
318 | bool blocked = rfkillBlocked(); |
319 | bool wasBtOperational = q->isBluetoothOperational(); |
320 | |
321 | if (m_bluetoothBlocked != blocked) { |
322 | m_bluetoothBlocked = blocked; |
323 | Q_EMIT q->bluetoothBlockedChanged(blocked: m_bluetoothBlocked); |
324 | if (wasBtOperational != q->isBluetoothOperational()) { |
325 | Q_EMIT q->bluetoothOperationalChanged(operational: q->isBluetoothOperational()); |
326 | } |
327 | } |
328 | } |
329 | |
330 | void ManagerPrivate::addAdapter(const QString &adapterPath, const QVariantMap &properties) |
331 | { |
332 | AdapterPtr adapter = AdapterPtr(new Adapter(adapterPath, properties)); |
333 | adapter->d->q = adapter.toWeakRef(); |
334 | m_adapters.insert(key: adapterPath, value: adapter); |
335 | |
336 | Q_EMIT q->adapterAdded(adapter); |
337 | |
338 | // Powered adapter was added, set it as usable |
339 | if (!m_usableAdapter && adapter->isPowered()) { |
340 | setUsableAdapter(adapter); |
341 | } |
342 | |
343 | connect(sender: adapter.data(), signal: &Adapter::deviceAdded, context: q, slot: &Manager::deviceAdded); |
344 | connect(sender: adapter.data(), signal: &Adapter::adapterRemoved, context: q, slot: &Manager::adapterRemoved); |
345 | connect(sender: adapter.data(), signal: &Adapter::adapterChanged, context: q, slot: &Manager::adapterChanged); |
346 | connect(sender: adapter.data(), signal: &Adapter::poweredChanged, context: this, slot: &ManagerPrivate::adapterPoweredChanged); |
347 | } |
348 | |
349 | void ManagerPrivate::addDevice(const QString &devicePath, const QVariantMap &properties) |
350 | { |
351 | AdapterPtr adapter = m_adapters.value(key: properties.value(QStringLiteral("Adapter" )).value<QDBusObjectPath>().path()); |
352 | if (!adapter) { |
353 | return; |
354 | } |
355 | |
356 | DevicePtr device = DevicePtr(new Device(devicePath, properties, adapter)); |
357 | device->d->q = device.toWeakRef(); |
358 | m_devices.insert(key: devicePath, value: device); |
359 | adapter->d->addDevice(device); |
360 | |
361 | connect(sender: device.data(), signal: &Device::deviceRemoved, context: q, slot: &Manager::deviceRemoved); |
362 | connect(sender: device.data(), signal: &Device::deviceChanged, context: q, slot: &Manager::deviceChanged); |
363 | } |
364 | |
365 | void ManagerPrivate::removeAdapter(const QString &adapterPath) |
366 | { |
367 | AdapterPtr adapter = m_adapters.value(key: adapterPath); |
368 | if (!adapter) { |
369 | return; |
370 | } |
371 | |
372 | // Make sure we always remove all devices before removing the adapter |
373 | const auto devices = adapter->devices(); |
374 | for (const DevicePtr &device : devices) { |
375 | removeDevice(devicePath: device->ubi()); |
376 | } |
377 | |
378 | m_adapters.remove(key: adapterPath); |
379 | Q_EMIT adapter->adapterRemoved(adapter); |
380 | |
381 | if (m_adapters.isEmpty()) { |
382 | Q_EMIT q->allAdaptersRemoved(); |
383 | } |
384 | |
385 | disconnect(sender: adapter.data(), signal: &Adapter::adapterChanged, receiver: q, slot: &Manager::adapterChanged); |
386 | disconnect(sender: adapter.data(), signal: &Adapter::poweredChanged, receiver: this, slot: &ManagerPrivate::adapterPoweredChanged); |
387 | } |
388 | |
389 | void ManagerPrivate::removeDevice(const QString &devicePath) |
390 | { |
391 | DevicePtr device = m_devices.take(key: devicePath); |
392 | if (!device) { |
393 | return; |
394 | } |
395 | |
396 | device->adapter()->d->removeDevice(device); |
397 | |
398 | disconnect(sender: device.data(), signal: &Device::deviceChanged, receiver: q, slot: &Manager::deviceChanged); |
399 | } |
400 | |
401 | bool ManagerPrivate::rfkillBlocked() const |
402 | { |
403 | return m_rfkill->state() == Rfkill::SoftBlocked || m_rfkill->state() == Rfkill::HardBlocked; |
404 | } |
405 | |
406 | void ManagerPrivate::setUsableAdapter(const AdapterPtr &adapter) |
407 | { |
408 | if (m_usableAdapter == adapter) { |
409 | return; |
410 | } |
411 | |
412 | qCDebug(BLUEZQT) << "Setting usable adapter" << adapter; |
413 | |
414 | bool wasBtOperational = q->isBluetoothOperational(); |
415 | |
416 | m_usableAdapter = adapter; |
417 | Q_EMIT q->usableAdapterChanged(adapter: m_usableAdapter); |
418 | |
419 | if (wasBtOperational != q->isBluetoothOperational()) { |
420 | Q_EMIT q->bluetoothOperationalChanged(operational: q->isBluetoothOperational()); |
421 | } |
422 | } |
423 | |
424 | void ManagerPrivate::propertiesChanged(const QString &interface, const QVariantMap &changed, const QStringList &invalidated) |
425 | { |
426 | // Cut anything after device path to forward it to Device to handle |
427 | const QString path_full = message().path(); |
428 | const QString path_device = path_full.section(asep: QLatin1Char('/'), astart: 0, aend: 4); |
429 | |
430 | QTimer::singleShot(interval: 0, receiver: this, slot: [this, interface, changed, invalidated, path_full, path_device]() { |
431 | AdapterPtr adapter = m_adapters.value(key: path_device); |
432 | if (adapter) { |
433 | adapter->d->propertiesChanged(interface, changed, invalidated); |
434 | return; |
435 | } |
436 | DevicePtr device = m_devices.value(key: path_device); |
437 | if (device) { |
438 | device->d->propertiesChanged(path: path_full, interface, changed, invalidated); |
439 | return; |
440 | } |
441 | qCDebug(BLUEZQT) << "Unhandled property change" << interface << changed << invalidated; |
442 | }); |
443 | } |
444 | |
445 | void ManagerPrivate::dummy() |
446 | { |
447 | } |
448 | |
449 | } // namespace BluezQt |
450 | |
451 | #include "moc_manager_p.cpp" |
452 | |