summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bt.cc953
-rw-r--r--src/bt.hh138
-rw-r--r--src/looper.hh30
-rw-r--r--src/looper_poll.cc176
-rw-r--r--src/main.cc95
-rw-r--r--src/signals.cc89
-rw-r--r--src/signals.hh33
-rw-r--r--src/unique_pipe.cc32
-rw-r--r--src/unique_pipe.hh42
9 files changed, 1539 insertions, 49 deletions
diff --git a/src/bt.cc b/src/bt.cc
new file mode 100644
index 0000000..6ab7d1d
--- /dev/null
+++ b/src/bt.cc
@@ -0,0 +1,953 @@
+#include "bt.hh"
+
+#include "cfg.hh"
+#include "logger.hh"
+#include "looper.hh"
+
+#include <chrono>
+#include <cstddef>
+#include <cstdint>
+#include <format>
+#include <functional>
+#include <map>
+#include <memory>
+#include <optional>
+#include <poll.h>
+#include <sdbus-c++/Error.h>
+#include <sdbus-c++/IConnection.h>
+#include <sdbus-c++/IObject.h>
+#include <sdbus-c++/IProxy.h>
+#include <sdbus-c++/MethodResult.h>
+#include <sdbus-c++/ProxyInterfaces.h>
+#include <sdbus-c++/StandardInterfaces.h>
+#include <sdbus-c++/Types.h>
+#include <sdbus-c++/VTableItems.h>
+#include <string>
+#include <utility>
+#include <vector>
+
+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<sdbus::PropertyName, sdbus::Variant> const& properties) = 0;
+ virtual void add_adapter(
+ sdbus::ObjectPath const& path,
+ std::map<sdbus::PropertyName, sdbus::Variant> 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 std::vector<Device*> get_devices(
+ sdbus::ObjectPath const& adapter_path) = 0;
+
+ virtual void agent_request_pincode(
+ sdbus::ObjectPath const& device,
+ std::function<void(std::optional<std::string>)> 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<void(std::optional<uint32_t>)> 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<void(bool)> callback) = 0;
+ virtual void agent_request_authorization(
+ sdbus::ObjectPath const& device, std::function<void(bool)> callback) = 0;
+ virtual void agent_authorize_service(sdbus::ObjectPath const& device,
+ std::string const& uuid,
+ std::function<void(bool)> 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 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<sdbus::InterfaceName,
+ std::map<sdbus::PropertyName, sdbus::Variant>> const&
+ interfaces_and_properties) {
+ interfaces_added(path, interfaces_and_properties);
+ });
+
+ proxy_->uponSignal("InterfacesRemoved")
+ .onInterface(kInterface)
+ .call([this](sdbus::ObjectPath const& path,
+ std::vector<sdbus::InterfaceName> const& interfaces) {
+ this->interfaces_removed(path, interfaces);
+ });
+
+ proxy_->callMethodAsync("GetManagedObjects")
+ .onInterface(kInterface)
+ .uponReplyInvoke(
+ [this](
+ std::optional<sdbus::Error> err,
+ std::map<sdbus::ObjectPath,
+ std::map<sdbus::InterfaceName,
+ std::map<sdbus::PropertyName,
+ sdbus::Variant>>> 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<sdbus::InterfaceName,
+ std::map<sdbus::PropertyName, sdbus::Variant>> 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);
+ }
+ }
+
+ void interfaces_removed(sdbus::ObjectPath const& object_path,
+ std::vector<sdbus::InterfaceName> 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);
+ }
+ }
+ }
+
+ std::unique_ptr<sdbus::IProxy> proxy_;
+ IManager& manager_;
+ sdbus::ObjectPath filter_;
+};
+
+class BaseProxy : public sdbus::ProxyInterfaces<sdbus::Properties_proxy> {
+ public:
+ virtual ~BaseProxy() { unregisterProxy(); }
+
+ protected:
+ BaseProxy(sdbus::IConnection& connection, sdbus::ObjectPath const& path,
+ std::map<sdbus::PropertyName, size_t> const& names)
+ : sdbus::ProxyInterfaces<sdbus::Properties_proxy>(connection, kService,
+ path),
+ names_(names) {}
+
+ virtual void update(size_t field, sdbus::Variant const& value) = 0;
+ virtual void notify_updated() = 0;
+
+ void init(std::map<sdbus::PropertyName, sdbus::Variant> 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<sdbus::PropertyName, sdbus::Variant>& changed,
+ const std::vector<sdbus::PropertyName>& invalidated) override {
+ if (interface_name != kAdapterInterface)
+ 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<sdbus::Error> 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();
+ }
+ }
+
+ std::map<sdbus::PropertyName, size_t> const& names_;
+ uint8_t waiting_{0};
+ bool updated_{false};
+};
+
+class DeviceProxy : public BaseProxy, public Device {
+ private:
+ static inline const std::map<sdbus::PropertyName, size_t> 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<sdbus::PropertyName, sdbus::Variant> const& properties)
+ : BaseProxy(connection, path, 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]]
+ sdbus::ObjectPath const& adapter() const {
+ return adapter_;
+ }
+
+ private:
+ void update(size_t field, sdbus::Variant const& value) override {
+ switch (field) {
+ case 0:
+ address_ = value.get<std::string>();
+ break;
+ case 1:
+ name_ = value.isEmpty()
+ ? std::nullopt
+ : std::optional<std::string>(value.get<std::string>());
+ break;
+ case 2:
+ alias_ = value.isEmpty()
+ ? std::nullopt
+ : std::optional<std::string>(value.get<std::string>());
+ break;
+ case 3:
+ paired_ = value.get<bool>();
+ break;
+ case 4:
+ connected_ = value.get<bool>();
+ break;
+ case 5:
+ adapter_ = value.get<std::string>();
+ break;
+ default:
+ std::unreachable();
+ }
+ }
+
+ void notify_updated() override {
+ manager_.update_device(getProxy().getObjectPath());
+ }
+
+ IManager& manager_;
+ std::string address_;
+ std::optional<std::string> name_;
+ std::optional<std::string> alias_;
+ bool connected_{false};
+ bool paired_{false};
+ sdbus::ObjectPath adapter_;
+};
+
+class AdapterProxy : public BaseProxy, public Adapter {
+ private:
+ static inline const std::map<sdbus::PropertyName, size_t> 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},
+ };
+
+ public:
+ AdapterProxy(IManager& manager, sdbus::IConnection& connection,
+ sdbus::ObjectPath const& path,
+ std::map<sdbus::PropertyName, sdbus::Variant> const& properties)
+ : BaseProxy(connection, path, 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]]
+ bool pairable() const override {
+ return pairable_;
+ }
+
+ [[nodiscard]]
+ bool pairing() const override {
+ return pairing_;
+ }
+
+ [[nodiscard]]
+ std::vector<Device*> devices() const override {
+ return manager_.get_devices(getProxy().getObjectPath());
+ }
+
+ private:
+ void update(size_t field, sdbus::Variant const& value) override {
+ switch (field) {
+ case 0:
+ address_ = value.get<std::string>();
+ break;
+ case 1:
+ name_ = value.get<std::string>();
+ break;
+ case 2:
+ alias_ = value.get<std::string>();
+ break;
+ case 3:
+ discoverable_ = value.get<bool>();
+ break;
+ case 4:
+ pairable_ = value.get<bool>();
+ manager_.logger().dbg(std::format("pairable: {}", pairable_));
+ break;
+ case 5:
+ pairing_ = value.get<bool>();
+ break;
+ default:
+ std::unreachable();
+ }
+ }
+
+ 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};
+};
+
+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<void(std::optional<sdbus::Error> 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<sdbus::IProxy> 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<std::string>&& 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<uint32_t>&& 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")
+ .withOutputParamNames("result")
+ .implementedAs([this](sdbus::Result<sdbus::Variant>&& 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({});
+ } else {
+ pending_confirm_->returnError(
+ sdbus::Error{kErrorRejected});
+ }
+ pending_confirm_ = std::nullopt;
+ });
+ }),
+ sdbus::registerMethod("RequestAuthorization")
+ .withInputParamNames("device")
+ .withOutputParamNames("result")
+ .implementedAs([this](sdbus::Result<sdbus::Variant>&& 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({});
+ } else {
+ pending_confirm_->returnError(
+ sdbus::Error{kErrorRejected});
+ }
+ pending_confirm_ = std::nullopt;
+ });
+ }),
+ sdbus::registerMethod("AuthorizeService")
+ .withInputParamNames("device", "uuid")
+ .withOutputParamNames("result")
+ .implementedAs([this](sdbus::Result<sdbus::Variant>&& 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({});
+ } 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<sdbus::IObject> object_;
+
+ std::optional<sdbus::Result<std::string>> pending_pincode_;
+ std::optional<sdbus::Result<uint32_t>> pending_passkey_;
+ std::optional<sdbus::Result<sdbus::Variant>> 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<ObjectManagerProxy>(
+ *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<sdbus::PropertyName, sdbus::Variant> const&
+ properties) override {
+ logger_.dbg(std::format("Add device: {}", std::string(path)));
+ auto [it, inserted] = devices_.emplace(
+ path, std::make_unique<DeviceProxy>(*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<sdbus::PropertyName, sdbus::Variant> const&
+ properties) override {
+ logger_.dbg(std::format("Add adapter: {}", std::string(path)));
+ adapters_.emplace(
+ path, std::make_unique<AdapterProxy>(*this, *conn_, path, properties));
+
+ if (primary_adapter_.empty()) {
+ update_primary_adapter();
+ }
+ }
+
+ 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<AgentManagerProxy>(*conn_, path);
+ agent_ = std::make_unique<AgentObject>(*this, *conn_);
+ agent_manager_proxy_->register_agent(
+ agent_->object_path(), "", [this](std::optional<sdbus::Error> 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_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));
+ }
+ }
+
+ std::vector<Device*> get_devices(
+ sdbus::ObjectPath const& adapter_path) override {
+ std::vector<Device*> ret;
+ ret.reserve(devices_.size());
+ for (auto& pair : devices_) {
+ if (pair.second->adapter() == adapter_path) {
+ ret.push_back(pair.second.get());
+ }
+ }
+ return ret;
+ }
+
+ [[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<void(std::optional<std::string>)> 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<void(std::optional<uint32_t>)> 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<void(bool)> 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<void(bool)> 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<void(bool)> 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<std::chrono::milliseconds>(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()) {
+ primary_adapter_ = pair.first;
+ delegate_.new_adapter(pair.second.get());
+ return;
+ }
+ }
+ } else {
+ // Pick first (map is sorted) that is pairable.
+ for (auto const& pair : adapters_) {
+ if (pair.second->pairable()) {
+ primary_adapter_ = pair.first;
+ delegate_.new_adapter(pair.second.get());
+ return;
+ }
+ }
+ }
+
+ if (!was_empty)
+ delegate_.new_adapter(nullptr);
+ }
+
+ logger::Logger& logger_;
+ cfg::Config const& cfg_;
+ looper::Looper& looper_;
+ Manager::Delegate& delegate_;
+ std::unique_ptr<sdbus::IConnection> conn_;
+ std::unique_ptr<ObjectManagerProxy> root_proxy_;
+ std::unique_ptr<AgentManagerProxy> agent_manager_proxy_;
+ std::unique_ptr<AgentObject> agent_;
+ std::map<sdbus::ObjectPath, std::unique_ptr<DeviceProxy>> devices_;
+ std::map<sdbus::ObjectPath, std::unique_ptr<AdapterProxy>> adapters_;
+ sdbus::ObjectPath primary_adapter_;
+};
+
+} // namespace
+
+std::unique_ptr<Manager> create_manager(logger::Logger& logger,
+ cfg::Config const& cfg,
+ looper::Looper& looper,
+ Manager::Delegate& delegate) {
+ return std::make_unique<ManagerImpl>(logger, cfg, looper, delegate);
+}
+
+} // namespace bt
diff --git a/src/bt.hh b/src/bt.hh
new file mode 100644
index 0000000..53d3919
--- /dev/null
+++ b/src/bt.hh
@@ -0,0 +1,138 @@
+#ifndef BT_HH
+#define BT_HH
+
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace logger {
+class Logger;
+} // namespace logger
+
+namespace looper {
+class Looper;
+} // namespace looper
+
+namespace cfg {
+class Config;
+} // namespace cfg
+
+namespace bt {
+
+class Device;
+
+class Adapter {
+ public:
+ virtual ~Adapter() = default;
+
+ [[nodiscard]]
+ virtual std::string const& address() const = 0;
+
+ [[nodiscard]]
+ virtual std::string const& name() const = 0;
+
+ [[nodiscard]]
+ virtual bool discoverable() const = 0;
+
+ [[nodiscard]]
+ virtual bool pairable() const = 0;
+
+ [[nodiscard]]
+ virtual bool pairing() const = 0;
+
+ [[nodiscard]]
+ virtual std::vector<Device*> devices() const = 0;
+
+ protected:
+ Adapter() = default;
+ Adapter(Adapter const&) = delete;
+ Adapter& operator=(Adapter const&) = delete;
+};
+
+class Device {
+ public:
+ virtual ~Device() = default;
+
+ [[nodiscard]]
+ virtual std::string const& address() const = 0;
+
+ [[nodiscard]]
+ virtual std::string const& name() const = 0;
+
+ [[nodiscard]]
+ virtual bool paired() const = 0;
+
+ [[nodiscard]]
+ virtual bool connected() const = 0;
+
+ protected:
+ Device() = default;
+ Device(Device const&) = delete;
+ Device& operator=(Device const&) = delete;
+};
+
+class Manager {
+ public:
+ virtual ~Manager() = default;
+
+ class Delegate {
+ public:
+ virtual ~Delegate() = default;
+
+ // Called when "primary" adapter changes from one to another.
+ virtual void new_adapter(Adapter* /* adapter */) {}
+ // Called when "primary" adapter changes a property.
+ virtual void updated_adapter(Adapter& /* adapter */) {}
+
+ // Called when a new device is added to adapter.
+ virtual void added_device(Device& /* device */) {}
+ // Called when a device was removed from adapter.
+ virtual void removed_device(std::string const& /* address */) {}
+ // Called when device changes a property.
+ virtual void updated_device(Device& /* device */) {}
+
+ virtual void agent_request_pincode(
+ Device& device,
+ std::function<void(std::optional<std::string>)> callback) = 0;
+ virtual void agent_display_pincode(Device& device,
+ std::string const& pincode) = 0;
+ virtual void agent_request_passkey(
+ Device& device,
+ std::function<void(std::optional<uint32_t>)> callback) = 0;
+ virtual void agent_display_passkey(Device& device, uint32_t passkey,
+ uint16_t entered) = 0;
+ virtual void agent_request_confirmation(
+ Device& device, uint32_t passkey,
+ std::function<void(bool)> callback) = 0;
+ virtual void agent_request_authorization(
+ Device& device, std::function<void(bool)> callback) = 0;
+ virtual void agent_authorize_service(
+ Device& device, std::string const& uuid,
+ std::function<void(bool)> callback) = 0;
+ virtual void agent_cancel() = 0;
+
+ protected:
+ Delegate() = default;
+ };
+
+ // Returns null if there are no adapters.
+ // Otherwise, returns the "primary" adapter.
+ virtual Adapter* adapter() = 0;
+
+ protected:
+ Manager() = default;
+ Manager(Manager const&) = delete;
+ Manager& operator=(Manager const&) = delete;
+};
+
+std::unique_ptr<Manager> create_manager(logger::Logger& logger,
+ cfg::Config const& cfg,
+ looper::Looper& looper,
+ Manager::Delegate& delegate);
+
+} // namespace bt
+
+#endif // BT_HH
diff --git a/src/looper.hh b/src/looper.hh
index 2f4c672..092dfe5 100644
--- a/src/looper.hh
+++ b/src/looper.hh
@@ -1,6 +1,7 @@
#ifndef LOOPER_HH
#define LOOPER_HH
+#include <chrono>
#include <cstdint>
#include <functional>
#include <memory>
@@ -15,6 +16,8 @@ constexpr static uint8_t EVENT_READ = 1;
constexpr static uint8_t EVENT_WRITE = 2;
constexpr static uint8_t EVENT_ERROR = 4;
+class Hook;
+
class Looper {
public:
virtual ~Looper() = default;
@@ -32,12 +35,39 @@ class Looper {
virtual bool run(logger::Logger& logger) = 0;
virtual void quit() = 0;
+ virtual void add_hook(Hook* hook) = 0;
+ virtual void remove_hook(Hook* hook) = 0;
+
protected:
Looper() = default;
Looper(Looper const&) = delete;
Looper& operator=(Looper const&) = delete;
};
+class HookHelper {
+ public:
+ virtual ~HookHelper() = default;
+
+ virtual void monitor(int fd, uint8_t events) = 0;
+ virtual void timeout(std::chrono::milliseconds timeout) = 0;
+
+ protected:
+ HookHelper() = default;
+ HookHelper(HookHelper const&) = delete;
+ HookHelper& operator=(HookHelper const&) = delete;
+};
+
+class Hook {
+ public:
+ virtual ~Hook() = default;
+
+ virtual void before(HookHelper& helper) = 0;
+ virtual void after() = 0;
+
+ protected:
+ Hook() = default;
+};
+
[[nodiscard]]
std::unique_ptr<Looper> create();
diff --git a/src/looper_poll.cc b/src/looper_poll.cc
index 2fed7d2..e6bf2cd 100644
--- a/src/looper_poll.cc
+++ b/src/looper_poll.cc
@@ -21,6 +21,64 @@ namespace looper {
namespace {
+inline short events_looper2poll(uint8_t events) {
+ short ret = 0;
+ if (events & EVENT_READ)
+ // NOLINTNEXTLINE(misc-include-cleaner)
+ ret |= POLLIN | POLLPRI;
+ if (events & EVENT_WRITE)
+ // NOLINTNEXTLINE(misc-include-cleaner)
+ ret |= POLLOUT;
+ return ret;
+}
+
+inline uint8_t events_poll2looper(short events) {
+ uint8_t ret = 0;
+ // NOLINTNEXTLINE(misc-include-cleaner)
+ if (events & (POLLIN | POLLPRI | POLLHUP))
+ ret |= EVENT_READ;
+ // NOLINTNEXTLINE(misc-include-cleaner)
+ if (events & POLLOUT)
+ ret |= EVENT_WRITE;
+ // NOLINTNEXTLINE(misc-include-cleaner)
+ if (events & (POLLERR | POLLNVAL))
+ ret |= EVENT_ERROR;
+ return ret;
+}
+
+class HookHelperImpl : public HookHelper {
+ public:
+ // NOLINTNEXTLINE(misc-include-cleaner)
+ HookHelperImpl(std::vector<struct pollfd>& pollfd, int& timeout)
+ : pollfd_(pollfd), timeout_(timeout) {}
+
+ void monitor(int fd, uint8_t events) override {
+ // NOLINTNEXTLINE(misc-include-cleaner)
+ struct pollfd tmp;
+ tmp.fd = fd;
+ tmp.events = events_looper2poll(events);
+ pollfd_.push_back(tmp);
+ }
+
+ void timeout(std::chrono::milliseconds timeout) override {
+ int new_timeout;
+ if (timeout.count() <= std::numeric_limits<int>::max()) {
+ new_timeout = static_cast<int>(timeout.count());
+ } else {
+ new_timeout = std::numeric_limits<int>::max();
+ }
+ if (timeout_ == -1) {
+ timeout_ = new_timeout;
+ } else {
+ timeout_ = std::min(timeout_, new_timeout);
+ }
+ }
+
+ private:
+ std::vector<struct pollfd>& pollfd_;
+ int& timeout_;
+};
+
class LooperPoll : public Looper {
public:
LooperPoll() = default;
@@ -91,17 +149,31 @@ class LooperPoll : public Looper {
// NOLINTNEXTLINE(misc-include-cleaner)
std::vector<struct pollfd> pollfd;
pollfd.reserve(entry_.size());
- auto it = entry_.begin();
- while (it != entry_.end()) {
- if (it->second.delete_) {
- it = entry_.erase(it);
- } else {
- // NOLINTNEXTLINE(misc-include-cleaner)
- struct pollfd tmp;
- tmp.fd = it->first;
- tmp.events = events_looper2poll(it->second.events_);
- pollfd.push_back(tmp);
- ++it;
+ {
+ auto it = entry_.begin();
+ while (it != entry_.end()) {
+ if (it->second.delete_) {
+ it = entry_.erase(it);
+ } else {
+ // NOLINTNEXTLINE(misc-include-cleaner)
+ struct pollfd tmp;
+ tmp.fd = it->first;
+ tmp.events = events_looper2poll(it->second.events_);
+ pollfd.push_back(tmp);
+ ++it;
+ }
+ }
+ }
+ if (!hooks_.empty()) {
+ HookHelperImpl helper(pollfd, timeout);
+ auto it = hooks_.begin();
+ while (it != hooks_.end()) {
+ if (it->delete_) {
+ it = hooks_.erase(it);
+ } else {
+ it->hook_->before(helper);
+ ++it;
+ }
}
}
// NOLINTNEXTLINE(misc-include-cleaner)
@@ -112,21 +184,36 @@ class LooperPoll : public Looper {
logger.err(std::format("Poll failed: {}", strerror(errno)));
return false;
}
- for (auto it2 = pollfd.begin(); active; ++it2) {
- if (it2->revents == 0)
- continue;
- --active;
- auto events = events_poll2looper(it2->revents);
- if (events) {
- it = entry_.find(it2->fd);
- if (!it->second.delete_) {
- events &= (it->second.events_ | EVENT_ERROR);
- if (events) {
- it->second.callback_(events);
+ {
+ for (auto it = pollfd.begin(); active; ++it) {
+ if (it->revents == 0)
+ continue;
+ --active;
+ auto events = events_poll2looper(it->revents);
+ if (events) {
+ auto it2 = entry_.find(it->fd);
+ if (it2 == entry_.end())
+ break;
+ if (!it2->second.delete_) {
+ events &= (it2->second.events_ | EVENT_ERROR);
+ if (events) {
+ it2->second.callback_(events);
+ }
}
}
}
}
+ {
+ auto it = hooks_.begin();
+ while (it != hooks_.end()) {
+ if (it->delete_) {
+ it = hooks_.erase(it);
+ } else {
+ it->hook_->after();
+ ++it;
+ }
+ }
+ }
}
// Reset quit_ so run() can be called again
quit_ = false;
@@ -165,6 +252,18 @@ class LooperPoll : public Looper {
}
}
+ void add_hook(Hook* hook) override {
+ assert(hook);
+ hooks_.emplace_back(hook);
+ }
+
+ void remove_hook(Hook* hook) override {
+ auto it = std::ranges::find_if(
+ hooks_, [hook](auto const& entry) { return entry.hook_ == hook; });
+ if (it != hooks_.end())
+ it->delete_ = true;
+ }
+
private:
struct Entry {
uint8_t events_;
@@ -174,6 +273,13 @@ class LooperPoll : public Looper {
explicit Entry(uint8_t events) : events_(events) {}
};
+ struct HookEntry {
+ Hook* hook_;
+ bool delete_{false};
+
+ explicit HookEntry(Hook* hook) : hook_(hook) {}
+ };
+
struct Scheduled {
std::function<void(uint32_t)> callback_;
uint32_t id_;
@@ -184,31 +290,6 @@ class LooperPoll : public Looper {
: callback_(std::move(callback)), id_(id), target_(target) {}
};
- static short events_looper2poll(uint8_t events) {
- short ret = 0;
- if (events & EVENT_READ)
- // NOLINTNEXTLINE(misc-include-cleaner)
- ret |= POLLIN | POLLPRI;
- if (events & EVENT_WRITE)
- // NOLINTNEXTLINE(misc-include-cleaner)
- ret |= POLLOUT;
- return ret;
- }
-
- static uint8_t events_poll2looper(short events) {
- uint8_t ret = 0;
- // NOLINTNEXTLINE(misc-include-cleaner)
- if (events & (POLLIN | POLLPRI | POLLHUP))
- ret |= EVENT_READ;
- // NOLINTNEXTLINE(misc-include-cleaner)
- if (events & POLLOUT)
- ret |= EVENT_WRITE;
- // NOLINTNEXTLINE(misc-include-cleaner)
- if (events & (POLLERR | POLLNVAL))
- ret |= EVENT_ERROR;
- return ret;
- }
-
uint32_t next_schedule_id() {
while (true) {
uint32_t ret = next_schedule_id_++;
@@ -226,6 +307,7 @@ class LooperPoll : public Looper {
std::unordered_map<int, Entry> entry_;
uint32_t next_schedule_id_{1};
std::deque<Scheduled> scheduled_;
+ std::vector<HookEntry> hooks_;
};
} // namespace
diff --git a/src/main.cc b/src/main.cc
index 54f3d0a..6883f30 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -1,15 +1,21 @@
#include "args.hh"
+#include "bt.hh"
#include "cfg.hh"
#include "config.h"
#include "http.hh"
#include "logger.hh"
#include "looper.hh"
+#include "signals.hh"
#include <cerrno>
+#include <cstdint>
#include <cstring>
#include <format>
+#include <functional>
#include <iostream>
#include <memory>
+#include <optional>
+#include <string>
#include <unistd.h>
#include <utility>
#include <vector>
@@ -40,12 +46,97 @@ class HttpServerDelegate : public http::Server::Delegate {
}
};
+class BluetoothManagerDelegate : public bt::Manager::Delegate {
+ public:
+ explicit BluetoothManagerDelegate(logger::Logger& logger) : logger_(logger) {}
+
+ void new_adapter(bt::Adapter* adapter) override {
+ if (adapter) {
+ logger_.info(std::format("New adapter: {} [{}]", adapter->name(),
+ adapter->address()));
+ } else {
+ logger_.info("No adapter");
+ }
+ }
+
+ void added_device(bt::Device& device) override {
+ logger_.info(
+ std::format("New device: {} [{}]", device.name(), device.address()));
+ }
+
+ void removed_device(std::string const& address) override {
+ logger_.info(std::format("Remove device: [{}]", address));
+ }
+
+ void agent_request_pincode(
+ bt::Device& device,
+ std::function<void(std::optional<std::string>)> callback) override {
+ logger_.info(std::format("Device request pincode: {}", device.name()));
+ callback(std::nullopt);
+ }
+
+ void agent_display_pincode(bt::Device& device,
+ std::string const& pincode) override {
+ logger_.info(std::format("Device pincode: {} {}", device.name(), pincode));
+ }
+
+ void agent_request_passkey(
+ bt::Device& device,
+ std::function<void(std::optional<uint32_t>)> callback) override {
+ logger_.info(std::format("Device request passkey: {}", device.name()));
+ callback(std::nullopt);
+ }
+
+ void agent_display_passkey(bt::Device& device, uint32_t passkey,
+ uint16_t /* entered */) override {
+ logger_.info(std::format("Device passkey: {} {}", device.name(), passkey));
+ }
+
+ void agent_request_confirmation(bt::Device& device, uint32_t passkey,
+ std::function<void(bool)> callback) override {
+ logger_.info(std::format("Device request confirmation: {} {}",
+ device.name(), passkey));
+ callback(false);
+ }
+
+ void agent_request_authorization(
+ bt::Device& device, std::function<void(bool)> callback) override {
+ logger_.info(
+ std::format("Device request authorization: {}", device.name()));
+ callback(false);
+ }
+
+ void agent_authorize_service(bt::Device& device, std::string const& uuid,
+ std::function<void(bool)> callback) override {
+ logger_.info(std::format("Device request authorize service: {} {}",
+ device.name(), uuid));
+ callback(false);
+ }
+
+ void agent_cancel() override {}
+
+ private:
+ logger::Logger& logger_;
+};
+
bool run(logger::Logger& logger, cfg::Config const& cfg,
std::unique_ptr<http::OpenPort> port) {
auto looper = looper::create();
- HttpServerDelegate delegate;
+ HttpServerDelegate http_delegate;
auto server =
- http::create_server(logger, cfg, *looper, std::move(port), delegate);
+ http::create_server(logger, cfg, *looper, std::move(port), http_delegate);
+ BluetoothManagerDelegate bt_delegate(logger);
+ auto manager = bt::create_manager(logger, cfg, *looper, bt_delegate);
+ auto sigint_handler = signals::Handler::create(
+ *looper, signals::Signal::INT, [&looper, &logger]() {
+ logger.info("Received SIGINT, quitting...");
+ looper->quit();
+ });
+ auto sigterm_handler = signals::Handler::create(
+ *looper, signals::Signal::TERM, [&looper, &logger]() {
+ logger.info("Received SIGTERM, quitting...");
+ looper->quit();
+ });
return looper->run(logger);
}
diff --git a/src/signals.cc b/src/signals.cc
new file mode 100644
index 0000000..9f8bff7
--- /dev/null
+++ b/src/signals.cc
@@ -0,0 +1,89 @@
+#include "signals.hh"
+
+#include "looper.hh"
+#include "unique_pipe.hh"
+
+#include <cerrno>
+#include <csignal>
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <unistd.h>
+#include <unordered_map>
+#include <utility>
+
+namespace signals {
+
+namespace {
+
+std::unordered_map<int, int> g_fds;
+
+int signum(Signal signal) {
+ switch (signal) {
+ case Signal::INT:
+ return SIGINT;
+ case Signal::TERM:
+ return SIGTERM;
+ }
+ std::unreachable();
+}
+
+void signal_handler(int signum) {
+ auto it = g_fds.find(signum);
+ if (it != g_fds.end()) {
+ char c = 1;
+ while (true) {
+ auto ret = write(it->second, &c, 1);
+ if (ret < 0 && errno == EINTR)
+ continue;
+ break;
+ }
+ }
+}
+
+class HandlerImpl : public Handler {
+ public:
+ HandlerImpl(looper::Looper& looper, Signal signal,
+ std::function<void()> callback)
+ : looper_(looper),
+ signal_(signum(signal)),
+ callback_(std::move(callback)) {
+ looper_.add(pipe_.reader(), looper::EVENT_READ,
+ [this](auto event) { call(event); });
+ g_fds[signal_] = pipe_.writer();
+
+ ::signal(signal_, signal_handler);
+ }
+
+ ~HandlerImpl() override {
+ ::signal(signal_, SIG_DFL);
+ looper_.remove(pipe_.reader());
+ g_fds.erase(signal_);
+ }
+
+ private:
+ void call(uint8_t /* event */) {
+ char buf[10];
+ while (true) {
+ auto ret = read(pipe_.reader(), buf, 10);
+ if (ret < 0 && errno == EINTR)
+ continue;
+ break;
+ }
+ callback_();
+ }
+
+ unique_pipe pipe_;
+ looper::Looper& looper_;
+ int signal_;
+ std::function<void()> callback_;
+};
+
+} // namespace
+
+std::unique_ptr<Handler> Handler::create(looper::Looper& looper, Signal signal,
+ std::function<void()> callback) {
+ return std::make_unique<HandlerImpl>(looper, signal, std::move(callback));
+}
+
+} // namespace signals
diff --git a/src/signals.hh b/src/signals.hh
new file mode 100644
index 0000000..f387f9f
--- /dev/null
+++ b/src/signals.hh
@@ -0,0 +1,33 @@
+#ifndef SIGNALS_HH
+#define SIGNALS_HH
+
+#include <functional>
+#include <memory>
+
+namespace looper {
+class Looper;
+} // namespace looper
+
+namespace signals {
+
+enum class Signal {
+ INT,
+ TERM,
+};
+
+class Handler {
+ public:
+ virtual ~Handler() = default;
+
+ static std::unique_ptr<Handler> create(looper::Looper& looper, Signal signal,
+ std::function<void()> callback);
+
+ protected:
+ Handler() = default;
+ Handler(Handler const&) = delete;
+ Handler& operator=(Handler const&) = delete;
+};
+
+} // namespace signals
+
+#endif // SIGNALS_HH
diff --git a/src/unique_pipe.cc b/src/unique_pipe.cc
new file mode 100644
index 0000000..28c106d
--- /dev/null
+++ b/src/unique_pipe.cc
@@ -0,0 +1,32 @@
+#include "unique_pipe.hh"
+
+#include <unistd.h>
+#include <utility>
+
+unique_pipe::unique_pipe() {
+ int fd[2];
+ if (pipe(fd))
+ return;
+ fd_[0] = unique_fd(fd[0]);
+ fd_[1] = unique_fd(fd[1]);
+}
+
+unique_pipe::unique_pipe(unique_pipe&& fd) {
+ fd_[0] = unique_fd(fd.fd_[0].release());
+ fd_[1] = unique_fd(fd.fd_[1].release());
+}
+
+unique_pipe& unique_pipe::operator=(unique_pipe&& fd) {
+ fd_[0].reset(fd.fd_[0].release());
+ fd_[1].reset(fd.fd_[1].release());
+ return *this;
+}
+
+void unique_pipe::reset() {
+ fd_[0].reset();
+ fd_[1].reset();
+}
+
+unique_fd unique_pipe::release_reader() { return std::move(fd_[0]); }
+
+unique_fd unique_pipe::release_writer() { return std::move(fd_[1]); }
diff --git a/src/unique_pipe.hh b/src/unique_pipe.hh
new file mode 100644
index 0000000..c133018
--- /dev/null
+++ b/src/unique_pipe.hh
@@ -0,0 +1,42 @@
+#ifndef UNIQUE_PIPE_HH
+#define UNIQUE_PIPE_HH
+
+#include "unique_fd.hh"
+
+#include <cstddef>
+
+class unique_pipe {
+ public:
+ unique_pipe();
+ unique_pipe(unique_pipe&& fd);
+ unique_pipe(unique_pipe const&) = delete;
+ unique_pipe& operator=(unique_pipe const&) = delete;
+
+ ~unique_pipe() = default;
+
+ unique_pipe& operator=(unique_pipe&& fd);
+
+ [[nodiscard]]
+ int reader() const {
+ return fd_[0].get();
+ }
+ [[nodiscard]]
+ int writer() const {
+ return fd_[1].get();
+ }
+
+ [[nodiscard]]
+ explicit operator bool() const {
+ return fd_[0] || fd_[1];
+ }
+
+ void reset();
+
+ unique_fd release_reader();
+ unique_fd release_writer();
+
+ private:
+ unique_fd fd_[2];
+};
+
+#endif // UNIQUE_PIPE_HH