1 #pragma once 2 3 #include <mpd/client.h> 4 #include <fmt/format.h> 5 #include <spdlog/spdlog.h> 6 7 #include <condition_variable> 8 #include <thread> 9 10 #include "ALabel.hpp" 11 12 namespace waybar::modules { 13 class MPD; 14 } // namespace waybar::modules 15 16 namespace waybar::modules::detail { 17 18 using unique_connection = std::unique_ptr<mpd_connection, decltype(&mpd_connection_free)>; 19 using unique_status = std::unique_ptr<mpd_status, decltype(&mpd_status_free)>; 20 using unique_song = std::unique_ptr<mpd_song, decltype(&mpd_song_free)>; 21 22 class Context; 23 24 /// This state machine loosely follows a non-hierarchical, statechart 25 /// pattern, and includes ENTRY and EXIT actions. 26 /// 27 /// The State class is the base class for all other states. The 28 /// entry and exit methods are automatically called when entering 29 /// into a new state and exiting from the current state. This 30 /// includes initially entering (Disconnected class) and exiting 31 /// Waybar. 32 /// 33 /// The following nested "top-level" states are represented: 34 /// 1. Idle - await notification of MPD activity. 35 /// 2. All Non-Idle states: 36 /// 1. Playing - An active song is producing audio output. 37 /// 2. Paused - The current song is paused. 38 /// 3. Stopped - No song is actively playing. 39 /// 3. Disconnected - periodically attempt MPD (re-)connection. 40 /// 41 /// NOTE: Since this statechart is non-hierarchical, the above 42 /// states are flattened into a set. 43 44 class State { 45 public: 46 virtual ~State() noexcept = default; 47 entry()48 virtual void entry() noexcept { spdlog::debug("mpd: ignore entry action"); } exit()49 virtual void exit() noexcept { spdlog::debug("mpd: ignore exit action"); } 50 play()51 virtual void play() { spdlog::debug("mpd: ignore play state transition"); } stop()52 virtual void stop() { spdlog::debug("mpd: ignore stop state transition"); } pause()53 virtual void pause() { spdlog::debug("mpd: ignore pause state transition"); } 54 55 /// Request state update the GUI. update()56 virtual void update() noexcept { spdlog::debug("mpd: ignoring update method request"); } 57 }; 58 59 class Idle : public State { 60 Context* const ctx_; 61 sigc::connection idle_connection_; 62 63 public: Idle(Context * const ctx)64 Idle(Context* const ctx) : ctx_{ctx} {} ~Idle()65 virtual ~Idle() noexcept { this->exit(); }; 66 67 void entry() noexcept override; 68 void exit() noexcept override; 69 70 void play() override; 71 void stop() override; 72 void pause() override; 73 void update() noexcept override; 74 75 private: 76 Idle(const Idle&) = delete; 77 Idle& operator=(const Idle&) = delete; 78 79 bool on_io(Glib::IOCondition const&); 80 }; 81 82 class Playing : public State { 83 Context* const ctx_; 84 sigc::connection timer_connection_; 85 86 public: Playing(Context * const ctx)87 Playing(Context* const ctx) : ctx_{ctx} {} ~Playing()88 virtual ~Playing() noexcept { this->exit(); } 89 90 void entry() noexcept override; 91 void exit() noexcept override; 92 93 void pause() override; 94 void stop() override; 95 void update() noexcept override; 96 97 private: 98 Playing(Playing const&) = delete; 99 Playing& operator=(Playing const&) = delete; 100 101 bool on_timer(); 102 }; 103 104 class Paused : public State { 105 Context* const ctx_; 106 sigc::connection timer_connection_; 107 108 public: Paused(Context * const ctx)109 Paused(Context* const ctx) : ctx_{ctx} {} ~Paused()110 virtual ~Paused() noexcept { this->exit(); } 111 112 void entry() noexcept override; 113 void exit() noexcept override; 114 115 void play() override; 116 void stop() override; 117 void update() noexcept override; 118 119 private: 120 Paused(Paused const&) = delete; 121 Paused& operator=(Paused const&) = delete; 122 123 bool on_timer(); 124 }; 125 126 class Stopped : public State { 127 Context* const ctx_; 128 sigc::connection timer_connection_; 129 130 public: Stopped(Context * const ctx)131 Stopped(Context* const ctx) : ctx_{ctx} {} ~Stopped()132 virtual ~Stopped() noexcept { this->exit(); } 133 134 void entry() noexcept override; 135 void exit() noexcept override; 136 137 void play() override; 138 void pause() override; 139 void update() noexcept override; 140 141 private: 142 Stopped(Stopped const&) = delete; 143 Stopped& operator=(Stopped const&) = delete; 144 145 bool on_timer(); 146 }; 147 148 class Disconnected : public State { 149 Context* const ctx_; 150 sigc::connection timer_connection_; 151 152 public: Disconnected(Context * const ctx)153 Disconnected(Context* const ctx) : ctx_{ctx} {} ~Disconnected()154 virtual ~Disconnected() noexcept { this->exit(); } 155 156 void entry() noexcept override; 157 void exit() noexcept override; 158 159 void update() noexcept override; 160 161 private: 162 Disconnected(Disconnected const&) = delete; 163 Disconnected& operator=(Disconnected const&) = delete; 164 165 void arm_timer(int interval) noexcept; 166 void disarm_timer() noexcept; 167 168 bool on_timer(); 169 }; 170 171 class Context { 172 std::unique_ptr<State> state_; 173 waybar::modules::MPD* mpd_module_; 174 175 friend class State; 176 friend class Playing; 177 friend class Paused; 178 friend class Stopped; 179 friend class Disconnected; 180 friend class Idle; 181 182 protected: setState(std::unique_ptr<State> && new_state)183 void setState(std::unique_ptr<State>&& new_state) noexcept { 184 if (state_.get() != nullptr) { 185 state_->exit(); 186 } 187 state_ = std::move(new_state); 188 state_->entry(); 189 } 190 191 bool is_connected() const; 192 bool is_playing() const; 193 bool is_paused() const; 194 bool is_stopped() const; 195 constexpr std::size_t interval() const; 196 void tryConnect() const; 197 void checkErrors(mpd_connection*) const; 198 void do_update(); 199 void queryMPD() const; 200 void fetchState() const; 201 constexpr mpd_state state() const; 202 void emit() const; 203 [[nodiscard]] unique_connection& connection(); 204 205 public: Context(waybar::modules::MPD * const mpd_module)206 explicit Context(waybar::modules::MPD* const mpd_module) 207 : state_{std::make_unique<Disconnected>(this)}, mpd_module_{mpd_module} { 208 state_->entry(); 209 } 210 play()211 void play() { state_->play(); } stop()212 void stop() { state_->stop(); } pause()213 void pause() { state_->pause(); } update()214 void update() noexcept { state_->update(); } 215 }; 216 217 } // namespace waybar::modules::detail 218