#include "bt.hh" #include "cfg.hh" #include "logger.hh" #include "looper.hh" #include "sdbuscpp_no_throw_helper.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { // Cannot get sdbus::Result to work with void, so this is the workaround. struct Void { static Void const kInstance; }; Void const Void::kInstance; sdbus::Message& operator<<(sdbus::Message& msg, const Void& /* ignored */) { return msg; } struct MediaPlayerTrack { std::string Title; std::string Artist; std::string Album; std::string Genre; uint32_t NumberOfTracks; uint32_t Duration; std::string ImgHandle; }; } // namespace template <> struct sdbus::signature_of : sdbus::signature_of {}; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-function" SDBUSCPP_REGISTER_STRUCT_NO_THROW(MediaPlayerTrack, Title, Artist, Album, Genre, NumberOfTracks, Duration, ImgHandle); #pragma clang diagnostic pop namespace bt { namespace { class IManager { public: virtual ~IManager() = default; IManager(IManager const&) = delete; IManager& operator=(IManager const&) = delete; virtual void add_device( sdbus::ObjectPath const& path, std::map const& properties) = 0; virtual void add_adapter( sdbus::ObjectPath const& path, std::map const& properties) = 0; virtual void add_agent_manager(sdbus::ObjectPath const& path) = 0; virtual void remove_device(sdbus::ObjectPath const& path) = 0; virtual void remove_adapter(sdbus::ObjectPath const& path) = 0; virtual void remove_agent_manager(sdbus::ObjectPath const& path) = 0; virtual void remove_agent() = 0; virtual void update_adapter(sdbus::ObjectPath const& path) = 0; virtual void update_device(sdbus::ObjectPath const& path) = 0; virtual void update_player(sdbus::ObjectPath const& path) = 0; virtual void add_player( sdbus::ObjectPath const& path, std::map const& properties) = 0; virtual void remove_player(sdbus::ObjectPath const& path) = 0; [[nodiscard]] virtual std::vector get_devices( sdbus::ObjectPath const& adapter_path) const = 0; [[nodiscard]] virtual Player* get_player(sdbus::ObjectPath const& player_path) const = 0; virtual void agent_request_pincode( sdbus::ObjectPath const& device, std::function)> callback) = 0; virtual void agent_display_pincode(sdbus::ObjectPath const& device, std::string const& pincode) = 0; virtual void agent_request_passkey( sdbus::ObjectPath const& device, std::function)> callback) = 0; virtual void agent_display_passkey(sdbus::ObjectPath const& device, uint32_t passkey, uint16_t entered) = 0; virtual void agent_request_confirmation( sdbus::ObjectPath const& device, uint32_t passkey, std::function callback) = 0; virtual void agent_request_authorization( sdbus::ObjectPath const& device, std::function callback) = 0; virtual void agent_authorize_service(sdbus::ObjectPath const& device, std::string const& uuid, std::function callback) = 0; virtual void agent_cancel() = 0; virtual logger::Logger& logger() = 0; protected: IManager() = default; }; const sdbus::ServiceName kService{"org.bluez"}; const sdbus::InterfaceName kDeviceInterface{"org.bluez.Device1"}; const sdbus::InterfaceName kAdapterInterface{"org.bluez.Adapter1"}; const sdbus::InterfaceName kMediaPlayerInterface{"org.bluez.MediaPlayer1"}; const sdbus::InterfaceName kAgentInterface{"org.bluez.Agent1"}; const sdbus::InterfaceName kAgentManagerInterface{"org.bluez.AgentManager1"}; const sdbus::InterfaceName kPropertiesInterface{ "org.freedesktop.DBus.Properties"}; class ObjectManagerProxy { private: static inline sdbus::InterfaceName kInterface{ "org.freedesktop.DBus.ObjectManager"}; public: ObjectManagerProxy(IManager& manager, sdbus::IConnection& connection, sdbus::ServiceName destination, sdbus::ObjectPath path, sdbus::ObjectPath filter) : proxy_(sdbus::createProxy(connection, std::move(destination), std::move(path))), manager_(manager), filter_(std::move(filter)) { proxy_->uponSignal("InterfacesAdded") .onInterface(kInterface) .call([this]( sdbus::ObjectPath const& path, std::map> const& interfaces_and_properties) { interfaces_added(path, interfaces_and_properties); }); proxy_->uponSignal("InterfacesRemoved") .onInterface(kInterface) .call([this](sdbus::ObjectPath const& path, std::vector const& interfaces) { this->interfaces_removed(path, interfaces); }); proxy_->callMethodAsync("GetManagedObjects") .onInterface(kInterface) .uponReplyInvoke( [this]( std::optional err, std::map>> const& objects) { if (err.has_value()) { if (err->getName() == "org.freedesktop.DBus.Error.ServiceUnknown") { manager_.logger().info("Waiting for server to start ..."); } else { manager_.logger().err(std::format("GetManagedObjects {}: {}", std::string(err->getName()), err->getMessage())); } return; } for (auto const& pair : objects) { interfaces_added(pair.first, pair.second); } }); } ObjectManagerProxy(ObjectManagerProxy const&) = delete; ObjectManagerProxy& operator=(ObjectManagerProxy const&) = delete; virtual ~ObjectManagerProxy() { proxy_->unregister(); } private: void interfaces_added( sdbus::ObjectPath const& object_path, std::map> const& interfaces_and_properties) { if (!object_path.starts_with(filter_)) { manager_.logger().dbg( std::format("ignoring: {}", std::string(object_path))); return; } auto it = interfaces_and_properties.find(kDeviceInterface); if (it != interfaces_and_properties.end()) { manager_.add_device(object_path, it->second); } it = interfaces_and_properties.find(kAdapterInterface); if (it != interfaces_and_properties.end()) { manager_.add_adapter(object_path, it->second); } it = interfaces_and_properties.find(kAgentManagerInterface); if (it != interfaces_and_properties.end()) { manager_.add_agent_manager(object_path); } it = interfaces_and_properties.find(kMediaPlayerInterface); if (it != interfaces_and_properties.end()) { manager_.add_player(object_path, it->second); } } void interfaces_removed(sdbus::ObjectPath const& object_path, std::vector const& interfaces) { if (!object_path.starts_with(filter_)) return; for (auto const& interface : interfaces) { if (interface == kDeviceInterface) { manager_.remove_device(object_path); } else if (interface == kAdapterInterface) { manager_.remove_adapter(object_path); } else if (interface == kAgentManagerInterface) { manager_.remove_agent_manager(object_path); } else if (interface == kMediaPlayerInterface) { manager_.remove_player(object_path); } } } std::unique_ptr proxy_; IManager& manager_; sdbus::ObjectPath filter_; }; class BaseProxy : public sdbus::ProxyInterfaces { public: virtual ~BaseProxy() { unregisterProxy(); } protected: BaseProxy(sdbus::IConnection& connection, sdbus::ObjectPath const& path, sdbus::InterfaceName const& interface, std::map const& names) : sdbus::ProxyInterfaces(connection, kService, path), interface_(interface), names_(names) {} virtual void update(size_t field, sdbus::Variant const& value) = 0; virtual void notify_updated() = 0; void init(std::map const& properties) { for (auto const& pair : properties) { auto it = names_.find(pair.first); if (it != names_.end()) { update(it->second, pair.second); } } registerProxy(); } private: void onPropertiesChanged( const sdbus::InterfaceName& interface_name, const std::map& changed, const std::vector& invalidated) override { if (interface_name != interface_) return; ++waiting_; for (auto const& pair : changed) { auto it = names_.find(pair.first); if (it != names_.end()) { updated_ = true; update(it->second, pair.second); } } for (auto const& property_name : invalidated) { auto it = names_.find(property_name); if (it != names_.end()) { ++waiting_; GetAsync(interface_name, property_name, // NOLINTNEXTLINE(performance-unnecessary-value-param) [this, field = it->second](std::optional err, sdbus::Variant const& value) { if (!err.has_value()) { updated_ = true; update(field, value); } if (--waiting_ == 0 && updated_) { notify_updated(); } }); } } if (--waiting_ == 0 && updated_) { notify_updated(); } } sdbus::InterfaceName const& interface_; std::map const& names_; uint8_t waiting_{0}; bool updated_{false}; }; class DeviceProxy : public BaseProxy, public Device { private: static inline const std::map kNames{ {sdbus::PropertyName{"Address"}, 0}, {sdbus::PropertyName{"Name"}, 1}, {sdbus::PropertyName{"Alias"}, 2}, {sdbus::PropertyName{"Paired"}, 3}, {sdbus::PropertyName{"Connected"}, 4}, {sdbus::PropertyName{"Adapter"}, 5}, }; public: DeviceProxy(IManager& manager, sdbus::IConnection& connection, sdbus::ObjectPath const& path, std::map const& properties) : BaseProxy(connection, path, kDeviceInterface, kNames), manager_(manager) { init(properties); } [[nodiscard]] std::string const& address() const override { return address_; } [[nodiscard]] std::string const& name() const override { if (alias_.has_value() && !alias_->empty()) return alias_.value(); if (name_.has_value() && !name_->empty()) return name_.value(); return address_; } [[nodiscard]] bool paired() const override { return paired_; } [[nodiscard]] bool connected() const override { return connected_; } [[nodiscard]] Player* player() const override { return manager_.get_player(primary_player_); } [[nodiscard]] sdbus::ObjectPath const& adapter() const { return adapter_; } [[nodiscard]] sdbus::ObjectPath const& primary_player() const { return primary_player_; } void set_primary_player(sdbus::ObjectPath const& path) { primary_player_ = path; } private: void update(size_t field, sdbus::Variant const& value) override { switch (field) { case 0: address_ = value.get(); break; case 1: name_ = value.isEmpty() ? std::nullopt : std::optional(value.get()); break; case 2: alias_ = value.isEmpty() ? std::nullopt : std::optional(value.get()); break; case 3: paired_ = value.get(); break; case 4: connected_ = value.get(); break; case 5: adapter_ = value.get(); break; default: std::unreachable(); } } void notify_updated() override { manager_.update_device(getProxy().getObjectPath()); } IManager& manager_; std::string address_; std::optional name_; std::optional alias_; bool connected_{false}; bool paired_{false}; sdbus::ObjectPath adapter_; sdbus::ObjectPath primary_player_; }; class AdapterProxy : public BaseProxy, public Adapter { private: static inline const std::map kNames{ {sdbus::PropertyName{"Address"}, 0}, {sdbus::PropertyName{"Name"}, 1}, {sdbus::PropertyName{"Alias"}, 2}, {sdbus::PropertyName{"Discoverable"}, 3}, {sdbus::PropertyName{"Pairable"}, 4}, {sdbus::PropertyName{"Pairing"}, 5}, {sdbus::PropertyName{"Connectable"}, 6}, {sdbus::PropertyName{"Powered"}, 7}, {sdbus::PropertyName{"DiscoverableTimeout"}, 8}, {sdbus::PropertyName{"PairableTimeout"}, 9}, }; public: AdapterProxy(IManager& manager, sdbus::IConnection& connection, sdbus::ObjectPath const& path, std::map const& properties) : BaseProxy(connection, path, kAdapterInterface, kNames), manager_(manager) { init(properties); } [[nodiscard]] std::string const& address() const override { return address_; } [[nodiscard]] std::string const& name() const override { if (!alias_.empty()) return alias_; if (!name_.empty()) return name_; return address_; } [[nodiscard]] bool discoverable() const override { return discoverable_; } [[nodiscard]] uint32_t discoverable_timeout_seconds() const override { return discoverable_timeout_; } [[nodiscard]] bool pairable() const override { return pairable_; } [[nodiscard]] uint32_t pairable_timeout_seconds() const override { return pairable_timeout_; } [[nodiscard]] bool pairing() const override { return pairing_; } [[nodiscard]] bool powered() const override { return powered_; } [[nodiscard]] bool connectable() const override { return connectable_; } [[nodiscard]] std::vector devices() const override { return manager_.get_devices(getProxy().getObjectPath()); } void set_discoverable(bool discoverable) override { if (discoverable == discoverable_) return; SetAsync(kAdapterInterface, sdbus::PropertyName{"Discoverable"}, sdbus::Variant{discoverable}, [this](std::optional err) { set_callback(std::move(err)); }); } void set_discoverable_timeout_seconds(uint32_t seconds) override { if (discoverable_timeout_ == seconds) return; SetAsync(kAdapterInterface, sdbus::PropertyName{"DiscoverableTimeout"}, sdbus::Variant{seconds}, [this](std::optional err) { set_callback(std::move(err)); }); } void set_pairable_timeout_seconds(uint32_t seconds) override { if (pairable_timeout_ == seconds) return; SetAsync(kAdapterInterface, sdbus::PropertyName{"PairableTimeout"}, sdbus::Variant{seconds}, [this](std::optional err) { set_callback(std::move(err)); }); } private: void update(size_t field, sdbus::Variant const& value) override { switch (field) { case 0: address_ = value.get(); break; case 1: name_ = value.get(); break; case 2: alias_ = value.get(); break; case 3: discoverable_ = value.get(); break; case 4: pairable_ = value.get(); break; case 5: pairing_ = value.get(); break; case 6: connectable_ = value.get(); break; case 7: powered_ = value.get(); break; case 8: discoverable_timeout_ = value.get(); break; case 9: pairable_timeout_ = value.get(); break; default: std::unreachable(); } } void set_callback(std::optional err) { if (err.has_value()) { manager_.logger().warn(std::format("Failed to set property: {} {}", std::string(err->getName()), err->getMessage())); } } void notify_updated() override { manager_.update_adapter(getProxy().getObjectPath()); } IManager& manager_; std::string address_; std::string name_; std::string alias_; bool discoverable_{false}; bool pairable_{false}; bool pairing_{false}; bool connectable_{false}; bool powered_{false}; uint32_t discoverable_timeout_{180}; uint32_t pairable_timeout_{0}; }; inline bool isActive(Player::Status status) { switch (status) { case Player::Status::kPlaying: case Player::Status::kForwardSeek: case Player::Status::kReverseSeek: return true; case Player::Status::kStopped: case Player::Status::kPaused: case Player::Status::kError: return false; } std::unreachable(); } class MediaPlayerProxy : public BaseProxy, public Player { private: static inline const std::map kNames{ {sdbus::PropertyName{"Status"}, 0}, {sdbus::PropertyName{"Track"}, 1}, {sdbus::PropertyName{"Device"}, 2}, {sdbus::PropertyName{"Name"}, 3}, }; public: MediaPlayerProxy( IManager& manager, sdbus::IConnection& connection, sdbus::ObjectPath const& path, std::map const& properties) : BaseProxy(connection, path, kMediaPlayerInterface, kNames), manager_(manager) { init(properties); } [[nodiscard]] Status status() const override { return status_; } [[nodiscard]] std::string const& track_title() const override { return track_.Title; } [[nodiscard]] std::string const& track_artist() const override { return track_.Artist; } [[nodiscard]] std::string const& track_album() const override { return track_.Album; } void play() override { switch (status_) { case Status::kPlaying: return; case Status::kStopped: case Status::kPaused: case Status::kForwardSeek: case Status::kReverseSeek: case Status::kError: break; } getProxy() .callMethodAsync("Play") .onInterface(kMediaPlayerInterface) .uponReplyInvoke([this](std::optional err) { if (err.has_value()) { manager_.logger().err(std::format("{}: Play {}: {}", name_, std::string(err->getName()), err->getMessage())); } }); } void pause() override { switch (status_) { case Status::kStopped: case Status::kPaused: return; case Status::kPlaying: case Status::kForwardSeek: case Status::kReverseSeek: case Status::kError: break; } getProxy() .callMethodAsync("Pause") .onInterface(kMediaPlayerInterface) .uponReplyInvoke([this](std::optional err) { if (err.has_value()) { manager_.logger().err(std::format("{}: Pause {}: {}", name_, std::string(err->getName()), err->getMessage())); } }); } [[nodiscard]] sdbus::ObjectPath const& device() const { return device_; } private: void update(size_t field, sdbus::Variant const& value) override { switch (field) { case 0: { auto tmp = value.get(); if (tmp == "playing") { status_ = Status::kPlaying; } else if (tmp == "stopped") { status_ = Status::kStopped; } else if (tmp == "paused") { status_ = Status::kPaused; } else if (tmp == "forward-seek") { status_ = Status::kForwardSeek; } else if (tmp == "reverse-seek") { status_ = Status::kReverseSeek; } else { status_ = Status::kError; } break; } case 1: track_ = value.get(); break; case 2: device_ = value.get(); break; case 3: name_ = value.get(); break; default: std::unreachable(); } } void notify_updated() override { manager_.update_player(getProxy().getObjectPath()); } IManager& manager_; Status status_{Status::kError}; MediaPlayerTrack track_{}; sdbus::ObjectPath device_; std::string name_; }; class AgentManagerProxy { public: AgentManagerProxy(sdbus::IConnection& connection, sdbus::ObjectPath const& path) : proxy_(sdbus::createProxy(connection, kService, path)) {} void register_agent( sdbus::ObjectPath const& path, std::string const& capability, std::function err)> callback) { proxy_->callMethodAsync("RegisterAgent") .onInterface(kAgentManagerInterface) .withArguments(path, capability) .uponReplyInvoke(std::move(callback)); } void unregister_agent_sync(sdbus::ObjectPath const& path) { proxy_->callMethod("UnregisterAgent") .onInterface(kAgentManagerInterface) .withArguments(path); } [[nodiscard]] sdbus::ObjectPath const& object_path() const { return proxy_->getObjectPath(); } private: std::unique_ptr proxy_; }; class AgentObject { private: static inline const sdbus::ObjectPath kObjectPath{"/org/bluez/agent"}; static inline const sdbus::Error::Name kErrorCanceled{ "org.bluez.Error.Canceled"}; static inline const sdbus::Error::Name kErrorRejected{ "org.bluez.Error.Rejected"}; public: AgentObject(IManager& manager, sdbus::IConnection& connection) : manager_(manager), object_(sdbus::createObject(connection, kObjectPath)) { object_ ->addVTable( sdbus::registerMethod("Release").withNoReply().implementedAs( [this]() { manager_.remove_agent(); }), sdbus::registerMethod("RequestPinCode") .withInputParamNames("device") .withOutputParamNames("pincode") .implementedAs([this](sdbus::Result&& result, sdbus::ObjectPath const& device) { cancel_pending(); pending_pincode_ = std::move(result); manager_.agent_request_pincode(device, [this](auto pincode) { if (!pending_pincode_.has_value()) return; if (pincode.has_value()) { pending_pincode_->returnResults(pincode.value()); } else { pending_pincode_->returnError( sdbus::Error{kErrorRejected}); } pending_pincode_ = std::nullopt; }); }), sdbus::registerMethod("DisplayPinCode") .withInputParamNames("device", "pincode") .implementedAs([this](sdbus::ObjectPath const& device, std::string const& pincode) { manager_.agent_display_pincode(device, pincode); }), sdbus::registerMethod("RequestPasskey") .withInputParamNames("device") .withOutputParamNames("passkey") .implementedAs([this](sdbus::Result&& result, sdbus::ObjectPath const& device) { cancel_pending(); pending_passkey_ = std::move(result); manager_.agent_request_passkey(device, [this](auto passkey) { if (!pending_passkey_.has_value()) return; if (passkey.has_value()) { pending_passkey_->returnResults(passkey.value()); } else { pending_passkey_->returnError( sdbus::Error{kErrorRejected}); } pending_passkey_ = std::nullopt; }); }), sdbus::registerMethod("DisplayPasskey") .withInputParamNames("device", "passkey", "entered") .implementedAs([this](sdbus::ObjectPath const& device, uint32_t passkey, uint16_t entered) { manager_.agent_display_passkey(device, passkey, entered); }), sdbus::registerMethod("RequestConfirmation") .withInputParamNames("device", "passkey") .withNoReply() .implementedAs([this](sdbus::Result&& result, sdbus::ObjectPath const& device, uint32_t passkey) { cancel_pending(); pending_confirm_ = std::move(result); manager_.agent_request_confirmation( device, passkey, [this](auto ok) { if (!pending_confirm_.has_value()) return; if (ok) { pending_confirm_->returnResults(Void::kInstance); } else { pending_confirm_->returnError( sdbus::Error{kErrorRejected}); } pending_confirm_ = std::nullopt; }); }), sdbus::registerMethod("RequestAuthorization") .withInputParamNames("device") .withNoReply() .implementedAs([this](sdbus::Result&& result, sdbus::ObjectPath const& device) { cancel_pending(); pending_confirm_ = std::move(result); manager_.agent_request_authorization(device, [this](auto ok) { if (!pending_confirm_.has_value()) return; if (ok) { pending_confirm_->returnResults(Void::kInstance); } else { pending_confirm_->returnError( sdbus::Error{kErrorRejected}); } pending_confirm_ = std::nullopt; }); }), sdbus::registerMethod("AuthorizeService") .withInputParamNames("device", "uuid") .withNoReply() .implementedAs([this](sdbus::Result&& result, sdbus::ObjectPath const& device, std::string const& uuid) { cancel_pending(); pending_confirm_ = std::move(result); manager_.agent_authorize_service( device, uuid, [this](auto ok) { if (!pending_confirm_.has_value()) return; if (ok) { pending_confirm_->returnResults(Void::kInstance); } else { pending_confirm_->returnError( sdbus::Error{kErrorRejected}); } pending_confirm_ = std::nullopt; }); }), sdbus::registerMethod("Cancel").withNoReply().implementedAs( [this]() { cancel_pending(); manager_.agent_cancel(); })) .forInterface(kAgentInterface); } [[nodiscard]] sdbus::ObjectPath const& object_path() const { return object_->getObjectPath(); } private: void cancel_pending() { if (pending_pincode_.has_value()) { pending_pincode_->returnError(sdbus::Error{kErrorCanceled}); pending_pincode_ = std::nullopt; } if (pending_passkey_.has_value()) { pending_passkey_->returnError(sdbus::Error{kErrorCanceled}); pending_passkey_ = std::nullopt; } if (pending_confirm_.has_value()) { pending_confirm_->returnError(sdbus::Error{kErrorCanceled}); pending_confirm_ = std::nullopt; } } IManager& manager_; std::unique_ptr object_; std::optional> pending_pincode_; std::optional> pending_passkey_; std::optional> pending_confirm_; }; class ManagerImpl : public Manager, public IManager, looper::Hook { public: ManagerImpl(logger::Logger& logger, cfg::Config const& cfg, looper::Looper& looper, Manager::Delegate& delegate) : logger_(logger), cfg_(cfg), looper_(looper), delegate_(delegate), conn_(sdbus::createSystemBusConnection()) { looper_.add_hook(this); root_proxy_ = std::make_unique( *this, *conn_, kService, sdbus::ObjectPath{"/"}, sdbus::ObjectPath{"/org/bluez"}); } ~ManagerImpl() override { if (agent_ && agent_manager_proxy_) { agent_manager_proxy_->unregister_agent_sync(agent_->object_path()); } looper_.remove_hook(this); } Adapter* adapter() override { if (primary_adapter_.empty()) return nullptr; return adapters_.at(primary_adapter_).get(); } private: void add_device(sdbus::ObjectPath const& path, std::map const& properties) override { logger_.dbg(std::format("Add device: {}", std::string(path))); auto [it, inserted] = devices_.emplace( path, std::make_unique(*this, *conn_, path, properties)); auto& device = *it->second; if (device.adapter() == primary_adapter_) { delegate_.added_device(device); } } void add_adapter(sdbus::ObjectPath const& path, std::map const& properties) override { logger_.dbg(std::format("Add adapter: {}", std::string(path))); adapters_.emplace( path, std::make_unique(*this, *conn_, path, properties)); if (primary_adapter_.empty()) { update_primary_adapter(); } } void add_player(sdbus::ObjectPath const& path, std::map const& properties) override { logger_.dbg(std::format("Add player: {}", std::string(path))); auto [it, inserted] = players_.emplace( path, std::make_unique(*this, *conn_, path, properties)); auto it2 = devices_.find(it->second->device()); if (it2 != devices_.end()) { update_device_player(*it2->second); } } void add_agent_manager(sdbus::ObjectPath const& path) override { logger_.dbg(std::format("Add agent manager: {}", std::string(path))); if (agent_manager_proxy_) return; agent_manager_proxy_ = std::make_unique(*conn_, path); agent_ = std::make_unique(*this, *conn_); agent_manager_proxy_->register_agent( agent_->object_path(), "", [this](std::optional err) { if (err.has_value()) { agent_.reset(); logger_.warn(std::format("Failed to register agent: {} {}", std::string(err->getName()), err->getMessage())); } else { logger_.info("Registered agent"); } }); } void remove_device(sdbus::ObjectPath const& path) override { logger_.dbg(std::format("Remove device: {}", std::string(path))); auto it = devices_.find(path); if (it == devices_.end()) return; auto& device = *it->second; if (device.adapter() == primary_adapter_) { auto address = device.address(); devices_.erase(it); delegate_.removed_device(address); } else { devices_.erase(it); } } void remove_player(sdbus::ObjectPath const& path) override { logger_.dbg(std::format("Remove player: {}", std::string(path))); auto it = players_.find(path); if (it == players_.end()) return; auto it2 = devices_.find(it->second->device()); players_.erase(it); if (it2 != devices_.end()) { update_device_player(*it2->second); } } void remove_adapter(sdbus::ObjectPath const& path) override { logger_.dbg(std::format("Remove adapter: {}", std::string(path))); auto it = adapters_.find(path); if (it != adapters_.end()) adapters_.erase(it); if (path == primary_adapter_) { update_primary_adapter(); } } void remove_agent_manager(sdbus::ObjectPath const& path) override { logger_.dbg(std::format("Remove agemtn manager: {}", std::string(path))); if (!agent_manager_proxy_) return; if (agent_manager_proxy_->object_path() != path) return; agent_manager_proxy_.reset(); agent_.reset(); } void update_device(sdbus::ObjectPath const& path) override { logger_.dbg(std::format("Update device: {}", std::string(path))); auto& device = *devices_.at(path); if (device.adapter() == primary_adapter_) { delegate_.updated_device(device); } } void update_adapter(sdbus::ObjectPath const& path) override { logger_.dbg(std::format("Update adapter: {}", std::string(path))); if (path == primary_adapter_) { delegate_.updated_adapter(*adapters_.at(path)); } else if (primary_adapter_.empty()) { update_primary_adapter(); } } void update_player(sdbus::ObjectPath const& path) override { logger_.dbg(std::format("Update player: {}", std::string(path))); auto it = players_.find(path); if (it == players_.end()) return; auto it2 = devices_.find(it->second->device()); if (it2 != devices_.end()) { update_device_player(*it2->second); if (it2->second->player() == it->second.get()) { delegate_.updated_player(*it2->second, *it->second); } } } [[nodiscard]] std::vector get_devices( sdbus::ObjectPath const& adapter_path) const override { std::vector ret; ret.reserve(devices_.size()); for (auto& pair : devices_) { if (pair.second->adapter() == adapter_path) { ret.push_back(pair.second.get()); } } return ret; } [[nodiscard]] Player* get_player(sdbus::ObjectPath const& player_path) const override { if (player_path.empty()) return nullptr; auto it = players_.find(player_path); if (it == players_.end()) return nullptr; return it->second.get(); } [[nodiscard]] logger::Logger& logger() override { return logger_; } void remove_agent() override { logger_.info("Remove agent"); agent_ = nullptr; } void agent_request_pincode( sdbus::ObjectPath const& device, std::function)> callback) override { auto it = devices_.find(device); if (it == devices_.end() || it->second->adapter() != primary_adapter_) { callback(std::nullopt); return; } delegate_.agent_request_pincode(*it->second, std::move(callback)); } void agent_display_pincode(sdbus::ObjectPath const& device, std::string const& pincode) override { auto it = devices_.find(device); if (it == devices_.end() || it->second->adapter() != primary_adapter_) return; delegate_.agent_display_pincode(*it->second, pincode); } void agent_request_passkey( sdbus::ObjectPath const& device, std::function)> callback) override { auto it = devices_.find(device); if (it == devices_.end() || it->second->adapter() != primary_adapter_) { callback(std::nullopt); return; } delegate_.agent_request_passkey(*it->second, std::move(callback)); } void agent_display_passkey(sdbus::ObjectPath const& device, uint32_t passkey, uint16_t entered) override { auto it = devices_.find(device); if (it == devices_.end() || it->second->adapter() != primary_adapter_) return; delegate_.agent_display_passkey(*it->second, passkey, entered); } void agent_request_confirmation(sdbus::ObjectPath const& device, uint32_t passkey, std::function callback) override { auto it = devices_.find(device); if (it == devices_.end() || it->second->adapter() != primary_adapter_) { callback(false); return; } delegate_.agent_request_confirmation(*it->second, passkey, std::move(callback)); } void agent_request_authorization( sdbus::ObjectPath const& device, std::function callback) override { auto it = devices_.find(device); if (it == devices_.end() || it->second->adapter() != primary_adapter_) { callback(false); return; } delegate_.agent_request_authorization(*it->second, std::move(callback)); } void agent_authorize_service(sdbus::ObjectPath const& device, std::string const& uuid, std::function callback) override { auto it = devices_.find(device); if (it == devices_.end() || it->second->adapter() != primary_adapter_) { callback(false); return; } delegate_.agent_authorize_service(*it->second, uuid, std::move(callback)); } void agent_cancel() override { delegate_.agent_cancel(); } void before(looper::HookHelper& helper) override { auto data = conn_->getEventLoopPollData(); uint8_t events = 0; // NOLINTNEXTLINE(misc-include-cleaner) if (data.events & (POLLIN | POLLPRI | POLLHUP)) events |= looper::EVENT_READ; // NOLINTNEXTLINE(misc-include-cleaner) if (data.events & POLLOUT) events |= looper::EVENT_WRITE; // NOLINTNEXTLINE(misc-include-cleaner) if (data.events & (POLLERR | POLLNVAL)) events |= looper::EVENT_ERROR; if (data.fd != -1 && events != 0) helper.monitor(data.fd, events); helper.monitor(data.eventFd, looper::EVENT_READ); auto timeout = data.getRelativeTimeout(); if (timeout != std::chrono::microseconds::max()) { helper.timeout( std::chrono::duration_cast(timeout)); } } void after() override { while (conn_->processPendingEvent()) ; } void update_primary_adapter() { bool const was_empty = primary_adapter_.empty(); primary_adapter_.clear(); auto cfg_adapter = cfg_.get("bluetooth.adapter"); if (cfg_adapter.has_value()) { for (auto const& pair : adapters_) { if (pair.second->address() == cfg_adapter.value() && pair.second->powered()) { primary_adapter_ = pair.first; delegate_.new_adapter(pair.second.get()); return; } } } else { // Pick first (map is sorted) that is pairable and powered. for (auto const& pair : adapters_) { if (pair.second->powered() && pair.second->pairable()) { primary_adapter_ = pair.first; delegate_.new_adapter(pair.second.get()); return; } } } if (!was_empty) delegate_.new_adapter(nullptr); } void update_device_player(DeviceProxy& device) { if (device.adapter() != primary_adapter_) return; sdbus::ObjectPath primary_player; for (auto& pair : players_) { if (pair.second->device() == device.getProxy().getObjectPath()) { if (primary_player.empty() || isActive(pair.second->status())) { primary_player = pair.first; if (isActive(pair.second->status())) break; } } } if (device.primary_player() == primary_player) return; device.set_primary_player(primary_player); if (primary_player.empty()) { delegate_.removed_player(device); } else { delegate_.added_player(device, *device.player()); } } logger::Logger& logger_; cfg::Config const& cfg_; looper::Looper& looper_; Manager::Delegate& delegate_; std::unique_ptr conn_; std::unique_ptr root_proxy_; std::unique_ptr agent_manager_proxy_; std::unique_ptr agent_; std::map> devices_; std::map> adapters_; std::map> players_; sdbus::ObjectPath primary_adapter_; }; } // namespace std::unique_ptr create_manager(logger::Logger& logger, cfg::Config const& cfg, looper::Looper& looper, Manager::Delegate& delegate) { return std::make_unique(logger, cfg, looper, delegate); } } // namespace bt