Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/bluetooth/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/bluetooth/adapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ BluetoothAdapter::BluetoothAdapter(const QString& path, QObject* parent): QObjec
}

this->properties.setInterface(this->mInterface);
this->properties.updateAllViaGetAll();
}

QString BluetoothAdapter::adapterId() const {
Expand Down
205 changes: 205 additions & 0 deletions src/bluetooth/agent.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
#include "agent.hpp"
#include <atomic>

#include <qdbusconnection.h>
#include <qdbuserror.h>
#include <qdbusextratypes.h>
#include <qdebug.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qstringliteral.h>

#include "../core/logcat.hpp"

namespace qs::bluetooth {

namespace {
QS_LOGGING_CATEGORY(logAgent, "quickshell.bluetooth.agent", QtWarningMsg);
std::atomic<quint32> gTokenCounter {1};
} // namespace

BluetoothAgent::BluetoothAgent(QObject* parent)
: QObject(parent)
, mObjectPath(QStringLiteral("/org/quickshell/bluetooth/agent")) {}

QString BluetoothAgent::objectPath() const { return this->mObjectPath; }

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) {
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::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() {
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
75 changes: 75 additions & 0 deletions src/bluetooth/agent.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#pragma once

#include <qdbuscontext.h>
#include <qdbusextratypes.h>
#include <qdbusmessage.h>
#include <qhash.h>
#include <qobject.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>

namespace qs::bluetooth {

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;

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<quint32, PendingRequest> mPendingRequests;
quint32 mLastSerial = 0;
};

} // namespace qs::bluetooth
Loading