From e8f33f3730aaa614bee7990ac2cc3c053e3a61a6 Mon Sep 17 00:00:00 2001 From: bbedward Date: Tue, 22 Jul 2025 17:05:54 -0400 Subject: [PATCH 1/2] bluetooth: register an agent adapter --- src/bluetooth/CMakeLists.txt | 18 ++++++++++ src/bluetooth/adapter.cpp | 1 + src/bluetooth/agent.cpp | 28 +++++++++++++++ src/bluetooth/agent.hpp | 27 +++++++++++++++ src/bluetooth/bluez.cpp | 44 ++++++++++++++++++++++++ src/bluetooth/bluez.hpp | 3 ++ src/bluetooth/device.cpp | 5 ++- src/bluetooth/device.hpp | 9 +++++ src/bluetooth/org.bluez.Agent.xml | 15 ++++++++ src/bluetooth/org.bluez.AgentManager.xml | 14 ++++++++ 10 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 src/bluetooth/agent.cpp create mode 100644 src/bluetooth/agent.hpp create mode 100644 src/bluetooth/org.bluez.Agent.xml create mode 100644 src/bluetooth/org.bluez.AgentManager.xml diff --git a/src/bluetooth/CMakeLists.txt b/src/bluetooth/CMakeLists.txt index 806ff04d..8ff29a65 100644 --- a/src/bluetooth/CMakeLists.txt +++ b/src/bluetooth/CMakeLists.txt @@ -6,6 +6,10 @@ set_source_files_properties(org.bluez.Device.xml PROPERTIES CLASSNAME DBusBluezDeviceInterface ) +set_source_files_properties(org.bluez.AgentManager.xml PROPERTIES + CLASSNAME DBusBluezAgentManagerInterface +) + qt_add_dbus_interface(DBUS_INTERFACES org.bluez.Adapter.xml dbus_adapter @@ -16,11 +20,25 @@ qt_add_dbus_interface(DBUS_INTERFACES dbus_device ) +qt_add_dbus_interface(DBUS_INTERFACES + org.bluez.AgentManager.xml + dbus_agent_manager +) + +qt_add_dbus_adaptor(DBUS_ADAPTORS + org.bluez.Agent.xml + agent.hpp + qs::bluetooth::BluetoothAgent + dbus_agent_adaptor +) + qt_add_library(quickshell-bluetooth STATIC adapter.cpp + agent.cpp bluez.cpp device.cpp ${DBUS_INTERFACES} + ${DBUS_ADAPTORS} ) qt_add_qml_module(quickshell-bluetooth diff --git a/src/bluetooth/adapter.cpp b/src/bluetooth/adapter.cpp index 92ab8379..2c1a181e 100644 --- a/src/bluetooth/adapter.cpp +++ b/src/bluetooth/adapter.cpp @@ -44,6 +44,7 @@ BluetoothAdapter::BluetoothAdapter(const QString& path, QObject* parent): QObjec } this->properties.setInterface(this->mInterface); + this->properties.updateAllViaGetAll(); } QString BluetoothAdapter::adapterId() const { diff --git a/src/bluetooth/agent.cpp b/src/bluetooth/agent.cpp new file mode 100644 index 00000000..69b69459 --- /dev/null +++ b/src/bluetooth/agent.cpp @@ -0,0 +1,28 @@ +#include "agent.hpp" + +#include +#include +#include +#include +#include + +namespace qs::bluetooth { + +BluetoothAgent::BluetoothAgent(QObject* parent) + : QObject(parent) + , mObjectPath(QStringLiteral("/org/quickshell/bluetooth/agent")) {} + +QString BluetoothAgent::objectPath() const { return this->mObjectPath; } + +void BluetoothAgent::RequestAuthorization(const QDBusObjectPath& device) { Q_UNUSED(device); } + +void BluetoothAgent::RequestConfirmation(const QDBusObjectPath& device, quint32 passkey) { + Q_UNUSED(device); + Q_UNUSED(passkey); +} + +void BluetoothAgent::Cancel() {} + +void BluetoothAgent::Release() {} + +} // namespace qs::bluetooth \ No newline at end of file diff --git a/src/bluetooth/agent.hpp b/src/bluetooth/agent.hpp new file mode 100644 index 00000000..c732385f --- /dev/null +++ b/src/bluetooth/agent.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +namespace qs::bluetooth { + +class BluetoothAgent: public QObject { + Q_OBJECT; + +public: + explicit BluetoothAgent(QObject* parent = nullptr); + + [[nodiscard]] QString objectPath() const; + +public Q_SLOTS: + void RequestAuthorization(const QDBusObjectPath& device); + void RequestConfirmation(const QDBusObjectPath& device, quint32 passkey); + void Cancel(); + void Release(); + +private: + QString mObjectPath; +}; + +} // namespace qs::bluetooth \ No newline at end of file diff --git a/src/bluetooth/bluez.cpp b/src/bluetooth/bluez.cpp index f2c4300d..72f4628d 100644 --- a/src/bluetooth/bluez.cpp +++ b/src/bluetooth/bluez.cpp @@ -12,6 +12,9 @@ #include "../dbus/dbus_objectmanager_types.hpp" #include "../dbus/objectmanager.hpp" #include "adapter.hpp" +#include "agent.hpp" +#include "dbus_agent_adaptor.h" +#include "dbus_agent_manager.h" #include "device.hpp" namespace qs::bluetooth { @@ -62,6 +65,8 @@ void Bluez::init() { qCDebug(logBluetooth) << "BlueZ is not running. Bluetooth integration will not work."; return; } + + this->registerAgent(); } void Bluez::onInterfacesAdded( @@ -165,4 +170,43 @@ BluezQml::BluezQml() { ); } +void Bluez::registerAgent() { + + this->agent = new BluetoothAgent(this); + new Agent1Adaptor(this->agent); + + auto bus = QDBusConnection::systemBus(); + if (!bus.registerObject(this->agent->objectPath(), this->agent)) { + qCWarning(logBluetooth) << "Failed to register Bluetooth agent on D-Bus"; + delete this->agent; + this->agent = nullptr; + return; + } + + auto* agentManager = new DBusBluezAgentManagerInterface("org.bluez", "/org/bluez", bus, this); + + if (!agentManager->isValid()) { + qCWarning(logBluetooth) << "Could not create AgentManager interface"; + bus.unregisterObject(this->agent->objectPath()); + delete this->agent; + this->agent = nullptr; + return; + } + + auto reply = agentManager->RegisterAgent( + QDBusObjectPath(this->agent->objectPath()), + QStringLiteral("NoInputNoOutput") + ); + + if (reply.isError()) { + qCWarning(logBluetooth) << "Failed to register agent:" << reply.error().message(); + bus.unregisterObject(this->agent->objectPath()); + delete this->agent; + this->agent = nullptr; + return; + } + + agentManager->RequestDefaultAgent(QDBusObjectPath(this->agent->objectPath())); +} + } // namespace qs::bluetooth diff --git a/src/bluetooth/bluez.hpp b/src/bluetooth/bluez.hpp index 9d7c93ca..05ccd918 100644 --- a/src/bluetooth/bluez.hpp +++ b/src/bluetooth/bluez.hpp @@ -15,6 +15,7 @@ namespace qs::bluetooth { class BluetoothAdapter; +class BluetoothAgent; class BluetoothDevice; class Bluez: public QObject { @@ -42,8 +43,10 @@ private slots: private: explicit Bluez(); void init(); + void registerAgent(); qs::dbus::DBusObjectManager* objectManager = nullptr; + BluetoothAgent* agent = nullptr; QHash mAdapterMap; QHash mDeviceMap; ObjectModel mAdapters {this}; diff --git a/src/bluetooth/device.cpp b/src/bluetooth/device.cpp index 7265b241..d7e2c38f 100644 --- a/src/bluetooth/device.cpp +++ b/src/bluetooth/device.cpp @@ -46,6 +46,7 @@ BluetoothDevice::BluetoothDevice(const QString& path, QObject* parent): QObject( } this->properties.setInterface(this->mInterface); + this->properties.updateAllViaGetAll(); } BluetoothAdapter* BluetoothDevice::adapter() const { @@ -117,7 +118,7 @@ void BluetoothDevice::connect() { this->bState = this->bConnected ? BluetoothDeviceState::Connected : BluetoothDeviceState::Disconnected; } else { - qCDebug(logDevice) << "Successfully connected to to device" << this; + qCDebug(logDevice) << "Successfully connected to device" << this; } delete watcher; @@ -293,6 +294,8 @@ void BluetoothDevice::onConnectedChanged() { emit this->connectedChanged(); } +void BluetoothDevice::onServicesResolvedChanged() { emit this->servicesResolvedChanged(); } + } // namespace qs::bluetooth namespace qs::dbus { diff --git a/src/bluetooth/device.hpp b/src/bluetooth/device.hpp index 23f230f5..12f5f5ea 100644 --- a/src/bluetooth/device.hpp +++ b/src/bluetooth/device.hpp @@ -95,6 +95,8 @@ class BluetoothDevice: public QObject { 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 device services (GATT/SDP) have been resolved and device is fully ready. + Q_PROPERTY(bool servicesResolved READ servicesResolved NOTIFY servicesResolvedChanged); /// 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. @@ -145,6 +147,8 @@ class BluetoothDevice: public QObject { [[nodiscard]] bool wakeAllowed() const { return this->bWakeAllowed; } void setWakeAllowed(bool wakeAllowed); + [[nodiscard]] bool servicesResolved() const { return this->bServicesResolved; } + [[nodiscard]] bool pairing() const { return this->bPairing; } [[nodiscard]] QBindable bindableAddress() { return &this->bAddress; } @@ -156,6 +160,7 @@ class BluetoothDevice: public QObject { [[nodiscard]] QBindable bindableTrusted() { return &this->bTrusted; } [[nodiscard]] QBindable bindableBlocked() { return &this->bBlocked; } [[nodiscard]] QBindable bindableWakeAllowed() { return &this->bWakeAllowed; } + [[nodiscard]] QBindable bindableServicesResolved() { return &this->bServicesResolved; } [[nodiscard]] QBindable bindableIcon() { return &this->bIcon; } [[nodiscard]] QBindable bindableBattery() { return &this->bBattery; } [[nodiscard]] QBindable bindableState() { return &this->bState; } @@ -175,6 +180,7 @@ class BluetoothDevice: public QObject { void trustedChanged(); void blockedChanged(); void wakeAllowedChanged(); + void servicesResolvedChanged(); void iconChanged(); void batteryAvailableChanged(); void batteryChanged(); @@ -182,6 +188,7 @@ class BluetoothDevice: public QObject { private: void onConnectedChanged(); + void onServicesResolvedChanged(); DBusBluezDeviceInterface* mInterface = nullptr; QDBusInterface* mBatteryInterface = nullptr; @@ -196,6 +203,7 @@ class BluetoothDevice: public QObject { 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, bool, bServicesResolved, &BluetoothDevice::onServicesResolvedChanged); 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); @@ -212,6 +220,7 @@ class BluetoothDevice: public QObject { 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, pServicesResolved, bServicesResolved, properties, "ServicesResolved"); QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pIcon, bIcon, properties, "Icon"); QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pAdapterPath, bAdapterPath, properties, "Adapter"); diff --git a/src/bluetooth/org.bluez.Agent.xml b/src/bluetooth/org.bluez.Agent.xml new file mode 100644 index 00000000..b5462ab0 --- /dev/null +++ b/src/bluetooth/org.bluez.Agent.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/bluetooth/org.bluez.AgentManager.xml b/src/bluetooth/org.bluez.AgentManager.xml new file mode 100644 index 00000000..dc44a369 --- /dev/null +++ b/src/bluetooth/org.bluez.AgentManager.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file From 43f330bc66f0477f9de14ae72b37f559579e886f Mon Sep 17 00:00:00 2001 From: bbedward Date: Tue, 22 Jul 2025 18:46:24 -0400 Subject: [PATCH 2/2] bluetooth: implement pairing agent responses --- src/bluetooth/agent.cpp | 187 +++++++++++++++++++++++++++++- src/bluetooth/agent.hpp | 52 ++++++++- src/bluetooth/bluez.cpp | 44 ++++++- src/bluetooth/bluez.hpp | 14 +++ src/bluetooth/device.cpp | 4 +- src/bluetooth/device.hpp | 9 -- src/bluetooth/org.bluez.Agent.xml | 19 +++ 7 files changed, 309 insertions(+), 20 deletions(-) diff --git a/src/bluetooth/agent.cpp b/src/bluetooth/agent.cpp index 69b69459..b24c7e51 100644 --- a/src/bluetooth/agent.cpp +++ b/src/bluetooth/agent.cpp @@ -1,28 +1,205 @@ #include "agent.hpp" +#include +#include +#include #include #include #include #include #include +#include "../core/logcat.hpp" + namespace qs::bluetooth { +namespace { +QS_LOGGING_CATEGORY(logAgent, "quickshell.bluetooth.agent", QtWarningMsg); +std::atomic gTokenCounter {1}; +} // namespace + BluetoothAgent::BluetoothAgent(QObject* parent) : QObject(parent) , mObjectPath(QStringLiteral("/org/quickshell/bluetooth/agent")) {} QString BluetoothAgent::objectPath() const { return this->mObjectPath; } -void BluetoothAgent::RequestAuthorization(const QDBusObjectPath& device) { Q_UNUSED(device); } +QString BluetoothAgent::getDeviceAddress(const QDBusObjectPath& device) { + auto path = device.path(); + auto lastSlash = path.lastIndexOf('/'); + + if (lastSlash >= 0) { + auto devicePart = path.mid(lastSlash + 1); + + if (devicePart.startsWith("dev_")) { + auto macPart = devicePart.mid(4); + return macPart.replace('_', ':'); + } + } + + return path; +} + +void BluetoothAgent::enqueuePendingRequest( + const QDBusObjectPath& device, + BluetoothPairingRequestType::Enum type +) { + auto msg = message(); + PendingRequest request; + request.message = msg; + request.type = type; + request.devicePath = device.path(); + request.serial = gTokenCounter.fetch_add(1); + + this->mPendingRequests.insert(request.serial, request); + this->mLastSerial = request.serial; + setDelayedReply(true); +} + +void BluetoothAgent::RequestAuthorization(const QDBusObjectPath& device) { + qCDebug(logAgent) << "Authorization request for device" << device.path(); + this->enqueuePendingRequest(device, BluetoothPairingRequestType::Authorization); + emit this->pairingRequested( + getDeviceAddress(device), + BluetoothPairingRequestType::Authorization, + 0, + this->mLastSerial + ); +} void BluetoothAgent::RequestConfirmation(const QDBusObjectPath& device, quint32 passkey) { - Q_UNUSED(device); - Q_UNUSED(passkey); + qCDebug(logAgent) << "Confirmation request for device" << device.path() << "passkey" << passkey; + this->enqueuePendingRequest(device, BluetoothPairingRequestType::Confirmation); + emit this->pairingRequested( + getDeviceAddress(device), + BluetoothPairingRequestType::Confirmation, + passkey, + this->mLastSerial + ); } -void BluetoothAgent::Cancel() {} +void BluetoothAgent::AuthorizeService(const QDBusObjectPath& device, const QString& uuid) { + qCDebug(logAgent) << "Service authorization request for device" << device.path() << "uuid" + << uuid; + this->enqueuePendingRequest(device, BluetoothPairingRequestType::ServiceAuthorization); + emit this->pairingRequested( + getDeviceAddress(device), + BluetoothPairingRequestType::ServiceAuthorization, + 0, + this->mLastSerial + ); +} + +void BluetoothAgent::RequestPinCode(const QDBusObjectPath& device) { + qCDebug(logAgent) << "PIN code request for device" << device.path(); + this->enqueuePendingRequest(device, BluetoothPairingRequestType::PinCode); + emit this->pairingRequested( + getDeviceAddress(device), + BluetoothPairingRequestType::PinCode, + 0, + this->mLastSerial + ); +} + +void BluetoothAgent::RequestPasskey(const QDBusObjectPath& device) { + qCDebug(logAgent) << "Passkey request for device" << device.path(); + this->enqueuePendingRequest(device, BluetoothPairingRequestType::Passkey); + emit this->pairingRequested( + getDeviceAddress(device), + BluetoothPairingRequestType::Passkey, + 0, + this->mLastSerial + ); +} + +void BluetoothAgent::DisplayPinCode(const QDBusObjectPath& device, const QString& pincode) { + qCDebug(logAgent) << "Display PIN code for device" << device.path() << "pincode" << pincode; + emit this->pairingRequested(getDeviceAddress(device), BluetoothPairingRequestType::PinCode, 0, 0); +} + +void BluetoothAgent::DisplayPasskey( + const QDBusObjectPath& device, + quint32 passkey, + quint16 entered +) { + qCDebug(logAgent) << "Display passkey for device" << device.path() << "passkey" << passkey + << "entered" << entered; + emit this->pairingRequested( + getDeviceAddress(device), + BluetoothPairingRequestType::Passkey, + passkey, + 0 + ); +} + +void BluetoothAgent::Cancel() { + qCDebug(logAgent) << "Agent operation cancelled"; + + if (this->mLastSerial != 0 && this->mPendingRequests.contains(this->mLastSerial)) { + qCDebug(logAgent) << "Cancelling most recent request serial" << this->mLastSerial; + this->mPendingRequests.remove(this->mLastSerial); + } +} -void BluetoothAgent::Release() {} +void BluetoothAgent::Release() { + qCDebug(logAgent) << "Agent released by BlueZ"; + + for (auto& request: this->mPendingRequests) { + auto reply = request.message.createErrorReply(QDBusError::NoReply, "Agent released"); + QDBusConnection::systemBus().send(reply); + } + + this->mPendingRequests.clear(); + this->mLastSerial = 0; + + emit this->agentReleased(); +} + +void BluetoothAgent::respondToRequest(quint32 token, bool accept) { + auto it = this->mPendingRequests.find(token); + if (it == this->mPendingRequests.end()) return; + + auto request = it.value(); + this->mPendingRequests.erase(it); + + qCDebug(logAgent) << (accept ? "Accepting" : "Rejecting") << "request token" << token; + + auto reply = accept ? request.message.createReply() + : request.message.createErrorReply(QDBusError::AccessDenied, "User rejected"); + + QDBusConnection::systemBus().send(reply); +} + +void BluetoothAgent::respondWithPinCode(quint32 token, const QString& pinCode) { + auto it = this->mPendingRequests.find(token); + if (it == this->mPendingRequests.end() || it->type != BluetoothPairingRequestType::PinCode) + return; + + auto request = it.value(); + this->mPendingRequests.erase(it); + + qCDebug(logAgent) << "Responding with PIN code:" << pinCode << "for token" << token; + + auto reply = request.message.createReply(); + reply << pinCode; + + QDBusConnection::systemBus().send(reply); +} + +void BluetoothAgent::respondWithPasskey(quint32 token, quint32 passkey) { + auto it = this->mPendingRequests.find(token); + if (it == this->mPendingRequests.end() || it->type != BluetoothPairingRequestType::Passkey) + return; + + auto request = it.value(); + this->mPendingRequests.erase(it); + + qCDebug(logAgent) << "Responding with passkey:" << passkey << "for token" << token; + + auto reply = request.message.createReply(); + reply << passkey; + + QDBusConnection::systemBus().send(reply); +} } // namespace qs::bluetooth \ No newline at end of file diff --git a/src/bluetooth/agent.hpp b/src/bluetooth/agent.hpp index c732385f..6e755c33 100644 --- a/src/bluetooth/agent.hpp +++ b/src/bluetooth/agent.hpp @@ -1,27 +1,75 @@ #pragma once +#include #include +#include +#include #include +#include #include namespace qs::bluetooth { -class BluetoothAgent: public QObject { +class BluetoothPairingRequestType: public QObject { Q_OBJECT; + QML_ELEMENT; + QML_UNCREATABLE("Enum type"); + +public: + enum Enum { Authorization, Confirmation, PinCode, Passkey, ServiceAuthorization }; + Q_ENUM(Enum); +}; + +class BluetoothAgent + : public QObject + , protected QDBusContext { + Q_OBJECT; + QML_ELEMENT; public: explicit BluetoothAgent(QObject* parent = nullptr); [[nodiscard]] QString objectPath() const; -public Q_SLOTS: + Q_INVOKABLE void respondToRequest(quint32 token, bool accept); + Q_INVOKABLE void respondWithPinCode(quint32 token, const QString& pinCode); + Q_INVOKABLE void respondWithPasskey(quint32 token, quint32 passkey); + + // NOLINTBEGIN void RequestAuthorization(const QDBusObjectPath& device); void RequestConfirmation(const QDBusObjectPath& device, quint32 passkey); + void AuthorizeService(const QDBusObjectPath& device, const QString& uuid); + void RequestPinCode(const QDBusObjectPath& device); + void RequestPasskey(const QDBusObjectPath& device); + void DisplayPinCode(const QDBusObjectPath& device, const QString& pincode); + void DisplayPasskey(const QDBusObjectPath& device, quint32 passkey, quint16 entered); void Cancel(); void Release(); + // NOLINTEND + +signals: + void pairingRequested( + const QString& deviceAddress, + BluetoothPairingRequestType::Enum type, + quint32 passkey, + quint32 token + ); + void agentReleased(); private: + struct PendingRequest { + QDBusMessage message; + BluetoothPairingRequestType::Enum type; + QString devicePath; + quint32 serial; + }; + + [[nodiscard]] static QString getDeviceAddress(const QDBusObjectPath& device); + void enqueuePendingRequest(const QDBusObjectPath& device, BluetoothPairingRequestType::Enum type); + QString mObjectPath; + QHash mPendingRequests; + quint32 mLastSerial = 0; }; } // namespace qs::bluetooth \ No newline at end of file diff --git a/src/bluetooth/bluez.cpp b/src/bluetooth/bluez.cpp index 72f4628d..72c0e88f 100644 --- a/src/bluetooth/bluez.cpp +++ b/src/bluetooth/bluez.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -30,6 +31,8 @@ Bluez* Bluez::instance() { Bluez::Bluez() { this->init(); } +Bluez::~Bluez() { this->unregisterAgent(); } + void Bluez::updateDefaultAdapter() { const auto& adapters = this->mAdapters.valueList(); this->bDefaultAdapter = adapters.empty() ? nullptr : adapters.first(); @@ -67,6 +70,7 @@ void Bluez::init() { } this->registerAgent(); + this->setupBlueZWatcher(); } void Bluez::onInterfacesAdded( @@ -195,7 +199,7 @@ void Bluez::registerAgent() { auto reply = agentManager->RegisterAgent( QDBusObjectPath(this->agent->objectPath()), - QStringLiteral("NoInputNoOutput") + QStringLiteral("KeyboardDisplay") ); if (reply.isError()) { @@ -209,4 +213,42 @@ void Bluez::registerAgent() { agentManager->RequestDefaultAgent(QDBusObjectPath(this->agent->objectPath())); } +void Bluez::unregisterAgent() { + if (!this->agent) return; + + auto bus = QDBusConnection::systemBus(); + auto* agentManager = new DBusBluezAgentManagerInterface("org.bluez", "/org/bluez", bus, this); + + if (agentManager->isValid()) { + agentManager->UnregisterAgent(QDBusObjectPath(this->agent->objectPath())); + } + + bus.unregisterObject(this->agent->objectPath()); + delete this->agent; + this->agent = nullptr; + delete agentManager; +} + +void Bluez::setupBlueZWatcher() { + auto bus = QDBusConnection::systemBus(); + auto* iface = bus.interface(); + + QObject::connect( + iface, + &QDBusConnectionInterface::serviceOwnerChanged, + this, + [this](const QString& name, const QString&, const QString& newOwner) { + if (name == QLatin1String("org.bluez")) { + if (!newOwner.isEmpty()) { + qCDebug(logBluetooth) << "BlueZ came back, re-registering agent"; + this->registerAgent(); + } else { + qCDebug(logBluetooth) << "BlueZ went away, cleaning agent"; + this->unregisterAgent(); + } + } + } + ); +} + } // namespace qs::bluetooth diff --git a/src/bluetooth/bluez.hpp b/src/bluetooth/bluez.hpp index 05ccd918..7dd7fc94 100644 --- a/src/bluetooth/bluez.hpp +++ b/src/bluetooth/bluez.hpp @@ -42,9 +42,16 @@ private slots: private: explicit Bluez(); + ~Bluez(); void init(); void registerAgent(); + void unregisterAgent(); + void setupBlueZWatcher(); +public: + [[nodiscard]] BluetoothAgent* getAgent() const { return this->agent; } + +private: qs::dbus::DBusObjectManager* objectManager = nullptr; BluetoothAgent* agent = nullptr; QHash mAdapterMap; @@ -77,6 +84,8 @@ class BluezQml: public QObject { /// 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); + /// The bluetooth agent for handling pairing requests. + Q_PROPERTY(BluetoothAgent* agent READ agent CONSTANT); // clang-format on signals: @@ -96,6 +105,11 @@ class BluezQml: public QObject { [[nodiscard]] static QBindable bindableDefaultAdapter() { return &Bluez::instance()->bDefaultAdapter; } + + [[nodiscard]] static BluetoothAgent* agent() { + auto* instance = Bluez::instance(); + return instance ? instance->getAgent() : nullptr; + } }; } // namespace qs::bluetooth diff --git a/src/bluetooth/device.cpp b/src/bluetooth/device.cpp index d7e2c38f..ab620a1e 100644 --- a/src/bluetooth/device.cpp +++ b/src/bluetooth/device.cpp @@ -118,7 +118,7 @@ void BluetoothDevice::connect() { this->bState = this->bConnected ? BluetoothDeviceState::Connected : BluetoothDeviceState::Disconnected; } else { - qCDebug(logDevice) << "Successfully connected to device" << this; + qCDebug(logDevice) << "Successfully connected to to device" << this; } delete watcher; @@ -294,8 +294,6 @@ void BluetoothDevice::onConnectedChanged() { emit this->connectedChanged(); } -void BluetoothDevice::onServicesResolvedChanged() { emit this->servicesResolvedChanged(); } - } // namespace qs::bluetooth namespace qs::dbus { diff --git a/src/bluetooth/device.hpp b/src/bluetooth/device.hpp index 12f5f5ea..23f230f5 100644 --- a/src/bluetooth/device.hpp +++ b/src/bluetooth/device.hpp @@ -95,8 +95,6 @@ class BluetoothDevice: public QObject { 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 device services (GATT/SDP) have been resolved and device is fully ready. - Q_PROPERTY(bool servicesResolved READ servicesResolved NOTIFY servicesResolvedChanged); /// 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. @@ -147,8 +145,6 @@ class BluetoothDevice: public QObject { [[nodiscard]] bool wakeAllowed() const { return this->bWakeAllowed; } void setWakeAllowed(bool wakeAllowed); - [[nodiscard]] bool servicesResolved() const { return this->bServicesResolved; } - [[nodiscard]] bool pairing() const { return this->bPairing; } [[nodiscard]] QBindable bindableAddress() { return &this->bAddress; } @@ -160,7 +156,6 @@ class BluetoothDevice: public QObject { [[nodiscard]] QBindable bindableTrusted() { return &this->bTrusted; } [[nodiscard]] QBindable bindableBlocked() { return &this->bBlocked; } [[nodiscard]] QBindable bindableWakeAllowed() { return &this->bWakeAllowed; } - [[nodiscard]] QBindable bindableServicesResolved() { return &this->bServicesResolved; } [[nodiscard]] QBindable bindableIcon() { return &this->bIcon; } [[nodiscard]] QBindable bindableBattery() { return &this->bBattery; } [[nodiscard]] QBindable bindableState() { return &this->bState; } @@ -180,7 +175,6 @@ class BluetoothDevice: public QObject { void trustedChanged(); void blockedChanged(); void wakeAllowedChanged(); - void servicesResolvedChanged(); void iconChanged(); void batteryAvailableChanged(); void batteryChanged(); @@ -188,7 +182,6 @@ class BluetoothDevice: public QObject { private: void onConnectedChanged(); - void onServicesResolvedChanged(); DBusBluezDeviceInterface* mInterface = nullptr; QDBusInterface* mBatteryInterface = nullptr; @@ -203,7 +196,6 @@ class BluetoothDevice: public QObject { 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, bool, bServicesResolved, &BluetoothDevice::onServicesResolvedChanged); 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); @@ -220,7 +212,6 @@ class BluetoothDevice: public QObject { 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, pServicesResolved, bServicesResolved, properties, "ServicesResolved"); QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pIcon, bIcon, properties, "Icon"); QS_DBUS_PROPERTY_BINDING(BluetoothDevice, pAdapterPath, bAdapterPath, properties, "Adapter"); diff --git a/src/bluetooth/org.bluez.Agent.xml b/src/bluetooth/org.bluez.Agent.xml index b5462ab0..192c0b4f 100644 --- a/src/bluetooth/org.bluez.Agent.xml +++ b/src/bluetooth/org.bluez.Agent.xml @@ -7,6 +7,25 @@ + + + + + + + + + + + + + + + + + + +