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
|