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