218 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			218 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #pragma once
 | |
| 
 | |
| #include <fmt/format.h>
 | |
| #include <mpd/client.h>
 | |
| #include <spdlog/spdlog.h>
 | |
| 
 | |
| #include <condition_variable>
 | |
| #include <thread>
 | |
| 
 | |
| #include "ALabel.hpp"
 | |
| 
 | |
| namespace waybar::modules {
 | |
| class MPD;
 | |
| }  // namespace waybar::modules
 | |
| 
 | |
| namespace waybar::modules::detail {
 | |
| 
 | |
| using unique_connection = std::unique_ptr<mpd_connection, decltype(&mpd_connection_free)>;
 | |
| using unique_status = std::unique_ptr<mpd_status, decltype(&mpd_status_free)>;
 | |
| using unique_song = std::unique_ptr<mpd_song, decltype(&mpd_song_free)>;
 | |
| 
 | |
| 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_;
 | |
| 
 | |
|  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;
 | |
| 
 | |
|   void arm_timer(int interval) noexcept;
 | |
|   void disarm_timer() noexcept;
 | |
| 
 | |
|   bool on_timer();
 | |
| };
 | |
| 
 | |
| class Context {
 | |
|   std::unique_ptr<State> 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<State>&& 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<Disconnected>(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
 |