#pragma once #include #include #include #include #include #include "ALabel.hpp" namespace waybar::modules { class MPD; } // namespace waybar::modules namespace waybar::modules::detail { using unique_connection = std::unique_ptr; using unique_status = std::unique_ptr; using unique_song = std::unique_ptr; class Context; /// This state machine loosely follows a non-hierarchical, statechart /// pattern, and includes ENTRY and EXIT actions. /// /// The State class is the base class for all other states. The /// entry and exit methods are automatically called when entering /// into a new state and exiting from the current state. This /// includes initially entering (Disconnected class) and exiting /// Waybar. /// /// The following nested "top-level" states are represented: /// 1. Idle - await notification of MPD activity. /// 2. All Non-Idle states: /// 1. Playing - An active song is producing audio output. /// 2. Paused - The current song is paused. /// 3. Stopped - No song is actively playing. /// 3. Disconnected - periodically attempt MPD (re-)connection. /// /// NOTE: Since this statechart is non-hierarchical, the above /// states are flattened into a set. class State { public: virtual ~State() noexcept = default; virtual void entry() noexcept { spdlog::debug("mpd: ignore entry action"); } virtual void exit() noexcept { spdlog::debug("mpd: ignore exit action"); } virtual void play() { spdlog::debug("mpd: ignore play state transition"); } virtual void stop() { spdlog::debug("mpd: ignore stop state transition"); } virtual void pause() { spdlog::debug("mpd: ignore pause state transition"); } /// Request state update the GUI. virtual void update() noexcept { spdlog::debug("mpd: ignoring update method request"); } }; class Idle : public State { Context* const ctx_; sigc::connection idle_connection_; public: Idle(Context* const ctx) : ctx_{ctx} {} virtual ~Idle() noexcept { this->exit(); }; void entry() noexcept override; void exit() noexcept override; void play() override; void stop() override; void pause() override; void update() noexcept override; private: Idle(const Idle&) = delete; Idle& operator=(const Idle&) = delete; bool on_io(Glib::IOCondition const&); }; class Playing : public State { Context* const ctx_; sigc::connection timer_connection_; public: Playing(Context* const ctx) : ctx_{ctx} {} virtual ~Playing() noexcept { this->exit(); } void entry() noexcept override; void exit() noexcept override; void pause() override; void stop() override; void update() noexcept override; private: Playing(Playing const&) = delete; Playing& operator=(Playing const&) = delete; bool on_timer(); }; class Paused : public State { Context* const ctx_; sigc::connection timer_connection_; public: Paused(Context* const ctx) : ctx_{ctx} {} virtual ~Paused() noexcept { this->exit(); } void entry() noexcept override; void exit() noexcept override; void play() override; void stop() override; void update() noexcept override; private: Paused(Paused const&) = delete; Paused& operator=(Paused const&) = delete; bool on_timer(); }; class Stopped : public State { Context* const ctx_; sigc::connection timer_connection_; public: Stopped(Context* const ctx) : ctx_{ctx} {} virtual ~Stopped() noexcept { this->exit(); } void entry() noexcept override; void exit() noexcept override; void play() override; void pause() override; void update() noexcept override; private: Stopped(Stopped const&) = delete; Stopped& operator=(Stopped const&) = delete; bool on_timer(); }; class Disconnected : public State { Context* const ctx_; sigc::connection timer_connection_; int last_interval_; public: Disconnected(Context* const ctx) : ctx_{ctx} {} virtual ~Disconnected() noexcept { this->exit(); } void entry() noexcept override; void exit() noexcept override; void update() noexcept override; private: Disconnected(Disconnected const&) = delete; Disconnected& operator=(Disconnected const&) = delete; bool arm_timer(int interval) noexcept; void disarm_timer() noexcept; bool on_timer(); }; class Context { std::unique_ptr state_; waybar::modules::MPD* mpd_module_; friend class State; friend class Playing; friend class Paused; friend class Stopped; friend class Disconnected; friend class Idle; protected: void setState(std::unique_ptr&& new_state) noexcept { if (state_.get() != nullptr) { state_->exit(); } state_ = std::move(new_state); state_->entry(); } bool is_connected() const; bool is_playing() const; bool is_paused() const; bool is_stopped() const; constexpr std::size_t interval() const; void tryConnect() const; void checkErrors(mpd_connection*) const; void do_update(); void queryMPD() const; void fetchState() const; constexpr mpd_state state() const; void emit() const; [[nodiscard]] unique_connection& connection(); public: explicit Context(waybar::modules::MPD* const mpd_module) : state_{std::make_unique(this)}, mpd_module_{mpd_module} { state_->entry(); } void play() { state_->play(); } void stop() { state_->stop(); } void pause() { state_->pause(); } void update() noexcept { state_->update(); } }; } // namespace waybar::modules::detail