diff --git a/include/modules/mpd/state.hpp b/include/modules/mpd/state.hpp index 2c9071b4..35e8dd39 100644 --- a/include/modules/mpd/state.hpp +++ b/include/modules/mpd/state.hpp @@ -82,6 +82,7 @@ class Idle : public State { class Playing : public State { Context* const ctx_; sigc::connection timer_connection_; + sigc::connection idle_connection_; public: Playing(Context* const ctx) : ctx_{ctx} {} @@ -98,7 +99,10 @@ class Playing : public State { Playing(Playing const&) = delete; Playing& operator=(Playing const&) = delete; + void timer() noexcept; + void idle() noexcept; bool on_timer(); + bool on_io(Glib::IOCondition const&); }; class Paused : public State { diff --git a/src/modules/mpd/state.cpp b/src/modules/mpd/state.cpp index 3d7c8561..80b4933d 100644 --- a/src/modules/mpd/state.cpp +++ b/src/modules/mpd/state.cpp @@ -19,13 +19,13 @@ auto format_as(enum mpd_idle val) { namespace waybar::modules::detail { -#define IDLE_RUN_NOIDLE_AND_CMD(...) \ +#define RUN_NOIDLE_AND_CMD(STATE, ...) \ if (idle_connection_.connected()) { \ idle_connection_.disconnect(); \ auto conn = ctx_->connection().get(); \ if (!mpd_run_noidle(conn)) { \ if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { \ - spdlog::error("mpd: Idle: failed to unregister for IDLE events"); \ + spdlog::error("mpd: STATE: failed to unregister for IDLE events"); \ ctx_->checkErrors(conn); \ } \ } \ @@ -33,24 +33,23 @@ namespace waybar::modules::detail { } void Idle::play() { - IDLE_RUN_NOIDLE_AND_CMD(mpd_run_play(conn)); + RUN_NOIDLE_AND_CMD(Idle, mpd_run_play(conn)); ctx_->setState(std::make_unique(ctx_)); } void Idle::pause() { - IDLE_RUN_NOIDLE_AND_CMD(mpd_run_pause(conn, true)); + RUN_NOIDLE_AND_CMD(Idle, mpd_run_pause(conn, true)); ctx_->setState(std::make_unique(ctx_)); } void Idle::stop() { - IDLE_RUN_NOIDLE_AND_CMD(mpd_run_stop(conn)); + RUN_NOIDLE_AND_CMD(Idle, mpd_run_stop(conn)); ctx_->setState(std::make_unique(ctx_)); } -#undef IDLE_RUN_NOIDLE_AND_CMD void Idle::update() noexcept { // This is intentionally blank. @@ -118,18 +117,50 @@ bool Idle::on_io(Glib::IOCondition const&) { } void Playing::entry() noexcept { - sigc::slot timer_slot = sigc::mem_fun(*this, &Playing::on_timer); - timer_connection_ = Glib::signal_timeout().connect_seconds(timer_slot, 1); + timer(); + idle(); spdlog::debug("mpd: Playing: enabled 1 second periodic timer."); } void Playing::exit() noexcept { + if (idle_connection_.connected()) { + idle_connection_.disconnect(); + spdlog::debug("mpd: Playing: unwatching FD"); + } + if (timer_connection_.connected()) { timer_connection_.disconnect(); spdlog::debug("mpd: Playing: disabled 1 second periodic timer."); } } +void Playing::timer() noexcept { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + } + + sigc::slot timer_slot = sigc::mem_fun(*this, &Playing::on_timer); + timer_connection_ = Glib::signal_timeout().connect_seconds(timer_slot, 1); +} + + +void Playing::idle() noexcept { + auto conn = ctx_->connection().get(); + assert(conn != nullptr); + + if (!mpd_send_idle_mask( + conn, static_cast(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) { + ctx_->checkErrors(conn); + spdlog::error("mpd: Playing: failed to register for IDLE events"); + } else if (!idle_connection_.connected()) { + spdlog::trace("mpd: Playing: watching FD"); + sigc::slot idle_slot = sigc::mem_fun(*this, &Playing::on_io); + idle_connection_ = + Glib::signal_io().connect(idle_slot, mpd_connection_get_fd(conn), + Glib::IO_IN | Glib::IO_PRI | Glib::IO_ERR | Glib::IO_HUP); + } +} + bool Playing::on_timer() { // Attempt to connect with MPD. try { @@ -141,6 +172,8 @@ bool Playing::on_timer() { return false; } + RUN_NOIDLE_AND_CMD(Playing); + ctx_->fetchState(); if (!ctx_->is_playing()) { @@ -154,6 +187,50 @@ bool Playing::on_timer() { ctx_->queryMPD(); ctx_->emit(); + + idle(); + } catch (std::exception const& e) { + spdlog::warn("mpd: Playing: error: {}", e.what()); + ctx_->setState(std::make_unique(ctx_)); + return false; + } + + return true; +} + +bool Playing::on_io(Glib::IOCondition const&) { + auto conn = ctx_->connection().get(); + + // callback should do this: + enum mpd_idle events = mpd_recv_idle(conn, /* ignore_timeout?= */ false); + spdlog::debug("mpd: Playing: recv_idle events -> {}", events); + + mpd_response_finish(conn); + try { + ctx_->checkErrors(conn); + + ctx_->fetchState(); + + if (!ctx_->is_playing()) { + if (ctx_->is_paused()) { + ctx_->setState(std::make_unique(ctx_)); + } else { + ctx_->setState(std::make_unique(ctx_)); + } + return false; + } + + ctx_->queryMPD(); + ctx_->emit(); + + if (!mpd_send_idle_mask( + conn, static_cast(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) { + ctx_->checkErrors(conn); + spdlog::error("mpd: Playing: failed to register for IDLE events"); + } + + // Defer the next timer + timer(); } catch (std::exception const& e) { spdlog::warn("mpd: Playing: error: {}", e.what()); ctx_->setState(std::make_unique(ctx_)); @@ -164,21 +241,25 @@ bool Playing::on_timer() { } void Playing::stop() { - if (timer_connection_.connected()) { - timer_connection_.disconnect(); + RUN_NOIDLE_AND_CMD(Playing, + if (timer_connection_.connected()) { + timer_connection_.disconnect(); - mpd_run_stop(ctx_->connection().get()); - } + mpd_run_stop(ctx_->connection().get()); + } + ); ctx_->setState(std::make_unique(ctx_)); } void Playing::pause() { - if (timer_connection_.connected()) { - timer_connection_.disconnect(); + RUN_NOIDLE_AND_CMD(Playing, + if (timer_connection_.connected()) { + timer_connection_.disconnect(); - mpd_run_pause(ctx_->connection().get(), true); - } + mpd_run_pause(ctx_->connection().get(), true); + } + ); ctx_->setState(std::make_unique(ctx_)); } @@ -387,4 +468,5 @@ bool Disconnected::on_timer() { void Disconnected::update() noexcept { ctx_->do_update(); } +#undef RUN_NOIDLE_AND_CMD } // namespace waybar::modules::detail