Skip to content

Commit f681e20

Browse files
committed
bluetooth: add bluetooth integration
Missing support for things that require an agent, but has most basics. Closes #17
1 parent 1d02292 commit f681e20

22 files changed

+1623
-14
lines changed

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ boption(SERVICE_PAM "Pam" ON)
7070
boption(SERVICE_GREETD "Greetd" ON)
7171
boption(SERVICE_UPOWER "UPower" ON)
7272
boption(SERVICE_NOTIFICATIONS "Notifications" ON)
73+
boption(BLUETOOTH "Bluetooth" ON)
7374

7475
include(cmake/install-qml-module.cmake)
7576
include(cmake/util.cmake)
@@ -116,7 +117,7 @@ if (WAYLAND)
116117
list(APPEND QT_FPDEPS WaylandClient)
117118
endif()
118119

119-
if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS)
120+
if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH)
120121
set(DBUS ON)
121122
endif()
122123

src/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,7 @@ if (X11)
2929
endif()
3030

3131
add_subdirectory(services)
32+
33+
if (BLUETOOTH)
34+
add_subdirectory(bluetooth)
35+
endif()

src/bluetooth/CMakeLists.txt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
set_source_files_properties(org.bluez.Adapter.xml PROPERTIES
2+
CLASSNAME DBusBluezAdapterInterface
3+
)
4+
5+
set_source_files_properties(org.bluez.Device.xml PROPERTIES
6+
CLASSNAME DBusBluezDeviceInterface
7+
)
8+
9+
qt_add_dbus_interface(DBUS_INTERFACES
10+
org.bluez.Adapter.xml
11+
dbus_adapter
12+
)
13+
14+
qt_add_dbus_interface(DBUS_INTERFACES
15+
org.bluez.Device.xml
16+
dbus_device
17+
)
18+
19+
qt_add_library(quickshell-bluetooth STATIC
20+
adapter.cpp
21+
bluez.cpp
22+
device.cpp
23+
${DBUS_INTERFACES}
24+
)
25+
26+
qt_add_qml_module(quickshell-bluetooth
27+
URI Quickshell.Bluetooth
28+
VERSION 0.1
29+
DEPENDENCIES QtQml
30+
)
31+
32+
install_qml_module(quickshell-bluetooth)
33+
34+
# dbus headers
35+
target_include_directories(quickshell-bluetooth PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
36+
37+
target_link_libraries(quickshell-bluetooth PRIVATE Qt::Qml Qt::DBus)
38+
qs_add_link_dependencies(quickshell-bluetooth quickshell-dbus)
39+
40+
qs_module_pch(quickshell-bluetooth SET dbus)
41+
42+
target_link_libraries(quickshell PRIVATE quickshell-bluetoothplugin)

src/bluetooth/adapter.cpp

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
#include "adapter.hpp"
2+
3+
#include <qcontainerfwd.h>
4+
#include <qdbusconnection.h>
5+
#include <qdbusextratypes.h>
6+
#include <qdbuspendingcall.h>
7+
#include <qdbuspendingreply.h>
8+
#include <qdebug.h>
9+
#include <qlogging.h>
10+
#include <qloggingcategory.h>
11+
#include <qstring.h>
12+
#include <qstringliteral.h>
13+
#include <qtypes.h>
14+
15+
#include "../dbus/properties.hpp"
16+
#include "dbus_adapter.h"
17+
18+
namespace qs::bluetooth {
19+
20+
namespace {
21+
Q_LOGGING_CATEGORY(logAdapter, "quickshell.bluetooth.adapter", QtWarningMsg);
22+
}
23+
24+
QString BluetoothAdapterState::toString(BluetoothAdapterState::Enum state) {
25+
switch (state) {
26+
case BluetoothAdapterState::Disabled: return QStringLiteral("Disabled");
27+
case BluetoothAdapterState::Enabled: return QStringLiteral("Enabled");
28+
case BluetoothAdapterState::Enabling: return QStringLiteral("Enabling");
29+
case BluetoothAdapterState::Disabling: return QStringLiteral("Disabling");
30+
case BluetoothAdapterState::Blocked: return QStringLiteral("Blocked");
31+
default: return QStringLiteral("Unknown");
32+
}
33+
}
34+
35+
BluetoothAdapter::BluetoothAdapter(const QString& path, QObject* parent): QObject(parent) {
36+
this->mInterface =
37+
new DBusBluezAdapterInterface("org.bluez", path, QDBusConnection::systemBus(), this);
38+
39+
if (!this->mInterface->isValid()) {
40+
qCWarning(logAdapter) << "Could not create DBus interface for adapter at" << path;
41+
this->mInterface = nullptr;
42+
return;
43+
}
44+
45+
this->properties.setInterface(this->mInterface);
46+
}
47+
48+
QString BluetoothAdapter::adapterId() const {
49+
auto path = this->path();
50+
return path.sliced(path.lastIndexOf('/') + 1);
51+
}
52+
53+
void BluetoothAdapter::setEnabled(bool enabled) {
54+
if (enabled == this->bEnabled) return;
55+
this->bEnabled = enabled;
56+
this->pEnabled.write();
57+
}
58+
59+
void BluetoothAdapter::setDiscoverable(bool discoverable) {
60+
if (discoverable == this->bDiscoverable) return;
61+
this->bDiscoverable = discoverable;
62+
this->pDiscoverable.write();
63+
}
64+
65+
void BluetoothAdapter::setDiscovering(bool discovering) {
66+
if (discovering) {
67+
this->startDiscovery();
68+
} else {
69+
this->stopDiscovery();
70+
}
71+
}
72+
73+
void BluetoothAdapter::setDiscoverableTimeout(quint32 timeout) {
74+
if (timeout == this->bDiscoverableTimeout) return;
75+
this->bDiscoverableTimeout = timeout;
76+
this->pDiscoverableTimeout.write();
77+
}
78+
79+
void BluetoothAdapter::setPairable(bool pairable) {
80+
if (pairable == this->bPairable) return;
81+
this->bPairable = pairable;
82+
this->pPairable.write();
83+
}
84+
85+
void BluetoothAdapter::setPairableTimeout(quint32 timeout) {
86+
if (timeout == this->bPairableTimeout) return;
87+
this->bPairableTimeout = timeout;
88+
this->pPairableTimeout.write();
89+
}
90+
91+
void BluetoothAdapter::addInterface(const QString& interface, const QVariantMap& properties) {
92+
if (interface == "org.bluez.Adapter1") {
93+
this->properties.updatePropertySet(properties, false);
94+
qCDebug(logAdapter) << "Updated Adapter properties for" << this;
95+
}
96+
}
97+
98+
void BluetoothAdapter::removeDevice(const QString& devicePath) {
99+
qCDebug(logAdapter) << "Removing device" << devicePath << "from adapter" << this;
100+
101+
auto reply = this->mInterface->RemoveDevice(QDBusObjectPath(devicePath));
102+
103+
auto* watcher = new QDBusPendingCallWatcher(reply, this);
104+
105+
QObject::connect(
106+
watcher,
107+
&QDBusPendingCallWatcher::finished,
108+
this,
109+
[this, devicePath](QDBusPendingCallWatcher* watcher) {
110+
const QDBusPendingReply<> reply = *watcher;
111+
112+
if (reply.isError()) {
113+
qCWarning(logAdapter).nospace()
114+
<< "Failed to remove device " << devicePath << " from adapter" << this << ": "
115+
<< reply.error().message();
116+
} else {
117+
qCDebug(logAdapter) << "Successfully removed device" << devicePath << "from adapter"
118+
<< this;
119+
}
120+
121+
delete watcher;
122+
}
123+
);
124+
}
125+
126+
void BluetoothAdapter::startDiscovery() {
127+
if (this->bDiscovering) return;
128+
qCDebug(logAdapter) << "Starting discovery for adapter" << this;
129+
130+
auto reply = this->mInterface->StartDiscovery();
131+
auto* watcher = new QDBusPendingCallWatcher(reply, this);
132+
133+
QObject::connect(
134+
watcher,
135+
&QDBusPendingCallWatcher::finished,
136+
this,
137+
[this](QDBusPendingCallWatcher* watcher) {
138+
const QDBusPendingReply<> reply = *watcher;
139+
140+
if (reply.isError()) {
141+
qCWarning(logAdapter).nospace()
142+
<< "Failed to start discovery on adapter" << this << ": " << reply.error().message();
143+
} else {
144+
qCDebug(logAdapter) << "Successfully started discovery on adapter" << this;
145+
}
146+
147+
delete watcher;
148+
}
149+
);
150+
}
151+
152+
void BluetoothAdapter::stopDiscovery() {
153+
if (!this->bDiscovering) return;
154+
qCDebug(logAdapter) << "Stopping discovery for adapter" << this;
155+
156+
auto reply = this->mInterface->StopDiscovery();
157+
auto* watcher = new QDBusPendingCallWatcher(reply, this);
158+
159+
QObject::connect(
160+
watcher,
161+
&QDBusPendingCallWatcher::finished,
162+
this,
163+
[this](QDBusPendingCallWatcher* watcher) {
164+
const QDBusPendingReply<> reply = *watcher;
165+
166+
if (reply.isError()) {
167+
qCWarning(logAdapter).nospace()
168+
<< "Failed to stop discovery on adapter " << this << ": " << reply.error().message();
169+
} else {
170+
qCDebug(logAdapter) << "Successfully stopped discovery on adapter" << this;
171+
}
172+
173+
delete watcher;
174+
}
175+
);
176+
}
177+
178+
} // namespace qs::bluetooth
179+
180+
namespace qs::dbus {
181+
182+
using namespace qs::bluetooth;
183+
184+
DBusResult<BluetoothAdapterState::Enum>
185+
DBusDataTransform<BluetoothAdapterState::Enum>::fromWire(const Wire& wire) {
186+
if (wire == QStringLiteral("off")) {
187+
return BluetoothAdapterState::Disabled;
188+
} else if (wire == QStringLiteral("on")) {
189+
return BluetoothAdapterState::Enabled;
190+
} else if (wire == QStringLiteral("off-enabling")) {
191+
return BluetoothAdapterState::Enabling;
192+
} else if (wire == QStringLiteral("on-disabling")) {
193+
return BluetoothAdapterState::Disabling;
194+
} else if (wire == QStringLiteral("off-blocked")) {
195+
return BluetoothAdapterState::Blocked;
196+
} else {
197+
return QDBusError(
198+
QDBusError::InvalidArgs,
199+
QString("Invalid BluetoothAdapterState: %1").arg(wire)
200+
);
201+
}
202+
}
203+
204+
} // namespace qs::dbus
205+
206+
QDebug operator<<(QDebug debug, const qs::bluetooth::BluetoothAdapter* adapter) {
207+
auto saver = QDebugStateSaver(debug);
208+
209+
if (adapter) {
210+
debug.nospace() << "BluetoothAdapter(" << static_cast<const void*>(adapter)
211+
<< ", path=" << adapter->path() << ")";
212+
} else {
213+
debug << "BluetoothAdapter(nullptr)";
214+
}
215+
216+
return debug;
217+
}

0 commit comments

Comments
 (0)