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