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_UPOWER "UPower" ON)
 | 
			
		||||
boption(SERVICE_NOTIFICATIONS "Notifications" ON)
 | 
			
		||||
boption(BLUETOOTH "Bluetooth" ON)
 | 
			
		||||
 | 
			
		||||
include(cmake/install-qml-module.cmake)
 | 
			
		||||
include(cmake/util.cmake)
 | 
			
		||||
| 
						 | 
				
			
			@ -116,7 +117,7 @@ if (WAYLAND)
 | 
			
		|||
	list(APPEND QT_FPDEPS WaylandClient)
 | 
			
		||||
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)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,3 +29,7 @@ if (X11)
 | 
			
		|||
endif()
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
	org.freedesktop.DBus.Properties.xml
 | 
			
		||||
	dbus_properties
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
qt_add_dbus_interface(DBUS_INTERFACES
 | 
			
		||||
	org.freedesktop.DBus.ObjectManager.xml
 | 
			
		||||
	dbus_objectmanager
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
qt_add_library(quickshell-dbus STATIC
 | 
			
		||||
	properties.cpp
 | 
			
		||||
	objectmanager.cpp
 | 
			
		||||
	bus.cpp
 | 
			
		||||
	${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 <qobject.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qtversionchecks.h>
 | 
			
		||||
#include <qvariant.h>
 | 
			
		||||
 | 
			
		||||
#include "dbus_properties.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -326,3 +327,10 @@ void DBusPropertyGroup::onPropertiesChanged(
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
} // 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 <qtclasshelpermacros.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qtversionchecks.h>
 | 
			
		||||
#include <qvariant.h>
 | 
			
		||||
 | 
			
		||||
#include "../core/util.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -234,6 +235,7 @@ public:
 | 
			
		|||
	void attachProperty(DBusPropertyCore* property);
 | 
			
		||||
	void updateAllDirect();
 | 
			
		||||
	void updateAllViaGetAll();
 | 
			
		||||
	void updatePropertySet(const QVariantMap& properties, bool complainMissing = true);
 | 
			
		||||
	[[nodiscard]] QString toString() const;
 | 
			
		||||
	[[nodiscard]] bool isConnected() const { return this->interface; }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -252,7 +254,6 @@ private slots:
 | 
			
		|||
	);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void updatePropertySet(const QVariantMap& properties, bool complainMissing);
 | 
			
		||||
	void tryUpdateProperty(DBusPropertyCore* property, const QVariant& variant) const;
 | 
			
		||||
	[[nodiscard]] QString propertyString(const DBusPropertyCore* property) const;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -265,6 +266,10 @@ private:
 | 
			
		|||
 | 
			
		||||
} // namespace qs::dbus
 | 
			
		||||
 | 
			
		||||
#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
 | 
			
		||||
QDebug operator<<(QDebug debug, const QDBusObjectPath& path);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// NOLINTBEGIN
 | 
			
		||||
#define QS_DBUS_BINDABLE_PROPERTY_GROUP(Class, name) qs::dbus::DBusPropertyGroup name {this};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,6 @@
 | 
			
		|||
#include <qlogging.h>
 | 
			
		||||
#include <qmetatype.h>
 | 
			
		||||
#include <qsysinfo.h>
 | 
			
		||||
#include <qtversionchecks.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
bool DBusSniIconPixmap::operator==(const DBusSniIconPixmap& other) const {
 | 
			
		||||
| 
						 | 
				
			
			@ -122,10 +121,3 @@ QDebug operator<<(QDebug debug, const DBusSniTooltip& tooltip) {
 | 
			
		|||
 | 
			
		||||
	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 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