fix bar crashing on monitor add/remove

This commit is contained in:
2026-02-09 13:49:52 +01:00
parent a90d1c2f6c
commit e1217305a5
30 changed files with 1186 additions and 247 deletions

View File

@@ -0,0 +1,448 @@
#include "connection/dbus/bluetooth.hpp"
#include <spdlog/spdlog.h>
// ---------------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------------
BluetoothController::BluetoothController() {
connect_system_async(sigc::mem_fun(*this, &BluetoothController::onBusConnected));
}
// ---------------------------------------------------------------------------
// Signal accessors
// ---------------------------------------------------------------------------
sigc::signal<void(bool)> &BluetoothController::signalPoweredChanged() { return m_powered_signal; }
sigc::signal<void(bool)> &BluetoothController::signalDiscoveringChanged() { return m_discovering_signal; }
sigc::signal<void(const BluetoothDevice &)> &BluetoothController::signalDeviceAdded() { return m_device_added_signal; }
sigc::signal<void(const std::string &)> &BluetoothController::signalDeviceRemoved() { return m_device_removed_signal; }
sigc::signal<void(const BluetoothDevice &)> &BluetoothController::signalDeviceChanged() { return m_device_changed_signal; }
// ---------------------------------------------------------------------------
// Queries
// ---------------------------------------------------------------------------
bool BluetoothController::isPowered() const { return m_powered; }
bool BluetoothController::isDiscovering() const { return m_discovering; }
std::vector<BluetoothDevice> BluetoothController::getDevices() const {
std::vector<BluetoothDevice> result;
result.reserve(m_devices.size());
for (const auto &[_, device] : m_devices) {
result.push_back(device);
}
return result;
}
std::vector<BluetoothDevice> BluetoothController::getPairedDevices() const {
std::vector<BluetoothDevice> result;
for (const auto &[_, device] : m_devices) {
if (device.paired) {
result.push_back(device);
}
}
return result;
}
// ---------------------------------------------------------------------------
// Adapter control
// ---------------------------------------------------------------------------
void BluetoothController::setPowered(bool powered) {
spdlog::info("Bluetooth: setting powered to {}", powered);
setDbusProperty(m_adapter_path, "org.bluez.Adapter1", "Powered",
Glib::Variant<bool>::create(powered));
}
void BluetoothController::startDiscovery() {
spdlog::info("Bluetooth: starting discovery");
if (m_adapter_proxy) {
m_adapter_proxy->call("StartDiscovery");
}
}
void BluetoothController::stopDiscovery() {
spdlog::info("Bluetooth: stopping discovery");
if (m_adapter_proxy) {
m_adapter_proxy->call("StopDiscovery");
}
}
// ---------------------------------------------------------------------------
// Device actions
// ---------------------------------------------------------------------------
void BluetoothController::pairDevice(const std::string &object_path) {
spdlog::info("Bluetooth: pairing device {}", object_path);
auto it = m_device_proxies.find(object_path);
if (it != m_device_proxies.end() && it->second) {
it->second->call("Pair");
}
}
void BluetoothController::unpairDevice(const std::string &object_path) {
spdlog::info("Bluetooth: unpairing device {}", object_path);
if (!m_adapter_proxy) return;
auto params = Glib::VariantContainerBase::create_tuple(
Glib::Variant<Glib::DBusObjectPathString>::create(object_path));
m_adapter_proxy->call("RemoveDevice", params);
}
void BluetoothController::connectDevice(const std::string &object_path) {
spdlog::info("Bluetooth: connecting device {}", object_path);
auto it = m_device_proxies.find(object_path);
if (it != m_device_proxies.end() && it->second) {
it->second->call("Connect");
}
}
void BluetoothController::disconnectDevice(const std::string &object_path) {
spdlog::info("Bluetooth: disconnecting device {}", object_path);
auto it = m_device_proxies.find(object_path);
if (it != m_device_proxies.end() && it->second) {
it->second->call("Disconnect");
}
}
void BluetoothController::trustDevice(const std::string &object_path, bool trusted) {
spdlog::info("Bluetooth: setting trusted={} for device {}", trusted, object_path);
setDbusProperty(object_path, "org.bluez.Device1", "Trusted",
Glib::Variant<bool>::create(trusted));
}
// ---------------------------------------------------------------------------
// Bus connection
// ---------------------------------------------------------------------------
void BluetoothController::onBusConnected(const Glib::RefPtr<Gio::AsyncResult> &result) {
if (!result) {
spdlog::error("Bluetooth: null async result");
return;
}
try {
connection = Gio::DBus::Connection::get_finish(result);
m_object_manager_proxy = Gio::DBus::Proxy::create_sync(
connection,
"org.bluez",
"/",
"org.freedesktop.DBus.ObjectManager");
if (m_object_manager_proxy) {
m_object_manager_proxy->signal_signal().connect(
sigc::mem_fun(*this, &BluetoothController::onObjectManagerSignal));
enumerateObjects();
}
} catch (const Glib::Error &ex) {
spdlog::error("Bluetooth DBus Connection Error: {}", ex.what());
}
}
// ---------------------------------------------------------------------------
// Enumerate existing BlueZ objects
// ---------------------------------------------------------------------------
void BluetoothController::enumerateObjects() {
if (!m_object_manager_proxy) return;
try {
auto result = m_object_manager_proxy->call_sync("GetManagedObjects");
auto objects = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(
result.get_child(0));
for (gsize i = 0; i < objects.get_n_children(); i++) {
auto entry = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(
objects.get_child(i));
auto path = static_cast<std::string>(
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::DBusObjectPathString>>(
entry.get_child(0))
.get());
parseInterfaces(path, entry.get_child(1));
}
} catch (const Glib::Error &ex) {
spdlog::error("Bluetooth: Error enumerating objects: {}", ex.what());
}
}
// ---------------------------------------------------------------------------
// Parse an interfaces-and-properties dict a{sa{sv}}
// ---------------------------------------------------------------------------
void BluetoothController::parseInterfaces(const std::string &object_path,
const Glib::VariantBase &interfaces_var) {
auto ifaces = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(interfaces_var);
for (gsize i = 0; i < ifaces.get_n_children(); i++) {
auto entry = Glib::VariantBase::cast_dynamic<Glib::VariantContainerBase>(
ifaces.get_child(i));
auto iface_name = static_cast<std::string>(
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(
entry.get_child(0))
.get());
if (iface_name != "org.bluez.Device1" && iface_name != "org.bluez.Adapter1") {
continue;
}
auto props = Glib::VariantBase::cast_dynamic<Glib::Variant<PropertiesMap>>(
entry.get_child(1))
.get();
if (iface_name == "org.bluez.Device1") {
addDevice(object_path, props);
} else {
setupAdapter(object_path, props);
}
}
}
// ---------------------------------------------------------------------------
// ObjectManager signal handler (InterfacesAdded / InterfacesRemoved)
// ---------------------------------------------------------------------------
void BluetoothController::onObjectManagerSignal(
const Glib::ustring &,
const Glib::ustring &signal_name,
const Glib::VariantContainerBase &parameters) {
if (signal_name == "InterfacesAdded") {
// signature: (oa{sa{sv}})
auto path = static_cast<std::string>(
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::DBusObjectPathString>>(
parameters.get_child(0))
.get());
parseInterfaces(path, parameters.get_child(1));
} else if (signal_name == "InterfacesRemoved") {
// signature: (oas)
auto path = static_cast<std::string>(
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::DBusObjectPathString>>(
parameters.get_child(0))
.get());
auto ifaces = Glib::VariantBase::cast_dynamic<
Glib::Variant<std::vector<Glib::ustring>>>(parameters.get_child(1)).get();
for (const auto &iface : ifaces) {
if (iface == "org.bluez.Device1") {
removeDevice(path);
} else if (iface == "org.bluez.Adapter1" && path == m_adapter_path) {
m_adapter_proxy.reset();
m_adapter_path.clear();
m_powered = false;
m_discovering = false;
m_powered_signal.emit(false);
m_discovering_signal.emit(false);
spdlog::info("Bluetooth adapter removed");
}
}
}
}
// ---------------------------------------------------------------------------
// Adapter
// ---------------------------------------------------------------------------
void BluetoothController::setupAdapter(const std::string &path,
const PropertiesMap &properties) {
m_adapter_path = path;
spdlog::info("Bluetooth adapter found: {}", path);
auto it = properties.find("Powered");
if (it != properties.end() && it->second.is_of_type(Glib::VariantType("b"))) {
m_powered = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(it->second).get();
m_powered_signal.emit(m_powered);
}
it = properties.find("Discovering");
if (it != properties.end() && it->second.is_of_type(Glib::VariantType("b"))) {
m_discovering = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(it->second).get();
m_discovering_signal.emit(m_discovering);
}
try {
m_adapter_proxy = Gio::DBus::Proxy::create_sync(
connection, "org.bluez", path, "org.bluez.Adapter1");
if (m_adapter_proxy) {
m_adapter_proxy->signal_properties_changed().connect(
sigc::mem_fun(*this, &BluetoothController::onAdapterPropertiesChanged));
}
} catch (const Glib::Error &ex) {
spdlog::error("Bluetooth: Error creating adapter proxy: {}", ex.what());
}
}
void BluetoothController::onAdapterPropertiesChanged(
const Gio::DBus::Proxy::MapChangedProperties &changed,
const std::vector<Glib::ustring> &) {
auto it = changed.find("Powered");
if (it != changed.end() && it->second.is_of_type(Glib::VariantType("b"))) {
m_powered = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(it->second).get();
spdlog::info("Bluetooth powered: {}", m_powered);
m_powered_signal.emit(m_powered);
}
it = changed.find("Discovering");
if (it != changed.end() && it->second.is_of_type(Glib::VariantType("b"))) {
m_discovering = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(it->second).get();
spdlog::info("Bluetooth discovering: {}", m_discovering);
m_discovering_signal.emit(m_discovering);
}
}
// ---------------------------------------------------------------------------
// Device management
// ---------------------------------------------------------------------------
void BluetoothController::addDevice(const std::string &path,
const PropertiesMap &properties) {
auto device = parseDeviceProperties(path, properties);
m_devices[path] = device;
spdlog::info("Bluetooth device added: {} ({}) {}", device.name, device.address, device.icon);
try {
auto proxy = Gio::DBus::Proxy::create_sync(
connection, "org.bluez", path, "org.bluez.Device1");
if (proxy) {
proxy->signal_properties_changed().connect(
[this, path](const Gio::DBus::Proxy::MapChangedProperties &changed,
const std::vector<Glib::ustring> &invalidated) {
onDevicePropertiesChanged(path, changed, invalidated);
});
m_device_proxies[path] = proxy;
}
} catch (const Glib::Error &ex) {
spdlog::error("Bluetooth: Error creating device proxy for {}: {}", path, ex.what());
}
m_device_added_signal.emit(device);
}
void BluetoothController::removeDevice(const std::string &path) {
spdlog::info("Bluetooth device removed: {}", path);
m_devices.erase(path);
m_device_proxies.erase(path);
m_device_removed_signal.emit(path);
}
void BluetoothController::onDevicePropertiesChanged(
const std::string &object_path,
const Gio::DBus::Proxy::MapChangedProperties &changed,
const std::vector<Glib::ustring> &) {
auto it = m_devices.find(object_path);
if (it == m_devices.end()) return;
auto &device = it->second;
for (const auto &[key, value] : changed) {
if (key == "Name" && value.is_of_type(Glib::VariantType("s"))) {
device.name = static_cast<std::string>(
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(value).get());
} else if (key == "Alias" && value.is_of_type(Glib::VariantType("s"))) {
// Only use Alias as fallback if Name is still the MAC address
auto alias = static_cast<std::string>(
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(value).get());
if (device.name == device.address) {
device.name = alias;
}
} else if (key == "Paired" && value.is_of_type(Glib::VariantType("b"))) {
device.paired = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(value).get();
} else if (key == "Connected" && value.is_of_type(Glib::VariantType("b"))) {
device.connected = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(value).get();
} else if (key == "Trusted" && value.is_of_type(Glib::VariantType("b"))) {
device.trusted = Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(value).get();
} else if (key == "RSSI" && value.is_of_type(Glib::VariantType("n"))) {
device.rssi = Glib::VariantBase::cast_dynamic<Glib::Variant<gint16>>(value).get();
} else if (key == "Icon" && value.is_of_type(Glib::VariantType("s"))) {
device.icon = static_cast<std::string>(
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(value).get());
}
}
m_device_changed_signal.emit(device);
}
// ---------------------------------------------------------------------------
// Property parsing helper
// ---------------------------------------------------------------------------
BluetoothDevice BluetoothController::parseDeviceProperties(
const std::string &path,
const PropertiesMap &properties) {
BluetoothDevice device;
device.object_path = path;
auto getString = [&](const Glib::ustring &key) -> std::string {
auto it = properties.find(key);
if (it != properties.end() && it->second.is_of_type(Glib::VariantType("s"))) {
return static_cast<std::string>(
Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(it->second).get());
}
return "";
};
auto getBool = [&](const Glib::ustring &key) -> bool {
auto it = properties.find(key);
if (it != properties.end() && it->second.is_of_type(Glib::VariantType("b"))) {
return Glib::VariantBase::cast_dynamic<Glib::Variant<bool>>(it->second).get();
}
return false;
};
device.address = getString("Address");
device.name = getString("Name");
if (device.name.empty()) device.name = getString("Alias");
if (device.name.empty()) device.name = device.address;
device.icon = getString("Icon");
device.paired = getBool("Paired");
device.connected = getBool("Connected");
device.trusted = getBool("Trusted");
auto rssi_it = properties.find("RSSI");
if (rssi_it != properties.end() && rssi_it->second.is_of_type(Glib::VariantType("n"))) {
device.rssi = Glib::VariantBase::cast_dynamic<Glib::Variant<gint16>>(rssi_it->second).get();
}
return device;
}
// ---------------------------------------------------------------------------
// D-Bus property setter (calls org.freedesktop.DBus.Properties.Set)
// ---------------------------------------------------------------------------
void BluetoothController::setDbusProperty(const std::string &object_path,
const std::string &interface,
const std::string &property,
const Glib::VariantBase &value) {
if (!connection || object_path.empty()) return;
try {
auto props_proxy = Gio::DBus::Proxy::create_sync(
connection, "org.bluez", object_path, "org.freedesktop.DBus.Properties");
auto wrapped = Glib::Variant<Glib::VariantBase>::create(value);
auto params = Glib::VariantContainerBase::create_tuple({
Glib::Variant<Glib::ustring>::create(interface),
Glib::Variant<Glib::ustring>::create(property),
wrapped});
props_proxy->call_sync("Set", params);
} catch (const Glib::Error &ex) {
spdlog::error("Error setting {}.{}: {}", interface, property, ex.what());
}
}