#include "args.hh" #include "bt.hh" #include "cfg.hh" #include "config.h" #include "http.hh" #include "logger.hh" #include "looper.hh" #include "server.hh" #include "signals.hh" #include "websocket.hh" #include #include #include #include #include #include #include #include #include #include #include #include #ifndef VERSION # define VERSION "unknown" #endif namespace { class FakeConfig : public cfg::Config { public: [[nodiscard]] std::optional get( std::string_view /* name */) const override { return std::nullopt; } }; class FakeApi { public: virtual ~FakeApi() = default; [[nodiscard]] virtual std::vector devices() = 0; virtual void update_adapter() = 0; virtual void update_device(bt::Device& device) = 0; virtual void update_player(bt::Player& player) = 0; protected: FakeApi() = default; }; class FakeAdapter : public bt::Adapter { public: explicit FakeAdapter(FakeApi& api) : api_(api) {} [[nodiscard]] std::string const& address() const override { return address_; } [[nodiscard]] std::string const& name() const override { return name_; } [[nodiscard]] bool discoverable() const override { return discoverable_; } [[nodiscard]] uint32_t discoverable_timeout_seconds() const override { return discoverable_timeout_seconds_; } [[nodiscard]] bool pairable() const override { return true; } [[nodiscard]] uint32_t pairable_timeout_seconds() const override { return pairable_timeout_seconds_; } [[nodiscard]] bool pairing() const override { return false; } [[nodiscard]] bool powered() const override { return true; } [[nodiscard]] bool connectable() const override { return true; } [[nodiscard]] std::vector devices() const override { return api_.devices(); } void set_discoverable(bool discoverable) override { if (discoverable == discoverable_) return; discoverable_ = discoverable; api_.update_adapter(); } void set_discoverable_timeout_seconds(uint32_t timeout) override { if (discoverable_timeout_seconds_ == timeout) return; discoverable_timeout_seconds_ = timeout; api_.update_adapter(); } void set_pairable_timeout_seconds(uint32_t timeout) override { if (pairable_timeout_seconds_ == timeout) return; pairable_timeout_seconds_ = timeout; api_.update_adapter(); } private: FakeApi& api_; std::string const address_{"00:11:22:33:44:66"}; std::string const name_{"Fake adapter"}; bool discoverable_{false}; uint32_t discoverable_timeout_seconds_{180}; uint32_t pairable_timeout_seconds_{0}; }; class FakeDevice : public bt::Device { public: FakeDevice(FakeApi& api, std::string address, std::string name) : api_(api), address_(std::move(address)), name_(std::move(name)) {} [[nodiscard]] std::string const& address() const override { return address_; } [[nodiscard]] std::string const& name() const override { return name_; } [[nodiscard]] bool paired() const override { return paired_; } [[nodiscard]] bool connected() const override { return player_ != nullptr; } // Returns null if there are no players. // Otherwise, returns the "primary" player. [[nodiscard]] bt::Player* player() const override { return player_; } void set_paired(bool paired) { if (paired_ == paired) return; paired_ = paired; api_.update_device(*this); } void set_player(bt::Player* player) { if (player_ == player) return; player_ = player; api_.update_device(*this); } private: FakeApi& api_; std::string const address_; std::string const name_; bool paired_{false}; bt::Player* player_{nullptr}; }; class FakePlayer : public bt::Player { public: explicit FakePlayer(FakeApi& api) : api_(api) {} [[nodiscard]] Status status() const override { return status_; } [[nodiscard]] std::string const& track_title() const override { return title_; } [[nodiscard]] std::string const& track_artist() const override { return artist_; } [[nodiscard]] std::string const& track_album() const override { return album_; } void play() override { if (status_ == Status::kPlaying) return; status_ = Status::kPlaying; api_.update_player(*this); } void pause() override { if (status_ == Status::kPaused) return; status_ = Status::kPaused; api_.update_player(*this); } void set(std::string const& title, std::string const& artist, std::string const& album) { if (title_ == title && artist_ == artist && album_ == album) return; title_ = title; artist_ = artist; album_ = album; api_.update_player(*this); } private: FakeApi& api_; Status status_{Status::kStopped}; std::string title_; std::string artist_; std::string album_; }; class FakeBluetoothManager : public FakeApi { public: FakeBluetoothManager(looper::Looper& looper, bt::Manager::Delegate& delegate) : looper_(looper), delegate_(delegate) { devices_.emplace_back(std::make_unique( *this, "11:22:33:44:55:66", "Fake device 1")); devices_.emplace_back(std::make_unique( *this, "66:55:44:33:22:11", "Fake device 2")); players_.emplace_back(std::make_unique(*this)); players_.emplace_back(std::make_unique(*this)); delegate_.new_adapter(&adapter_); schedule_next(); } ~FakeBluetoothManager() override { if (timer_) looper_.cancel(timer_); } std::vector devices() override { std::vector ret; std::ranges::copy(std::views::transform( devices_, [](auto& device) { return device.get(); }), std::back_inserter(ret)); return ret; } void update_adapter() override { delegate_.updated_adapter(adapter_); } void update_device(bt::Device& device) override { delegate_.updated_device(device); } void update_player(bt::Player& player) override { auto it = std::ranges::find_if(devices_, [&player](auto& device) { return device->player() == &player; }); if (it != devices_.end()) { delegate_.updated_player(**it, player); } } private: void schedule_next() { timer_ = looper_.schedule(5, [this](uint32_t /* timer */) { step(); schedule_next(); }); } void step() { switch (step_++) { case 0: devices_[0]->set_paired(true); break; case 1: devices_[1]->set_paired(true); break; case 2: devices_[1]->set_player(players_[1].get()); delegate_.added_player(*devices_[1], *players_[1]); break; case 3: players_[1]->play(); break; case 4: players_[1]->pause(); break; case 5: devices_[1]->set_player(nullptr); delegate_.removed_player(*devices_[1]); break; case 6: devices_[1]->set_paired(false); break; case 7: devices_[0]->set_paired(false); break; default: step_ = 0; break; } } FakeAdapter adapter_{*this}; std::vector> devices_; std::vector> players_; looper::Looper& looper_; bt::Manager::Delegate& delegate_; uint32_t timer_{0}; uint32_t step_{0}; }; bool run(logger::Logger& logger, std::unique_ptr port) { FakeConfig cfg; auto looper = looper::create(); auto signaler = server::create_signaler(logger, cfg, *looper); auto bt_delegate = server::create_bt_delegate(logger, *signaler); auto http_delegate = server::create_http_delegate(*bt_delegate, *signaler); auto server = http::create_server(logger, cfg, *looper, std::move(port), *http_delegate); FakeBluetoothManager fake_bt(*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); } } // namespace int main(int argc, char** argv) { auto args = Args::create(); auto opt_help = args->option('h', "help", "display this text and exit."); auto opt_version = args->option('V', "version", "display version and exit."); auto opt_bind = args->option_argument('B', "bind", "HOST:PORT", "HOST:PORT to bind to, defaults to localhost:5555"); if (!args->run(argc, argv)) { args->print_error(std::cerr); std::cerr << "Try 'fake-api --help' for more information.\n"; return 1; } if (opt_help->is_set()) { std::cout << "Usage: fake-api [OPTION...]\n" << "\n"; args->print_help(std::cout); return 0; } if (opt_version->is_set()) { std::cout << "bluetooth-jukebox " << VERSION << " written by Joel Klinghed .\n"; return 0; } auto logger = logger::stderr(/* verbose */ true); std::string_view bind = "localhost:5555"; if (opt_bind->is_set()) bind = opt_bind->argument(); auto port = http::open_port(bind, *logger); if (!port) return 1; return run(*logger, std::move(port)) ? 0 : 1; }