forked from quickshell/quickshell
		
	bluetooth: add bluetooth integration
Missing support for things that require an agent, but has most basics. Closes #17
This commit is contained in:
		
							parent
							
								
									1d02292fbf
								
							
						
					
					
						commit
						f681e2016f
					
				
					 22 changed files with 1623 additions and 14 deletions
				
			
		| 
						 | 
					@ -70,6 +70,7 @@ boption(SERVICE_PAM "Pam" ON)
 | 
				
			||||||
boption(SERVICE_GREETD "Greetd" ON)
 | 
					boption(SERVICE_GREETD "Greetd" ON)
 | 
				
			||||||
boption(SERVICE_UPOWER "UPower" ON)
 | 
					boption(SERVICE_UPOWER "UPower" ON)
 | 
				
			||||||
boption(SERVICE_NOTIFICATIONS "Notifications" ON)
 | 
					boption(SERVICE_NOTIFICATIONS "Notifications" ON)
 | 
				
			||||||
 | 
					boption(BLUETOOTH "Bluetooth" ON)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
include(cmake/install-qml-module.cmake)
 | 
					include(cmake/install-qml-module.cmake)
 | 
				
			||||||
include(cmake/util.cmake)
 | 
					include(cmake/util.cmake)
 | 
				
			||||||
| 
						 | 
					@ -116,7 +117,7 @@ if (WAYLAND)
 | 
				
			||||||
	list(APPEND QT_FPDEPS WaylandClient)
 | 
						list(APPEND QT_FPDEPS WaylandClient)
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS)
 | 
					if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH)
 | 
				
			||||||
	set(DBUS ON)
 | 
						set(DBUS ON)
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,3 +29,7 @@ if (X11)
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
add_subdirectory(services)
 | 
					add_subdirectory(services)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (BLUETOOTH)
 | 
				
			||||||
 | 
						add_subdirectory(bluetooth)
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										42
									
								
								src/bluetooth/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/bluetooth/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,42 @@
 | 
				
			||||||
 | 
					set_source_files_properties(org.bluez.Adapter.xml PROPERTIES
 | 
				
			||||||
 | 
						CLASSNAME DBusBluezAdapterInterface
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set_source_files_properties(org.bluez.Device.xml PROPERTIES
 | 
				
			||||||
 | 
						CLASSNAME DBusBluezDeviceInterface
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qt_add_dbus_interface(DBUS_INTERFACES
 | 
				
			||||||
 | 
						org.bluez.Adapter.xml
 | 
				
			||||||
 | 
						dbus_adapter
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qt_add_dbus_interface(DBUS_INTERFACES
 | 
				
			||||||
 | 
						org.bluez.Device.xml
 | 
				
			||||||
 | 
						dbus_device
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qt_add_library(quickshell-bluetooth STATIC
 | 
				
			||||||
 | 
						adapter.cpp
 | 
				
			||||||
 | 
						bluez.cpp
 | 
				
			||||||
 | 
						device.cpp
 | 
				
			||||||
 | 
						${DBUS_INTERFACES}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qt_add_qml_module(quickshell-bluetooth
 | 
				
			||||||
 | 
						URI Quickshell.Bluetooth
 | 
				
			||||||
 | 
						VERSION 0.1
 | 
				
			||||||
 | 
						DEPENDENCIES QtQml
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					install_qml_module(quickshell-bluetooth)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# dbus headers
 | 
				
			||||||
 | 
					target_include_directories(quickshell-bluetooth PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					target_link_libraries(quickshell-bluetooth PRIVATE Qt::Qml Qt::DBus)
 | 
				
			||||||
 | 
					qs_add_link_dependencies(quickshell-bluetooth quickshell-dbus)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qs_module_pch(quickshell-bluetooth SET dbus)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					target_link_libraries(quickshell PRIVATE quickshell-bluetoothplugin)
 | 
				
			||||||
							
								
								
									
										217
									
								
								src/bluetooth/adapter.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								src/bluetooth/adapter.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,217 @@
 | 
				
			||||||
 | 
					#include "adapter.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qdbusconnection.h>
 | 
				
			||||||
 | 
					#include <qdbusextratypes.h>
 | 
				
			||||||
 | 
					#include <qdbuspendingcall.h>
 | 
				
			||||||
 | 
					#include <qdbuspendingreply.h>
 | 
				
			||||||
 | 
					#include <qdebug.h>
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
 | 
					#include <qstring.h>
 | 
				
			||||||
 | 
					#include <qstringliteral.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../dbus/properties.hpp"
 | 
				
			||||||
 | 
					#include "dbus_adapter.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::bluetooth {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace {
 | 
				
			||||||
 | 
					Q_LOGGING_CATEGORY(logAdapter, "quickshell.bluetooth.adapter", QtWarningMsg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString BluetoothAdapterState::toString(BluetoothAdapterState::Enum state) {
 | 
				
			||||||
 | 
						switch (state) {
 | 
				
			||||||
 | 
						case BluetoothAdapterState::Disabled: return QStringLiteral("Disabled");
 | 
				
			||||||
 | 
						case BluetoothAdapterState::Enabled: return QStringLiteral("Enabled");
 | 
				
			||||||
 | 
						case BluetoothAdapterState::Enabling: return QStringLiteral("Enabling");
 | 
				
			||||||
 | 
						case BluetoothAdapterState::Disabling: return QStringLiteral("Disabling");
 | 
				
			||||||
 | 
						case BluetoothAdapterState::Blocked: return QStringLiteral("Blocked");
 | 
				
			||||||
 | 
						default: return QStringLiteral("Unknown");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BluetoothAdapter::BluetoothAdapter(const QString& path, QObject* parent): QObject(parent) {
 | 
				
			||||||
 | 
						this->mInterface =
 | 
				
			||||||
 | 
						    new DBusBluezAdapterInterface("org.bluez", path, QDBusConnection::systemBus(), this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!this->mInterface->isValid()) {
 | 
				
			||||||
 | 
							qCWarning(logAdapter) << "Could not create DBus interface for adapter at" << path;
 | 
				
			||||||
 | 
							this->mInterface = nullptr;
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->properties.setInterface(this->mInterface);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString BluetoothAdapter::adapterId() const {
 | 
				
			||||||
 | 
						auto path = this->path();
 | 
				
			||||||
 | 
						return path.sliced(path.lastIndexOf('/') + 1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothAdapter::setEnabled(bool enabled) {
 | 
				
			||||||
 | 
						if (enabled == this->bEnabled) return;
 | 
				
			||||||
 | 
						this->bEnabled = enabled;
 | 
				
			||||||
 | 
						this->pEnabled.write();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothAdapter::setDiscoverable(bool discoverable) {
 | 
				
			||||||
 | 
						if (discoverable == this->bDiscoverable) return;
 | 
				
			||||||
 | 
						this->bDiscoverable = discoverable;
 | 
				
			||||||
 | 
						this->pDiscoverable.write();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothAdapter::setDiscovering(bool discovering) {
 | 
				
			||||||
 | 
						if (discovering) {
 | 
				
			||||||
 | 
							this->startDiscovery();
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							this->stopDiscovery();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothAdapter::setDiscoverableTimeout(quint32 timeout) {
 | 
				
			||||||
 | 
						if (timeout == this->bDiscoverableTimeout) return;
 | 
				
			||||||
 | 
						this->bDiscoverableTimeout = timeout;
 | 
				
			||||||
 | 
						this->pDiscoverableTimeout.write();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothAdapter::setPairable(bool pairable) {
 | 
				
			||||||
 | 
						if (pairable == this->bPairable) return;
 | 
				
			||||||
 | 
						this->bPairable = pairable;
 | 
				
			||||||
 | 
						this->pPairable.write();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothAdapter::setPairableTimeout(quint32 timeout) {
 | 
				
			||||||
 | 
						if (timeout == this->bPairableTimeout) return;
 | 
				
			||||||
 | 
						this->bPairableTimeout = timeout;
 | 
				
			||||||
 | 
						this->pPairableTimeout.write();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothAdapter::addInterface(const QString& interface, const QVariantMap& properties) {
 | 
				
			||||||
 | 
						if (interface == "org.bluez.Adapter1") {
 | 
				
			||||||
 | 
							this->properties.updatePropertySet(properties, false);
 | 
				
			||||||
 | 
							qCDebug(logAdapter) << "Updated Adapter properties for" << this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothAdapter::removeDevice(const QString& devicePath) {
 | 
				
			||||||
 | 
						qCDebug(logAdapter) << "Removing device" << devicePath << "from adapter" << this;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto reply = this->mInterface->RemoveDevice(QDBusObjectPath(devicePath));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto* watcher = new QDBusPendingCallWatcher(reply, this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::connect(
 | 
				
			||||||
 | 
						    watcher,
 | 
				
			||||||
 | 
						    &QDBusPendingCallWatcher::finished,
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    [this, devicePath](QDBusPendingCallWatcher* watcher) {
 | 
				
			||||||
 | 
							    const QDBusPendingReply<> reply = *watcher;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							    if (reply.isError()) {
 | 
				
			||||||
 | 
								    qCWarning(logAdapter).nospace()
 | 
				
			||||||
 | 
								        << "Failed to remove device " << devicePath << " from adapter" << this << ": "
 | 
				
			||||||
 | 
								        << reply.error().message();
 | 
				
			||||||
 | 
							    } else {
 | 
				
			||||||
 | 
								    qCDebug(logAdapter) << "Successfully removed device" << devicePath << "from adapter"
 | 
				
			||||||
 | 
								                        << this;
 | 
				
			||||||
 | 
							    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							    delete watcher;
 | 
				
			||||||
 | 
						    }
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothAdapter::startDiscovery() {
 | 
				
			||||||
 | 
						if (this->bDiscovering) return;
 | 
				
			||||||
 | 
						qCDebug(logAdapter) << "Starting discovery for adapter" << this;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto reply = this->mInterface->StartDiscovery();
 | 
				
			||||||
 | 
						auto* watcher = new QDBusPendingCallWatcher(reply, this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::connect(
 | 
				
			||||||
 | 
						    watcher,
 | 
				
			||||||
 | 
						    &QDBusPendingCallWatcher::finished,
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    [this](QDBusPendingCallWatcher* watcher) {
 | 
				
			||||||
 | 
							    const QDBusPendingReply<> reply = *watcher;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							    if (reply.isError()) {
 | 
				
			||||||
 | 
								    qCWarning(logAdapter).nospace()
 | 
				
			||||||
 | 
								        << "Failed to start discovery on adapter" << this << ": " << reply.error().message();
 | 
				
			||||||
 | 
							    } else {
 | 
				
			||||||
 | 
								    qCDebug(logAdapter) << "Successfully started discovery on adapter" << this;
 | 
				
			||||||
 | 
							    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							    delete watcher;
 | 
				
			||||||
 | 
						    }
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothAdapter::stopDiscovery() {
 | 
				
			||||||
 | 
						if (!this->bDiscovering) return;
 | 
				
			||||||
 | 
						qCDebug(logAdapter) << "Stopping discovery for adapter" << this;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto reply = this->mInterface->StopDiscovery();
 | 
				
			||||||
 | 
						auto* watcher = new QDBusPendingCallWatcher(reply, this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::connect(
 | 
				
			||||||
 | 
						    watcher,
 | 
				
			||||||
 | 
						    &QDBusPendingCallWatcher::finished,
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    [this](QDBusPendingCallWatcher* watcher) {
 | 
				
			||||||
 | 
							    const QDBusPendingReply<> reply = *watcher;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							    if (reply.isError()) {
 | 
				
			||||||
 | 
								    qCWarning(logAdapter).nospace()
 | 
				
			||||||
 | 
								        << "Failed to stop discovery on adapter " << this << ": " << reply.error().message();
 | 
				
			||||||
 | 
							    } else {
 | 
				
			||||||
 | 
								    qCDebug(logAdapter) << "Successfully stopped discovery on adapter" << this;
 | 
				
			||||||
 | 
							    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							    delete watcher;
 | 
				
			||||||
 | 
						    }
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::bluetooth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::dbus {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace qs::bluetooth;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DBusResult<BluetoothAdapterState::Enum>
 | 
				
			||||||
 | 
					DBusDataTransform<BluetoothAdapterState::Enum>::fromWire(const Wire& wire) {
 | 
				
			||||||
 | 
						if (wire == QStringLiteral("off")) {
 | 
				
			||||||
 | 
							return BluetoothAdapterState::Disabled;
 | 
				
			||||||
 | 
						} else if (wire == QStringLiteral("on")) {
 | 
				
			||||||
 | 
							return BluetoothAdapterState::Enabled;
 | 
				
			||||||
 | 
						} else if (wire == QStringLiteral("off-enabling")) {
 | 
				
			||||||
 | 
							return BluetoothAdapterState::Enabling;
 | 
				
			||||||
 | 
						} else if (wire == QStringLiteral("on-disabling")) {
 | 
				
			||||||
 | 
							return BluetoothAdapterState::Disabling;
 | 
				
			||||||
 | 
						} else if (wire == QStringLiteral("off-blocked")) {
 | 
				
			||||||
 | 
							return BluetoothAdapterState::Blocked;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return QDBusError(
 | 
				
			||||||
 | 
							    QDBusError::InvalidArgs,
 | 
				
			||||||
 | 
							    QString("Invalid BluetoothAdapterState: %1").arg(wire)
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::dbus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QDebug operator<<(QDebug debug, const qs::bluetooth::BluetoothAdapter* adapter) {
 | 
				
			||||||
 | 
						auto saver = QDebugStateSaver(debug);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (adapter) {
 | 
				
			||||||
 | 
							debug.nospace() << "BluetoothAdapter(" << static_cast<const void*>(adapter)
 | 
				
			||||||
 | 
							                << ", path=" << adapter->path() << ")";
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							debug << "BluetoothAdapter(nullptr)";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return debug;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										173
									
								
								src/bluetooth/adapter.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								src/bluetooth/adapter.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,173 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlintegration.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../core/doc.hpp"
 | 
				
			||||||
 | 
					#include "../core/model.hpp"
 | 
				
			||||||
 | 
					#include "../dbus/properties.hpp"
 | 
				
			||||||
 | 
					#include "dbus_adapter.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::bluetooth {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///! Power state of a Bluetooth adapter.
 | 
				
			||||||
 | 
					class BluetoothAdapterState: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
						QML_SINGLETON;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						enum Enum : quint8 {
 | 
				
			||||||
 | 
							/// The adapter is powered off.
 | 
				
			||||||
 | 
							Disabled = 0,
 | 
				
			||||||
 | 
							/// The adapter is powered on.
 | 
				
			||||||
 | 
							Enabled = 1,
 | 
				
			||||||
 | 
							/// The adapter is transitioning from off to on.
 | 
				
			||||||
 | 
							Enabling = 2,
 | 
				
			||||||
 | 
							/// The adapter is transitioning from on to off.
 | 
				
			||||||
 | 
							Disabling = 3,
 | 
				
			||||||
 | 
							/// The adapter is blocked by rfkill.
 | 
				
			||||||
 | 
							Blocked = 4,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						Q_ENUM(Enum);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Q_INVOKABLE static QString toString(BluetoothAdapterState::Enum state);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::bluetooth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::dbus {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <>
 | 
				
			||||||
 | 
					struct DBusDataTransform<qs::bluetooth::BluetoothAdapterState::Enum> {
 | 
				
			||||||
 | 
						using Wire = QString;
 | 
				
			||||||
 | 
						using Data = qs::bluetooth::BluetoothAdapterState::Enum;
 | 
				
			||||||
 | 
						static DBusResult<Data> fromWire(const Wire& wire);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::dbus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::bluetooth {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BluetoothAdapter;
 | 
				
			||||||
 | 
					class BluetoothDevice;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///! A Bluetooth adapter
 | 
				
			||||||
 | 
					class BluetoothAdapter: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
						QML_UNCREATABLE("");
 | 
				
			||||||
 | 
						// clang-format off
 | 
				
			||||||
 | 
						/// System provided name of the adapter. See @@adapterId for the internal identifier.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName);
 | 
				
			||||||
 | 
						/// True if the adapter is currently enabled. More detailed state is available from @@state.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged);
 | 
				
			||||||
 | 
						/// Detailed power state of the adapter.
 | 
				
			||||||
 | 
						Q_PROPERTY(BluetoothAdapterState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
 | 
				
			||||||
 | 
						/// True if the adapter can be discovered by other bluetooth devices.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool discoverable READ discoverable WRITE setDiscoverable NOTIFY discoverableChanged);
 | 
				
			||||||
 | 
						/// Timeout in seconds for how long the adapter stays discoverable after @@discoverable is set to true.
 | 
				
			||||||
 | 
						/// A value of 0 means the adapter stays discoverable forever.
 | 
				
			||||||
 | 
						Q_PROPERTY(quint32 discoverableTimeout READ discoverableTimeout WRITE setDiscoverableTimeout NOTIFY discoverableTimeoutChanged);
 | 
				
			||||||
 | 
						/// True if the adapter is scanning for new devices.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool discovering READ discovering WRITE setDiscovering NOTIFY discoveringChanged);
 | 
				
			||||||
 | 
						/// True if the adapter is accepting incoming pairing requests.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// This only affects incoming pairing requests and should typically only be changed
 | 
				
			||||||
 | 
						/// by system settings applications. Defaults to true.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool pairable READ pairable WRITE setPairable NOTIFY pairableChanged);
 | 
				
			||||||
 | 
						/// Timeout in seconds for how long the adapter stays pairable after @@pairable is set to true.
 | 
				
			||||||
 | 
						/// A value of 0 means the adapter stays pairable forever. Defaults to 0.
 | 
				
			||||||
 | 
						Q_PROPERTY(quint32 pairableTimeout READ pairableTimeout WRITE setPairableTimeout NOTIFY pairableTimeoutChanged);
 | 
				
			||||||
 | 
						/// Bluetooth devices connected to this adapter.
 | 
				
			||||||
 | 
						QSDOC_TYPE_OVERRIDE(ObjectModel<qs::bluetooth::BluetoothDevice>*);
 | 
				
			||||||
 | 
						Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT);
 | 
				
			||||||
 | 
						/// The internal ID of the adapter (e.g., "hci0").
 | 
				
			||||||
 | 
						Q_PROPERTY(QString adapterId READ adapterId CONSTANT);
 | 
				
			||||||
 | 
						/// DBus path of the adapter under the `org.bluez` system service.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString dbusPath READ path CONSTANT);
 | 
				
			||||||
 | 
						// clang-format on
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit BluetoothAdapter(const QString& path, QObject* parent = nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool isValid() const { return this->mInterface->isValid(); }
 | 
				
			||||||
 | 
						[[nodiscard]] QString path() const { return this->mInterface->path(); }
 | 
				
			||||||
 | 
						[[nodiscard]] QString adapterId() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool enabled() const { return this->bEnabled; }
 | 
				
			||||||
 | 
						void setEnabled(bool enabled);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool discoverable() const { return this->bDiscoverable; }
 | 
				
			||||||
 | 
						void setDiscoverable(bool discoverable);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool discovering() const { return this->bDiscovering; }
 | 
				
			||||||
 | 
						void setDiscovering(bool discovering);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] quint32 discoverableTimeout() const { return this->bDiscoverableTimeout; }
 | 
				
			||||||
 | 
						void setDiscoverableTimeout(quint32 timeout);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool pairable() const { return this->bPairable; }
 | 
				
			||||||
 | 
						void setPairable(bool pairable);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] quint32 pairableTimeout() const { return this->bPairableTimeout; }
 | 
				
			||||||
 | 
						void setPairableTimeout(quint32 timeout);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<QString> bindableName() { return &this->bName; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<bool> bindableEnabled() { return &this->bEnabled; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<BluetoothAdapterState::Enum> bindableState() { return &this->bState; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<bool> bindableDiscoverable() { return &this->bDiscoverable; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<quint32> bindableDiscoverableTimeout() {
 | 
				
			||||||
 | 
							return &this->bDiscoverableTimeout;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<bool> bindableDiscovering() { return &this->bDiscovering; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<bool> bindablePairable() { return &this->bPairable; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<quint32> bindablePairableTimeout() { return &this->bPairableTimeout; }
 | 
				
			||||||
 | 
						[[nodiscard]] ObjectModel<BluetoothDevice>* devices() { return &this->mDevices; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void addInterface(const QString& interface, const QVariantMap& properties);
 | 
				
			||||||
 | 
						void removeDevice(const QString& devicePath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void startDiscovery();
 | 
				
			||||||
 | 
						void stopDiscovery();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						void nameChanged();
 | 
				
			||||||
 | 
						void enabledChanged();
 | 
				
			||||||
 | 
						void stateChanged();
 | 
				
			||||||
 | 
						void discoverableChanged();
 | 
				
			||||||
 | 
						void discoverableTimeoutChanged();
 | 
				
			||||||
 | 
						void discoveringChanged();
 | 
				
			||||||
 | 
						void pairableChanged();
 | 
				
			||||||
 | 
						void pairableTimeoutChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						DBusBluezAdapterInterface* mInterface = nullptr;
 | 
				
			||||||
 | 
						ObjectModel<BluetoothDevice> mDevices {this};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// clang-format off
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothAdapter, QString, bName, &BluetoothAdapter::nameChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothAdapter, bool, bEnabled, &BluetoothAdapter::enabledChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothAdapter, BluetoothAdapterState::Enum, bState, &BluetoothAdapter::stateChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothAdapter, bool, bDiscoverable, &BluetoothAdapter::discoverableChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothAdapter, quint32, bDiscoverableTimeout, &BluetoothAdapter::discoverableTimeoutChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothAdapter, bool, bDiscovering, &BluetoothAdapter::discoveringChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothAdapter, bool, bPairable, &BluetoothAdapter::pairableChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothAdapter, quint32, bPairableTimeout, &BluetoothAdapter::pairableTimeoutChanged);
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						QS_DBUS_BINDABLE_PROPERTY_GROUP(BluetoothAdapter, properties);
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothAdapter, pName, bName, properties, "Alias");
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothAdapter, pEnabled, bEnabled, properties, "Powered");
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothAdapter, pState, bState, properties, "PowerState");
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothAdapter, pDiscoverable, bDiscoverable, properties, "Discoverable");
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothAdapter, pDiscoverableTimeout, bDiscoverableTimeout, properties, "DiscoverableTimeout");
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothAdapter, pDiscovering, bDiscovering, properties, "Discovering");
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothAdapter, pPairable, bPairable, properties, "Pairable");
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothAdapter, pPairableTimeout, bPairableTimeout, properties, "PairableTimeout");
 | 
				
			||||||
 | 
						// clang-format on
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::bluetooth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QDebug operator<<(QDebug debug, const qs::bluetooth::BluetoothAdapter* adapter);
 | 
				
			||||||
							
								
								
									
										156
									
								
								src/bluetooth/bluez.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								src/bluetooth/bluez.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,156 @@
 | 
				
			||||||
 | 
					#include "bluez.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qdbusconnection.h>
 | 
				
			||||||
 | 
					#include <qdbusextratypes.h>
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../dbus/dbus_objectmanager_types.hpp"
 | 
				
			||||||
 | 
					#include "../dbus/objectmanager.hpp"
 | 
				
			||||||
 | 
					#include "adapter.hpp"
 | 
				
			||||||
 | 
					#include "device.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::bluetooth {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace {
 | 
				
			||||||
 | 
					Q_LOGGING_CATEGORY(logBluetooth, "quickshell.bluetooth", QtWarningMsg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Bluez* Bluez::instance() {
 | 
				
			||||||
 | 
						static auto* instance = new Bluez();
 | 
				
			||||||
 | 
						return instance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Bluez::Bluez() { this->init(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Bluez::init() {
 | 
				
			||||||
 | 
						qCDebug(logBluetooth) << "Connecting to BlueZ";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto bus = QDBusConnection::systemBus();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!bus.isConnected()) {
 | 
				
			||||||
 | 
							qCWarning(logBluetooth) << "Could not connect to DBus. Bluetooth integration is not available.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->objectManager = new qs::dbus::DBusObjectManager(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::connect(
 | 
				
			||||||
 | 
						    this->objectManager,
 | 
				
			||||||
 | 
						    &qs::dbus::DBusObjectManager::interfacesAdded,
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    &Bluez::onInterfacesAdded
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::connect(
 | 
				
			||||||
 | 
						    this->objectManager,
 | 
				
			||||||
 | 
						    &qs::dbus::DBusObjectManager::interfacesRemoved,
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    &Bluez::onInterfacesRemoved
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!this->objectManager->setInterface("org.bluez", "/", bus)) {
 | 
				
			||||||
 | 
							qCDebug(logBluetooth) << "BlueZ is not running. Bluetooth integration will not work.";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Bluez::onInterfacesAdded(
 | 
				
			||||||
 | 
					    const QDBusObjectPath& path,
 | 
				
			||||||
 | 
					    const DBusObjectManagerInterfaces& interfaces
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						if (auto* adapter = this->mAdapterMap.value(path.path())) {
 | 
				
			||||||
 | 
							for (const auto& [interface, properties]: interfaces.asKeyValueRange()) {
 | 
				
			||||||
 | 
								adapter->addInterface(interface, properties);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if (auto* device = this->mDeviceMap.value(path.path())) {
 | 
				
			||||||
 | 
							for (const auto& [interface, properties]: interfaces.asKeyValueRange()) {
 | 
				
			||||||
 | 
								device->addInterface(interface, properties);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if (interfaces.contains("org.bluez.Adapter1")) {
 | 
				
			||||||
 | 
							auto* adapter = new BluetoothAdapter(path.path(), this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!adapter->isValid()) {
 | 
				
			||||||
 | 
								qCWarning(logBluetooth) << "Adapter path is not valid, cannot track: " << device;
 | 
				
			||||||
 | 
								delete adapter;
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							qCDebug(logBluetooth) << "Tracked new adapter" << adapter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (const auto& [interface, properties]: interfaces.asKeyValueRange()) {
 | 
				
			||||||
 | 
								adapter->addInterface(interface, properties);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (auto* device: this->mDevices.valueList()) {
 | 
				
			||||||
 | 
								if (device->adapterPath() == path) {
 | 
				
			||||||
 | 
									adapter->devices()->insertObject(device);
 | 
				
			||||||
 | 
									qCDebug(logBluetooth) << "Added tracked device" << device << "to new adapter" << adapter;
 | 
				
			||||||
 | 
									emit device->adapterChanged();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this->mAdapterMap.insert(path.path(), adapter);
 | 
				
			||||||
 | 
							this->mAdapters.insertObject(adapter);
 | 
				
			||||||
 | 
						} else if (interfaces.contains("org.bluez.Device1")) {
 | 
				
			||||||
 | 
							auto* device = new BluetoothDevice(path.path(), this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!device->isValid()) {
 | 
				
			||||||
 | 
								qCWarning(logBluetooth) << "Device path is not valid, cannot track: " << device;
 | 
				
			||||||
 | 
								delete device;
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							qCDebug(logBluetooth) << "Tracked new device" << device;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (const auto& [interface, properties]: interfaces.asKeyValueRange()) {
 | 
				
			||||||
 | 
								device->addInterface(interface, properties);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (auto* adapter = device->adapter()) {
 | 
				
			||||||
 | 
								adapter->devices()->insertObject(device);
 | 
				
			||||||
 | 
								qCDebug(logBluetooth) << "Added device" << device << "to adapter" << adapter;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this->mDeviceMap.insert(path.path(), device);
 | 
				
			||||||
 | 
							this->mDevices.insertObject(device);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Bluez::onInterfacesRemoved(const QDBusObjectPath& path, const QStringList& interfaces) {
 | 
				
			||||||
 | 
						if (auto* adapter = this->mAdapterMap.value(path.path())) {
 | 
				
			||||||
 | 
							if (interfaces.contains("org.bluez.Adapter1")) {
 | 
				
			||||||
 | 
								qCDebug(logBluetooth) << "Adapter removed:" << adapter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								this->mAdapterMap.remove(path.path());
 | 
				
			||||||
 | 
								this->mAdapters.removeObject(adapter);
 | 
				
			||||||
 | 
								delete adapter;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if (auto* device = this->mDeviceMap.value(path.path())) {
 | 
				
			||||||
 | 
							if (interfaces.contains("org.bluez.Device1")) {
 | 
				
			||||||
 | 
								qCDebug(logBluetooth) << "Device removed:" << device;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (auto* adapter = device->adapter()) {
 | 
				
			||||||
 | 
									adapter->devices()->removeObject(device);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								this->mDeviceMap.remove(path.path());
 | 
				
			||||||
 | 
								this->mDevices.removeObject(device);
 | 
				
			||||||
 | 
								delete device;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								for (const auto& interface: interfaces) {
 | 
				
			||||||
 | 
									device->removeInterface(interface);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BluetoothAdapter* Bluez::defaultAdapter() const {
 | 
				
			||||||
 | 
						const auto& adapters = this->mAdapters.valueList();
 | 
				
			||||||
 | 
						return adapters.isEmpty() ? nullptr : adapters.first();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::bluetooth
 | 
				
			||||||
							
								
								
									
										81
									
								
								src/bluetooth/bluez.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/bluetooth/bluez.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,81 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qhash.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlintegration.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../core/doc.hpp"
 | 
				
			||||||
 | 
					#include "../core/model.hpp"
 | 
				
			||||||
 | 
					#include "../dbus/dbus_objectmanager_types.hpp"
 | 
				
			||||||
 | 
					#include "../dbus/objectmanager.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::bluetooth {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BluetoothAdapter;
 | 
				
			||||||
 | 
					class BluetoothDevice;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Bluez: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						[[nodiscard]] ObjectModel<BluetoothAdapter>* adapters() { return &this->mAdapters; }
 | 
				
			||||||
 | 
						[[nodiscard]] ObjectModel<BluetoothDevice>* devices() { return &this->mDevices; }
 | 
				
			||||||
 | 
						[[nodiscard]] BluetoothAdapter* defaultAdapter() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] BluetoothAdapter* adapter(const QString& path) {
 | 
				
			||||||
 | 
							return this->mAdapterMap.value(path);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static Bluez* instance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private slots:
 | 
				
			||||||
 | 
						void
 | 
				
			||||||
 | 
						onInterfacesAdded(const QDBusObjectPath& path, const DBusObjectManagerInterfaces& interfaces);
 | 
				
			||||||
 | 
						void onInterfacesRemoved(const QDBusObjectPath& path, const QStringList& interfaces);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						explicit Bluez();
 | 
				
			||||||
 | 
						void init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qs::dbus::DBusObjectManager* objectManager = nullptr;
 | 
				
			||||||
 | 
						QHash<QString, BluetoothAdapter*> mAdapterMap;
 | 
				
			||||||
 | 
						QHash<QString, BluetoothDevice*> mDeviceMap;
 | 
				
			||||||
 | 
						ObjectModel<BluetoothAdapter> mAdapters {this};
 | 
				
			||||||
 | 
						ObjectModel<BluetoothDevice> mDevices {this};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///! Bluetooth manager
 | 
				
			||||||
 | 
					/// Provides access to bluetooth devices and adapters.
 | 
				
			||||||
 | 
					class BluezQml: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						/// The default bluetooth adapter. Usually there is only one.
 | 
				
			||||||
 | 
						Q_PROPERTY(BluetoothAdapter* defaultAdapter READ defaultAdapter CONSTANT);
 | 
				
			||||||
 | 
						QSDOC_TYPE_OVERRIDE(ObjectModel<qs::bluetooth::BluetoothAdapter>*);
 | 
				
			||||||
 | 
						/// A list of all bluetooth adapters. See @@defaultAdapter for the default.
 | 
				
			||||||
 | 
						Q_PROPERTY(UntypedObjectModel* adapters READ adapters CONSTANT);
 | 
				
			||||||
 | 
						QSDOC_TYPE_OVERRIDE(ObjectModel<qs::bluetooth::BluetoothDevice>*);
 | 
				
			||||||
 | 
						/// A list of all connected bluetooth devices across all adapters.
 | 
				
			||||||
 | 
						/// See @@BluetoothAdapter.devices for the devices connected to a single adapter.
 | 
				
			||||||
 | 
						Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT);
 | 
				
			||||||
 | 
						QML_NAMED_ELEMENT(Bluetooth);
 | 
				
			||||||
 | 
						QML_SINGLETON;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit BluezQml(QObject* parent = nullptr): QObject(parent) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] static ObjectModel<BluetoothAdapter>* adapters() {
 | 
				
			||||||
 | 
							return Bluez::instance()->adapters();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] static ObjectModel<BluetoothDevice>* devices() {
 | 
				
			||||||
 | 
							return Bluez::instance()->devices();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] static BluetoothAdapter* defaultAdapter() {
 | 
				
			||||||
 | 
							return Bluez::instance()->defaultAdapter();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::bluetooth
 | 
				
			||||||
							
								
								
									
										318
									
								
								src/bluetooth/device.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										318
									
								
								src/bluetooth/device.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,318 @@
 | 
				
			||||||
 | 
					#include "device.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qdbusconnection.h>
 | 
				
			||||||
 | 
					#include <qdbuspendingcall.h>
 | 
				
			||||||
 | 
					#include <qdbuspendingreply.h>
 | 
				
			||||||
 | 
					#include <qdebug.h>
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
 | 
					#include <qstring.h>
 | 
				
			||||||
 | 
					#include <qstringliteral.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../dbus/properties.hpp"
 | 
				
			||||||
 | 
					#include "adapter.hpp"
 | 
				
			||||||
 | 
					#include "bluez.hpp"
 | 
				
			||||||
 | 
					#include "dbus_device.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::bluetooth {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace {
 | 
				
			||||||
 | 
					Q_LOGGING_CATEGORY(logDevice, "quickshell.bluetooth.device", QtWarningMsg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString BluetoothDeviceState::toString(BluetoothDeviceState::Enum state) {
 | 
				
			||||||
 | 
						switch (state) {
 | 
				
			||||||
 | 
						case BluetoothDeviceState::Disconnected: return QStringLiteral("Disconnected");
 | 
				
			||||||
 | 
						case BluetoothDeviceState::Connected: return QStringLiteral("Connected");
 | 
				
			||||||
 | 
						case BluetoothDeviceState::Disconnecting: return QStringLiteral("Disconnecting");
 | 
				
			||||||
 | 
						case BluetoothDeviceState::Connecting: return QStringLiteral("Connecting");
 | 
				
			||||||
 | 
						default: return QStringLiteral("Unknown");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BluetoothDevice::BluetoothDevice(const QString& path, QObject* parent): QObject(parent) {
 | 
				
			||||||
 | 
						this->mInterface =
 | 
				
			||||||
 | 
						    new DBusBluezDeviceInterface("org.bluez", path, QDBusConnection::systemBus(), this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!this->mInterface->isValid()) {
 | 
				
			||||||
 | 
							qCWarning(logDevice) << "Could not create DBus interface for device at" << path;
 | 
				
			||||||
 | 
							delete this->mInterface;
 | 
				
			||||||
 | 
							this->mInterface = nullptr;
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->properties.setInterface(this->mInterface);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BluetoothAdapter* BluetoothDevice::adapter() const {
 | 
				
			||||||
 | 
						return Bluez::instance()->adapter(this->bAdapterPath.value().path());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothDevice::setConnected(bool connected) {
 | 
				
			||||||
 | 
						if (connected == this->bConnected) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (connected) {
 | 
				
			||||||
 | 
							this->connect();
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							this->disconnect();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothDevice::setTrusted(bool trusted) {
 | 
				
			||||||
 | 
						if (trusted == this->bTrusted) return;
 | 
				
			||||||
 | 
						this->bTrusted = trusted;
 | 
				
			||||||
 | 
						this->pTrusted.write();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothDevice::setBlocked(bool blocked) {
 | 
				
			||||||
 | 
						if (blocked == this->bBlocked) return;
 | 
				
			||||||
 | 
						this->bBlocked = blocked;
 | 
				
			||||||
 | 
						this->pBlocked.write();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothDevice::setName(const QString& name) {
 | 
				
			||||||
 | 
						if (name == this->bName) return;
 | 
				
			||||||
 | 
						this->bName = name;
 | 
				
			||||||
 | 
						this->pName.write();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothDevice::setWakeAllowed(bool wakeAllowed) {
 | 
				
			||||||
 | 
						if (wakeAllowed == this->bWakeAllowed) return;
 | 
				
			||||||
 | 
						this->bWakeAllowed = wakeAllowed;
 | 
				
			||||||
 | 
						this->pWakeAllowed.write();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothDevice::connect() {
 | 
				
			||||||
 | 
						if (this->bConnected) {
 | 
				
			||||||
 | 
							qCCritical(logDevice) << "Device" << this << "is already connected";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->bState == BluetoothDeviceState::Connecting) {
 | 
				
			||||||
 | 
							qCCritical(logDevice) << "Device" << this << "is already connecting";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qCDebug(logDevice) << "Connecting to device" << this;
 | 
				
			||||||
 | 
						this->bState = BluetoothDeviceState::Connecting;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto reply = this->mInterface->Connect();
 | 
				
			||||||
 | 
						auto* watcher = new QDBusPendingCallWatcher(reply, this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::connect(
 | 
				
			||||||
 | 
						    watcher,
 | 
				
			||||||
 | 
						    &QDBusPendingCallWatcher::finished,
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    [this](QDBusPendingCallWatcher* watcher) {
 | 
				
			||||||
 | 
							    const QDBusPendingReply<> reply = *watcher;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							    if (reply.isError()) {
 | 
				
			||||||
 | 
								    qCWarning(logDevice).nospace()
 | 
				
			||||||
 | 
								        << "Failed to connect to device " << this << ": " << reply.error().message();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								    this->bState = this->bConnected ? BluetoothDeviceState::Connected
 | 
				
			||||||
 | 
								                                    : BluetoothDeviceState::Disconnected;
 | 
				
			||||||
 | 
							    } else {
 | 
				
			||||||
 | 
								    qCDebug(logDevice) << "Successfully connected to to device" << this;
 | 
				
			||||||
 | 
							    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							    delete watcher;
 | 
				
			||||||
 | 
						    }
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothDevice::disconnect() {
 | 
				
			||||||
 | 
						if (!this->bConnected) {
 | 
				
			||||||
 | 
							qCCritical(logDevice) << "Device" << this << "is already disconnected";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->bState == BluetoothDeviceState::Disconnecting) {
 | 
				
			||||||
 | 
							qCCritical(logDevice) << "Device" << this << "is already disconnecting";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qCDebug(logDevice) << "Disconnecting from device" << this;
 | 
				
			||||||
 | 
						this->bState = BluetoothDeviceState::Disconnecting;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto reply = this->mInterface->Disconnect();
 | 
				
			||||||
 | 
						auto* watcher = new QDBusPendingCallWatcher(reply, this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::connect(
 | 
				
			||||||
 | 
						    watcher,
 | 
				
			||||||
 | 
						    &QDBusPendingCallWatcher::finished,
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    [this](QDBusPendingCallWatcher* watcher) {
 | 
				
			||||||
 | 
							    const QDBusPendingReply<> reply = *watcher;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							    if (reply.isError()) {
 | 
				
			||||||
 | 
								    qCWarning(logDevice).nospace()
 | 
				
			||||||
 | 
								        << "Failed to disconnect from device " << this << ": " << reply.error().message();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								    this->bState = this->bConnected ? BluetoothDeviceState::Connected
 | 
				
			||||||
 | 
								                                    : BluetoothDeviceState::Disconnected;
 | 
				
			||||||
 | 
							    } else {
 | 
				
			||||||
 | 
								    qCDebug(logDevice) << "Successfully disconnected from from device" << this;
 | 
				
			||||||
 | 
							    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							    delete watcher;
 | 
				
			||||||
 | 
						    }
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothDevice::pair() {
 | 
				
			||||||
 | 
						if (this->bPaired) {
 | 
				
			||||||
 | 
							qCCritical(logDevice) << "Device" << this << "is already paired";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->bPairing) {
 | 
				
			||||||
 | 
							qCCritical(logDevice) << "Device" << this << "is already pairing";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qCDebug(logDevice) << "Pairing with device" << this;
 | 
				
			||||||
 | 
						this->bPairing = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto reply = this->mInterface->Pair();
 | 
				
			||||||
 | 
						auto* watcher = new QDBusPendingCallWatcher(reply, this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::connect(
 | 
				
			||||||
 | 
						    watcher,
 | 
				
			||||||
 | 
						    &QDBusPendingCallWatcher::finished,
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    [this](QDBusPendingCallWatcher* watcher) {
 | 
				
			||||||
 | 
							    const QDBusPendingReply<> reply = *watcher;
 | 
				
			||||||
 | 
							    if (reply.isError()) {
 | 
				
			||||||
 | 
								    qCWarning(logDevice).nospace()
 | 
				
			||||||
 | 
								        << "Failed to pair with device " << this << ": " << reply.error().message();
 | 
				
			||||||
 | 
							    } else {
 | 
				
			||||||
 | 
								    qCDebug(logDevice) << "Successfully initiated pairing with device" << this;
 | 
				
			||||||
 | 
							    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							    this->bPairing = false;
 | 
				
			||||||
 | 
							    delete watcher;
 | 
				
			||||||
 | 
						    }
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothDevice::cancelPair() {
 | 
				
			||||||
 | 
						if (!this->bPairing) {
 | 
				
			||||||
 | 
							qCCritical(logDevice) << "Device" << this << "is not currently pairing";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qCDebug(logDevice) << "Cancelling pairing with device" << this;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto reply = this->mInterface->CancelPairing();
 | 
				
			||||||
 | 
						auto* watcher = new QDBusPendingCallWatcher(reply, this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::connect(
 | 
				
			||||||
 | 
						    watcher,
 | 
				
			||||||
 | 
						    &QDBusPendingCallWatcher::finished,
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    [this](QDBusPendingCallWatcher* watcher) {
 | 
				
			||||||
 | 
							    const QDBusPendingReply<> reply = *watcher;
 | 
				
			||||||
 | 
							    if (reply.isError()) {
 | 
				
			||||||
 | 
								    qCWarning(logDevice) << "Failed to cancel pairing with device" << this << ":"
 | 
				
			||||||
 | 
								                         << reply.error().message();
 | 
				
			||||||
 | 
							    } else {
 | 
				
			||||||
 | 
								    qCDebug(logDevice) << "Successfully cancelled pairing with device" << this;
 | 
				
			||||||
 | 
							    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							    this->bPairing = false;
 | 
				
			||||||
 | 
							    delete watcher;
 | 
				
			||||||
 | 
						    }
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothDevice::forget() {
 | 
				
			||||||
 | 
						if (!this->mInterface || !this->mInterface->isValid()) {
 | 
				
			||||||
 | 
							qCCritical(logDevice) << "Cannot forget - device interface is invalid";
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (auto* adapter = Bluez::instance()->adapter(this->bAdapterPath.value().path())) {
 | 
				
			||||||
 | 
							qCDebug(logDevice) << "Forgetting device" << this << "via adapter" << adapter;
 | 
				
			||||||
 | 
							adapter->removeDevice(this->path());
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							qCCritical(logDevice) << "Could not find adapter for path" << this->bAdapterPath.value().path()
 | 
				
			||||||
 | 
							                      << "to forget from";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothDevice::addInterface(const QString& interface, const QVariantMap& properties) {
 | 
				
			||||||
 | 
						if (interface == "org.bluez.Device1") {
 | 
				
			||||||
 | 
							this->properties.updatePropertySet(properties, false);
 | 
				
			||||||
 | 
							qCDebug(logDevice) << "Updated Device properties for" << this;
 | 
				
			||||||
 | 
						} else if (interface == "org.bluez.Battery1") {
 | 
				
			||||||
 | 
							if (!this->mBatteryInterface) {
 | 
				
			||||||
 | 
								this->mBatteryInterface = new QDBusInterface(
 | 
				
			||||||
 | 
								    "org.bluez",
 | 
				
			||||||
 | 
								    this->path(),
 | 
				
			||||||
 | 
								    "org.bluez.Battery1",
 | 
				
			||||||
 | 
								    QDBusConnection::systemBus(),
 | 
				
			||||||
 | 
								    this
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!this->mBatteryInterface->isValid()) {
 | 
				
			||||||
 | 
									qCWarning(logDevice) << "Could not create Battery interface for device at" << this;
 | 
				
			||||||
 | 
									delete this->mBatteryInterface;
 | 
				
			||||||
 | 
									this->mBatteryInterface = nullptr;
 | 
				
			||||||
 | 
									return;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this->batteryProperties.setInterface(this->mBatteryInterface);
 | 
				
			||||||
 | 
							this->batteryProperties.updatePropertySet(properties, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							emit this->batteryAvailableChanged();
 | 
				
			||||||
 | 
							qCDebug(logDevice) << "Updated Battery properties for" << this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothDevice::removeInterface(const QString& interface) {
 | 
				
			||||||
 | 
						if (interface == "org.bluez.Battery1" && this->mBatteryInterface) {
 | 
				
			||||||
 | 
							this->batteryProperties.setInterface(nullptr);
 | 
				
			||||||
 | 
							delete this->mBatteryInterface;
 | 
				
			||||||
 | 
							this->mBatteryInterface = nullptr;
 | 
				
			||||||
 | 
							this->bBattery = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							emit this->batteryAvailableChanged();
 | 
				
			||||||
 | 
							qCDebug(logDevice) << "Battery interface removed from device" << this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BluetoothDevice::onConnectedChanged() {
 | 
				
			||||||
 | 
						this->bState =
 | 
				
			||||||
 | 
						    this->bConnected ? BluetoothDeviceState::Connected : BluetoothDeviceState::Disconnected;
 | 
				
			||||||
 | 
						emit this->connectedChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::bluetooth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::dbus {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace qs::bluetooth;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DBusResult<qreal> DBusDataTransform<BatteryPercentage>::fromWire(quint8 percentage) {
 | 
				
			||||||
 | 
						return DBusResult(percentage * 0.01);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::dbus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QDebug operator<<(QDebug debug, const qs::bluetooth::BluetoothDevice* device) {
 | 
				
			||||||
 | 
						auto saver = QDebugStateSaver(debug);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (device) {
 | 
				
			||||||
 | 
							debug.nospace() << "BluetoothDevice(" << static_cast<const void*>(device)
 | 
				
			||||||
 | 
							                << ", path=" << device->path() << ")";
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							debug << "BluetoothDevice(nullptr)";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return debug;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										225
									
								
								src/bluetooth/device.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								src/bluetooth/device.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,225 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlintegration.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../dbus/properties.hpp"
 | 
				
			||||||
 | 
					#include "dbus_device.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::bluetooth {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///! Connection state of a Bluetooth device.
 | 
				
			||||||
 | 
					class BluetoothDeviceState: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
						QML_SINGLETON;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						enum Enum : quint8 {
 | 
				
			||||||
 | 
							/// The device is not connected.
 | 
				
			||||||
 | 
							Disconnected = 0,
 | 
				
			||||||
 | 
							/// The device is connected.
 | 
				
			||||||
 | 
							Connected = 1,
 | 
				
			||||||
 | 
							/// The device is disconnecting.
 | 
				
			||||||
 | 
							Disconnecting = 2,
 | 
				
			||||||
 | 
							/// The device is connecting.
 | 
				
			||||||
 | 
							Connecting = 3,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						Q_ENUM(Enum);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Q_INVOKABLE static QString toString(BluetoothDeviceState::Enum state);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct BatteryPercentage {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::bluetooth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::dbus {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <>
 | 
				
			||||||
 | 
					struct DBusDataTransform<qs::bluetooth::BatteryPercentage> {
 | 
				
			||||||
 | 
						using Wire = quint8;
 | 
				
			||||||
 | 
						using Data = qreal;
 | 
				
			||||||
 | 
						static DBusResult<Data> fromWire(Wire percentage);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::dbus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::bluetooth {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BluetoothAdapter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///! A tracked Bluetooth device.
 | 
				
			||||||
 | 
					class BluetoothDevice: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
						QML_UNCREATABLE("");
 | 
				
			||||||
 | 
						// clang-format off
 | 
				
			||||||
 | 
						/// MAC address of the device.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress);
 | 
				
			||||||
 | 
						/// The name of the Bluetooth device. This property may be written to create an alias, or set to
 | 
				
			||||||
 | 
						/// an empty string to fall back to the device provided name.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// See @@deviceName for the name provided by the device.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged);
 | 
				
			||||||
 | 
						/// The name of the Bluetooth device, ignoring user provided aliases. See also @@name
 | 
				
			||||||
 | 
						/// which returns a user provided alias if set.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString deviceName READ default NOTIFY deviceNameChanged BINDABLE bindableDeviceName);
 | 
				
			||||||
 | 
						/// System icon representing the device type. Use @@Quickshell.Quickshell.iconPath() to display this in an image.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString icon READ default NOTIFY iconChanged BINDABLE bindableIcon);
 | 
				
			||||||
 | 
						/// Connection state of the device.
 | 
				
			||||||
 | 
						Q_PROPERTY(BluetoothDeviceState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
 | 
				
			||||||
 | 
						/// True if the device is currently connected to the computer.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Setting this property is equivalent to calling @@connect() and @@disconnect().
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// > [!NOTE] @@state provides more detailed information if required.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool connected READ connected WRITE setConnected NOTIFY connectedChanged);
 | 
				
			||||||
 | 
						/// True if the device is paired to the computer.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// > [!NOTE] @@pair() can be used to pair a device, however you must @@forget() the device to unpair it.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool paired READ default NOTIFY pairedChanged BINDABLE bindablePaired);
 | 
				
			||||||
 | 
						/// True if pairing information is stored for future connections.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool bonded READ default NOTIFY bondedChanged BINDABLE bindableBonded);
 | 
				
			||||||
 | 
						/// True if the device is currently being paired.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// > [!NOTE] @@cancelPair() can be used to cancel the pairing process.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool pairing READ pairing NOTIFY pairingChanged);
 | 
				
			||||||
 | 
						/// True if the device is considered to be trusted by the system.
 | 
				
			||||||
 | 
						/// Trusted devices are allowed to reconnect themselves to the system without intervention.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool trusted READ trusted WRITE setTrusted NOTIFY trustedChanged);
 | 
				
			||||||
 | 
						/// True if the device is blocked from connecting.
 | 
				
			||||||
 | 
						/// If a device is blocked, any connection attempts will be immediately rejected by the system.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool blocked READ blocked WRITE setBlocked NOTIFY blockedChanged);
 | 
				
			||||||
 | 
						/// True if the device is allowed to wake up the host system from suspend.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool wakeAllowed READ wakeAllowed WRITE setWakeAllowed NOTIFY wakeAllowedChanged);
 | 
				
			||||||
 | 
						/// True if the connected device reports its battery level. Battery level can be accessed via @@battery.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool batteryAvailable READ batteryAvailable NOTIFY batteryAvailableChanged);
 | 
				
			||||||
 | 
						/// Battery level of the connected device, from `0.0` to `1.0`. Only valid if @@batteryAvailable is true.
 | 
				
			||||||
 | 
						Q_PROPERTY(qreal battery READ default NOTIFY batteryChanged BINDABLE bindableBattery);
 | 
				
			||||||
 | 
						/// The Bluetooth adapter this device belongs to.
 | 
				
			||||||
 | 
						Q_PROPERTY(BluetoothAdapter* adapter READ adapter NOTIFY adapterChanged);
 | 
				
			||||||
 | 
						/// DBus path of the device under the `org.bluez` system service.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString dbusPath READ path CONSTANT);
 | 
				
			||||||
 | 
						// clang-format on
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit BluetoothDevice(const QString& path, QObject* parent = nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Attempt to connect to the device.
 | 
				
			||||||
 | 
						Q_INVOKABLE void connect();
 | 
				
			||||||
 | 
						/// Disconnect from the device.
 | 
				
			||||||
 | 
						Q_INVOKABLE void disconnect();
 | 
				
			||||||
 | 
						/// Attempt to pair the device.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// > [!NOTE] @@paired and @@pairing return the current pairing status of the device.
 | 
				
			||||||
 | 
						Q_INVOKABLE void pair();
 | 
				
			||||||
 | 
						/// Cancel an active pairing attempt.
 | 
				
			||||||
 | 
						Q_INVOKABLE void cancelPair();
 | 
				
			||||||
 | 
						/// Forget the device.
 | 
				
			||||||
 | 
						Q_INVOKABLE void forget();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool isValid() const { return this->mInterface && this->mInterface->isValid(); }
 | 
				
			||||||
 | 
						[[nodiscard]] QString path() const {
 | 
				
			||||||
 | 
							return this->mInterface ? this->mInterface->path() : QString();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool batteryAvailable() const { return this->mBatteryInterface != nullptr; }
 | 
				
			||||||
 | 
						[[nodiscard]] BluetoothAdapter* adapter() const;
 | 
				
			||||||
 | 
						[[nodiscard]] QDBusObjectPath adapterPath() const { return this->bAdapterPath.value(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool connected() const { return this->bConnected; }
 | 
				
			||||||
 | 
						void setConnected(bool connected);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool trusted() const { return this->bTrusted; }
 | 
				
			||||||
 | 
						void setTrusted(bool trusted);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool blocked() const { return this->bBlocked; }
 | 
				
			||||||
 | 
						void setBlocked(bool blocked);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QString name() const { return this->bName; }
 | 
				
			||||||
 | 
						void setName(const QString& name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool wakeAllowed() const { return this->bWakeAllowed; }
 | 
				
			||||||
 | 
						void setWakeAllowed(bool wakeAllowed);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool pairing() const { return this->bPairing; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<QString> bindableAddress() { return &this->bAddress; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<QString> bindableDeviceName() { return &this->bDeviceName; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<QString> bindableName() { return &this->bName; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<bool> bindableConnected() { return &this->bConnected; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<bool> bindablePaired() { return &this->bPaired; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<bool> bindableBonded() { return &this->bBonded; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<bool> bindableTrusted() { return &this->bTrusted; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<bool> bindableBlocked() { return &this->bBlocked; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<bool> bindableWakeAllowed() { return &this->bWakeAllowed; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<QString> bindableIcon() { return &this->bIcon; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<qreal> bindableBattery() { return &this->bBattery; }
 | 
				
			||||||
 | 
						[[nodiscard]] QBindable<BluetoothDeviceState::Enum> bindableState() { return &this->bState; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void addInterface(const QString& interface, const QVariantMap& properties);
 | 
				
			||||||
 | 
						void removeInterface(const QString& interface);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						void addressChanged();
 | 
				
			||||||
 | 
						void deviceNameChanged();
 | 
				
			||||||
 | 
						void nameChanged();
 | 
				
			||||||
 | 
						void connectedChanged();
 | 
				
			||||||
 | 
						void stateChanged();
 | 
				
			||||||
 | 
						void pairedChanged();
 | 
				
			||||||
 | 
						void bondedChanged();
 | 
				
			||||||
 | 
						void pairingChanged();
 | 
				
			||||||
 | 
						void trustedChanged();
 | 
				
			||||||
 | 
						void blockedChanged();
 | 
				
			||||||
 | 
						void wakeAllowedChanged();
 | 
				
			||||||
 | 
						void iconChanged();
 | 
				
			||||||
 | 
						void batteryAvailableChanged();
 | 
				
			||||||
 | 
						void batteryChanged();
 | 
				
			||||||
 | 
						void adapterChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						void onConnectedChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DBusBluezDeviceInterface* mInterface = nullptr;
 | 
				
			||||||
 | 
						QDBusInterface* mBatteryInterface = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// clang-format off
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, QString, bAddress, &BluetoothDevice::addressChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, QString, bDeviceName, &BluetoothDevice::deviceNameChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, QString, bName, &BluetoothDevice::nameChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, bool, bConnected, &BluetoothDevice::onConnectedChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, bool, bPaired, &BluetoothDevice::pairedChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, bool, bBonded, &BluetoothDevice::bondedChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, bool, bTrusted, &BluetoothDevice::trustedChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, bool, bBlocked, &BluetoothDevice::blockedChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, bool, bWakeAllowed, &BluetoothDevice::wakeAllowedChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, QString, bIcon, &BluetoothDevice::iconChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, QDBusObjectPath, bAdapterPath);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, qreal, bBattery, &BluetoothDevice::batteryChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, BluetoothDeviceState::Enum, bState, &BluetoothDevice::stateChanged);
 | 
				
			||||||
 | 
						Q_OBJECT_BINDABLE_PROPERTY(BluetoothDevice, bool, bPairing, &BluetoothDevice::pairingChanged);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QS_DBUS_BINDABLE_PROPERTY_GROUP(BluetoothDevice, properties);
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pAddress, bAddress, properties, "Address");
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pDeviceName, bDeviceName, properties, "Name");
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pName, bName, properties, "Alias");
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pConnected, bConnected, properties, "Connected");
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pPaired, bPaired, properties, "Paired");
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pBonded, bBonded, properties, "Bonded");
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pTrusted, bTrusted, properties, "Trusted");
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pBlocked, bBlocked, properties, "Blocked");
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pWakeAllowed, bWakeAllowed, properties, "WakeAllowed");
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pIcon, bIcon, properties, "Icon");
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pAdapterPath, bAdapterPath, properties, "Adapter");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QS_DBUS_BINDABLE_PROPERTY_GROUP(BluetoothDevice, batteryProperties);
 | 
				
			||||||
 | 
						QS_DBUS_PROPERTY_BINDING(BluetoothDevice, BatteryPercentage, pBattery, bBattery, batteryProperties, "Percentage", true);
 | 
				
			||||||
 | 
						// clang-format on
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::bluetooth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QDebug operator<<(QDebug debug, const qs::bluetooth::BluetoothDevice* device);
 | 
				
			||||||
							
								
								
									
										12
									
								
								src/bluetooth/module.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/bluetooth/module.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					name = "Quickshell.Bluetooth"
 | 
				
			||||||
 | 
					description = "Bluetooth API"
 | 
				
			||||||
 | 
					headers = [
 | 
				
			||||||
 | 
						"bluez.hpp",
 | 
				
			||||||
 | 
						"adapter.hpp",
 | 
				
			||||||
 | 
						"device.hpp",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					-----
 | 
				
			||||||
 | 
					This module exposes Bluetooth management APIs provided by the BlueZ DBus interface.
 | 
				
			||||||
 | 
					Both DBus and BlueZ must be running to use it.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					See the @@Quickshell.Bluetooth.Bluetooth singleton.
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/bluetooth/org.bluez.Adapter.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/bluetooth/org.bluez.Adapter.xml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					<node>
 | 
				
			||||||
 | 
						<interface name="org.bluez.Adapter1">
 | 
				
			||||||
 | 
							<method name="StartDiscovery"/>
 | 
				
			||||||
 | 
							<method name="StopDiscovery"/>
 | 
				
			||||||
 | 
							<method name="RemoveDevice">
 | 
				
			||||||
 | 
								<arg name="device" type="o"/>
 | 
				
			||||||
 | 
							</method>
 | 
				
			||||||
 | 
						</interface>
 | 
				
			||||||
 | 
					</node>
 | 
				
			||||||
							
								
								
									
										8
									
								
								src/bluetooth/org.bluez.Device.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/bluetooth/org.bluez.Device.xml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					<node>
 | 
				
			||||||
 | 
						<interface name="org.bluez.Device1">
 | 
				
			||||||
 | 
							<method name="Connect"/>
 | 
				
			||||||
 | 
							<method name="Disconnect"/>
 | 
				
			||||||
 | 
							<method name="Pair"/>
 | 
				
			||||||
 | 
							<method name="CancelPairing"/>
 | 
				
			||||||
 | 
						</interface>
 | 
				
			||||||
 | 
					</node>
 | 
				
			||||||
							
								
								
									
										200
									
								
								src/bluetooth/test/manual/test.qml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								src/bluetooth/test/manual/test.qml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,200 @@
 | 
				
			||||||
 | 
					import QtQuick
 | 
				
			||||||
 | 
					import QtQuick.Controls
 | 
				
			||||||
 | 
					import QtQuick.Layouts
 | 
				
			||||||
 | 
					import Quickshell
 | 
				
			||||||
 | 
					import Quickshell.Widgets
 | 
				
			||||||
 | 
					import Quickshell.Bluetooth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FloatingWindow {
 | 
				
			||||||
 | 
						color: contentItem.palette.window
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ListView {
 | 
				
			||||||
 | 
							anchors.fill: parent
 | 
				
			||||||
 | 
							anchors.margins: 5
 | 
				
			||||||
 | 
							model: Bluetooth.adapters
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							delegate: WrapperRectangle {
 | 
				
			||||||
 | 
								width: parent.width
 | 
				
			||||||
 | 
								color: "transparent"
 | 
				
			||||||
 | 
								border.color: palette.button
 | 
				
			||||||
 | 
								border.width: 1
 | 
				
			||||||
 | 
								margin: 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ColumnLayout {
 | 
				
			||||||
 | 
									Label { text: `Adapter: ${modelData.name} (${modelData.adapterId})` }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									RowLayout {
 | 
				
			||||||
 | 
										Layout.fillWidth: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										CheckBox {
 | 
				
			||||||
 | 
											text: "Enable"
 | 
				
			||||||
 | 
											checked: modelData.enabled
 | 
				
			||||||
 | 
											onToggled: modelData.enabled = checked
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										Label {
 | 
				
			||||||
 | 
											color: modelData.state === BluetoothAdapterState.Blocked ? palette.errorText : palette.placeholderText
 | 
				
			||||||
 | 
											text: BluetoothAdapterState.toString(modelData.state)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										CheckBox {
 | 
				
			||||||
 | 
											text: "Discoverable"
 | 
				
			||||||
 | 
											checked: modelData.discoverable
 | 
				
			||||||
 | 
											onToggled: modelData.discoverable = checked
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										CheckBox {
 | 
				
			||||||
 | 
											text: "Discovering"
 | 
				
			||||||
 | 
											checked: modelData.discovering
 | 
				
			||||||
 | 
											onToggled: modelData.discovering = checked
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										CheckBox {
 | 
				
			||||||
 | 
											text: "Pairable"
 | 
				
			||||||
 | 
											checked: modelData.pairable
 | 
				
			||||||
 | 
											onToggled: modelData.pairable = checked
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									RowLayout {
 | 
				
			||||||
 | 
										Layout.fillWidth: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										Label { text: "Discoverable timeout:" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										SpinBox {
 | 
				
			||||||
 | 
											from: 0
 | 
				
			||||||
 | 
											to: 3600
 | 
				
			||||||
 | 
											value: modelData.discoverableTimeout
 | 
				
			||||||
 | 
											onValueModified: modelData.discoverableTimeout = value
 | 
				
			||||||
 | 
											textFromValue: time => time === 0 ? "∞" : time + "s"
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										Label { text: "Pairable timeout:" }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										SpinBox {
 | 
				
			||||||
 | 
											from: 0
 | 
				
			||||||
 | 
											to: 3600
 | 
				
			||||||
 | 
											value: modelData.pairableTimeout
 | 
				
			||||||
 | 
											onValueModified: modelData.pairableTimeout = value
 | 
				
			||||||
 | 
											textFromValue: time => time === 0 ? "∞" : time + "s"
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									Repeater {
 | 
				
			||||||
 | 
										model: modelData.devices
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										WrapperRectangle {
 | 
				
			||||||
 | 
											Layout.fillWidth: true
 | 
				
			||||||
 | 
											color: palette.button
 | 
				
			||||||
 | 
											border.color: palette.mid
 | 
				
			||||||
 | 
											border.width: 1
 | 
				
			||||||
 | 
											margin: 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											RowLayout {
 | 
				
			||||||
 | 
												ColumnLayout {
 | 
				
			||||||
 | 
													Layout.fillWidth: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
													RowLayout {
 | 
				
			||||||
 | 
														IconImage {
 | 
				
			||||||
 | 
															Layout.fillHeight: true
 | 
				
			||||||
 | 
															implicitWidth: height
 | 
				
			||||||
 | 
															source: Quickshell.iconPath(modelData.icon)
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
														TextField {
 | 
				
			||||||
 | 
															text: modelData.name
 | 
				
			||||||
 | 
															font.bold: true
 | 
				
			||||||
 | 
															background: null
 | 
				
			||||||
 | 
															readOnly: false
 | 
				
			||||||
 | 
															selectByMouse: true
 | 
				
			||||||
 | 
															onEditingFinished: modelData.name = text
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
														Label {
 | 
				
			||||||
 | 
															visible: modelData.name && modelData.name !== modelData.deviceName
 | 
				
			||||||
 | 
															text: `(${modelData.deviceName})`
 | 
				
			||||||
 | 
															color: palette.placeholderText
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
													RowLayout {
 | 
				
			||||||
 | 
														Label {
 | 
				
			||||||
 | 
															text: modelData.address
 | 
				
			||||||
 | 
															color: palette.placeholderText
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
														Label {
 | 
				
			||||||
 | 
															visible: modelData.batteryAvailable
 | 
				
			||||||
 | 
															text: `| Battery: ${Math.round(modelData.battery * 100)}%`
 | 
				
			||||||
 | 
															color: palette.placeholderText
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
													RowLayout {
 | 
				
			||||||
 | 
														Label {
 | 
				
			||||||
 | 
															text: BluetoothDeviceState.toString(modelData.state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
															color: modelData.connected ? palette.link : palette.placeholderText
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
														Label {
 | 
				
			||||||
 | 
															text: modelData.pairing ? "Pairing" : (modelData.paired ? "Paired" : "Not Paired")
 | 
				
			||||||
 | 
															color: modelData.paired || modelData.pairing ? palette.link : palette.placeholderText
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
														Label {
 | 
				
			||||||
 | 
															visible: modelData.bonded
 | 
				
			||||||
 | 
															text: "| Bonded"
 | 
				
			||||||
 | 
															color: palette.link
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
														CheckBox {
 | 
				
			||||||
 | 
															text: "Trusted"
 | 
				
			||||||
 | 
															checked: modelData.trusted
 | 
				
			||||||
 | 
															onToggled: modelData.trusted = checked
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
														CheckBox {
 | 
				
			||||||
 | 
															text: "Blocked"
 | 
				
			||||||
 | 
															checked: modelData.blocked
 | 
				
			||||||
 | 
															onToggled: modelData.blocked = checked
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
														CheckBox {
 | 
				
			||||||
 | 
															text: "Wake Allowed"
 | 
				
			||||||
 | 
															checked: modelData.wakeAllowed
 | 
				
			||||||
 | 
															onToggled: modelData.wakeAllowed = checked
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												ColumnLayout {
 | 
				
			||||||
 | 
													Layout.alignment: Qt.AlignRight
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
													Button {
 | 
				
			||||||
 | 
														Layout.alignment: Qt.AlignRight
 | 
				
			||||||
 | 
														text: modelData.connected ? "Disconnect" : "Connect"
 | 
				
			||||||
 | 
														onClicked: modelData.connected = !modelData.connected
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
													Button {
 | 
				
			||||||
 | 
														Layout.alignment: Qt.AlignRight
 | 
				
			||||||
 | 
														text: modelData.pairing ? "Cancel" : (modelData.paired ? "Forget" : "Pair")
 | 
				
			||||||
 | 
														onClicked: {
 | 
				
			||||||
 | 
															if (modelData.pairing) {
 | 
				
			||||||
 | 
																modelData.cancelPair();
 | 
				
			||||||
 | 
															} else if (modelData.paired) {
 | 
				
			||||||
 | 
																modelData.forget();
 | 
				
			||||||
 | 
															} else {
 | 
				
			||||||
 | 
																modelData.pair();
 | 
				
			||||||
 | 
															}
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -2,13 +2,24 @@ set_source_files_properties(org.freedesktop.DBus.Properties.xml PROPERTIES
 | 
				
			||||||
	CLASSNAME DBusPropertiesInterface
 | 
						CLASSNAME DBusPropertiesInterface
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set_source_files_properties(org.freedesktop.DBus.ObjectManager.xml PROPERTIES
 | 
				
			||||||
 | 
						CLASSNAME DBusObjectManagerInterface
 | 
				
			||||||
 | 
						INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/dbus_objectmanager_types.hpp
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
qt_add_dbus_interface(DBUS_INTERFACES
 | 
					qt_add_dbus_interface(DBUS_INTERFACES
 | 
				
			||||||
	org.freedesktop.DBus.Properties.xml
 | 
						org.freedesktop.DBus.Properties.xml
 | 
				
			||||||
	dbus_properties
 | 
						dbus_properties
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qt_add_dbus_interface(DBUS_INTERFACES
 | 
				
			||||||
 | 
						org.freedesktop.DBus.ObjectManager.xml
 | 
				
			||||||
 | 
						dbus_objectmanager
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
qt_add_library(quickshell-dbus STATIC
 | 
					qt_add_library(quickshell-dbus STATIC
 | 
				
			||||||
	properties.cpp
 | 
						properties.cpp
 | 
				
			||||||
 | 
						objectmanager.cpp
 | 
				
			||||||
	bus.cpp
 | 
						bus.cpp
 | 
				
			||||||
	${DBUS_INTERFACES}
 | 
						${DBUS_INTERFACES}
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/dbus/dbus_objectmanager_types.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/dbus/dbus_objectmanager_types.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qdbusextratypes.h>
 | 
				
			||||||
 | 
					#include <qhash.h>
 | 
				
			||||||
 | 
					#include <qmap.h>
 | 
				
			||||||
 | 
					#include <qstring.h>
 | 
				
			||||||
 | 
					#include <qvariant.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using DBusObjectManagerInterfaces = QHash<QString, QVariantMap>;
 | 
				
			||||||
 | 
					using DBusObjectManagerObjects = QHash<QDBusObjectPath, DBusObjectManagerInterfaces>;
 | 
				
			||||||
							
								
								
									
										86
									
								
								src/dbus/objectmanager.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/dbus/objectmanager.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,86 @@
 | 
				
			||||||
 | 
					#include "objectmanager.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
 | 
					#include <qdbusconnection.h>
 | 
				
			||||||
 | 
					#include <qdbusmetatype.h>
 | 
				
			||||||
 | 
					#include <qdbuspendingcall.h>
 | 
				
			||||||
 | 
					#include <qdbuspendingreply.h>
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "dbus_objectmanager.h"
 | 
				
			||||||
 | 
					#include "dbus_objectmanager_types.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace {
 | 
				
			||||||
 | 
					Q_LOGGING_CATEGORY(logDbusObjectManager, "quickshell.dbus.objectmanager", QtWarningMsg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::dbus {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DBusObjectManager::DBusObjectManager(QObject* parent): QObject(parent) {
 | 
				
			||||||
 | 
						qDBusRegisterMetaType<DBusObjectManagerInterfaces>();
 | 
				
			||||||
 | 
						qDBusRegisterMetaType<DBusObjectManagerObjects>();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool DBusObjectManager::setInterface(
 | 
				
			||||||
 | 
					    const QString& service,
 | 
				
			||||||
 | 
					    const QString& path,
 | 
				
			||||||
 | 
					    const QDBusConnection& connection
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						delete this->mInterface;
 | 
				
			||||||
 | 
						this->mInterface = new DBusObjectManagerInterface(service, path, connection, this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!this->mInterface->isValid()) {
 | 
				
			||||||
 | 
							qCWarning(logDbusObjectManager) << "Failed to create DBusObjectManagerInterface for" << service
 | 
				
			||||||
 | 
							                                << path << ":" << this->mInterface->lastError();
 | 
				
			||||||
 | 
							delete this->mInterface;
 | 
				
			||||||
 | 
							this->mInterface = nullptr;
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::connect(
 | 
				
			||||||
 | 
						    this->mInterface,
 | 
				
			||||||
 | 
						    &DBusObjectManagerInterface::InterfacesAdded,
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    &DBusObjectManager::interfacesAdded
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::connect(
 | 
				
			||||||
 | 
						    this->mInterface,
 | 
				
			||||||
 | 
						    &DBusObjectManagerInterface::InterfacesRemoved,
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    &DBusObjectManager::interfacesRemoved
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->fetchInitialObjects();
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DBusObjectManager::fetchInitialObjects() {
 | 
				
			||||||
 | 
						if (!this->mInterface) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto reply = this->mInterface->GetManagedObjects();
 | 
				
			||||||
 | 
						auto* watcher = new QDBusPendingCallWatcher(reply, this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::connect(
 | 
				
			||||||
 | 
						    watcher,
 | 
				
			||||||
 | 
						    &QDBusPendingCallWatcher::finished,
 | 
				
			||||||
 | 
						    this,
 | 
				
			||||||
 | 
						    [this](QDBusPendingCallWatcher* watcher) {
 | 
				
			||||||
 | 
							    const QDBusPendingReply<DBusObjectManagerObjects> reply = *watcher;
 | 
				
			||||||
 | 
							    watcher->deleteLater();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							    if (reply.isError()) {
 | 
				
			||||||
 | 
								    qCWarning(logDbusObjectManager) << "Failed to get managed objects:" << reply.error();
 | 
				
			||||||
 | 
								    return;
 | 
				
			||||||
 | 
							    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							    for (const auto& [path, interfaces]: reply.value().asKeyValueRange()) {
 | 
				
			||||||
 | 
								    emit this->interfacesAdded(path, interfaces);
 | 
				
			||||||
 | 
							    }
 | 
				
			||||||
 | 
						    }
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::dbus
 | 
				
			||||||
							
								
								
									
										37
									
								
								src/dbus/objectmanager.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/dbus/objectmanager.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,37 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qdbusconnection.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qstring.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "dbus_objectmanager_types.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DBusObjectManagerInterface;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::dbus {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DBusObjectManager: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit DBusObjectManager(QObject* parent = nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool setInterface(
 | 
				
			||||||
 | 
						    const QString& service,
 | 
				
			||||||
 | 
						    const QString& path,
 | 
				
			||||||
 | 
						    const QDBusConnection& connection = QDBusConnection::sessionBus()
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						void
 | 
				
			||||||
 | 
						interfacesAdded(const QDBusObjectPath& objectPath, const DBusObjectManagerInterfaces& interfaces);
 | 
				
			||||||
 | 
						void interfacesRemoved(const QDBusObjectPath& objectPath, const QStringList& interfaces);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						void fetchInitialObjects();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DBusObjectManagerInterface* mInterface = nullptr;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::dbus
 | 
				
			||||||
							
								
								
									
										18
									
								
								src/dbus/org.freedesktop.DBus.ObjectManager.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/dbus/org.freedesktop.DBus.ObjectManager.xml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					<node>
 | 
				
			||||||
 | 
						<interface name="org.freedesktop.DBus.ObjectManager">
 | 
				
			||||||
 | 
							<method name="GetManagedObjects">
 | 
				
			||||||
 | 
								<arg name="objects" direction="out" type="a{oa{sa{sv}}}"/>
 | 
				
			||||||
 | 
								<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="DBusObjectManagerObjects"/>
 | 
				
			||||||
 | 
							</method>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<signal name="InterfacesAdded">
 | 
				
			||||||
 | 
								<arg name="object" type="o"/>
 | 
				
			||||||
 | 
								<arg name="interfaces" type="a{sa{sv}}"/>
 | 
				
			||||||
 | 
								<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="DBusObjectManagerInterfaces"/>
 | 
				
			||||||
 | 
							</signal>
 | 
				
			||||||
 | 
							<signal name="InterfacesRemoved">
 | 
				
			||||||
 | 
								<arg name="object" type="o"/>
 | 
				
			||||||
 | 
								<arg name="interfaces" type="as"/>
 | 
				
			||||||
 | 
							</signal>
 | 
				
			||||||
 | 
						</interface>
 | 
				
			||||||
 | 
					</node>
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,7 @@
 | 
				
			||||||
#include <qmetatype.h>
 | 
					#include <qmetatype.h>
 | 
				
			||||||
#include <qobject.h>
 | 
					#include <qobject.h>
 | 
				
			||||||
#include <qtmetamacros.h>
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtversionchecks.h>
 | 
				
			||||||
#include <qvariant.h>
 | 
					#include <qvariant.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "dbus_properties.h"
 | 
					#include "dbus_properties.h"
 | 
				
			||||||
| 
						 | 
					@ -326,3 +327,10 @@ void DBusPropertyGroup::onPropertiesChanged(
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace qs::dbus
 | 
					} // namespace qs::dbus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
 | 
				
			||||||
 | 
					QDebug operator<<(QDebug debug, const QDBusObjectPath& path) {
 | 
				
			||||||
 | 
						debug.nospace() << "QDBusObjectPath(" << path.path() << ")";
 | 
				
			||||||
 | 
						return debug;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,7 @@
 | 
				
			||||||
#include <qstringview.h>
 | 
					#include <qstringview.h>
 | 
				
			||||||
#include <qtclasshelpermacros.h>
 | 
					#include <qtclasshelpermacros.h>
 | 
				
			||||||
#include <qtmetamacros.h>
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtversionchecks.h>
 | 
				
			||||||
#include <qvariant.h>
 | 
					#include <qvariant.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../core/util.hpp"
 | 
					#include "../core/util.hpp"
 | 
				
			||||||
| 
						 | 
					@ -234,6 +235,7 @@ public:
 | 
				
			||||||
	void attachProperty(DBusPropertyCore* property);
 | 
						void attachProperty(DBusPropertyCore* property);
 | 
				
			||||||
	void updateAllDirect();
 | 
						void updateAllDirect();
 | 
				
			||||||
	void updateAllViaGetAll();
 | 
						void updateAllViaGetAll();
 | 
				
			||||||
 | 
						void updatePropertySet(const QVariantMap& properties, bool complainMissing = true);
 | 
				
			||||||
	[[nodiscard]] QString toString() const;
 | 
						[[nodiscard]] QString toString() const;
 | 
				
			||||||
	[[nodiscard]] bool isConnected() const { return this->interface; }
 | 
						[[nodiscard]] bool isConnected() const { return this->interface; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -252,7 +254,6 @@ private slots:
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
	void updatePropertySet(const QVariantMap& properties, bool complainMissing);
 | 
					 | 
				
			||||||
	void tryUpdateProperty(DBusPropertyCore* property, const QVariant& variant) const;
 | 
						void tryUpdateProperty(DBusPropertyCore* property, const QVariant& variant) const;
 | 
				
			||||||
	[[nodiscard]] QString propertyString(const DBusPropertyCore* property) const;
 | 
						[[nodiscard]] QString propertyString(const DBusPropertyCore* property) const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -265,6 +266,10 @@ private:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace qs::dbus
 | 
					} // namespace qs::dbus
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
 | 
				
			||||||
 | 
					QDebug operator<<(QDebug debug, const QDBusObjectPath& path);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NOLINTBEGIN
 | 
					// NOLINTBEGIN
 | 
				
			||||||
#define QS_DBUS_BINDABLE_PROPERTY_GROUP(Class, name) qs::dbus::DBusPropertyGroup name {this};
 | 
					#define QS_DBUS_BINDABLE_PROPERTY_GROUP(Class, name) qs::dbus::DBusPropertyGroup name {this};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,6 @@
 | 
				
			||||||
#include <qlogging.h>
 | 
					#include <qlogging.h>
 | 
				
			||||||
#include <qmetatype.h>
 | 
					#include <qmetatype.h>
 | 
				
			||||||
#include <qsysinfo.h>
 | 
					#include <qsysinfo.h>
 | 
				
			||||||
#include <qtversionchecks.h>
 | 
					 | 
				
			||||||
#include <qtypes.h>
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool DBusSniIconPixmap::operator==(const DBusSniIconPixmap& other) const {
 | 
					bool DBusSniIconPixmap::operator==(const DBusSniIconPixmap& other) const {
 | 
				
			||||||
| 
						 | 
					@ -122,10 +121,3 @@ QDebug operator<<(QDebug debug, const DBusSniTooltip& tooltip) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return debug;
 | 
						return debug;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
 | 
					 | 
				
			||||||
QDebug operator<<(QDebug debug, const QDBusObjectPath& path) {
 | 
					 | 
				
			||||||
	debug.nospace() << "QDBusObjectPath(" << path.path() << ")";
 | 
					 | 
				
			||||||
	return debug;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,7 +35,3 @@ const QDBusArgument& operator<<(QDBusArgument& argument, const DBusSniTooltip& t
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QDebug operator<<(QDebug debug, const DBusSniIconPixmap& pixmap);
 | 
					QDebug operator<<(QDebug debug, const DBusSniIconPixmap& pixmap);
 | 
				
			||||||
QDebug operator<<(QDebug debug, const DBusSniTooltip& tooltip);
 | 
					QDebug operator<<(QDebug debug, const DBusSniTooltip& tooltip);
 | 
				
			||||||
 | 
					 | 
				
			||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
 | 
					 | 
				
			||||||
QDebug operator<<(QDebug debug, const QDBusObjectPath& path);
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue