#include "connection/dbus/bluetooth.hpp" #include // --------------------------------------------------------------------------- // Constructor // --------------------------------------------------------------------------- BluetoothController::BluetoothController() { connect_system_async(sigc::mem_fun(*this, &BluetoothController::onBusConnected)); } // --------------------------------------------------------------------------- // Signal accessors // --------------------------------------------------------------------------- sigc::signal &BluetoothController::signalPoweredChanged() { return m_powered_signal; } sigc::signal &BluetoothController::signalDiscoveringChanged() { return m_discovering_signal; } sigc::signal &BluetoothController::signalDeviceAdded() { return m_device_added_signal; } sigc::signal &BluetoothController::signalDeviceRemoved() { return m_device_removed_signal; } sigc::signal &BluetoothController::signalDeviceChanged() { return m_device_changed_signal; } // --------------------------------------------------------------------------- // Queries // --------------------------------------------------------------------------- bool BluetoothController::isPowered() const { return m_powered; } bool BluetoothController::isDiscovering() const { return m_discovering; } std::vector BluetoothController::getDevices() const { std::vector result; result.reserve(m_devices.size()); for (const auto &[_, device] : m_devices) { result.push_back(device); } return result; } std::vector BluetoothController::getPairedDevices() const { std::vector 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::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::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::create(trusted)); } // --------------------------------------------------------------------------- // Bus connection // --------------------------------------------------------------------------- void BluetoothController::onBusConnected(const Glib::RefPtr &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( result.get_child(0)); for (gsize i = 0; i < objects.get_n_children(); i++) { auto entry = Glib::VariantBase::cast_dynamic( objects.get_child(i)); auto path = static_cast( Glib::VariantBase::cast_dynamic>( 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(interfaces_var); for (gsize i = 0; i < ifaces.get_n_children(); i++) { auto entry = Glib::VariantBase::cast_dynamic( ifaces.get_child(i)); auto iface_name = static_cast( Glib::VariantBase::cast_dynamic>( entry.get_child(0)) .get()); if (iface_name != "org.bluez.Device1" && iface_name != "org.bluez.Adapter1") { continue; } auto props = Glib::VariantBase::cast_dynamic>( 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 ¶meters) { if (signal_name == "InterfacesAdded") { // signature: (oa{sa{sv}}) auto path = static_cast( Glib::VariantBase::cast_dynamic>( parameters.get_child(0)) .get()); parseInterfaces(path, parameters.get_child(1)); } else if (signal_name == "InterfacesRemoved") { // signature: (oas) auto path = static_cast( Glib::VariantBase::cast_dynamic>( parameters.get_child(0)) .get()); auto ifaces = Glib::VariantBase::cast_dynamic< Glib::Variant>>(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>(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>(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 &) { auto it = changed.find("Powered"); if (it != changed.end() && it->second.is_of_type(Glib::VariantType("b"))) { m_powered = Glib::VariantBase::cast_dynamic>(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>(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 &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 &) { 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( Glib::VariantBase::cast_dynamic>(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( Glib::VariantBase::cast_dynamic>(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>(value).get(); } else if (key == "Connected" && value.is_of_type(Glib::VariantType("b"))) { device.connected = Glib::VariantBase::cast_dynamic>(value).get(); } else if (key == "Trusted" && value.is_of_type(Glib::VariantType("b"))) { device.trusted = Glib::VariantBase::cast_dynamic>(value).get(); } else if (key == "RSSI" && value.is_of_type(Glib::VariantType("n"))) { device.rssi = Glib::VariantBase::cast_dynamic>(value).get(); } else if (key == "Icon" && value.is_of_type(Glib::VariantType("s"))) { device.icon = static_cast( Glib::VariantBase::cast_dynamic>(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( Glib::VariantBase::cast_dynamic>(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>(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>(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::create(value); auto params = Glib::VariantContainerBase::create_tuple({ Glib::Variant::create(interface), Glib::Variant::create(property), wrapped}); props_proxy->call_sync("Set", params); } catch (const Glib::Error &ex) { spdlog::error("Error setting {}.{}: {}", interface, property, ex.what()); } }