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