From b54fb247456e3e4980891a8c9e6e545d5f808597 Mon Sep 17 00:00:00 2001 From: dmitry Date: Sun, 16 Aug 2020 15:54:21 +0300 Subject: [PATCH 01/94] Remove trim usage in format Some clang-tidy fixes --- include/modules/wlr/taskbar.hpp | 2 +- src/modules/wlr/taskbar.cpp | 20 +++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp index 53a2f8c7..7085d79d 100644 --- a/include/modules/wlr/taskbar.hpp +++ b/include/modules/wlr/taskbar.hpp @@ -70,7 +70,7 @@ class Task std::string title_; std::string app_id_; - uint32_t state_; + uint32_t state_ = 0; private: std::string repr() const; diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 23a91661..5c0dcb3b 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -49,8 +49,8 @@ static std::vector search_prefix() auto xdg_data_dirs = std::getenv("XDG_DATA_DIRS"); if (!xdg_data_dirs) { - prefixes.push_back("/usr/share/"); - prefixes.push_back("/usr/local/share/"); + prefixes.emplace_back("/usr/share/"); + prefixes.emplace_back("/usr/local/share/"); } else { std::string xdg_data_dirs_str(xdg_data_dirs); size_t start = 0, end = 0; @@ -102,7 +102,7 @@ static std::string get_from_desktop_app_info(const std::string &app_id) } /* Method 2 - use the app_id and check whether there is an icon with this name in the icon theme */ -static std::string get_from_icon_theme(Glib::RefPtr icon_theme, +static std::string get_from_icon_theme(const Glib::RefPtr& icon_theme, const std::string &app_id) { if (icon_theme->lookup_icon(app_id, 24)) @@ -111,7 +111,7 @@ static std::string get_from_icon_theme(Glib::RefPtr icon_theme, return ""; } -static bool image_load_icon(Gtk::Image& image, Glib::RefPtr icon_theme, +static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr& icon_theme, const std::string &app_id_list, int size) { std::string app_id; @@ -231,13 +231,13 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, auto icon_pos = format.find("{icon}"); if (icon_pos == 0) { with_icon_ = true; - format_after_ = trim(format.substr(6)); + format_after_ = format.substr(6); } else if (icon_pos == std::string::npos) { format_before_ = format; } else { with_icon_ = true; - format_before_ = trim(format.substr(0, icon_pos)); - format_after_ = trim(format.substr(icon_pos + 6)); + format_before_ = format.substr(0, icon_pos); + format_after_ = format.substr(icon_pos + 6); } } else { /* The default is to only show the icon */ @@ -360,7 +360,7 @@ void Task::handle_output_leave(struct wl_output *output) void Task::handle_state(struct wl_array *state) { state_ = 0; - for (uint32_t* entry = static_cast(state->data); + for (auto* entry = static_cast(state->data); entry < static_cast(state->data) + state->size; entry++) { if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED) @@ -709,9 +709,7 @@ bool Taskbar::show_output(struct wl_output *output) const bool Taskbar::all_outputs() const { - static bool result = config_["all_outputs"].isBool() ? config_["all_outputs"].asBool() : false; - - return result; + return config_["all_outputs"].isBool() && config_["all_outputs"].asBool(); } std::vector> Taskbar::icon_themes() const From ea722615c46d07f5859d2c6a78caebf1f069bc2e Mon Sep 17 00:00:00 2001 From: Daniel De Graaf Date: Mon, 10 Aug 2020 19:23:16 -0400 Subject: [PATCH 02/94] Allow enabing pango markup in the taskbar string The fix for taskbar tooltips in 6a2d214b55 was incomplete: it causes the label to contain escaped titles. Use set_markup so that GTK decodes markup again, but only if requested by the user (disabling markup is needed if using format strings like "{title:.15}" to avoid terminating the string in the middle of an XML entity). --- man/waybar-wlr-taskbar.5.scd | 5 ++++ src/modules/wlr/taskbar.cpp | 51 ++++++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/man/waybar-wlr-taskbar.5.scd b/man/waybar-wlr-taskbar.5.scd index f0444122..55d2afdc 100644 --- a/man/waybar-wlr-taskbar.5.scd +++ b/man/waybar-wlr-taskbar.5.scd @@ -32,6 +32,11 @@ Addressed by *wlr/taskbar* default: 16 ++ The size of the icon. +*markup*: ++ + typeof: bool ++ + default: false ++ + If set to true, pango markup will be accepted in format and tooltip-format. + *tooltip*: ++ typeof: bool ++ default: true ++ diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index f7e11df9..57c88fe1 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -306,7 +306,7 @@ std::string Task::state_string(bool shortened) const void Task::handle_title(const char *title) { - title_ = Glib::Markup::escape_text(title); + title_ = title; } void Task::handle_app_id(const char *app_id) @@ -460,38 +460,51 @@ bool Task::operator!=(const Task &o) const void Task::update() { + bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false; + std::string title = title_; + std::string app_id = app_id_; + if (markup) { + title = Glib::Markup::escape_text(title); + app_id = Glib::Markup::escape_text(app_id); + } if (!format_before_.empty()) { - text_before_.set_label( - fmt::format(format_before_, - fmt::arg("title", title_), - fmt::arg("app_id", app_id_), + auto txt = fmt::format(format_before_, + fmt::arg("title", title), + fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::arg("short_state", state_string(true)) - ) - ); + ); + if (markup) + text_before_.set_markup(txt); + else + text_before_.set_label(txt); text_before_.show(); } if (!format_after_.empty()) { - text_after_.set_label( - fmt::format(format_after_, - fmt::arg("title", title_), - fmt::arg("app_id", app_id_), + auto txt = fmt::format(format_after_, + fmt::arg("title", title), + fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::arg("short_state", state_string(true)) - ) - ); + ); + if (markup) + text_after_.set_markup(txt); + else + text_after_.set_label(txt); text_after_.show(); } if (!format_tooltip_.empty()) { - button_.set_tooltip_markup( - fmt::format(format_tooltip_, - fmt::arg("title", title_), - fmt::arg("app_id", app_id_), + auto txt = fmt::format(format_tooltip_, + fmt::arg("title", title), + fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::arg("short_state", state_string(true)) - ) - ); + ); + if (markup) + button_.set_tooltip_markup(txt); + else + button_.set_tooltip_text(txt); } } From ba78199dd1a8bc7e240ea560e70d6626500eadf1 Mon Sep 17 00:00:00 2001 From: Tamir Zahavi-Brunner Date: Fri, 28 Aug 2020 01:16:41 +0300 Subject: [PATCH 03/94] custom: Fix "restart-interval" This commit fixes the issue where the process would restart immediately and the thread would sleep after the process has restarted, and not before. Fixes #621 --- src/modules/custom.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 56431609..5ee9bb52 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -50,7 +50,6 @@ void waybar::modules::Custom::continuousWorker() { thread_ = [this, cmd] { char* buff = nullptr; size_t len = 0; - bool restart = false; if (getline(&buff, &len, fp_) == -1) { int exit_code = 1; if (fp_) { @@ -63,8 +62,8 @@ void waybar::modules::Custom::continuousWorker() { spdlog::error("{} stopped unexpectedly, is it endless?", name_); } if (config_["restart-interval"].isUInt()) { - restart = true; pid_ = -1; + thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt())); fp_ = util::command::open(cmd, pid_); if (!fp_) { throw std::runtime_error("Unable to open " + cmd); @@ -83,9 +82,6 @@ void waybar::modules::Custom::continuousWorker() { output_ = {0, output}; dp.emit(); } - if (restart) { - thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt())); - } }; } From 943b6bc51bbe16c8db72a7b16cabd1aa95c1b853 Mon Sep 17 00:00:00 2001 From: Renee D'Netto Date: Thu, 27 Aug 2020 22:07:19 +1000 Subject: [PATCH 04/94] Implement support for reloading of config files. Fixes #759. --- include/client.hpp | 1 + src/client.cpp | 9 ++++----- src/main.cpp | 15 ++++++++++++++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/include/client.hpp b/include/client.hpp index 39b6ae3b..37281a29 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -14,6 +14,7 @@ class Client { public: static Client *inst(); int main(int argc, char *argv[]); + void reset(); Glib::RefPtr gtk_app; Glib::RefPtr gdk_display; diff --git a/src/client.cpp b/src/client.cpp index 316e7ec6..042f61d8 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -269,10 +269,9 @@ int waybar::Client::main(int argc, char *argv[]) { gtk_app->hold(); gtk_app->run(); bars.clear(); - zxdg_output_manager_v1_destroy(xdg_output_manager); - zwlr_layer_shell_v1_destroy(layer_shell); - zwp_idle_inhibit_manager_v1_destroy(idle_inhibit_manager); - wl_registry_destroy(registry); - wl_display_disconnect(wl_display); return 0; } + +void waybar::Client::reset() { + gtk_app->quit(); +} diff --git a/src/main.cpp b/src/main.cpp index 19a8de1e..13a25670 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ std::mutex reap_mtx; std::list reap; +volatile bool reload; void* signalThread(void* args) { int err, signum; @@ -70,12 +71,19 @@ void startSignalThread(void) { int main(int argc, char* argv[]) { try { auto client = waybar::Client::inst(); + std::signal(SIGUSR1, [](int /*signal*/) { for (auto& bar : waybar::Client::inst()->bars) { bar->toggle(); } }); + std::signal(SIGUSR2, [](int /*signal*/) { + spdlog::info("Reloading..."); + reload = true; + waybar::Client::inst()->reset(); + }); + for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) { std::signal(sig, [](int sig) { for (auto& bar : waybar::Client::inst()->bars) { @@ -85,7 +93,12 @@ int main(int argc, char* argv[]) { } startSignalThread(); - auto ret = client->main(argc, argv); + auto ret = 0; + do { + reload = false; + ret = client->main(argc, argv); + } while (reload); + delete client; return ret; } catch (const std::exception& e) { From 1b22e2b3200de7fdb94e52e33f3e8846587c98db Mon Sep 17 00:00:00 2001 From: Thomas Hebb Date: Sat, 29 Aug 2020 22:56:26 -0700 Subject: [PATCH 05/94] style(workspaces): align text with other modules Currently, the bottom border on workspace buttons eats into the box size and causes the text to sit higher than in other modules. This is ugly when there are other modules (like the window title) right next to the workspace module. To fix the issue, create the bottom border using an inset box-shadow, which doesn't affect the box's content sizing. --- resources/style.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/style.css b/resources/style.css index e21ae00e..c454bffa 100644 --- a/resources/style.css +++ b/resources/style.css @@ -41,19 +41,19 @@ window#waybar.chromium { padding: 0 5px; background-color: transparent; color: #ffffff; - border-bottom: 3px solid transparent; + /* Use box-shadow instead of border so the text isn't offset */ + box-shadow: inset 0 -3px transparent; } /* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */ #workspaces button:hover { background: rgba(0, 0, 0, 0.2); - box-shadow: inherit; - border-bottom: 3px solid #ffffff; + box-shadow: inset 0 -3px #ffffff; } #workspaces button.focused { background-color: #64727D; - border-bottom: 3px solid #ffffff; + box-shadow: inset 0 -3px #ffffff; } #workspaces button.urgent { From 225a0eccddd777c474d5efb720611dd5589df914 Mon Sep 17 00:00:00 2001 From: MusiKid Date: Wed, 2 Sep 2020 14:25:57 +0200 Subject: [PATCH 06/94] Add support for memory tooltip --- src/modules/memory/common.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp index 4875ec8f..a332d58c 100644 --- a/src/modules/memory/common.cpp +++ b/src/modules/memory/common.cpp @@ -36,7 +36,17 @@ auto waybar::modules::Memory::update() -> void { fmt::arg("used", used_ram_gigabytes), fmt::arg("avail", available_ram_gigabytes))); if (tooltipEnabled()) { - label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1)); + if (config_["tooltip-format"].isString()) { + auto tooltip_format = config_["tooltip-format"].asString(); + label_.set_tooltip_text(fmt::format(tooltip_format, + used_ram_percentage, + fmt::arg("total", total_ram_gigabytes), + fmt::arg("percentage", used_ram_percentage), + fmt::arg("used", used_ram_gigabytes), + fmt::arg("avail", available_ram_gigabytes))); + } else { + label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1)); + } } event_box_.show(); } else { From 98b6d7f283f6cefa2fb8e43f0d175041dcb1c943 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Sun, 6 Sep 2020 21:48:42 +0200 Subject: [PATCH 07/94] Fix non-standard usage of Fixes the following build warning with musl libc: In file included from ../src/util/rfkill.cpp:24: /usr/include/sys/poll.h:1:2: warning: #warning redirecting incorrect #include to [-Wcpp] 1 | #warning redirecting incorrect #include to | ^~~~~~~ --- src/util/rfkill.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/rfkill.cpp b/src/util/rfkill.cpp index f987f4cc..82d29e91 100644 --- a/src/util/rfkill.cpp +++ b/src/util/rfkill.cpp @@ -20,8 +20,8 @@ #include #include +#include #include -#include #include #include From 9e3e4368c733b9d13d78253aacb37173b7ec83cf Mon Sep 17 00:00:00 2001 From: Tamir Zahavi-Brunner Date: Sun, 6 Sep 2020 22:47:34 +0300 Subject: [PATCH 08/94] custom: Add "exec-on-event" config This config allows disabling the default behavior of re-executing the script whenever an event that has a command set is triggered. Fixes #841 --- include/modules/custom.hpp | 1 + man/waybar-custom.5.scd | 6 ++++++ src/modules/custom.cpp | 10 ++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/include/modules/custom.hpp b/include/modules/custom.hpp index b8dad9dd..7c771450 100644 --- a/include/modules/custom.hpp +++ b/include/modules/custom.hpp @@ -22,6 +22,7 @@ class Custom : public ALabel { void continuousWorker(); void parseOutputRaw(); void parseOutputJson(); + void handleEvent(); bool handleScroll(GdkEventScroll* e); bool handleToggle(GdkEventButton* const& e); diff --git a/man/waybar-custom.5.scd b/man/waybar-custom.5.scd index 121585a9..3e820c63 100644 --- a/man/waybar-custom.5.scd +++ b/man/waybar-custom.5.scd @@ -22,6 +22,12 @@ Addressed by *custom/* The path to a script, which determines if the script in *exec* should be executed. *exec* will be executed if the exit code of *exec-if* equals 0. +*exec-on-event*: ++ + typeof: bool ++ + default: true ++ + If an event command is set (e.g. *on-click* or *on-scroll-up*) then re-execute the script after + executing the event command. + *return-type*: ++ typeof: string ++ See *return-type* diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 5ee9bb52..92eedaaa 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -91,15 +91,21 @@ void waybar::modules::Custom::refresh(int sig) { } } +void waybar::modules::Custom::handleEvent() { + if (!config_["exec-on-event"].isBool() || config_["exec-on-event"].asBool()) { + thread_.wake_up(); + } +} + bool waybar::modules::Custom::handleScroll(GdkEventScroll* e) { auto ret = ALabel::handleScroll(e); - thread_.wake_up(); + handleEvent(); return ret; } bool waybar::modules::Custom::handleToggle(GdkEventButton* const& e) { auto ret = ALabel::handleToggle(e); - thread_.wake_up(); + handleEvent(); return ret; } From c65167022223abc8e86650924c7da7e1b1a4b19d Mon Sep 17 00:00:00 2001 From: koffeinfriedhof Date: Sun, 13 Sep 2020 17:32:00 +0200 Subject: [PATCH 09/94] Added song position and queue length. --- man/waybar-mpd.5.scd | 4 ++++ resources/config | 2 +- src/modules/mpd.cpp | 7 +++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/man/waybar-mpd.5.scd b/man/waybar-mpd.5.scd index 1ee7a988..e8105deb 100644 --- a/man/waybar-mpd.5.scd +++ b/man/waybar-mpd.5.scd @@ -148,6 +148,10 @@ Addressed by *mpd* *{totalTime}*: The length of the current song. To format as a date/time (see example configuration) +*{songPosition}*: The position of the current song. + +*{queueLength}*: The length of the current queue. + *{stateIcon}*: The icon corresponding the playing or paused status of the player (see *state-icons* option) *{consumeIcon}*: The icon corresponding the "consume" option (see *consume-icons* option) diff --git a/resources/config b/resources/config index 832f76c8..36c4f967 100644 --- a/resources/config +++ b/resources/config @@ -27,7 +27,7 @@ "format": "{}" }, "mpd": { - "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ", + "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ ", "format-disconnected": "Disconnected ", "format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ", "unknown-tag": "N/A", diff --git a/src/modules/mpd.cpp b/src/modules/mpd.cpp index d2877f35..26878b11 100644 --- a/src/modules/mpd.cpp +++ b/src/modules/mpd.cpp @@ -133,6 +133,7 @@ void waybar::modules::MPD::setLabel() { auto format = format_; std::string artist, album_artist, album, title, date; + int song_pos, queue_length; std::chrono::seconds elapsedTime, totalTime; std::string stateIcon = ""; @@ -161,6 +162,8 @@ void waybar::modules::MPD::setLabel() { album = getTag(MPD_TAG_ALBUM); title = getTag(MPD_TAG_TITLE); date = getTag(MPD_TAG_DATE); + song_pos = mpd_status_get_song_pos(status_.get()); + queue_length = mpd_status_get_queue_length(status_.get()); elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get())); totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get())); } @@ -184,6 +187,8 @@ void waybar::modules::MPD::setLabel() { fmt::arg("date", Glib::Markup::escape_text(date).raw()), fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime), + fmt::arg("songPosition", song_pos), + fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), @@ -200,6 +205,8 @@ void waybar::modules::MPD::setLabel() { fmt::arg("album", album), fmt::arg("title", title), fmt::arg("date", date), + fmt::arg("songPosition", song_pos), + fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), From 95f505a457def2f148d6aa9795dd3f4e6264c061 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 21 Sep 2020 10:56:40 +0200 Subject: [PATCH 10/94] revert: restore eventfd --- include/modules/network.hpp | 2 ++ src/modules/network.cpp | 45 +++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index 4441dc07..c02d3c5e 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -54,6 +54,8 @@ class Network : public ALabel { struct sockaddr_nl nladdr_ = {0}; struct nl_sock* sock_ = nullptr; struct nl_sock* ev_sock_ = nullptr; + int efd_; + int ev_fd_; int nl80211_id_; std::mutex mutex_; diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 39141208..74ae9130 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -83,6 +83,8 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf : ALabel(config, "network", id, "{ifname}", 60), ifid_(-1), family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET), + efd_(-1), + ev_fd_(-1), cidr_(-1), signal_strength_dbm_(0), signal_strength_(0), @@ -119,6 +121,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf } waybar::modules::Network::~Network() { + if (ev_fd_ > -1) { + close(ev_fd_); + } + if (efd_ > -1) { + close(efd_); + } if (ev_sock_ != nullptr) { nl_socket_drop_membership(ev_sock_, RTNLGRP_LINK); if (family_ == AF_INET) { @@ -150,6 +158,30 @@ void waybar::modules::Network::createEventSocket() { } else { nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_IFADDR); } + efd_ = epoll_create1(EPOLL_CLOEXEC); + if (efd_ < 0) { + throw std::runtime_error("Can't create epoll"); + } + { + ev_fd_ = eventfd(0, EFD_NONBLOCK); + struct epoll_event event; + memset(&event, 0, sizeof(event)); + event.events = EPOLLIN | EPOLLET; + event.data.fd = ev_fd_; + if (epoll_ctl(efd_, EPOLL_CTL_ADD, ev_fd_, &event) == -1) { + throw std::runtime_error("Can't add epoll event"); + } + } + { + auto fd = nl_socket_get_fd(ev_sock_); + struct epoll_event event; + memset(&event, 0, sizeof(event)); + event.events = EPOLLIN | EPOLLET | EPOLLRDHUP; + event.data.fd = fd; + if (epoll_ctl(efd_, EPOLL_CTL_ADD, fd, &event) == -1) { + throw std::runtime_error("Can't add epoll event"); + } + } } void waybar::modules::Network::createInfoSocket() { @@ -192,6 +224,19 @@ void waybar::modules::Network::worker() { #else spdlog::warn("Waybar has been built without rfkill support."); #endif + thread_ = [this] { + std::array events{}; + + int ec = epoll_wait(efd_, events.data(), EPOLL_MAX, -1); + if (ec > 0) { + for (auto i = 0; i < ec; i++) { + if (events[i].data.fd != nl_socket_get_fd(ev_sock_) || nl_recvmsgs_default(ev_sock_) < 0) { + thread_.stop(); + break; + } + } + } + }; } const std::string waybar::modules::Network::getNetworkState() const { From 6db795401a6c060a1e9c6d2a72be3caa69ad8ed0 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 21 Sep 2020 12:18:42 +0200 Subject: [PATCH 11/94] chore: v0.9.4 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 16da9575..acc8a470 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'waybar', 'cpp', 'c', - version: '0.9.3', + version: '0.9.4', license: 'MIT', default_options : [ 'cpp_std=c++17', From 12016d35bb09b9628a0a37272557fad4e4b4a7c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorben=20G=C3=BCnther?= Date: Mon, 21 Sep 2020 13:17:11 +0200 Subject: [PATCH 12/94] disk module: add state for percentage_used --- man/waybar-disk.5.scd | 4 ++++ src/modules/disk.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/man/waybar-disk.5.scd b/man/waybar-disk.5.scd index 1a9320ce..431d7c8f 100644 --- a/man/waybar-disk.5.scd +++ b/man/waybar-disk.5.scd @@ -31,6 +31,10 @@ Addressed by *disk* typeof: integer ++ Positive value to rotate the text label. +*states*: ++ + typeof: array ++ + A number of disk utilization states which get activated on certain percentage thresholds (percentage_used). See *waybar-states(5)*. + *max-length*: ++ typeof: integer ++ The maximum length in character the module should display. diff --git a/src/modules/disk.cpp b/src/modules/disk.cpp index 59ffea67..83d612b1 100644 --- a/src/modules/disk.cpp +++ b/src/modules/disk.cpp @@ -47,13 +47,14 @@ auto waybar::modules::Disk::update() -> void { auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true); auto used = pow_format((stats.f_blocks - stats.f_bavail) * stats.f_frsize, "B", true); auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true); + auto percentage_used = (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks; label_.set_markup(fmt::format(format_ , stats.f_bavail * 100 / stats.f_blocks , fmt::arg("free", free) , fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks) , fmt::arg("used", used) - , fmt::arg("percentage_used", (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks) + , fmt::arg("percentage_used", percentage_used) , fmt::arg("total", total) , fmt::arg("path", path_) )); @@ -67,12 +68,13 @@ auto waybar::modules::Disk::update() -> void { , fmt::arg("free", free) , fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks) , fmt::arg("used", used) - , fmt::arg("percentage_used", (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks) + , fmt::arg("percentage_used", percentage_used) , fmt::arg("total", total) , fmt::arg("path", path_) )); } event_box_.show(); + getState(percentage_used); // Call parent update ALabel::update(); } From 7ba14c2097c43866fca5e20ccb736f7ca854fcfd Mon Sep 17 00:00:00 2001 From: Daniel De Graaf Date: Sat, 26 Sep 2020 15:55:06 -0400 Subject: [PATCH 13/94] Fix "on-click-backward" when "on-click-forward" is not present --- src/AModule.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AModule.cpp b/src/AModule.cpp index 10bd0775..7da942e0 100644 --- a/src/AModule.cpp +++ b/src/AModule.cpp @@ -44,9 +44,9 @@ bool AModule::handleToggle(GdkEventButton* const& e) { format = config_["on-click-middle"].asString(); } else if (config_["on-click-right"].isString() && e->button == 3) { format = config_["on-click-right"].asString(); - } else if (config_["on-click-forward"].isString() && e->button == 8) { + } else if (config_["on-click-backward"].isString() && e->button == 8) { format = config_["on-click-backward"].asString(); - } else if (config_["on-click-backward"].isString() && e->button == 9) { + } else if (config_["on-click-forward"].isString() && e->button == 9) { format = config_["on-click-forward"].asString(); } if (!format.empty()) { From 83d679bf72da86c9121b285e5ee6323fb3cbd1a1 Mon Sep 17 00:00:00 2001 From: lrhel Date: Sat, 26 Sep 2020 23:06:12 +0000 Subject: [PATCH 14/94] Add format-icons for workspace's name entry --- src/modules/sway/workspaces.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 17a0be29..8d78bf50 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -283,6 +283,8 @@ std::string Workspaces::getIcon(const std::string &name, const Json::Value &node return config_["format-icons"]["persistent"].asString(); } else if (config_["format-icons"][key].isString()) { return config_["format-icons"][key].asString(); + } else if (config_["format-icons"][trimWorkspaceName(key)].isString()) { + return config_["format-icons"][trimWorkspaceName(key)].asString(); } } return name; From e9b5be9adbfcc8982c8fc1117d92a250a682c8a1 Mon Sep 17 00:00:00 2001 From: Minijackson Date: Tue, 29 Sep 2020 22:28:39 +0200 Subject: [PATCH 15/94] fix: add global /etc/xdg/waybar back. fixes #714 --- src/client.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/client.cpp b/src/client.cpp index 316e7ec6..70cc22cf 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -162,6 +162,7 @@ std::tuple waybar::Client::getConfigs( "$XDG_CONFIG_HOME/waybar/config", "$HOME/.config/waybar/config", "$HOME/waybar/config", + "/etc/xdg/waybar/config", SYSCONFDIR "/xdg/waybar/config", "./resources/config", }) @@ -170,6 +171,7 @@ std::tuple waybar::Client::getConfigs( "$XDG_CONFIG_HOME/waybar/style.css", "$HOME/.config/waybar/style.css", "$HOME/waybar/style.css", + "/etc/xdg/waybar/style.css", SYSCONFDIR "/xdg/waybar/style.css", "./resources/style.css", }) From 73681a30e5ad70a3308b883deedd2b5ad694d8c6 Mon Sep 17 00:00:00 2001 From: Minijackson Date: Tue, 29 Sep 2020 22:31:28 +0200 Subject: [PATCH 16/94] man: add the prefixed path were config is loaded --- man/{waybar.5.scd => waybar.5.scd.in} | 1 + meson.build | 27 ++++++++++++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) rename man/{waybar.5.scd => waybar.5.scd.in} (99%) diff --git a/man/waybar.5.scd b/man/waybar.5.scd.in similarity index 99% rename from man/waybar.5.scd rename to man/waybar.5.scd.in index cd64f7d5..430b9fcd 100644 --- a/man/waybar.5.scd +++ b/man/waybar.5.scd.in @@ -14,6 +14,7 @@ Valid locations for this file are: - *~/.config/waybar/config* - *~/waybar/config* - */etc/xdg/waybar/config* +- *@sysconfdir@/xdg/waybar/config* A good starting point is the default configuration found at https://github.com/Alexays/Waybar/blob/master/resources/config Also a minimal example configuration can be found on the at the bottom of this man page. diff --git a/meson.build b/meson.build index acc8a470..5ac09869 100644 --- a/meson.build +++ b/meson.build @@ -9,6 +9,8 @@ project( ], ) +fs = import('fs') + compiler = meson.get_compiler('cpp') cpp_args = [] @@ -256,9 +258,20 @@ scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_opti if scdoc.found() scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true) sh = find_program('sh', native: true) + + main_manpage = configure_file( + input: 'man/waybar.5.scd.in', + output: 'waybar.5.scd', + configuration: { + 'sysconfdir': join_paths(prefix, sysconfdir) + } + ) + + main_manpage_path = join_paths(meson.build_root(), '@0@'.format(main_manpage)) + mandir = get_option('mandir') man_files = [ - 'waybar.5.scd', + main_manpage_path, 'waybar-backlight.5.scd', 'waybar-battery.5.scd', 'waybar-clock.5.scd', @@ -281,14 +294,18 @@ if scdoc.found() 'waybar-bluetooth.5.scd', ] - foreach filename : man_files - topic = filename.split('.')[-3].split('/')[-1] - section = filename.split('.')[-2] + foreach file : man_files + path = '@0@'.format(file) + basename = fs.name(path) + + topic = basename.split('.')[-3].split('/')[-1] + section = basename.split('.')[-2] output = '@0@.@1@'.format(topic, section) custom_target( output, - input: 'man/@0@'.format(filename), + # drops the 'man' if `path` is an absolute path + input: join_paths('man', path), output: output, command: [ sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.path(), output) From e4427cb017f235cda521c4a024bc4da55bf7c976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Rolim?= Date: Sun, 6 Sep 2020 14:44:13 -0300 Subject: [PATCH 17/94] sndio: Add module. - can control sndio: change volume, toggle mute - appearance is somewhat dynamic: takes muted status into account - uses polling inside sleeper thread to update values - uses sioctl_* functions, requires sndio>=1.7.0. --- README.md | 1 + include/factory.hpp | 3 + include/modules/sndio.hpp | 29 +++++++ man/waybar-sndio.5.scd | 83 +++++++++++++++++++ meson.build | 20 +++++ meson_options.txt | 1 + src/factory.cpp | 5 ++ src/modules/sndio.cpp | 167 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 309 insertions(+) create mode 100644 include/modules/sndio.hpp create mode 100644 man/waybar-sndio.5.scd create mode 100644 src/modules/sndio.cpp diff --git a/README.md b/README.md index 74f46600..b104adec 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ libnl [Network module] libappindicator-gtk3 [Tray module] libdbusmenu-gtk3 [Tray module] libmpdclient [MPD module] +libsndio [sndio module] ``` **Build dependencies** diff --git a/include/factory.hpp b/include/factory.hpp index ebc2359f..3efe6cbd 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -39,6 +39,9 @@ #ifdef HAVE_LIBMPDCLIENT #include "modules/mpd.hpp" #endif +#ifdef HAVE_LIBSNDIO +#include "modules/sndio.hpp" +#endif #include "bar.hpp" #include "modules/custom.hpp" #include "modules/temperature.hpp" diff --git a/include/modules/sndio.hpp b/include/modules/sndio.hpp new file mode 100644 index 00000000..f14d0622 --- /dev/null +++ b/include/modules/sndio.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include "ALabel.hpp" +#include "util/sleeper_thread.hpp" + +namespace waybar::modules { + +class Sndio : public ALabel { + public: + Sndio(const std::string&, const Json::Value&); + ~Sndio(); + auto update() -> void; + auto set_desc(struct sioctl_desc *, unsigned int) -> void; + auto put_val(unsigned int, unsigned int) -> void; + bool handleScroll(GdkEventScroll *); + bool handleToggle(GdkEventButton* const&); + + private: + util::SleeperThread thread_; + struct sioctl_hdl *hdl_; + std::vector pfds_; + unsigned int addr_; + unsigned int volume_, old_volume_, maxval_; + bool muted_; +}; + +} // namespace waybar::modules diff --git a/man/waybar-sndio.5.scd b/man/waybar-sndio.5.scd new file mode 100644 index 00000000..a61c3325 --- /dev/null +++ b/man/waybar-sndio.5.scd @@ -0,0 +1,83 @@ +waybar-sndio(5) + +# NAME + +waybar - sndio module + +# DESCRIPTION + +The *sndio* module displays the current volume reported by sndio(7). + +Additionally, you can control the volume by scrolling *up* or *down* while the +cursor is over the module, and clicking on the module toggles mute. + +# CONFIGURATION + +*format*: ++ + typeof: string ++ + default: {volume}% ++ + The format for how information should be displayed. + +*rotate*: ++ + typeof: integer ++ + Positive value to rotate the text label. + +*max-length*: ++ + typeof: integer ++ + The maximum length in character the module should display. + +*scroll-step*: ++ + typeof: int ++ + default: 5 ++ + The speed in which to change the volume when scrolling. + +*on-click*: ++ + typeof: string ++ + Command to execute when clicked on the module. + This replaces the default behaviour of toggling mute. + +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + +*on-click-right*: ++ + typeof: string ++ + Command to execute when you right clicked on the module. + +*on-update*: ++ + typeof: string ++ + Command to execute when the module is updated. + +*on-scroll-up*: ++ + typeof: string ++ + Command to execute when scrolling up on the module. + This replaces the default behaviour of volume control. + +*on-scroll-down*: ++ + typeof: string ++ + Command to execute when scrolling down on the module. + This replaces the default behaviour of volume control. + +*smooth-scrolling-threshold*: ++ + typeof: double ++ + Threshold to be used when scrolling. + +# FORMAT REPLACEMENTS + +*{volume}*: Volume in percentage. + +*{raw_value}*: Volume as value reported by sndio. + +# EXAMPLES + +``` +"sndio": { + "format": "{raw_value} 🎜", + "scroll-step": 3 +} +``` + +# STYLE + +- *#sndio* +- *#sndio.muted* diff --git a/meson.build b/meson.build index 5ac09869..023894cd 100644 --- a/meson.build +++ b/meson.build @@ -96,6 +96,19 @@ libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl')) libpulse = dependency('libpulse', required: get_option('pulseaudio')) libudev = dependency('libudev', required: get_option('libudev')) libmpdclient = dependency('libmpdclient', required: get_option('mpd')) + +libsndio = compiler.find_library('sndio', required: get_option('sndio')) +if libsndio.found() + if not compiler.has_function('sioctl_open', prefix: '#include ', dependencies: libsndio) + if get_option('sndio').enabled() + error('libsndio is too old, required >=1.7.0') + else + warning('libsndio is too old, required >=1.7.0') + libsndio = dependency('', required: false) + endif + endif +endif + gtk_layer_shell = dependency('gtk-layer-shell-0', required: get_option('gtk-layer-shell'), fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep']) @@ -207,6 +220,11 @@ if gtk_layer_shell.found() add_project_arguments('-DHAVE_GTK_LAYER_SHELL', language: 'cpp') endif +if libsndio.found() + add_project_arguments('-DHAVE_LIBSNDIO', language: 'cpp') + src_files += 'src/modules/sndio.cpp' +endif + if get_option('rfkill').enabled() if is_linux add_project_arguments('-DWANT_RFKILL', language: 'cpp') @@ -241,6 +259,7 @@ executable( libepoll, libmpdclient, gtk_layer_shell, + libsndio, tz_dep ], include_directories: [include_directories('include')], @@ -292,6 +311,7 @@ if scdoc.found() 'waybar-states.5.scd', 'waybar-wlr-taskbar.5.scd', 'waybar-bluetooth.5.scd', + 'waybar-sndio.5.scd', ] foreach file : man_files diff --git a/meson_options.txt b/meson_options.txt index de47da74..cb5581b3 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -8,3 +8,4 @@ option('man-pages', type: 'feature', value: 'auto', description: 'Generate and i option('mpd', type: 'feature', value: 'auto', description: 'Enable support for the Music Player Daemon') option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support') option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL') +option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio') diff --git a/src/factory.cpp b/src/factory.cpp index af93b20f..8a5e8250 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -76,6 +76,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "mpd") { return new waybar::modules::MPD(id, config_[name]); } +#endif +#ifdef HAVE_LIBSNDIO + if (ref == "sndio") { + return new waybar::modules::Sndio(id, config_[name]); + } #endif if (ref == "temperature") { return new waybar::modules::Temperature(id, config_[name]); diff --git a/src/modules/sndio.cpp b/src/modules/sndio.cpp new file mode 100644 index 00000000..e72c56f9 --- /dev/null +++ b/src/modules/sndio.cpp @@ -0,0 +1,167 @@ +#include "modules/sndio.hpp" +#include +#include +#include +#include + +namespace waybar::modules { + +void ondesc(void *arg, struct sioctl_desc *d, int curval) { + auto self = static_cast(arg); + if (d == NULL) { + // d is NULL when the list is done + return; + } + self->set_desc(d, curval); +} + +void onval(void *arg, unsigned int addr, unsigned int val) { + auto self = static_cast(arg); + self->put_val(addr, val); +} + +Sndio::Sndio(const std::string &id, const Json::Value &config) + : ALabel(config, "sndio", id, "{volume}%"), + hdl_(nullptr), + pfds_(0), + addr_(0), + volume_(0), + old_volume_(0), + maxval_(0), + muted_(false) { + hdl_ = sioctl_open(SIO_DEVANY, SIOCTL_READ | SIOCTL_WRITE, 0); + if (hdl_ == nullptr) { + throw std::runtime_error("sioctl_open() failed."); + } + + if(sioctl_ondesc(hdl_, ondesc, this) == 0) { + throw std::runtime_error("sioctl_ondesc() failed."); + } + + sioctl_onval(hdl_, onval, this); + + pfds_.reserve(sioctl_nfds(hdl_)); + + event_box_.show(); + + event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK | Gdk::BUTTON_PRESS_MASK); + event_box_.signal_scroll_event().connect( + sigc::mem_fun(*this, &Sndio::handleScroll)); + event_box_.signal_button_press_event().connect( + sigc::mem_fun(*this, &Sndio::handleToggle)); + + thread_ = [this] { + dp.emit(); + + int nfds = sioctl_pollfd(hdl_, pfds_.data(), POLLIN); + if (nfds == 0) { + throw std::runtime_error("sioctl_pollfd() failed."); + } + while (poll(pfds_.data(), nfds, -1) < 0) { + if (errno != EINTR) { + throw std::runtime_error("poll() failed."); + } + } + + int revents = sioctl_revents(hdl_, pfds_.data()); + if (revents & POLLHUP) { + throw std::runtime_error("disconnected!"); + } + }; +} + +Sndio::~Sndio() { + sioctl_close(hdl_); +} + +auto Sndio::update() -> void { + auto format = format_; + unsigned int vol = 100. * static_cast(volume_) / static_cast(maxval_); + + if (volume_ == 0) { + label_.get_style_context()->add_class("muted"); + } else { + label_.get_style_context()->remove_class("muted"); + } + + label_.set_markup(fmt::format(format, + fmt::arg("volume", vol), + fmt::arg("raw_value", volume_))); + + ALabel::update(); +} + +auto Sndio::set_desc(struct sioctl_desc *d, unsigned int val) -> void { + std::string name{d->func}; + std::string node_name{d->node0.name}; + + if (name == "level" && node_name == "output" && d->type == SIOCTL_NUM) { + // store addr for output.level value, used in put_val + addr_ = d->addr; + maxval_ = d->maxval; + volume_ = val; + } +} + +auto Sndio::put_val(unsigned int addr, unsigned int val) -> void { + if (addr == addr_) { + volume_ = val; + } +} + +bool Sndio::handleScroll(GdkEventScroll *e) { + // change the volume only when no user provided + // events are configured + if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { + return AModule::handleScroll(e); + } + + auto dir = AModule::getScrollDir(e); + if (dir == SCROLL_DIR::NONE) { + return true; + } + + int step = 5; + if (config_["scroll-step"].isInt()) { + step = config_["scroll-step"].asInt(); + } + + int new_volume = volume_; + if (muted_) { + new_volume = old_volume_; + } + + if (dir == SCROLL_DIR::UP) { + new_volume += step; + } else if (dir == SCROLL_DIR::DOWN) { + new_volume -= step; + } + new_volume = std::clamp(new_volume, 0, static_cast(maxval_)); + + // quits muted mode if volume changes + muted_ = false; + + sioctl_setval(hdl_, addr_, new_volume); + + return true; +} + +bool Sndio::handleToggle(GdkEventButton* const& e) { + // toggle mute only when no user provided events are configured + if (config_["on-click"].isString()) { + return AModule::handleToggle(e); + } + + muted_ = !muted_; + if (muted_) { + // store old volume to be able to restore it later + old_volume_ = volume_; + sioctl_setval(hdl_, addr_, 0); + } else { + sioctl_setval(hdl_, addr_, old_volume_); + } + + return true; +} + +} /* namespace waybar::modules */ From 1f66b06f93ca740767acc6925ae66c50e5e9a453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Rolim?= Date: Wed, 9 Sep 2020 14:48:27 -0300 Subject: [PATCH 18/94] Dockerfiles/alpine: add sndio-dev. --- Dockerfiles/alpine | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfiles/alpine b/Dockerfiles/alpine index 7b718375..21d1cbba 100644 --- a/Dockerfiles/alpine +++ b/Dockerfiles/alpine @@ -2,4 +2,4 @@ FROM alpine:latest -RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev scdoc +RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc From aa625f51967ad7bb6a71ed018e814892332441e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Rolim?= Date: Sun, 6 Sep 2020 15:15:58 -0300 Subject: [PATCH 19/94] .travis.yml: add sndio to FreeBSD run. Also add necessary environment variables and move to /latest, which has sndio-1.7.0. --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 62f78633..abc739cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,9 @@ jobs: compiler: clang env: before_install: - - sudo pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu + - export CPPFLAGS+=-isystem/usr/local/include LDFLAGS+=-L/usr/local/lib # sndio + - sudo sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf + - sudo pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu sndio libfmt libmpdclient libudev-devd meson pulseaudio scdoc spdlog script: - meson build -Dman-pages=enabled From 22e46ea6cc64e35f7ab1536d3b43a6a523658242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Rolim?= Date: Sun, 4 Oct 2020 02:53:21 -0300 Subject: [PATCH 20/94] sndio: Add reconnection support. --- include/modules/sndio.hpp | 1 + src/modules/sndio.cpp | 62 ++++++++++++++++++++++++++++++--------- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/include/modules/sndio.hpp b/include/modules/sndio.hpp index f14d0622..32ed7066 100644 --- a/include/modules/sndio.hpp +++ b/include/modules/sndio.hpp @@ -18,6 +18,7 @@ class Sndio : public ALabel { bool handleToggle(GdkEventButton* const&); private: + auto connect_to_sndio() -> void; util::SleeperThread thread_; struct sioctl_hdl *hdl_; std::vector pfds_; diff --git a/src/modules/sndio.cpp b/src/modules/sndio.cpp index e72c56f9..34c46bdd 100644 --- a/src/modules/sndio.cpp +++ b/src/modules/sndio.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace waybar::modules { @@ -20,8 +21,25 @@ void onval(void *arg, unsigned int addr, unsigned int val) { self->put_val(addr, val); } +auto Sndio::connect_to_sndio() -> void { + hdl_ = sioctl_open(SIO_DEVANY, SIOCTL_READ | SIOCTL_WRITE, 0); + if (hdl_ == nullptr) { + throw std::runtime_error("sioctl_open() failed."); + } + + if (sioctl_ondesc(hdl_, ondesc, this) == 0) { + throw std::runtime_error("sioctl_ondesc() failed."); + } + + if (sioctl_onval(hdl_, onval, this) == 0) { + throw std::runtime_error("sioctl_onval() failed."); + } + + pfds_.reserve(sioctl_nfds(hdl_)); +} + Sndio::Sndio(const std::string &id, const Json::Value &config) - : ALabel(config, "sndio", id, "{volume}%"), + : ALabel(config, "sndio", id, "{volume}%", 1), hdl_(nullptr), pfds_(0), addr_(0), @@ -29,18 +47,7 @@ Sndio::Sndio(const std::string &id, const Json::Value &config) old_volume_(0), maxval_(0), muted_(false) { - hdl_ = sioctl_open(SIO_DEVANY, SIOCTL_READ | SIOCTL_WRITE, 0); - if (hdl_ == nullptr) { - throw std::runtime_error("sioctl_open() failed."); - } - - if(sioctl_ondesc(hdl_, ondesc, this) == 0) { - throw std::runtime_error("sioctl_ondesc() failed."); - } - - sioctl_onval(hdl_, onval, this); - - pfds_.reserve(sioctl_nfds(hdl_)); + connect_to_sndio(); event_box_.show(); @@ -65,7 +72,28 @@ Sndio::Sndio(const std::string &id, const Json::Value &config) int revents = sioctl_revents(hdl_, pfds_.data()); if (revents & POLLHUP) { - throw std::runtime_error("disconnected!"); + spdlog::warn("sndio disconnected!"); + sioctl_close(hdl_); + hdl_ = nullptr; + + // reconnection loop + while (thread_.isRunning()) { + try { + connect_to_sndio(); + } catch(std::runtime_error const& e) { + // avoid leaking hdl_ + if (hdl_) { + sioctl_close(hdl_); + hdl_ = nullptr; + } + // rate limiting for the retries + thread_.sleep_for(interval_); + continue; + } + + spdlog::warn("sndio reconnected!"); + break; + } } }; } @@ -116,6 +144,9 @@ bool Sndio::handleScroll(GdkEventScroll *e) { return AModule::handleScroll(e); } + // only try to talk to sndio if connected + if (hdl_ == nullptr) return true; + auto dir = AModule::getScrollDir(e); if (dir == SCROLL_DIR::NONE) { return true; @@ -152,6 +183,9 @@ bool Sndio::handleToggle(GdkEventButton* const& e) { return AModule::handleToggle(e); } + // only try to talk to sndio if connected + if (hdl_ == nullptr) return true; + muted_ = !muted_; if (muted_) { // store old volume to be able to restore it later From 21fdcf41c3aacf381bf4756b6a4ec765e5f8c1db Mon Sep 17 00:00:00 2001 From: Joseph Benden Date: Sat, 3 Oct 2020 22:01:51 -0700 Subject: [PATCH 21/94] mpd: revamped to event-driven, single-threaded Fix MPD connection issues by converting/rewriting module into a state-machine driven system. It is fully single-threaded and uses events for transitioning between states. It supports all features and functionality of the previous MPD module. Signed-off-by: Joseph Benden --- include/factory.hpp | 2 +- include/modules/mpd.hpp | 74 ------ include/modules/mpd/mpd.hpp | 66 ++++++ include/modules/mpd/state.hpp | 217 +++++++++++++++++ include/modules/mpd/state.inl.hpp | 24 ++ meson.build | 3 +- src/modules/{ => mpd}/mpd.cpp | 156 ++++-------- src/modules/mpd/state.cpp | 382 ++++++++++++++++++++++++++++++ 8 files changed, 736 insertions(+), 188 deletions(-) delete mode 100644 include/modules/mpd.hpp create mode 100644 include/modules/mpd/mpd.hpp create mode 100644 include/modules/mpd/state.hpp create mode 100644 include/modules/mpd/state.inl.hpp rename src/modules/{ => mpd}/mpd.cpp (72%) create mode 100644 src/modules/mpd/state.cpp diff --git a/include/factory.hpp b/include/factory.hpp index 3efe6cbd..f73c6e19 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -37,7 +37,7 @@ #include "modules/pulseaudio.hpp" #endif #ifdef HAVE_LIBMPDCLIENT -#include "modules/mpd.hpp" +#include "modules/mpd/mpd.hpp" #endif #ifdef HAVE_LIBSNDIO #include "modules/sndio.hpp" diff --git a/include/modules/mpd.hpp b/include/modules/mpd.hpp deleted file mode 100644 index d08b28b2..00000000 --- a/include/modules/mpd.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include "ALabel.hpp" - -namespace waybar::modules { - -class MPD : public ALabel { - public: - MPD(const std::string&, const Json::Value&); - auto update() -> void; - - private: - std::thread periodic_updater(); - std::string getTag(mpd_tag_type type, unsigned idx = 0); - void setLabel(); - std::string getStateIcon(); - std::string getOptionIcon(std::string optionName, bool activated); - - std::thread event_listener(); - - // Assumes `connection_lock_` is locked - void tryConnect(); - // If checking errors on the main connection, make sure to lock it using - // `connection_lock_` before calling checkErrors - void checkErrors(mpd_connection* conn); - - // Assumes `connection_lock_` is locked - void fetchState(); - void waitForEvent(); - - bool handlePlayPause(GdkEventButton* const&); - - bool stopped(); - bool playing(); - bool paused(); - - const std::string module_name_; - - using unique_connection = std::unique_ptr; - using unique_status = std::unique_ptr; - using unique_song = std::unique_ptr; - - // Not using unique_ptr since we don't manage the pointer - // (It's either nullptr, or from the config) - const char* server_; - const unsigned port_; - - unsigned timeout_; - - // We need a mutex here because we can trigger updates from multiple thread: - // the event based updates, the periodic updates needed for the elapsed time, - // and the click play/pause feature - std::mutex connection_lock_; - unique_connection connection_; - // The alternate connection will be used to wait for events: since it will - // be blocking (idle) we can't send commands via this connection - // - // No lock since only used in the event listener thread - unique_connection alternate_connection_; - - // Protect them using the `connection_lock_` - unique_status status_; - mpd_state state_; - unique_song song_; - - // To make sure the previous periodic_updater stops before creating a new one - std::mutex periodic_lock_; -}; - -} // namespace waybar::modules diff --git a/include/modules/mpd/mpd.hpp b/include/modules/mpd/mpd.hpp new file mode 100644 index 00000000..effe6334 --- /dev/null +++ b/include/modules/mpd/mpd.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "ALabel.hpp" +#include "modules/mpd/state.hpp" + +namespace waybar::modules { + +class MPD : public ALabel { + friend class detail::Context; + + // State machine + detail::Context context_{this}; + + const std::string module_name_; + + // Not using unique_ptr since we don't manage the pointer + // (It's either nullptr, or from the config) + const char* server_; + const unsigned port_; + + unsigned timeout_; + + detail::unique_connection connection_; + + detail::unique_status status_; + mpd_state state_; + detail::unique_song song_; + + public: + MPD(const std::string&, const Json::Value&); + virtual ~MPD() noexcept = default; + auto update() -> void; + + private: + std::string getTag(mpd_tag_type type, unsigned idx = 0) const; + void setLabel(); + std::string getStateIcon() const; + std::string getOptionIcon(std::string optionName, bool activated) const; + + // GUI-side methods + bool handlePlayPause(GdkEventButton* const&); + void emit() { dp.emit(); } + + // MPD-side, Non-GUI methods. + void tryConnect(); + void checkErrors(mpd_connection* conn); + void fetchState(); + void queryMPD(); + + inline bool stopped() const { return connection_ && state_ == MPD_STATE_STOP; } + inline bool playing() const { return connection_ && state_ == MPD_STATE_PLAY; } + inline bool paused() const { return connection_ && state_ == MPD_STATE_PAUSE; } +}; + +#if !defined(MPD_NOINLINE) +#include "modules/mpd/state.inl.hpp" +#endif + +} // namespace waybar::modules diff --git a/include/modules/mpd/state.hpp b/include/modules/mpd/state.hpp new file mode 100644 index 00000000..79e4f638 --- /dev/null +++ b/include/modules/mpd/state.hpp @@ -0,0 +1,217 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "ALabel.hpp" + +namespace waybar::modules { +class MPD; +} // namespace waybar::modules + +namespace waybar::modules::detail { + +using unique_connection = std::unique_ptr; +using unique_status = std::unique_ptr; +using unique_song = std::unique_ptr; + +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_; + 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&& 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(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 diff --git a/include/modules/mpd/state.inl.hpp b/include/modules/mpd/state.inl.hpp new file mode 100644 index 00000000..0d83b0b3 --- /dev/null +++ b/include/modules/mpd/state.inl.hpp @@ -0,0 +1,24 @@ +#pragma once + +namespace detail { + +inline bool Context::is_connected() const { return mpd_module_->connection_ != nullptr; } +inline bool Context::is_playing() const { return mpd_module_->playing(); } +inline bool Context::is_paused() const { return mpd_module_->paused(); } +inline bool Context::is_stopped() const { return mpd_module_->stopped(); } + +constexpr inline std::size_t Context::interval() const { return mpd_module_->interval_.count(); } +inline void Context::tryConnect() const { mpd_module_->tryConnect(); } +inline unique_connection& Context::connection() { return mpd_module_->connection_; } +constexpr inline mpd_state Context::state() const { return mpd_module_->state_; } + +inline void Context::do_update() { + mpd_module_->setLabel(); +} + +inline void Context::checkErrors(mpd_connection* conn) const { mpd_module_->checkErrors(conn); } +inline void Context::queryMPD() const { mpd_module_->queryMPD(); } +inline void Context::fetchState() const { mpd_module_->fetchState(); } +inline void Context::emit() const { mpd_module_->emit(); } + +} // namespace detail diff --git a/meson.build b/meson.build index 023894cd..de203826 100644 --- a/meson.build +++ b/meson.build @@ -213,7 +213,8 @@ endif if libmpdclient.found() add_project_arguments('-DHAVE_LIBMPDCLIENT', language: 'cpp') - src_files += 'src/modules/mpd.cpp' + src_files += 'src/modules/mpd/mpd.cpp' + src_files += 'src/modules/mpd/state.cpp' endif if gtk_layer_shell.found() diff --git a/src/modules/mpd.cpp b/src/modules/mpd/mpd.cpp similarity index 72% rename from src/modules/mpd.cpp rename to src/modules/mpd/mpd.cpp index 26878b11..50f28179 100644 --- a/src/modules/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -1,8 +1,15 @@ -#include "modules/mpd.hpp" +#include "modules/mpd/mpd.hpp" -#include +#include #include +#include "modules/mpd/state.hpp" +#if defined(MPD_NOINLINE) +namespace waybar::modules { +#include "modules/mpd/state.inl.hpp" +} // namespace waybar::modules +#endif + waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config) : ALabel(config, "mpd", id, "{album} - {artist} - {title}", 5), module_name_(id.empty() ? "mpd" : "mpd#" + id), @@ -10,7 +17,6 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config) port_(config_["port"].isUInt() ? config["port"].asUInt() : 0), timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000), connection_(nullptr, &mpd_connection_free), - alternate_connection_(nullptr, &mpd_connection_free), status_(nullptr, &mpd_status_free), song_(nullptr, &mpd_song_free) { if (!config_["port"].isNull() && !config_["port"].isUInt()) { @@ -28,73 +34,33 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config) server_ = config["server"].asCString(); } - event_listener().detach(); - event_box_.add_events(Gdk::BUTTON_PRESS_MASK); event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &MPD::handlePlayPause)); } auto waybar::modules::MPD::update() -> void { - std::lock_guard guard(connection_lock_); - tryConnect(); - - if (connection_ != nullptr) { - try { - bool wasPlaying = playing(); - if(!wasPlaying) { - // Wait until the periodic_updater has stopped - std::lock_guard periodic_guard(periodic_lock_); - } - fetchState(); - if (!wasPlaying && playing()) { - periodic_updater().detach(); - } - } catch (const std::exception& e) { - spdlog::error("{}: {}", module_name_, e.what()); - state_ = MPD_STATE_UNKNOWN; - } - } - - setLabel(); + context_.update(); // Call parent update ALabel::update(); } -std::thread waybar::modules::MPD::event_listener() { - return std::thread([this] { - while (true) { - try { - if (connection_ == nullptr) { - // Retry periodically if no connection - dp.emit(); - std::this_thread::sleep_for(interval_); - } else { - waitForEvent(); - dp.emit(); - } - } catch (const std::exception& e) { - if (strcmp(e.what(), "Connection to MPD closed") == 0) { - spdlog::debug("{}: {}", module_name_, e.what()); - } else { - spdlog::warn("{}: {}", module_name_, e.what()); - } - } +void waybar::modules::MPD::queryMPD() { + if (connection_ != nullptr) { + spdlog::debug("{}: fetching state information", module_name_); + try { + fetchState(); + spdlog::debug("{}: fetch complete", module_name_); + } catch (std::exception const& e) { + spdlog::error("{}: {}", module_name_, e.what()); + state_ = MPD_STATE_UNKNOWN; } - }); + + dp.emit(); + } } -std::thread waybar::modules::MPD::periodic_updater() { - return std::thread([this] { - std::lock_guard guard(periodic_lock_); - while (connection_ != nullptr && playing()) { - dp.emit(); - std::this_thread::sleep_for(std::chrono::seconds(1)); - } - }); -} - -std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) { +std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) const { std::string result = config_["unknown-tag"].isString() ? config_["unknown-tag"].asString() : "N/A"; const char* tag = mpd_song_get_tag(song_.get(), type, idx); @@ -133,7 +99,7 @@ void waybar::modules::MPD::setLabel() { auto format = format_; std::string artist, album_artist, album, title, date; - int song_pos, queue_length; + int song_pos, queue_length; std::chrono::seconds elapsedTime, totalTime; std::string stateIcon = ""; @@ -149,8 +115,8 @@ void waybar::modules::MPD::setLabel() { label_.get_style_context()->add_class("playing"); label_.get_style_context()->remove_class("paused"); } else if (paused()) { - format = - config_["format-paused"].isString() ? config_["format-paused"].asString() : config_["format"].asString(); + format = config_["format-paused"].isString() ? config_["format-paused"].asString() + : config_["format"].asString(); label_.get_style_context()->add_class("paused"); label_.get_style_context()->remove_class("playing"); } @@ -216,7 +182,7 @@ void waybar::modules::MPD::setLabel() { } } -std::string waybar::modules::MPD::getStateIcon() { +std::string waybar::modules::MPD::getStateIcon() const { if (!config_["state-icons"].isObject()) { return ""; } @@ -238,7 +204,7 @@ std::string waybar::modules::MPD::getStateIcon() { } } -std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) { +std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) const { if (!config_[optionName + "-icons"].isObject()) { return ""; } @@ -261,15 +227,11 @@ void waybar::modules::MPD::tryConnect() { } connection_ = - unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free); + detail::unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free); - alternate_connection_ = - unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free); - - if (connection_ == nullptr || alternate_connection_ == nullptr) { + if (connection_ == nullptr) { spdlog::error("{}: Failed to connect to MPD", module_name_); connection_.reset(); - alternate_connection_.reset(); return; } @@ -279,7 +241,6 @@ void waybar::modules::MPD::tryConnect() { } catch (std::runtime_error& e) { spdlog::error("{}: Failed to connect to MPD: {}", module_name_, e.what()); connection_.reset(); - alternate_connection_.reset(); } } @@ -292,7 +253,6 @@ void waybar::modules::MPD::checkErrors(mpd_connection* conn) { case MPD_ERROR_CLOSED: mpd_connection_clear_error(conn); connection_.reset(); - alternate_connection_.reset(); state_ = MPD_STATE_UNKNOWN; throw std::runtime_error("Connection to MPD closed"); default: @@ -306,37 +266,20 @@ void waybar::modules::MPD::checkErrors(mpd_connection* conn) { } void waybar::modules::MPD::fetchState() { + if (connection_ == nullptr) { + spdlog::error("{}: Not connected to MPD", module_name_); + return; + } + auto conn = connection_.get(); - status_ = unique_status(mpd_run_status(conn), &mpd_status_free); + + status_ = detail::unique_status(mpd_run_status(conn), &mpd_status_free); checkErrors(conn); + state_ = mpd_status_get_state(status_.get()); checkErrors(conn); - song_ = unique_song(mpd_run_current_song(conn), &mpd_song_free); - checkErrors(conn); -} - -void waybar::modules::MPD::waitForEvent() { - auto conn = alternate_connection_.get(); - // Wait for a player (play/pause), option (random, shuffle, etc.), or playlist - // change - if (!mpd_send_idle_mask( - conn, static_cast(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) { - checkErrors(conn); - return; - } - // alternate_idle_ = true; - - // See issue #277: - // https://github.com/Alexays/Waybar/issues/277 - mpd_recv_idle(conn, /* disable_timeout = */ false); - // See issue #281: - // https://github.com/Alexays/Waybar/issues/281 - std::lock_guard guard(connection_lock_); - - checkErrors(conn); - mpd_response_finish(conn); - + song_ = detail::unique_song(mpd_run_current_song(conn), &mpd_song_free); checkErrors(conn); } @@ -346,24 +289,13 @@ bool waybar::modules::MPD::handlePlayPause(GdkEventButton* const& e) { } if (e->button == 1) { - std::lock_guard guard(connection_lock_); - if (stopped()) { - mpd_run_play(connection_.get()); - } else { - mpd_run_toggle_pause(connection_.get()); - } + if (state_ == MPD_STATE_PLAY) + context_.pause(); + else + context_.play(); } else if (e->button == 3) { - std::lock_guard guard(connection_lock_); - mpd_run_stop(connection_.get()); + context_.stop(); } return true; } - -bool waybar::modules::MPD::stopped() { - return connection_ == nullptr || state_ == MPD_STATE_UNKNOWN || state_ == MPD_STATE_STOP || status_ == nullptr; -} - -bool waybar::modules::MPD::playing() { return connection_ != nullptr && state_ == MPD_STATE_PLAY; } - -bool waybar::modules::MPD::paused() { return connection_ != nullptr && state_ == MPD_STATE_PAUSE; } diff --git a/src/modules/mpd/state.cpp b/src/modules/mpd/state.cpp new file mode 100644 index 00000000..21b67aec --- /dev/null +++ b/src/modules/mpd/state.cpp @@ -0,0 +1,382 @@ +#include "modules/mpd/state.hpp" + +#include +#include + +#include "modules/mpd/mpd.hpp" +#if defined(MPD_NOINLINE) +namespace waybar::modules { +#include "modules/mpd/state.inl.hpp" +} // namespace waybar::modules +#endif + +namespace waybar::modules::detail { + +#define IDLE_RUN_NOIDLE_AND_CMD(...) \ + 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"); \ + ctx_->checkErrors(conn); \ + } \ + } \ + __VA_ARGS__; \ + } + +void Idle::play() { + IDLE_RUN_NOIDLE_AND_CMD(mpd_run_play(conn)); + + ctx_->setState(std::make_unique(ctx_)); +} + +void Idle::pause() { + IDLE_RUN_NOIDLE_AND_CMD(mpd_run_pause(conn, true)); + + ctx_->setState(std::make_unique(ctx_)); +} + +void Idle::stop() { + IDLE_RUN_NOIDLE_AND_CMD(mpd_run_stop(conn)); + + ctx_->setState(std::make_unique(ctx_)); +} + +#undef IDLE_RUN_NOIDLE_AND_CMD + +void Idle::update() noexcept { + // This is intentionally blank. +} + +void Idle::entry() 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: Idle: failed to register for IDLE events"); + } else { + spdlog::debug("mpd: Idle: watching FD"); + sigc::slot idle_slot = sigc::mem_fun(*this, &Idle::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); + } +} + +void Idle::exit() noexcept { + if (idle_connection_.connected()) { + idle_connection_.disconnect(); + spdlog::debug("mpd: Idle: unwatching FD"); + } +} + +bool Idle::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: Idle: recv_idle events -> {}", events); + + mpd_response_finish(conn); + try { + ctx_->checkErrors(conn); + } catch (std::exception const& e) { + spdlog::warn("mpd: Idle: error: {}", e.what()); + ctx_->setState(std::make_unique(ctx_)); + return false; + } + + ctx_->fetchState(); + mpd_state state = ctx_->state(); + + if (state == MPD_STATE_STOP) { + ctx_->emit(); + ctx_->setState(std::make_unique(ctx_)); + } else if (state == MPD_STATE_PLAY) { + ctx_->emit(); + ctx_->setState(std::make_unique(ctx_)); + } else if (state == MPD_STATE_PAUSE) { + ctx_->emit(); + ctx_->setState(std::make_unique(ctx_)); + } else { + ctx_->emit(); + // self transition + ctx_->setState(std::make_unique(ctx_)); + } + + return false; +} + +void Playing::entry() noexcept { + sigc::slot timer_slot = sigc::mem_fun(*this, &Playing::on_timer); + timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 1'000); + spdlog::debug("mpd: Playing: enabled 1 second periodic timer."); +} + +void Playing::exit() noexcept { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + spdlog::debug("mpd: Playing: disabled 1 second periodic timer."); + } +} + +bool Playing::on_timer() { + // Attempt to connect with MPD. + try { + ctx_->tryConnect(); + + // Success? + if (!ctx_->is_connected()) { + ctx_->setState(std::make_unique(ctx_)); + return false; + } + + 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(); + } catch (std::exception const& e) { + spdlog::warn("mpd: Playing: error: {}", e.what()); + ctx_->setState(std::make_unique(ctx_)); + return false; + } + + return true; +} + +void Playing::stop() { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + + mpd_run_stop(ctx_->connection().get()); + } + + ctx_->setState(std::make_unique(ctx_)); +} + +void Playing::pause() { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + + mpd_run_pause(ctx_->connection().get(), true); + } + + ctx_->setState(std::make_unique(ctx_)); +} + +void Playing::update() noexcept { ctx_->do_update(); } + +void Paused::entry() noexcept { + sigc::slot timer_slot = sigc::mem_fun(*this, &Paused::on_timer); + timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200); + spdlog::debug("mpd: Paused: enabled 200 ms periodic timer."); +} + +void Paused::exit() noexcept { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + spdlog::debug("mpd: Paused: disabled 200 ms periodic timer."); + } +} + +bool Paused::on_timer() { + bool rc = true; + + // Attempt to connect with MPD. + try { + ctx_->tryConnect(); + + // Success? + if (!ctx_->is_connected()) { + ctx_->setState(std::make_unique(ctx_)); + return false; + } + + ctx_->fetchState(); + + ctx_->emit(); + + if (ctx_->is_paused()) { + ctx_->setState(std::make_unique(ctx_)); + rc = false; + } else if (ctx_->is_playing()) { + ctx_->setState(std::make_unique(ctx_)); + rc = false; + } else if (ctx_->is_stopped()) { + ctx_->setState(std::make_unique(ctx_)); + rc = false; + } + } catch (std::exception const& e) { + spdlog::warn("mpd: Paused: error: {}", e.what()); + ctx_->setState(std::make_unique(ctx_)); + rc = false; + } + + return rc; +} + +void Paused::play() { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + + mpd_run_play(ctx_->connection().get()); + } + + ctx_->setState(std::make_unique(ctx_)); +} + +void Paused::stop() { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + + mpd_run_stop(ctx_->connection().get()); + } + + ctx_->setState(std::make_unique(ctx_)); +} + +void Paused::update() noexcept { ctx_->do_update(); } + +void Stopped::entry() noexcept { + sigc::slot timer_slot = sigc::mem_fun(*this, &Stopped::on_timer); + timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200); + spdlog::debug("mpd: Stopped: enabled 200 ms periodic timer."); +} + +void Stopped::exit() noexcept { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + spdlog::debug("mpd: Stopped: disabled 200 ms periodic timer."); + } +} + +bool Stopped::on_timer() { + bool rc = true; + + // Attempt to connect with MPD. + try { + ctx_->tryConnect(); + + // Success? + if (!ctx_->is_connected()) { + ctx_->setState(std::make_unique(ctx_)); + return false; + } + + ctx_->fetchState(); + + ctx_->emit(); + + if (ctx_->is_stopped()) { + ctx_->setState(std::make_unique(ctx_)); + rc = false; + } else if (ctx_->is_playing()) { + ctx_->setState(std::make_unique(ctx_)); + rc = false; + } else if (ctx_->is_paused()) { + ctx_->setState(std::make_unique(ctx_)); + rc = false; + } + } catch (std::exception const& e) { + spdlog::warn("mpd: Stopped: error: {}", e.what()); + ctx_->setState(std::make_unique(ctx_)); + rc = false; + } + + return rc; +} + +void Stopped::play() { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + + mpd_run_play(ctx_->connection().get()); + } + + ctx_->setState(std::make_unique(ctx_)); +} + +void Stopped::pause() { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + + mpd_run_pause(ctx_->connection().get(), true); + } + + ctx_->setState(std::make_unique(ctx_)); +} + +void Stopped::update() noexcept { ctx_->do_update(); } + +void Disconnected::arm_timer(int interval) noexcept { + // unregister timer, if present + disarm_timer(); + + // register timer + sigc::slot timer_slot = sigc::mem_fun(*this, &Disconnected::on_timer); + timer_connection_ = + Glib::signal_timeout().connect(timer_slot, interval); + spdlog::debug("mpd: Disconnected: enabled interval timer."); +} + +void Disconnected::disarm_timer() noexcept { + // unregister timer, if present + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + spdlog::debug("mpd: Disconnected: disabled interval timer."); + } +} + +void Disconnected::entry() noexcept { + arm_timer(1'000); +} + +void Disconnected::exit() noexcept { + disarm_timer(); +} + +bool Disconnected::on_timer() { + // Attempt to connect with MPD. + try { + ctx_->tryConnect(); + + // Success? + if (ctx_->is_connected()) { + ctx_->fetchState(); + ctx_->emit(); + + if (ctx_->is_playing()) { + ctx_->setState(std::make_unique(ctx_)); + } else if (ctx_->is_paused()) { + ctx_->setState(std::make_unique(ctx_)); + } else { + ctx_->setState(std::make_unique(ctx_)); + } + + return false; // do not rearm timer + } + } catch (std::exception const& e) { + spdlog::warn("mpd: Disconnected: error: {}", e.what()); + } + + arm_timer(ctx_->interval() * 1'000); + + return false; +} + +void Disconnected::update() noexcept { ctx_->do_update(); } + +} // namespace waybar::modules::detail From cc3acf8102c71d470b00fd55126aef4fb335f728 Mon Sep 17 00:00:00 2001 From: nikto_b Date: Sat, 10 Oct 2020 19:09:18 +0300 Subject: [PATCH 22/94] feature: created sway language submodule; added styles & config part for a sway language submodule --- include/factory.hpp | 1 + include/modules/sway/language.hpp | 28 +++++++++++++ meson.build | 1 + resources/config | 2 +- resources/style.css | 8 ++++ src/factory.cpp | 3 ++ src/modules/sway/language.cpp | 70 +++++++++++++++++++++++++++++++ 7 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 include/modules/sway/language.hpp create mode 100644 src/modules/sway/language.cpp diff --git a/include/factory.hpp b/include/factory.hpp index 3efe6cbd..7eed15a5 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -6,6 +6,7 @@ #include "modules/sway/mode.hpp" #include "modules/sway/window.hpp" #include "modules/sway/workspaces.hpp" +#include "modules/sway/language.hpp" #endif #ifdef HAVE_WLR #include "modules/wlr/taskbar.hpp" diff --git a/include/modules/sway/language.hpp b/include/modules/sway/language.hpp new file mode 100644 index 00000000..7cd6bf62 --- /dev/null +++ b/include/modules/sway/language.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include "ALabel.hpp" +#include "bar.hpp" +#include "client.hpp" +#include "modules/sway/ipc/client.hpp" +#include "util/json.hpp" + +namespace waybar::modules::sway { + +class Language : public ALabel, public sigc::trackable { + public: + Language(const std::string& id, const Json::Value& config); + ~Language() = default; + auto update() -> void; + + private: + void onEvent(const struct Ipc::ipc_response&); + void onCmd(const struct Ipc::ipc_response&); + + std::string lang_; + util::JsonParser parser_; + std::mutex mutex_; + Ipc ipc_; +}; + +} // namespace waybar::modules::sway diff --git a/meson.build b/meson.build index 023894cd..e563de07 100644 --- a/meson.build +++ b/meson.build @@ -172,6 +172,7 @@ add_project_arguments('-DHAVE_SWAY', language: 'cpp') src_files += [ 'src/modules/sway/ipc/client.cpp', 'src/modules/sway/mode.cpp', + 'src/modules/sway/language.cpp', 'src/modules/sway/window.cpp', 'src/modules/sway/workspaces.cpp' ] diff --git a/resources/config b/resources/config index 36c4f967..f3c0a778 100644 --- a/resources/config +++ b/resources/config @@ -6,7 +6,7 @@ // Choose the order of the modules "modules-left": ["sway/workspaces", "sway/mode", "custom/media"], "modules-center": ["sway/window"], - "modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "battery", "battery#bat2", "clock", "tray"], + "modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "sway/language", "battery", "battery#bat2", "clock", "tray"], // Modules configuration // "sway/workspaces": { // "disable-scroll": true, diff --git a/resources/style.css b/resources/style.css index e21ae00e..a16afd09 100644 --- a/resources/style.css +++ b/resources/style.css @@ -200,3 +200,11 @@ label:focus { #mpd.paused { background-color: #51a37a; } + +#language { + background: #00b093; + color: #740864; + padding: 0 5px; + margin: 0 5px; + min-width: 16px; +} diff --git a/src/factory.cpp b/src/factory.cpp index 8a5e8250..1f907894 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -22,6 +22,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "sway/window") { return new waybar::modules::sway::Window(id, bar_, config_[name]); } + if (ref == "sway/language") { + return new waybar::modules::sway::Language(id, config_[name]); + } #endif #ifdef HAVE_WLR if (ref == "wlr/taskbar") { diff --git a/src/modules/sway/language.cpp b/src/modules/sway/language.cpp new file mode 100644 index 00000000..a3186477 --- /dev/null +++ b/src/modules/sway/language.cpp @@ -0,0 +1,70 @@ +#include "modules/sway/language.hpp" +#include + +namespace waybar::modules::sway { + +Language::Language(const std::string& id, const Json::Value& config) + : ALabel(config, "language", id, "{}", 0, true) { + ipc_.subscribe(R"(["input"])"); + ipc_.signal_event.connect(sigc::mem_fun(*this, &Language::onEvent)); + ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Language::onCmd)); + ipc_.sendCmd(IPC_GET_INPUTS); + // Launch worker + ipc_.setWorker([this] { + try { + ipc_.handleEvent(); + } catch (const std::exception& e) { + spdlog::error("Language: {}", e.what()); + } + }); + dp.emit(); +} + +void Language::onCmd(const struct Ipc::ipc_response& res) { + try { + auto payload = parser_.parse(res.payload); + //Display current layout of a device with a maximum count of layouts, expecting that all will be OK + Json::Value::ArrayIndex maxId = 0, max = 0; + for(Json::Value::ArrayIndex i = 0; i < payload.size(); i++) { + if(payload[i]["xkb_layout_names"].size() > max) { + max = payload[i]["xkb_layout_names"].size(); + maxId = i; + } + } + auto layout_name = payload[maxId]["xkb_active_layout_name"].asString().substr(0,2); + lang_ = Glib::Markup::escape_text(layout_name); + dp.emit(); + } catch (const std::exception& e) { + spdlog::error("Language: {}", e.what()); + } +} + +void Language::onEvent(const struct Ipc::ipc_response& res) { + try { + std::lock_guard lock(mutex_); + auto payload = parser_.parse(res.payload)["input"]; + if (payload["type"].asString() == "keyboard") { + auto layout_name = payload["xkb_active_layout_name"].asString().substr(0,2); + lang_ = Glib::Markup::escape_text(layout_name); + } + dp.emit(); + } catch (const std::exception& e) { + spdlog::error("Language: {}", e.what()); + } +} + +auto Language::update() -> void { + if (lang_.empty()) { + event_box_.hide(); + } else { + label_.set_markup(fmt::format(format_, lang_)); + if (tooltipEnabled()) { + label_.set_tooltip_text(lang_); + } + event_box_.show(); + } + // Call parent update + ALabel::update(); +} + +} // namespace waybar::modules::sway From e9b2d275c836a08d8d5eae4cf2766f362c3cb572 Mon Sep 17 00:00:00 2001 From: Christoffer Noerbjerg Date: Sun, 11 Oct 2020 22:36:30 +0200 Subject: [PATCH 23/94] added module group selectors for styling --- src/bar.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bar.cpp b/src/bar.cpp index 45e34201..8af6b971 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -24,6 +24,9 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) window.get_style_context()->add_class(output->name); window.get_style_context()->add_class(config["name"].asString()); window.get_style_context()->add_class(config["position"].asString()); + left_.get_style_context()->add_class("modules-left"); + center_.get_style_context()->add_class("modules-center"); + right_.get_style_context()->add_class("modules-right"); if (config["position"] == "right" || config["position"] == "left") { height_ = 0; From 4229e9b2ca8aaa31bbf624e019b4cf82aed06e68 Mon Sep 17 00:00:00 2001 From: Ole Martin Handeland Date: Mon, 12 Oct 2020 02:05:26 +0200 Subject: [PATCH 24/94] Implemented format-{state} for cpu/disk/memory --- src/modules/cpu/common.cpp | 15 +++++++++++++-- src/modules/disk.cpp | 32 +++++++++++++++++++++----------- src/modules/memory/common.cpp | 26 ++++++++++++++++++-------- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/modules/cpu/common.cpp b/src/modules/cpu/common.cpp index f2204cde..e86d10a0 100644 --- a/src/modules/cpu/common.cpp +++ b/src/modules/cpu/common.cpp @@ -15,8 +15,19 @@ auto waybar::modules::Cpu::update() -> void { if (tooltipEnabled()) { label_.set_tooltip_text(tooltip); } - label_.set_markup(fmt::format(format_, fmt::arg("load", cpu_load), fmt::arg("usage", cpu_usage))); - getState(cpu_usage); + auto format = format_; + auto state = getState(cpu_usage); + if (!state.empty() && config_["format-" + state].isString()) { + format = config_["format-" + state].asString(); + } + + if (format.empty()) { + event_box_.hide(); + } else { + event_box_.show(); + label_.set_markup(fmt::format(format, fmt::arg("load", cpu_load), fmt::arg("usage", cpu_usage))); + } + // Call parent update ALabel::update(); } diff --git a/src/modules/disk.cpp b/src/modules/disk.cpp index 83d612b1..e63db475 100644 --- a/src/modules/disk.cpp +++ b/src/modules/disk.cpp @@ -49,15 +49,27 @@ auto waybar::modules::Disk::update() -> void { auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true); auto percentage_used = (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks; - label_.set_markup(fmt::format(format_ - , stats.f_bavail * 100 / stats.f_blocks - , fmt::arg("free", free) - , fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks) - , fmt::arg("used", used) - , fmt::arg("percentage_used", percentage_used) - , fmt::arg("total", total) - , fmt::arg("path", path_) - )); + auto format = format_; + auto state = getState(percentage_used); + if (!state.empty() && config_["format-" + state].isString()) { + format = config_["format-" + state].asString(); + } + + if (format.empty()) { + event_box_.hide(); + } else { + event_box_.show(); + label_.set_markup(fmt::format(format + , stats.f_bavail * 100 / stats.f_blocks + , fmt::arg("free", free) + , fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks) + , fmt::arg("used", used) + , fmt::arg("percentage_used", percentage_used) + , fmt::arg("total", total) + , fmt::arg("path", path_) + )); + } + if (tooltipEnabled()) { std::string tooltip_format = "{used} used out of {total} on {path} ({percentage_used}%)"; if (config_["tooltip-format"].isString()) { @@ -73,8 +85,6 @@ auto waybar::modules::Disk::update() -> void { , fmt::arg("path", path_) )); } - event_box_.show(); - getState(percentage_used); // Call parent update ALabel::update(); } diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp index a332d58c..09ce8e83 100644 --- a/src/modules/memory/common.cpp +++ b/src/modules/memory/common.cpp @@ -28,13 +28,24 @@ auto waybar::modules::Memory::update() -> void { auto used_ram_gigabytes = (memtotal - memfree) / std::pow(1024, 2); auto available_ram_gigabytes = memfree / std::pow(1024, 2); - getState(used_ram_percentage); - label_.set_markup(fmt::format(format_, - used_ram_percentage, - fmt::arg("total", total_ram_gigabytes), - fmt::arg("percentage", used_ram_percentage), - fmt::arg("used", used_ram_gigabytes), - fmt::arg("avail", available_ram_gigabytes))); + auto format = format_; + auto state = getState(used_ram_percentage); + if (!state.empty() && config_["format-" + state].isString()) { + format = config_["format-" + state].asString(); + } + + if (format.empty()) { + event_box_.hide(); + } else { + event_box_.show(); + label_.set_markup(fmt::format(format, + used_ram_percentage, + fmt::arg("total", total_ram_gigabytes), + fmt::arg("percentage", used_ram_percentage), + fmt::arg("used", used_ram_gigabytes), + fmt::arg("avail", available_ram_gigabytes))); + } + if (tooltipEnabled()) { if (config_["tooltip-format"].isString()) { auto tooltip_format = config_["tooltip-format"].asString(); @@ -48,7 +59,6 @@ auto waybar::modules::Memory::update() -> void { label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1)); } } - event_box_.show(); } else { event_box_.hide(); } From 54beabb9dc7ce3bc3a80488c948ca6bfd437336d Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 18 Oct 2020 10:45:31 +0200 Subject: [PATCH 25/94] Revert "mpd: revamped to event-driven, single-threaded" --- include/factory.hpp | 2 +- include/modules/mpd.hpp | 74 ++++++ include/modules/mpd/mpd.hpp | 66 ------ include/modules/mpd/state.hpp | 217 ----------------- include/modules/mpd/state.inl.hpp | 24 -- meson.build | 3 +- src/modules/{mpd => }/mpd.cpp | 156 ++++++++---- src/modules/mpd/state.cpp | 382 ------------------------------ 8 files changed, 188 insertions(+), 736 deletions(-) create mode 100644 include/modules/mpd.hpp delete mode 100644 include/modules/mpd/mpd.hpp delete mode 100644 include/modules/mpd/state.hpp delete mode 100644 include/modules/mpd/state.inl.hpp rename src/modules/{mpd => }/mpd.cpp (72%) delete mode 100644 src/modules/mpd/state.cpp diff --git a/include/factory.hpp b/include/factory.hpp index f73c6e19..3efe6cbd 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -37,7 +37,7 @@ #include "modules/pulseaudio.hpp" #endif #ifdef HAVE_LIBMPDCLIENT -#include "modules/mpd/mpd.hpp" +#include "modules/mpd.hpp" #endif #ifdef HAVE_LIBSNDIO #include "modules/sndio.hpp" diff --git a/include/modules/mpd.hpp b/include/modules/mpd.hpp new file mode 100644 index 00000000..d08b28b2 --- /dev/null +++ b/include/modules/mpd.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include +#include +#include "ALabel.hpp" + +namespace waybar::modules { + +class MPD : public ALabel { + public: + MPD(const std::string&, const Json::Value&); + auto update() -> void; + + private: + std::thread periodic_updater(); + std::string getTag(mpd_tag_type type, unsigned idx = 0); + void setLabel(); + std::string getStateIcon(); + std::string getOptionIcon(std::string optionName, bool activated); + + std::thread event_listener(); + + // Assumes `connection_lock_` is locked + void tryConnect(); + // If checking errors on the main connection, make sure to lock it using + // `connection_lock_` before calling checkErrors + void checkErrors(mpd_connection* conn); + + // Assumes `connection_lock_` is locked + void fetchState(); + void waitForEvent(); + + bool handlePlayPause(GdkEventButton* const&); + + bool stopped(); + bool playing(); + bool paused(); + + const std::string module_name_; + + using unique_connection = std::unique_ptr; + using unique_status = std::unique_ptr; + using unique_song = std::unique_ptr; + + // Not using unique_ptr since we don't manage the pointer + // (It's either nullptr, or from the config) + const char* server_; + const unsigned port_; + + unsigned timeout_; + + // We need a mutex here because we can trigger updates from multiple thread: + // the event based updates, the periodic updates needed for the elapsed time, + // and the click play/pause feature + std::mutex connection_lock_; + unique_connection connection_; + // The alternate connection will be used to wait for events: since it will + // be blocking (idle) we can't send commands via this connection + // + // No lock since only used in the event listener thread + unique_connection alternate_connection_; + + // Protect them using the `connection_lock_` + unique_status status_; + mpd_state state_; + unique_song song_; + + // To make sure the previous periodic_updater stops before creating a new one + std::mutex periodic_lock_; +}; + +} // namespace waybar::modules diff --git a/include/modules/mpd/mpd.hpp b/include/modules/mpd/mpd.hpp deleted file mode 100644 index effe6334..00000000 --- a/include/modules/mpd/mpd.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include - -#include "ALabel.hpp" -#include "modules/mpd/state.hpp" - -namespace waybar::modules { - -class MPD : public ALabel { - friend class detail::Context; - - // State machine - detail::Context context_{this}; - - const std::string module_name_; - - // Not using unique_ptr since we don't manage the pointer - // (It's either nullptr, or from the config) - const char* server_; - const unsigned port_; - - unsigned timeout_; - - detail::unique_connection connection_; - - detail::unique_status status_; - mpd_state state_; - detail::unique_song song_; - - public: - MPD(const std::string&, const Json::Value&); - virtual ~MPD() noexcept = default; - auto update() -> void; - - private: - std::string getTag(mpd_tag_type type, unsigned idx = 0) const; - void setLabel(); - std::string getStateIcon() const; - std::string getOptionIcon(std::string optionName, bool activated) const; - - // GUI-side methods - bool handlePlayPause(GdkEventButton* const&); - void emit() { dp.emit(); } - - // MPD-side, Non-GUI methods. - void tryConnect(); - void checkErrors(mpd_connection* conn); - void fetchState(); - void queryMPD(); - - inline bool stopped() const { return connection_ && state_ == MPD_STATE_STOP; } - inline bool playing() const { return connection_ && state_ == MPD_STATE_PLAY; } - inline bool paused() const { return connection_ && state_ == MPD_STATE_PAUSE; } -}; - -#if !defined(MPD_NOINLINE) -#include "modules/mpd/state.inl.hpp" -#endif - -} // namespace waybar::modules diff --git a/include/modules/mpd/state.hpp b/include/modules/mpd/state.hpp deleted file mode 100644 index 79e4f638..00000000 --- a/include/modules/mpd/state.hpp +++ /dev/null @@ -1,217 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include - -#include "ALabel.hpp" - -namespace waybar::modules { -class MPD; -} // namespace waybar::modules - -namespace waybar::modules::detail { - -using unique_connection = std::unique_ptr; -using unique_status = std::unique_ptr; -using unique_song = std::unique_ptr; - -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_; - 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&& 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(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 diff --git a/include/modules/mpd/state.inl.hpp b/include/modules/mpd/state.inl.hpp deleted file mode 100644 index 0d83b0b3..00000000 --- a/include/modules/mpd/state.inl.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -namespace detail { - -inline bool Context::is_connected() const { return mpd_module_->connection_ != nullptr; } -inline bool Context::is_playing() const { return mpd_module_->playing(); } -inline bool Context::is_paused() const { return mpd_module_->paused(); } -inline bool Context::is_stopped() const { return mpd_module_->stopped(); } - -constexpr inline std::size_t Context::interval() const { return mpd_module_->interval_.count(); } -inline void Context::tryConnect() const { mpd_module_->tryConnect(); } -inline unique_connection& Context::connection() { return mpd_module_->connection_; } -constexpr inline mpd_state Context::state() const { return mpd_module_->state_; } - -inline void Context::do_update() { - mpd_module_->setLabel(); -} - -inline void Context::checkErrors(mpd_connection* conn) const { mpd_module_->checkErrors(conn); } -inline void Context::queryMPD() const { mpd_module_->queryMPD(); } -inline void Context::fetchState() const { mpd_module_->fetchState(); } -inline void Context::emit() const { mpd_module_->emit(); } - -} // namespace detail diff --git a/meson.build b/meson.build index de203826..023894cd 100644 --- a/meson.build +++ b/meson.build @@ -213,8 +213,7 @@ endif if libmpdclient.found() add_project_arguments('-DHAVE_LIBMPDCLIENT', language: 'cpp') - src_files += 'src/modules/mpd/mpd.cpp' - src_files += 'src/modules/mpd/state.cpp' + src_files += 'src/modules/mpd.cpp' endif if gtk_layer_shell.found() diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd.cpp similarity index 72% rename from src/modules/mpd/mpd.cpp rename to src/modules/mpd.cpp index 50f28179..26878b11 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd.cpp @@ -1,15 +1,8 @@ -#include "modules/mpd/mpd.hpp" +#include "modules/mpd.hpp" -#include +#include #include -#include "modules/mpd/state.hpp" -#if defined(MPD_NOINLINE) -namespace waybar::modules { -#include "modules/mpd/state.inl.hpp" -} // namespace waybar::modules -#endif - waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config) : ALabel(config, "mpd", id, "{album} - {artist} - {title}", 5), module_name_(id.empty() ? "mpd" : "mpd#" + id), @@ -17,6 +10,7 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config) port_(config_["port"].isUInt() ? config["port"].asUInt() : 0), timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000), connection_(nullptr, &mpd_connection_free), + alternate_connection_(nullptr, &mpd_connection_free), status_(nullptr, &mpd_status_free), song_(nullptr, &mpd_song_free) { if (!config_["port"].isNull() && !config_["port"].isUInt()) { @@ -34,33 +28,73 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config) server_ = config["server"].asCString(); } + event_listener().detach(); + event_box_.add_events(Gdk::BUTTON_PRESS_MASK); event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &MPD::handlePlayPause)); } auto waybar::modules::MPD::update() -> void { - context_.update(); + std::lock_guard guard(connection_lock_); + tryConnect(); + + if (connection_ != nullptr) { + try { + bool wasPlaying = playing(); + if(!wasPlaying) { + // Wait until the periodic_updater has stopped + std::lock_guard periodic_guard(periodic_lock_); + } + fetchState(); + if (!wasPlaying && playing()) { + periodic_updater().detach(); + } + } catch (const std::exception& e) { + spdlog::error("{}: {}", module_name_, e.what()); + state_ = MPD_STATE_UNKNOWN; + } + } + + setLabel(); // Call parent update ALabel::update(); } -void waybar::modules::MPD::queryMPD() { - if (connection_ != nullptr) { - spdlog::debug("{}: fetching state information", module_name_); - try { - fetchState(); - spdlog::debug("{}: fetch complete", module_name_); - } catch (std::exception const& e) { - spdlog::error("{}: {}", module_name_, e.what()); - state_ = MPD_STATE_UNKNOWN; +std::thread waybar::modules::MPD::event_listener() { + return std::thread([this] { + while (true) { + try { + if (connection_ == nullptr) { + // Retry periodically if no connection + dp.emit(); + std::this_thread::sleep_for(interval_); + } else { + waitForEvent(); + dp.emit(); + } + } catch (const std::exception& e) { + if (strcmp(e.what(), "Connection to MPD closed") == 0) { + spdlog::debug("{}: {}", module_name_, e.what()); + } else { + spdlog::warn("{}: {}", module_name_, e.what()); + } + } } - - dp.emit(); - } + }); } -std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) const { +std::thread waybar::modules::MPD::periodic_updater() { + return std::thread([this] { + std::lock_guard guard(periodic_lock_); + while (connection_ != nullptr && playing()) { + dp.emit(); + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + }); +} + +std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) { std::string result = config_["unknown-tag"].isString() ? config_["unknown-tag"].asString() : "N/A"; const char* tag = mpd_song_get_tag(song_.get(), type, idx); @@ -99,7 +133,7 @@ void waybar::modules::MPD::setLabel() { auto format = format_; std::string artist, album_artist, album, title, date; - int song_pos, queue_length; + int song_pos, queue_length; std::chrono::seconds elapsedTime, totalTime; std::string stateIcon = ""; @@ -115,8 +149,8 @@ void waybar::modules::MPD::setLabel() { label_.get_style_context()->add_class("playing"); label_.get_style_context()->remove_class("paused"); } else if (paused()) { - format = config_["format-paused"].isString() ? config_["format-paused"].asString() - : config_["format"].asString(); + format = + config_["format-paused"].isString() ? config_["format-paused"].asString() : config_["format"].asString(); label_.get_style_context()->add_class("paused"); label_.get_style_context()->remove_class("playing"); } @@ -182,7 +216,7 @@ void waybar::modules::MPD::setLabel() { } } -std::string waybar::modules::MPD::getStateIcon() const { +std::string waybar::modules::MPD::getStateIcon() { if (!config_["state-icons"].isObject()) { return ""; } @@ -204,7 +238,7 @@ std::string waybar::modules::MPD::getStateIcon() const { } } -std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) const { +std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) { if (!config_[optionName + "-icons"].isObject()) { return ""; } @@ -227,11 +261,15 @@ void waybar::modules::MPD::tryConnect() { } connection_ = - detail::unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free); + unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free); - if (connection_ == nullptr) { + alternate_connection_ = + unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free); + + if (connection_ == nullptr || alternate_connection_ == nullptr) { spdlog::error("{}: Failed to connect to MPD", module_name_); connection_.reset(); + alternate_connection_.reset(); return; } @@ -241,6 +279,7 @@ void waybar::modules::MPD::tryConnect() { } catch (std::runtime_error& e) { spdlog::error("{}: Failed to connect to MPD: {}", module_name_, e.what()); connection_.reset(); + alternate_connection_.reset(); } } @@ -253,6 +292,7 @@ void waybar::modules::MPD::checkErrors(mpd_connection* conn) { case MPD_ERROR_CLOSED: mpd_connection_clear_error(conn); connection_.reset(); + alternate_connection_.reset(); state_ = MPD_STATE_UNKNOWN; throw std::runtime_error("Connection to MPD closed"); default: @@ -266,20 +306,37 @@ void waybar::modules::MPD::checkErrors(mpd_connection* conn) { } void waybar::modules::MPD::fetchState() { - if (connection_ == nullptr) { - spdlog::error("{}: Not connected to MPD", module_name_); - return; - } - auto conn = connection_.get(); - - status_ = detail::unique_status(mpd_run_status(conn), &mpd_status_free); + status_ = unique_status(mpd_run_status(conn), &mpd_status_free); checkErrors(conn); - state_ = mpd_status_get_state(status_.get()); checkErrors(conn); - song_ = detail::unique_song(mpd_run_current_song(conn), &mpd_song_free); + song_ = unique_song(mpd_run_current_song(conn), &mpd_song_free); + checkErrors(conn); +} + +void waybar::modules::MPD::waitForEvent() { + auto conn = alternate_connection_.get(); + // Wait for a player (play/pause), option (random, shuffle, etc.), or playlist + // change + if (!mpd_send_idle_mask( + conn, static_cast(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) { + checkErrors(conn); + return; + } + // alternate_idle_ = true; + + // See issue #277: + // https://github.com/Alexays/Waybar/issues/277 + mpd_recv_idle(conn, /* disable_timeout = */ false); + // See issue #281: + // https://github.com/Alexays/Waybar/issues/281 + std::lock_guard guard(connection_lock_); + + checkErrors(conn); + mpd_response_finish(conn); + checkErrors(conn); } @@ -289,13 +346,24 @@ bool waybar::modules::MPD::handlePlayPause(GdkEventButton* const& e) { } if (e->button == 1) { - if (state_ == MPD_STATE_PLAY) - context_.pause(); - else - context_.play(); + std::lock_guard guard(connection_lock_); + if (stopped()) { + mpd_run_play(connection_.get()); + } else { + mpd_run_toggle_pause(connection_.get()); + } } else if (e->button == 3) { - context_.stop(); + std::lock_guard guard(connection_lock_); + mpd_run_stop(connection_.get()); } return true; } + +bool waybar::modules::MPD::stopped() { + return connection_ == nullptr || state_ == MPD_STATE_UNKNOWN || state_ == MPD_STATE_STOP || status_ == nullptr; +} + +bool waybar::modules::MPD::playing() { return connection_ != nullptr && state_ == MPD_STATE_PLAY; } + +bool waybar::modules::MPD::paused() { return connection_ != nullptr && state_ == MPD_STATE_PAUSE; } diff --git a/src/modules/mpd/state.cpp b/src/modules/mpd/state.cpp deleted file mode 100644 index 21b67aec..00000000 --- a/src/modules/mpd/state.cpp +++ /dev/null @@ -1,382 +0,0 @@ -#include "modules/mpd/state.hpp" - -#include -#include - -#include "modules/mpd/mpd.hpp" -#if defined(MPD_NOINLINE) -namespace waybar::modules { -#include "modules/mpd/state.inl.hpp" -} // namespace waybar::modules -#endif - -namespace waybar::modules::detail { - -#define IDLE_RUN_NOIDLE_AND_CMD(...) \ - 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"); \ - ctx_->checkErrors(conn); \ - } \ - } \ - __VA_ARGS__; \ - } - -void Idle::play() { - IDLE_RUN_NOIDLE_AND_CMD(mpd_run_play(conn)); - - ctx_->setState(std::make_unique(ctx_)); -} - -void Idle::pause() { - IDLE_RUN_NOIDLE_AND_CMD(mpd_run_pause(conn, true)); - - ctx_->setState(std::make_unique(ctx_)); -} - -void Idle::stop() { - IDLE_RUN_NOIDLE_AND_CMD(mpd_run_stop(conn)); - - ctx_->setState(std::make_unique(ctx_)); -} - -#undef IDLE_RUN_NOIDLE_AND_CMD - -void Idle::update() noexcept { - // This is intentionally blank. -} - -void Idle::entry() 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: Idle: failed to register for IDLE events"); - } else { - spdlog::debug("mpd: Idle: watching FD"); - sigc::slot idle_slot = sigc::mem_fun(*this, &Idle::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); - } -} - -void Idle::exit() noexcept { - if (idle_connection_.connected()) { - idle_connection_.disconnect(); - spdlog::debug("mpd: Idle: unwatching FD"); - } -} - -bool Idle::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: Idle: recv_idle events -> {}", events); - - mpd_response_finish(conn); - try { - ctx_->checkErrors(conn); - } catch (std::exception const& e) { - spdlog::warn("mpd: Idle: error: {}", e.what()); - ctx_->setState(std::make_unique(ctx_)); - return false; - } - - ctx_->fetchState(); - mpd_state state = ctx_->state(); - - if (state == MPD_STATE_STOP) { - ctx_->emit(); - ctx_->setState(std::make_unique(ctx_)); - } else if (state == MPD_STATE_PLAY) { - ctx_->emit(); - ctx_->setState(std::make_unique(ctx_)); - } else if (state == MPD_STATE_PAUSE) { - ctx_->emit(); - ctx_->setState(std::make_unique(ctx_)); - } else { - ctx_->emit(); - // self transition - ctx_->setState(std::make_unique(ctx_)); - } - - return false; -} - -void Playing::entry() noexcept { - sigc::slot timer_slot = sigc::mem_fun(*this, &Playing::on_timer); - timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 1'000); - spdlog::debug("mpd: Playing: enabled 1 second periodic timer."); -} - -void Playing::exit() noexcept { - if (timer_connection_.connected()) { - timer_connection_.disconnect(); - spdlog::debug("mpd: Playing: disabled 1 second periodic timer."); - } -} - -bool Playing::on_timer() { - // Attempt to connect with MPD. - try { - ctx_->tryConnect(); - - // Success? - if (!ctx_->is_connected()) { - ctx_->setState(std::make_unique(ctx_)); - return false; - } - - 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(); - } catch (std::exception const& e) { - spdlog::warn("mpd: Playing: error: {}", e.what()); - ctx_->setState(std::make_unique(ctx_)); - return false; - } - - return true; -} - -void Playing::stop() { - if (timer_connection_.connected()) { - timer_connection_.disconnect(); - - mpd_run_stop(ctx_->connection().get()); - } - - ctx_->setState(std::make_unique(ctx_)); -} - -void Playing::pause() { - if (timer_connection_.connected()) { - timer_connection_.disconnect(); - - mpd_run_pause(ctx_->connection().get(), true); - } - - ctx_->setState(std::make_unique(ctx_)); -} - -void Playing::update() noexcept { ctx_->do_update(); } - -void Paused::entry() noexcept { - sigc::slot timer_slot = sigc::mem_fun(*this, &Paused::on_timer); - timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200); - spdlog::debug("mpd: Paused: enabled 200 ms periodic timer."); -} - -void Paused::exit() noexcept { - if (timer_connection_.connected()) { - timer_connection_.disconnect(); - spdlog::debug("mpd: Paused: disabled 200 ms periodic timer."); - } -} - -bool Paused::on_timer() { - bool rc = true; - - // Attempt to connect with MPD. - try { - ctx_->tryConnect(); - - // Success? - if (!ctx_->is_connected()) { - ctx_->setState(std::make_unique(ctx_)); - return false; - } - - ctx_->fetchState(); - - ctx_->emit(); - - if (ctx_->is_paused()) { - ctx_->setState(std::make_unique(ctx_)); - rc = false; - } else if (ctx_->is_playing()) { - ctx_->setState(std::make_unique(ctx_)); - rc = false; - } else if (ctx_->is_stopped()) { - ctx_->setState(std::make_unique(ctx_)); - rc = false; - } - } catch (std::exception const& e) { - spdlog::warn("mpd: Paused: error: {}", e.what()); - ctx_->setState(std::make_unique(ctx_)); - rc = false; - } - - return rc; -} - -void Paused::play() { - if (timer_connection_.connected()) { - timer_connection_.disconnect(); - - mpd_run_play(ctx_->connection().get()); - } - - ctx_->setState(std::make_unique(ctx_)); -} - -void Paused::stop() { - if (timer_connection_.connected()) { - timer_connection_.disconnect(); - - mpd_run_stop(ctx_->connection().get()); - } - - ctx_->setState(std::make_unique(ctx_)); -} - -void Paused::update() noexcept { ctx_->do_update(); } - -void Stopped::entry() noexcept { - sigc::slot timer_slot = sigc::mem_fun(*this, &Stopped::on_timer); - timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200); - spdlog::debug("mpd: Stopped: enabled 200 ms periodic timer."); -} - -void Stopped::exit() noexcept { - if (timer_connection_.connected()) { - timer_connection_.disconnect(); - spdlog::debug("mpd: Stopped: disabled 200 ms periodic timer."); - } -} - -bool Stopped::on_timer() { - bool rc = true; - - // Attempt to connect with MPD. - try { - ctx_->tryConnect(); - - // Success? - if (!ctx_->is_connected()) { - ctx_->setState(std::make_unique(ctx_)); - return false; - } - - ctx_->fetchState(); - - ctx_->emit(); - - if (ctx_->is_stopped()) { - ctx_->setState(std::make_unique(ctx_)); - rc = false; - } else if (ctx_->is_playing()) { - ctx_->setState(std::make_unique(ctx_)); - rc = false; - } else if (ctx_->is_paused()) { - ctx_->setState(std::make_unique(ctx_)); - rc = false; - } - } catch (std::exception const& e) { - spdlog::warn("mpd: Stopped: error: {}", e.what()); - ctx_->setState(std::make_unique(ctx_)); - rc = false; - } - - return rc; -} - -void Stopped::play() { - if (timer_connection_.connected()) { - timer_connection_.disconnect(); - - mpd_run_play(ctx_->connection().get()); - } - - ctx_->setState(std::make_unique(ctx_)); -} - -void Stopped::pause() { - if (timer_connection_.connected()) { - timer_connection_.disconnect(); - - mpd_run_pause(ctx_->connection().get(), true); - } - - ctx_->setState(std::make_unique(ctx_)); -} - -void Stopped::update() noexcept { ctx_->do_update(); } - -void Disconnected::arm_timer(int interval) noexcept { - // unregister timer, if present - disarm_timer(); - - // register timer - sigc::slot timer_slot = sigc::mem_fun(*this, &Disconnected::on_timer); - timer_connection_ = - Glib::signal_timeout().connect(timer_slot, interval); - spdlog::debug("mpd: Disconnected: enabled interval timer."); -} - -void Disconnected::disarm_timer() noexcept { - // unregister timer, if present - if (timer_connection_.connected()) { - timer_connection_.disconnect(); - spdlog::debug("mpd: Disconnected: disabled interval timer."); - } -} - -void Disconnected::entry() noexcept { - arm_timer(1'000); -} - -void Disconnected::exit() noexcept { - disarm_timer(); -} - -bool Disconnected::on_timer() { - // Attempt to connect with MPD. - try { - ctx_->tryConnect(); - - // Success? - if (ctx_->is_connected()) { - ctx_->fetchState(); - ctx_->emit(); - - if (ctx_->is_playing()) { - ctx_->setState(std::make_unique(ctx_)); - } else if (ctx_->is_paused()) { - ctx_->setState(std::make_unique(ctx_)); - } else { - ctx_->setState(std::make_unique(ctx_)); - } - - return false; // do not rearm timer - } - } catch (std::exception const& e) { - spdlog::warn("mpd: Disconnected: error: {}", e.what()); - } - - arm_timer(ctx_->interval() * 1'000); - - return false; -} - -void Disconnected::update() noexcept { ctx_->do_update(); } - -} // namespace waybar::modules::detail From 8f961ac3976048d7a389d77737d9694f26fe863d Mon Sep 17 00:00:00 2001 From: Joseph Benden Date: Sat, 3 Oct 2020 22:01:51 -0700 Subject: [PATCH 26/94] mpd: revamped to event-driven, single-threaded Fix MPD connection issues by converting/rewriting module into a state-machine driven system. It is fully single-threaded and uses events for transitioning between states. It supports all features and functionality of the previous MPD module. Signed-off-by: Joseph Benden --- include/factory.hpp | 2 +- include/modules/mpd.hpp | 74 ------ include/modules/mpd/mpd.hpp | 66 ++++++ include/modules/mpd/state.hpp | 217 +++++++++++++++++ include/modules/mpd/state.inl.hpp | 24 ++ meson.build | 3 +- src/modules/{ => mpd}/mpd.cpp | 154 ++++-------- src/modules/mpd/state.cpp | 382 ++++++++++++++++++++++++++++++ 8 files changed, 735 insertions(+), 187 deletions(-) delete mode 100644 include/modules/mpd.hpp create mode 100644 include/modules/mpd/mpd.hpp create mode 100644 include/modules/mpd/state.hpp create mode 100644 include/modules/mpd/state.inl.hpp rename src/modules/{ => mpd}/mpd.cpp (73%) create mode 100644 src/modules/mpd/state.cpp diff --git a/include/factory.hpp b/include/factory.hpp index 3efe6cbd..f73c6e19 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -37,7 +37,7 @@ #include "modules/pulseaudio.hpp" #endif #ifdef HAVE_LIBMPDCLIENT -#include "modules/mpd.hpp" +#include "modules/mpd/mpd.hpp" #endif #ifdef HAVE_LIBSNDIO #include "modules/sndio.hpp" diff --git a/include/modules/mpd.hpp b/include/modules/mpd.hpp deleted file mode 100644 index d08b28b2..00000000 --- a/include/modules/mpd.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include "ALabel.hpp" - -namespace waybar::modules { - -class MPD : public ALabel { - public: - MPD(const std::string&, const Json::Value&); - auto update() -> void; - - private: - std::thread periodic_updater(); - std::string getTag(mpd_tag_type type, unsigned idx = 0); - void setLabel(); - std::string getStateIcon(); - std::string getOptionIcon(std::string optionName, bool activated); - - std::thread event_listener(); - - // Assumes `connection_lock_` is locked - void tryConnect(); - // If checking errors on the main connection, make sure to lock it using - // `connection_lock_` before calling checkErrors - void checkErrors(mpd_connection* conn); - - // Assumes `connection_lock_` is locked - void fetchState(); - void waitForEvent(); - - bool handlePlayPause(GdkEventButton* const&); - - bool stopped(); - bool playing(); - bool paused(); - - const std::string module_name_; - - using unique_connection = std::unique_ptr; - using unique_status = std::unique_ptr; - using unique_song = std::unique_ptr; - - // Not using unique_ptr since we don't manage the pointer - // (It's either nullptr, or from the config) - const char* server_; - const unsigned port_; - - unsigned timeout_; - - // We need a mutex here because we can trigger updates from multiple thread: - // the event based updates, the periodic updates needed for the elapsed time, - // and the click play/pause feature - std::mutex connection_lock_; - unique_connection connection_; - // The alternate connection will be used to wait for events: since it will - // be blocking (idle) we can't send commands via this connection - // - // No lock since only used in the event listener thread - unique_connection alternate_connection_; - - // Protect them using the `connection_lock_` - unique_status status_; - mpd_state state_; - unique_song song_; - - // To make sure the previous periodic_updater stops before creating a new one - std::mutex periodic_lock_; -}; - -} // namespace waybar::modules diff --git a/include/modules/mpd/mpd.hpp b/include/modules/mpd/mpd.hpp new file mode 100644 index 00000000..f92df933 --- /dev/null +++ b/include/modules/mpd/mpd.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "ALabel.hpp" +#include "modules/mpd/state.hpp" + +namespace waybar::modules { + +class MPD : public ALabel { + friend class detail::Context; + + // State machine + detail::Context context_{this}; + + const std::string module_name_; + + // Not using unique_ptr since we don't manage the pointer + // (It's either nullptr, or from the config) + const char* server_; + const unsigned port_; + + unsigned timeout_; + + detail::unique_connection connection_; + + detail::unique_status status_; + mpd_state state_; + detail::unique_song song_; + + public: + MPD(const std::string&, const Json::Value&); + virtual ~MPD() noexcept = default; + auto update() -> void; + + private: + std::string getTag(mpd_tag_type type, unsigned idx = 0) const; + void setLabel(); + std::string getStateIcon() const; + std::string getOptionIcon(std::string optionName, bool activated) const; + + // GUI-side methods + bool handlePlayPause(GdkEventButton* const&); + void emit() { dp.emit(); } + + // MPD-side, Non-GUI methods. + void tryConnect(); + void checkErrors(mpd_connection* conn); + void fetchState(); + void queryMPD(); + + inline bool stopped() const { return connection_ && state_ == MPD_STATE_STOP; } + inline bool playing() const { return connection_ && state_ == MPD_STATE_PLAY; } + inline bool paused() const { return connection_ && state_ == MPD_STATE_PAUSE; } +}; + +#if !defined(MPD_NOINLINE) +#include "modules/mpd/state.inl.hpp" +#endif + +} // namespace waybar::modules diff --git a/include/modules/mpd/state.hpp b/include/modules/mpd/state.hpp new file mode 100644 index 00000000..3b181598 --- /dev/null +++ b/include/modules/mpd/state.hpp @@ -0,0 +1,217 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "ALabel.hpp" + +namespace waybar::modules { +class MPD; +} // namespace waybar::modules + +namespace waybar::modules::detail { + +using unique_connection = std::unique_ptr; +using unique_status = std::unique_ptr; +using unique_song = std::unique_ptr; + +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_; + 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&& 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(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 diff --git a/include/modules/mpd/state.inl.hpp b/include/modules/mpd/state.inl.hpp new file mode 100644 index 00000000..0d83b0b3 --- /dev/null +++ b/include/modules/mpd/state.inl.hpp @@ -0,0 +1,24 @@ +#pragma once + +namespace detail { + +inline bool Context::is_connected() const { return mpd_module_->connection_ != nullptr; } +inline bool Context::is_playing() const { return mpd_module_->playing(); } +inline bool Context::is_paused() const { return mpd_module_->paused(); } +inline bool Context::is_stopped() const { return mpd_module_->stopped(); } + +constexpr inline std::size_t Context::interval() const { return mpd_module_->interval_.count(); } +inline void Context::tryConnect() const { mpd_module_->tryConnect(); } +inline unique_connection& Context::connection() { return mpd_module_->connection_; } +constexpr inline mpd_state Context::state() const { return mpd_module_->state_; } + +inline void Context::do_update() { + mpd_module_->setLabel(); +} + +inline void Context::checkErrors(mpd_connection* conn) const { mpd_module_->checkErrors(conn); } +inline void Context::queryMPD() const { mpd_module_->queryMPD(); } +inline void Context::fetchState() const { mpd_module_->fetchState(); } +inline void Context::emit() const { mpd_module_->emit(); } + +} // namespace detail diff --git a/meson.build b/meson.build index 023894cd..de203826 100644 --- a/meson.build +++ b/meson.build @@ -213,7 +213,8 @@ endif if libmpdclient.found() add_project_arguments('-DHAVE_LIBMPDCLIENT', language: 'cpp') - src_files += 'src/modules/mpd.cpp' + src_files += 'src/modules/mpd/mpd.cpp' + src_files += 'src/modules/mpd/state.cpp' endif if gtk_layer_shell.found() diff --git a/src/modules/mpd.cpp b/src/modules/mpd/mpd.cpp similarity index 73% rename from src/modules/mpd.cpp rename to src/modules/mpd/mpd.cpp index 26878b11..edce9417 100644 --- a/src/modules/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -1,8 +1,15 @@ -#include "modules/mpd.hpp" +#include "modules/mpd/mpd.hpp" #include #include +#include "modules/mpd/state.hpp" +#if defined(MPD_NOINLINE) +namespace waybar::modules { +#include "modules/mpd/state.inl.hpp" +} // namespace waybar::modules +#endif + waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config) : ALabel(config, "mpd", id, "{album} - {artist} - {title}", 5), module_name_(id.empty() ? "mpd" : "mpd#" + id), @@ -10,7 +17,6 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config) port_(config_["port"].isUInt() ? config["port"].asUInt() : 0), timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000), connection_(nullptr, &mpd_connection_free), - alternate_connection_(nullptr, &mpd_connection_free), status_(nullptr, &mpd_status_free), song_(nullptr, &mpd_song_free) { if (!config_["port"].isNull() && !config_["port"].isUInt()) { @@ -28,73 +34,33 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config) server_ = config["server"].asCString(); } - event_listener().detach(); - event_box_.add_events(Gdk::BUTTON_PRESS_MASK); event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &MPD::handlePlayPause)); } auto waybar::modules::MPD::update() -> void { - std::lock_guard guard(connection_lock_); - tryConnect(); - - if (connection_ != nullptr) { - try { - bool wasPlaying = playing(); - if(!wasPlaying) { - // Wait until the periodic_updater has stopped - std::lock_guard periodic_guard(periodic_lock_); - } - fetchState(); - if (!wasPlaying && playing()) { - periodic_updater().detach(); - } - } catch (const std::exception& e) { - spdlog::error("{}: {}", module_name_, e.what()); - state_ = MPD_STATE_UNKNOWN; - } - } - - setLabel(); + context_.update(); // Call parent update ALabel::update(); } -std::thread waybar::modules::MPD::event_listener() { - return std::thread([this] { - while (true) { - try { - if (connection_ == nullptr) { - // Retry periodically if no connection - dp.emit(); - std::this_thread::sleep_for(interval_); - } else { - waitForEvent(); - dp.emit(); - } - } catch (const std::exception& e) { - if (strcmp(e.what(), "Connection to MPD closed") == 0) { - spdlog::debug("{}: {}", module_name_, e.what()); - } else { - spdlog::warn("{}: {}", module_name_, e.what()); - } - } +void waybar::modules::MPD::queryMPD() { + if (connection_ != nullptr) { + spdlog::debug("{}: fetching state information", module_name_); + try { + fetchState(); + spdlog::debug("{}: fetch complete", module_name_); + } catch (std::exception const& e) { + spdlog::error("{}: {}", module_name_, e.what()); + state_ = MPD_STATE_UNKNOWN; } - }); + + dp.emit(); + } } -std::thread waybar::modules::MPD::periodic_updater() { - return std::thread([this] { - std::lock_guard guard(periodic_lock_); - while (connection_ != nullptr && playing()) { - dp.emit(); - std::this_thread::sleep_for(std::chrono::seconds(1)); - } - }); -} - -std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) { +std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) const { std::string result = config_["unknown-tag"].isString() ? config_["unknown-tag"].asString() : "N/A"; const char* tag = mpd_song_get_tag(song_.get(), type, idx); @@ -133,7 +99,7 @@ void waybar::modules::MPD::setLabel() { auto format = format_; std::string artist, album_artist, album, title, date; - int song_pos, queue_length; + int song_pos, queue_length; std::chrono::seconds elapsedTime, totalTime; std::string stateIcon = ""; @@ -149,8 +115,8 @@ void waybar::modules::MPD::setLabel() { label_.get_style_context()->add_class("playing"); label_.get_style_context()->remove_class("paused"); } else if (paused()) { - format = - config_["format-paused"].isString() ? config_["format-paused"].asString() : config_["format"].asString(); + format = config_["format-paused"].isString() ? config_["format-paused"].asString() + : config_["format"].asString(); label_.get_style_context()->add_class("paused"); label_.get_style_context()->remove_class("playing"); } @@ -216,7 +182,7 @@ void waybar::modules::MPD::setLabel() { } } -std::string waybar::modules::MPD::getStateIcon() { +std::string waybar::modules::MPD::getStateIcon() const { if (!config_["state-icons"].isObject()) { return ""; } @@ -238,7 +204,7 @@ std::string waybar::modules::MPD::getStateIcon() { } } -std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) { +std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) const { if (!config_[optionName + "-icons"].isObject()) { return ""; } @@ -261,15 +227,11 @@ void waybar::modules::MPD::tryConnect() { } connection_ = - unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free); + detail::unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free); - alternate_connection_ = - unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free); - - if (connection_ == nullptr || alternate_connection_ == nullptr) { + if (connection_ == nullptr) { spdlog::error("{}: Failed to connect to MPD", module_name_); connection_.reset(); - alternate_connection_.reset(); return; } @@ -279,7 +241,6 @@ void waybar::modules::MPD::tryConnect() { } catch (std::runtime_error& e) { spdlog::error("{}: Failed to connect to MPD: {}", module_name_, e.what()); connection_.reset(); - alternate_connection_.reset(); } } @@ -292,7 +253,6 @@ void waybar::modules::MPD::checkErrors(mpd_connection* conn) { case MPD_ERROR_CLOSED: mpd_connection_clear_error(conn); connection_.reset(); - alternate_connection_.reset(); state_ = MPD_STATE_UNKNOWN; throw std::runtime_error("Connection to MPD closed"); default: @@ -306,37 +266,20 @@ void waybar::modules::MPD::checkErrors(mpd_connection* conn) { } void waybar::modules::MPD::fetchState() { + if (connection_ == nullptr) { + spdlog::error("{}: Not connected to MPD", module_name_); + return; + } + auto conn = connection_.get(); - status_ = unique_status(mpd_run_status(conn), &mpd_status_free); + + status_ = detail::unique_status(mpd_run_status(conn), &mpd_status_free); checkErrors(conn); + state_ = mpd_status_get_state(status_.get()); checkErrors(conn); - song_ = unique_song(mpd_run_current_song(conn), &mpd_song_free); - checkErrors(conn); -} - -void waybar::modules::MPD::waitForEvent() { - auto conn = alternate_connection_.get(); - // Wait for a player (play/pause), option (random, shuffle, etc.), or playlist - // change - if (!mpd_send_idle_mask( - conn, static_cast(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) { - checkErrors(conn); - return; - } - // alternate_idle_ = true; - - // See issue #277: - // https://github.com/Alexays/Waybar/issues/277 - mpd_recv_idle(conn, /* disable_timeout = */ false); - // See issue #281: - // https://github.com/Alexays/Waybar/issues/281 - std::lock_guard guard(connection_lock_); - - checkErrors(conn); - mpd_response_finish(conn); - + song_ = detail::unique_song(mpd_run_current_song(conn), &mpd_song_free); checkErrors(conn); } @@ -346,24 +289,13 @@ bool waybar::modules::MPD::handlePlayPause(GdkEventButton* const& e) { } if (e->button == 1) { - std::lock_guard guard(connection_lock_); - if (stopped()) { - mpd_run_play(connection_.get()); - } else { - mpd_run_toggle_pause(connection_.get()); - } + if (state_ == MPD_STATE_PLAY) + context_.pause(); + else + context_.play(); } else if (e->button == 3) { - std::lock_guard guard(connection_lock_); - mpd_run_stop(connection_.get()); + context_.stop(); } return true; } - -bool waybar::modules::MPD::stopped() { - return connection_ == nullptr || state_ == MPD_STATE_UNKNOWN || state_ == MPD_STATE_STOP || status_ == nullptr; -} - -bool waybar::modules::MPD::playing() { return connection_ != nullptr && state_ == MPD_STATE_PLAY; } - -bool waybar::modules::MPD::paused() { return connection_ != nullptr && state_ == MPD_STATE_PAUSE; } diff --git a/src/modules/mpd/state.cpp b/src/modules/mpd/state.cpp new file mode 100644 index 00000000..570c167f --- /dev/null +++ b/src/modules/mpd/state.cpp @@ -0,0 +1,382 @@ +#include "modules/mpd/state.hpp" + +#include +#include + +#include "modules/mpd/mpd.hpp" +#if defined(MPD_NOINLINE) +namespace waybar::modules { +#include "modules/mpd/state.inl.hpp" +} // namespace waybar::modules +#endif + +namespace waybar::modules::detail { + +#define IDLE_RUN_NOIDLE_AND_CMD(...) \ + 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"); \ + ctx_->checkErrors(conn); \ + } \ + } \ + __VA_ARGS__; \ + } + +void Idle::play() { + IDLE_RUN_NOIDLE_AND_CMD(mpd_run_play(conn)); + + ctx_->setState(std::make_unique(ctx_)); +} + +void Idle::pause() { + IDLE_RUN_NOIDLE_AND_CMD(mpd_run_pause(conn, true)); + + ctx_->setState(std::make_unique(ctx_)); +} + +void Idle::stop() { + IDLE_RUN_NOIDLE_AND_CMD(mpd_run_stop(conn)); + + ctx_->setState(std::make_unique(ctx_)); +} + +#undef IDLE_RUN_NOIDLE_AND_CMD + +void Idle::update() noexcept { + // This is intentionally blank. +} + +void Idle::entry() 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: Idle: failed to register for IDLE events"); + } else { + spdlog::debug("mpd: Idle: watching FD"); + sigc::slot idle_slot = sigc::mem_fun(*this, &Idle::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); + } +} + +void Idle::exit() noexcept { + if (idle_connection_.connected()) { + idle_connection_.disconnect(); + spdlog::debug("mpd: Idle: unwatching FD"); + } +} + +bool Idle::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: Idle: recv_idle events -> {}", events); + + mpd_response_finish(conn); + try { + ctx_->checkErrors(conn); + } catch (std::exception const& e) { + spdlog::warn("mpd: Idle: error: {}", e.what()); + ctx_->setState(std::make_unique(ctx_)); + return false; + } + + ctx_->fetchState(); + mpd_state state = ctx_->state(); + + if (state == MPD_STATE_STOP) { + ctx_->emit(); + ctx_->setState(std::make_unique(ctx_)); + } else if (state == MPD_STATE_PLAY) { + ctx_->emit(); + ctx_->setState(std::make_unique(ctx_)); + } else if (state == MPD_STATE_PAUSE) { + ctx_->emit(); + ctx_->setState(std::make_unique(ctx_)); + } else { + ctx_->emit(); + // self transition + ctx_->setState(std::make_unique(ctx_)); + } + + return false; +} + +void Playing::entry() noexcept { + sigc::slot timer_slot = sigc::mem_fun(*this, &Playing::on_timer); + timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 1'000); + spdlog::debug("mpd: Playing: enabled 1 second periodic timer."); +} + +void Playing::exit() noexcept { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + spdlog::debug("mpd: Playing: disabled 1 second periodic timer."); + } +} + +bool Playing::on_timer() { + // Attempt to connect with MPD. + try { + ctx_->tryConnect(); + + // Success? + if (!ctx_->is_connected()) { + ctx_->setState(std::make_unique(ctx_)); + return false; + } + + 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(); + } catch (std::exception const& e) { + spdlog::warn("mpd: Playing: error: {}", e.what()); + ctx_->setState(std::make_unique(ctx_)); + return false; + } + + return true; +} + +void Playing::stop() { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + + mpd_run_stop(ctx_->connection().get()); + } + + ctx_->setState(std::make_unique(ctx_)); +} + +void Playing::pause() { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + + mpd_run_pause(ctx_->connection().get(), true); + } + + ctx_->setState(std::make_unique(ctx_)); +} + +void Playing::update() noexcept { ctx_->do_update(); } + +void Paused::entry() noexcept { + sigc::slot timer_slot = sigc::mem_fun(*this, &Paused::on_timer); + timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200); + spdlog::debug("mpd: Paused: enabled 200 ms periodic timer."); +} + +void Paused::exit() noexcept { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + spdlog::debug("mpd: Paused: disabled 200 ms periodic timer."); + } +} + +bool Paused::on_timer() { + bool rc = true; + + // Attempt to connect with MPD. + try { + ctx_->tryConnect(); + + // Success? + if (!ctx_->is_connected()) { + ctx_->setState(std::make_unique(ctx_)); + return false; + } + + ctx_->fetchState(); + + ctx_->emit(); + + if (ctx_->is_paused()) { + ctx_->setState(std::make_unique(ctx_)); + rc = false; + } else if (ctx_->is_playing()) { + ctx_->setState(std::make_unique(ctx_)); + rc = false; + } else if (ctx_->is_stopped()) { + ctx_->setState(std::make_unique(ctx_)); + rc = false; + } + } catch (std::exception const& e) { + spdlog::warn("mpd: Paused: error: {}", e.what()); + ctx_->setState(std::make_unique(ctx_)); + rc = false; + } + + return rc; +} + +void Paused::play() { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + + mpd_run_play(ctx_->connection().get()); + } + + ctx_->setState(std::make_unique(ctx_)); +} + +void Paused::stop() { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + + mpd_run_stop(ctx_->connection().get()); + } + + ctx_->setState(std::make_unique(ctx_)); +} + +void Paused::update() noexcept { ctx_->do_update(); } + +void Stopped::entry() noexcept { + sigc::slot timer_slot = sigc::mem_fun(*this, &Stopped::on_timer); + timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200); + spdlog::debug("mpd: Stopped: enabled 200 ms periodic timer."); +} + +void Stopped::exit() noexcept { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + spdlog::debug("mpd: Stopped: disabled 200 ms periodic timer."); + } +} + +bool Stopped::on_timer() { + bool rc = true; + + // Attempt to connect with MPD. + try { + ctx_->tryConnect(); + + // Success? + if (!ctx_->is_connected()) { + ctx_->setState(std::make_unique(ctx_)); + return false; + } + + ctx_->fetchState(); + + ctx_->emit(); + + if (ctx_->is_stopped()) { + ctx_->setState(std::make_unique(ctx_)); + rc = false; + } else if (ctx_->is_playing()) { + ctx_->setState(std::make_unique(ctx_)); + rc = false; + } else if (ctx_->is_paused()) { + ctx_->setState(std::make_unique(ctx_)); + rc = false; + } + } catch (std::exception const& e) { + spdlog::warn("mpd: Stopped: error: {}", e.what()); + ctx_->setState(std::make_unique(ctx_)); + rc = false; + } + + return rc; +} + +void Stopped::play() { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + + mpd_run_play(ctx_->connection().get()); + } + + ctx_->setState(std::make_unique(ctx_)); +} + +void Stopped::pause() { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + + mpd_run_pause(ctx_->connection().get(), true); + } + + ctx_->setState(std::make_unique(ctx_)); +} + +void Stopped::update() noexcept { ctx_->do_update(); } + +void Disconnected::arm_timer(int interval) noexcept { + // unregister timer, if present + disarm_timer(); + + // register timer + sigc::slot timer_slot = sigc::mem_fun(*this, &Disconnected::on_timer); + timer_connection_ = + Glib::signal_timeout().connect(timer_slot, interval); + spdlog::debug("mpd: Disconnected: enabled interval timer."); +} + +void Disconnected::disarm_timer() noexcept { + // unregister timer, if present + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + spdlog::debug("mpd: Disconnected: disabled interval timer."); + } +} + +void Disconnected::entry() noexcept { + arm_timer(1'000); +} + +void Disconnected::exit() noexcept { + disarm_timer(); +} + +bool Disconnected::on_timer() { + // Attempt to connect with MPD. + try { + ctx_->tryConnect(); + + // Success? + if (ctx_->is_connected()) { + ctx_->fetchState(); + ctx_->emit(); + + if (ctx_->is_playing()) { + ctx_->setState(std::make_unique(ctx_)); + } else if (ctx_->is_paused()) { + ctx_->setState(std::make_unique(ctx_)); + } else { + ctx_->setState(std::make_unique(ctx_)); + } + + return false; // do not rearm timer + } + } catch (std::exception const& e) { + spdlog::warn("mpd: Disconnected: error: {}", e.what()); + } + + arm_timer(ctx_->interval() * 1'000); + + return false; +} + +void Disconnected::update() noexcept { ctx_->do_update(); } + +} // namespace waybar::modules::detail From 587eb5fdb4d7de2891fa0a874f7c4ea0c9d4d882 Mon Sep 17 00:00:00 2001 From: Joseph Benden Date: Mon, 19 Oct 2020 11:54:36 -0700 Subject: [PATCH 27/94] mpd: support password protected MPD - Add MPD module option `password`, and document it. - Add logic to send the password, directly after connecting to MPD. Fixes: #576 Signed-off-by: Joseph Benden --- include/modules/mpd/mpd.hpp | 7 ++++--- man/waybar-mpd.5.scd | 4 ++++ src/modules/mpd/mpd.cpp | 11 +++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/include/modules/mpd/mpd.hpp b/include/modules/mpd/mpd.hpp index f92df933..0fc1ce99 100644 --- a/include/modules/mpd/mpd.hpp +++ b/include/modules/mpd/mpd.hpp @@ -1,7 +1,7 @@ #pragma once -#include #include +#include #include #include @@ -22,8 +22,9 @@ class MPD : public ALabel { // Not using unique_ptr since we don't manage the pointer // (It's either nullptr, or from the config) - const char* server_; - const unsigned port_; + const char* server_; + const unsigned port_; + const std::string password_; unsigned timeout_; diff --git a/man/waybar-mpd.5.scd b/man/waybar-mpd.5.scd index e8105deb..8c33c624 100644 --- a/man/waybar-mpd.5.scd +++ b/man/waybar-mpd.5.scd @@ -20,6 +20,10 @@ Addressed by *mpd* typeof: integer ++ The port MPD listens to. If empty, use the default port. +*password*: ++ + typeof: string ++ + The password required to connect to the MPD server. If empty, no password is sent to MPD. + *interval*: ++ typeof: integer++ default: 5 ++ diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index edce9417..6dc24e73 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -15,6 +15,7 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config) module_name_(id.empty() ? "mpd" : "mpd#" + id), server_(nullptr), port_(config_["port"].isUInt() ? config["port"].asUInt() : 0), + password_(config_["password"].empty() ? "" : config_["password"].asString()), timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000), connection_(nullptr, &mpd_connection_free), status_(nullptr, &mpd_status_free), @@ -238,6 +239,16 @@ void waybar::modules::MPD::tryConnect() { try { checkErrors(connection_.get()); spdlog::debug("{}: Connected to MPD", module_name_); + + if (!password_.empty()) { + bool res = mpd_run_password(connection_.get(), password_.c_str()); + if (!res) { + spdlog::error("{}: Wrong MPD password", module_name_); + connection_.reset(); + return; + } + checkErrors(connection_.get()); + } } catch (std::runtime_error& e) { spdlog::error("{}: Failed to connect to MPD: {}", module_name_, e.what()); connection_.reset(); From ed402d7583291eda34cac4a32b4caac58e7dc900 Mon Sep 17 00:00:00 2001 From: nikto_b Date: Tue, 20 Oct 2020 12:20:58 +0300 Subject: [PATCH 28/94] feature: language submodule - created man page --- man/waybar-sway-language.5.scd | 72 ++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 man/waybar-sway-language.5.scd diff --git a/man/waybar-sway-language.5.scd b/man/waybar-sway-language.5.scd new file mode 100644 index 00000000..a288cca9 --- /dev/null +++ b/man/waybar-sway-language.5.scd @@ -0,0 +1,72 @@ +waybar-sway-language(5) + +# NAME + +waybar - sway language module + +# DESCRIPTION + +The *language* module displays the current keyboard layout in Sway + +# CONFIGURATION + +Addressed by *sway/language* + +*format*: ++ + typeof: string ++ + default: {} ++ + The format, how information should be displayed. On {} data gets inserted. + +*rotate*: ++ + typeof: integer ++ + Positive value to rotate the text label. + +*max-length*: ++ + typeof: integer ++ + The maximum length in character the module should display. + +*on-click*: ++ + typeof: string ++ + Command to execute when clicked on the module. + +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + +*on-click-right*: ++ + typeof: string ++ + Command to execute when you right clicked on the module. + +*on-update*: ++ + typeof: string ++ + Command to execute when the module is updated. + +*on-scroll-up*: ++ + typeof: string ++ + Command to execute when scrolling up on the module. + +*on-scroll-down*: ++ + typeof: string ++ + Command to execute when scrolling down on the module. + +*smooth-scrolling-threshold*: ++ + typeof: double ++ + Threshold to be used when scrolling. + +*tooltip*: ++ + typeof: bool ++ + default: true ++ + Option to disable tooltip on hover. + +# EXAMPLES + +``` +"sway/language": { + "format": "{}", + "max-length": 50 +} +``` + +# STYLE + +- *#language* From be3f47b37437e11a84031b076ef46802c654c7fe Mon Sep 17 00:00:00 2001 From: Flakebi Date: Fri, 23 Oct 2020 21:13:20 +0200 Subject: [PATCH 29/94] Fix various mpd bugs - Add elapsedTime and totalTime to tooltip format arguments - Catch format exceptions and print error - Copy mpd connection error message before it gets freed - Update display after connection to mpd was lost --- src/modules/mpd/mpd.cpp | 74 ++++++++++++++++++++++----------------- src/modules/mpd/state.cpp | 1 + 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 6dc24e73..710f23ff 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -144,42 +144,51 @@ void waybar::modules::MPD::setLabel() { bool singleActivated = mpd_status_get_single(status_.get()); std::string singleIcon = getOptionIcon("single", singleActivated); - // TODO: format can fail - label_.set_markup( - fmt::format(format, - fmt::arg("artist", Glib::Markup::escape_text(artist).raw()), - fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()), - fmt::arg("album", Glib::Markup::escape_text(album).raw()), - fmt::arg("title", Glib::Markup::escape_text(title).raw()), - fmt::arg("date", Glib::Markup::escape_text(date).raw()), - fmt::arg("elapsedTime", elapsedTime), - fmt::arg("totalTime", totalTime), - fmt::arg("songPosition", song_pos), - fmt::arg("queueLength", queue_length), - fmt::arg("stateIcon", stateIcon), - fmt::arg("consumeIcon", consumeIcon), - fmt::arg("randomIcon", randomIcon), - fmt::arg("repeatIcon", repeatIcon), - fmt::arg("singleIcon", singleIcon))); + try { + label_.set_markup( + fmt::format(format, + fmt::arg("artist", Glib::Markup::escape_text(artist).raw()), + fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()), + fmt::arg("album", Glib::Markup::escape_text(album).raw()), + fmt::arg("title", Glib::Markup::escape_text(title).raw()), + fmt::arg("date", Glib::Markup::escape_text(date).raw()), + fmt::arg("elapsedTime", elapsedTime), + fmt::arg("totalTime", totalTime), + fmt::arg("songPosition", song_pos), + fmt::arg("queueLength", queue_length), + fmt::arg("stateIcon", stateIcon), + fmt::arg("consumeIcon", consumeIcon), + fmt::arg("randomIcon", randomIcon), + fmt::arg("repeatIcon", repeatIcon), + fmt::arg("singleIcon", singleIcon))); + } catch (fmt::format_error const& e) { + spdlog::warn("mpd: format error: {}", e.what()); + } if (tooltipEnabled()) { std::string tooltip_format; tooltip_format = config_["tooltip-format"].isString() ? config_["tooltip-format"].asString() : "MPD (connected)"; - auto tooltip_text = fmt::format(tooltip_format, - fmt::arg("artist", artist), - fmt::arg("albumArtist", album_artist), - fmt::arg("album", album), - fmt::arg("title", title), - fmt::arg("date", date), - fmt::arg("songPosition", song_pos), - fmt::arg("queueLength", queue_length), - fmt::arg("stateIcon", stateIcon), - fmt::arg("consumeIcon", consumeIcon), - fmt::arg("randomIcon", randomIcon), - fmt::arg("repeatIcon", repeatIcon), - fmt::arg("singleIcon", singleIcon)); - label_.set_tooltip_text(tooltip_text); + try { + auto tooltip_text = fmt::format(tooltip_format, + fmt::arg("artist", artist), + fmt::arg("albumArtist", album_artist), + fmt::arg("album", album), + fmt::arg("title", title), + fmt::arg("date", date), + fmt::arg("elapsedTime", elapsedTime), + fmt::arg("totalTime", totalTime), + fmt::arg("songPosition", song_pos), + fmt::arg("queueLength", queue_length), + fmt::arg("stateIcon", stateIcon), + fmt::arg("consumeIcon", consumeIcon), + fmt::arg("randomIcon", randomIcon), + fmt::arg("repeatIcon", repeatIcon), + fmt::arg("singleIcon", singleIcon)); + label_.set_tooltip_text(tooltip_text); + } catch (fmt::format_error const& e) { + spdlog::warn("mpd: format error (tooltip): {}", e.what()); + } } } @@ -269,8 +278,9 @@ void waybar::modules::MPD::checkErrors(mpd_connection* conn) { default: if (conn) { auto error_message = mpd_connection_get_error_message(conn); + std::string error(error_message); mpd_connection_clear_error(conn); - throw std::runtime_error(std::string(error_message)); + throw std::runtime_error(error); } throw std::runtime_error("Invalid connection"); } diff --git a/src/modules/mpd/state.cpp b/src/modules/mpd/state.cpp index 570c167f..ffe18e7c 100644 --- a/src/modules/mpd/state.cpp +++ b/src/modules/mpd/state.cpp @@ -341,6 +341,7 @@ void Disconnected::disarm_timer() noexcept { } void Disconnected::entry() noexcept { + ctx_->emit(); arm_timer(1'000); } From 67d54ef3d53e5257e465ec556177ea569100263e Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sat, 24 Oct 2020 22:56:24 -0700 Subject: [PATCH 30/94] fix(wlr/taskbar): do not bind to unsupported protocol versions It's not allowed to bind to a higher version of a wayland protocol than supported by the client. Binding wlr-foreign-toplevel-manager-v1 v3 to a generated code for v2 causes errors in libwayland due to a missing handler for `zwlr_foreign_toplevel_handle_v1.parent` event. --- src/client.cpp | 2 ++ src/modules/wlr/taskbar.cpp | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/client.cpp b/src/client.cpp index 70cc22cf..9dd93d81 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -32,6 +32,8 @@ void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint const char *interface, uint32_t version) { auto client = static_cast(data); if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { + // limit version to a highest supported by the client protocol file + version = std::min(version, zwlr_layer_shell_v1_interface.version); client->layer_shell = static_cast( wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version)); } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 && diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 45915dc6..8d6fa9b4 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -653,9 +653,11 @@ void Taskbar::register_manager(struct wl_registry *registry, uint32_t name, uint spdlog::warn("Register foreign toplevel manager again although already existing!"); return; } - if (version != 2) { + if (version < ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN_SINCE_VERSION) { spdlog::warn("Using different foreign toplevel manager protocol version: {}", version); } + // limit version to a highest supported by the client protocol file + version = std::min(version, zwlr_foreign_toplevel_manager_v1_interface.version); manager_ = static_cast(wl_registry_bind(registry, name, &zwlr_foreign_toplevel_manager_v1_interface, version)); @@ -672,6 +674,7 @@ void Taskbar::register_seat(struct wl_registry *registry, uint32_t name, uint32_ spdlog::warn("Register seat again although already existing!"); return; } + version = std::min(version, wl_seat_interface.version); seat_ = static_cast(wl_registry_bind(registry, name, &wl_seat_interface, version)); } From 7a0c0ca6134dc777e179980d77fc76ca9adb4410 Mon Sep 17 00:00:00 2001 From: 1sixth <67363572+1sixth@users.noreply.github.com> Date: Wed, 28 Oct 2020 19:39:50 +0800 Subject: [PATCH 31/94] replace lowercase "k" with uppercase "K" Other units are all uppercased, so using an uppercased "K" makes it look more consistent (especially when {bandwidthUpBits} or something like that is used). --- include/util/format.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/util/format.hpp b/include/util/format.hpp index 288d8f0c..d7d16090 100644 --- a/include/util/format.hpp +++ b/include/util/format.hpp @@ -42,7 +42,7 @@ namespace fmt { template auto format(const pow_format& s, FormatContext &ctx) -> decltype (ctx.out()) { - const char* units[] = { "", "k", "M", "G", "T", "P", nullptr}; + const char* units[] = { "", "K", "M", "G", "T", "P", nullptr}; auto base = s.binary_ ? 1024ull : 1000ll; auto fraction = (double) s.val_; From 2b3d7be9cb7253d852db0214f7c492d6aec8f74a Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Tue, 20 Oct 2020 23:18:58 -0700 Subject: [PATCH 32/94] feat(bar): let gtk-layer-shell manage exclusive zone Previous attempts to use auto exclusive zone from gtk-layer-shell failed because gls was expecting real booleans (`TRUE`/`FALSE`) as set_anchor arguments. With that being fixed, gtk_layer_auto_exclusive_zone_enable makes gls handle everything related to the bar resizing. The only remaining purpose of onConfigureGLS is to log warnings and bar size changes; gtk-layer-shell code path no longer needs saved width_ or height_ values. --- src/bar.cpp | 72 ++++++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/src/bar.cpp b/src/bar.cpp index 8af6b971..7f60b2fc 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -2,10 +2,11 @@ #include #endif +#include + #include "bar.hpp" #include "client.hpp" #include "factory.hpp" -#include waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) : output(w_output), @@ -134,33 +135,32 @@ void waybar::Bar::initGtkLayerShell() { gtk_layer_set_monitor(gtk_window, output->monitor->gobj()); gtk_layer_set_namespace(gtk_window, "waybar"); - gtk_layer_set_anchor( - gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); - gtk_layer_set_anchor( - gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT); - gtk_layer_set_anchor( - gtk_window, GTK_LAYER_SHELL_EDGE_TOP, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP); - gtk_layer_set_anchor( - gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM); + gtk_layer_set_anchor(gtk_window, + GTK_LAYER_SHELL_EDGE_LEFT, + (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) ? TRUE : FALSE); + gtk_layer_set_anchor(gtk_window, + GTK_LAYER_SHELL_EDGE_RIGHT, + (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) ? TRUE : FALSE); + gtk_layer_set_anchor(gtk_window, + GTK_LAYER_SHELL_EDGE_TOP, + (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) ? TRUE : FALSE); + gtk_layer_set_anchor(gtk_window, + GTK_LAYER_SHELL_EDGE_BOTTOM, + (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM) ? TRUE : FALSE); gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, margins_.left); gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, margins_.right); gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_TOP, margins_.top); gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, margins_.bottom); - if (width_ > 1 && height_ > 1) { - /* configure events are not emitted if the bar is using initial size */ - setExclusiveZone(width_, height_); - } + setExclusiveZone(width_, height_); } void waybar::Bar::onConfigureGLS(GdkEventConfigure* ev) { /* * GTK wants new size for the window. - * Actual resizing is done within the gtk-layer-shell code; the only remaining action is to apply - * exclusive zone. - * gtk_layer_auto_exclusive_zone_enable() could handle even that, but at the cost of ignoring - * margins on unanchored edge. + * Actual resizing and management of the exclusve zone is handled within the gtk-layer-shell code. + * This event handler only updates stored size of the window and prints some warnings. * * Note: forced resizing to a window smaller than required by GTK would not work with * gtk-layer-shell. @@ -170,14 +170,13 @@ void waybar::Bar::onConfigureGLS(GdkEventConfigure* ev) { spdlog::warn(MIN_WIDTH_MSG, width_, ev->width); } } else { - if (!vertical && height_ > 1 && ev->height > static_cast(height_)) { + if (height_ > 1 && ev->height > static_cast(height_)) { spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height); } } width_ = ev->width; height_ = ev->height; spdlog::info(BAR_SIZE_MSG, width_, height_, output->name); - setExclusiveZone(width_, height_); } void waybar::Bar::onMapGLS(GdkEventAny* ev) { @@ -262,26 +261,31 @@ void waybar::Bar::onMap(GdkEventAny* ev) { } void waybar::Bar::setExclusiveZone(uint32_t width, uint32_t height) { - auto zone = 0; - if (visible) { - // exclusive zone already includes margin for anchored edge, - // only opposite margin should be added - if (vertical) { - zone += width; - zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) ? margins_.right : margins_.left; - } else { - zone += height; - zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) ? margins_.bottom : margins_.top; - } - } - spdlog::debug("Set exclusive zone {} for output {}", zone, output->name); - #ifdef HAVE_GTK_LAYER_SHELL if (use_gls_) { - gtk_layer_set_exclusive_zone(window.gobj(), zone); + if (visible) { + spdlog::debug("Enable auto exclusive zone for output {}", output->name); + gtk_layer_auto_exclusive_zone_enable(window.gobj()); + } else { + spdlog::debug("Disable exclusive zone for output {}", output->name); + gtk_layer_set_exclusive_zone(window.gobj(), 0); + } } else #endif { + auto zone = 0; + if (visible) { + // exclusive zone already includes margin for anchored edge, + // only opposite margin should be added + if (vertical) { + zone += width; + zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) ? margins_.right : margins_.left; + } else { + zone += height; + zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) ? margins_.bottom : margins_.top; + } + } + spdlog::debug("Set exclusive zone {} for output {}", zone, output->name); zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_, zone); } } From 7735c80d0e421ab0787f99db6c269895ef856729 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Wed, 21 Oct 2020 22:16:12 -0700 Subject: [PATCH 33/94] refactor(bar): Split GLS and raw layer-shell implementations Extract two surface implementations from the bar class: GLSSurfaceImpl and RawSurfaceImpl. This change allowed to remove _all_ surface type conditionals and significantly simplify the Bar code. The change also applies PImpl pattern to the Bar, allowing to remove some headers and fields from `bar.hpp`. --- include/bar.hpp | 62 ++--- src/bar.cpp | 644 ++++++++++++++++++++++++++++-------------------- 2 files changed, 408 insertions(+), 298 deletions(-) diff --git a/include/bar.hpp b/include/bar.hpp index fdc5a739..df503275 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -7,6 +7,7 @@ #include #include #include + #include "AModule.hpp" #include "idle-inhibit-unstable-v1-client-protocol.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" @@ -23,13 +24,35 @@ struct waybar_output { nullptr, &zxdg_output_v1_destroy}; }; +struct bar_margins { + int top = 0; + int right = 0; + int bottom = 0; + int left = 0; +}; + +class BarSurface { + protected: + BarSurface() = default; + + public: + virtual void set_exclusive_zone(bool enable) = 0; + virtual void set_layer(const std::string_view &layer) = 0; + virtual void set_margins(const struct bar_margins &margins) = 0; + virtual void set_position(const std::string_view &position) = 0; + virtual void set_size(uint32_t width, uint32_t height) = 0; + + virtual ~BarSurface() = default; +}; + class Bar { public: Bar(struct waybar_output *w_output, const Json::Value &); Bar(const Bar &) = delete; ~Bar() = default; - auto toggle() -> void; + void setVisible(bool visible); + void toggle(); void handleSignal(int); struct waybar_output *output; @@ -40,48 +63,15 @@ class Bar { Gtk::Window window; private: - static constexpr const char *MIN_HEIGHT_MSG = - "Requested height: {} exceeds the minimum height: {} required by the modules"; - static constexpr const char *MIN_WIDTH_MSG = - "Requested width: {} exceeds the minimum width: {} required by the modules"; - static constexpr const char *BAR_SIZE_MSG = - "Bar configured (width: {}, height: {}) for output: {}"; - static constexpr const char *SIZE_DEFINED = - "{} size is defined in the config file so it will stay like that"; - static void layerSurfaceHandleConfigure(void *, struct zwlr_layer_surface_v1 *, uint32_t, - uint32_t, uint32_t); - static void layerSurfaceHandleClosed(void *, struct zwlr_layer_surface_v1 *); - -#ifdef HAVE_GTK_LAYER_SHELL - /* gtk-layer-shell code */ - void initGtkLayerShell(); - void onConfigureGLS(GdkEventConfigure *ev); - void onMapGLS(GdkEventAny *ev); -#endif - /* fallback layer-surface code */ - void onConfigure(GdkEventConfigure *ev); - void onRealize(); - void onMap(GdkEventAny *ev); - void setSurfaceSize(uint32_t width, uint32_t height); - /* common code */ - void setExclusiveZone(uint32_t width, uint32_t height); + void onMap(GdkEventAny *); auto setupWidgets() -> void; void getModules(const Factory &, const std::string &); void setupAltFormatKeyForModule(const std::string &module_name); void setupAltFormatKeyForModuleList(const char *module_list_name); - struct margins { - int top = 0; - int right = 0; - int bottom = 0; - int left = 0; - } margins_; - struct zwlr_layer_surface_v1 *layer_surface_; - // use gtk-layer-shell instead of handling layer surfaces directly - bool use_gls_ = false; + std::unique_ptr surface_impl_; uint32_t width_ = 0; uint32_t height_ = 1; - uint8_t anchor_; Gtk::Box left_; Gtk::Box center_; Gtk::Box right_; diff --git a/src/bar.cpp b/src/bar.cpp index 7f60b2fc..a684f12b 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -8,13 +8,346 @@ #include "client.hpp" #include "factory.hpp" +namespace waybar { +static constexpr const char* MIN_HEIGHT_MSG = + "Requested height: {} exceeds the minimum height: {} required by the modules"; + +static constexpr const char* MIN_WIDTH_MSG = + "Requested width: {} exceeds the minimum width: {} required by the modules"; + +static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height: {}) for output: {}"; + +static constexpr const char* SIZE_DEFINED = + "{} size is defined in the config file so it will stay like that"; + +#ifdef HAVE_GTK_LAYER_SHELL +struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { + GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} { + output_name_ = output.name; + // this has to be executed before GtkWindow.realize + gtk_layer_init_for_window(window_.gobj()); + gtk_layer_set_keyboard_interactivity(window.gobj(), FALSE); + gtk_layer_set_monitor(window_.gobj(), output.monitor->gobj()); + gtk_layer_set_namespace(window_.gobj(), "waybar"); + + window.signal_configure_event().connect_notify( + sigc::mem_fun(*this, &GLSSurfaceImpl::on_configure)); + } + + void set_exclusive_zone(bool enable) override { + if (enable) { + gtk_layer_auto_exclusive_zone_enable(window_.gobj()); + } else { + gtk_layer_set_exclusive_zone(window_.gobj(), 0); + } + } + + void set_margins(const struct bar_margins& margins) override { + gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_LEFT, margins.left); + gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, margins.right); + gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_TOP, margins.top); + gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, margins.bottom); + } + + void set_layer(const std::string_view& value) override { + auto layer = GTK_LAYER_SHELL_LAYER_BOTTOM; + if (value == "top") { + layer = GTK_LAYER_SHELL_LAYER_TOP; + } else if (value == "overlay") { + layer = GTK_LAYER_SHELL_LAYER_OVERLAY; + } + gtk_layer_set_layer(window_.gobj(), layer); + } + + void set_position(const std::string_view& position) override { + auto unanchored = GTK_LAYER_SHELL_EDGE_BOTTOM; + vertical_ = false; + if (position == "bottom") { + unanchored = GTK_LAYER_SHELL_EDGE_TOP; + } else if (position == "left") { + unanchored = GTK_LAYER_SHELL_EDGE_RIGHT; + vertical_ = true; + } else if (position == "right") { + vertical_ = true; + unanchored = GTK_LAYER_SHELL_EDGE_LEFT; + } + for (auto edge : {GTK_LAYER_SHELL_EDGE_LEFT, + GTK_LAYER_SHELL_EDGE_RIGHT, + GTK_LAYER_SHELL_EDGE_TOP, + GTK_LAYER_SHELL_EDGE_BOTTOM}) { + gtk_layer_set_anchor(window_.gobj(), edge, unanchored != edge); + } + } + + void set_size(uint32_t width, uint32_t height) override { + width_ = width; + height_ = height; + window_.set_size_request(width_, height_); + }; + + private: + Gtk::Window& window_; + std::string output_name_; + uint32_t width_; + uint32_t height_; + bool vertical_ = false; + + void on_configure(GdkEventConfigure* ev) { + /* + * GTK wants new size for the window. + * Actual resizing and management of the exclusve zone is handled within the gtk-layer-shell + * code. This event handler only updates stored size of the window and prints some warnings. + * + * Note: forced resizing to a window smaller than required by GTK would not work with + * gtk-layer-shell. + */ + if (vertical_) { + if (width_ > 1 && ev->width > static_cast(width_)) { + spdlog::warn(MIN_WIDTH_MSG, width_, ev->width); + } + } else { + if (height_ > 1 && ev->height > static_cast(height_)) { + spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height); + } + } + width_ = ev->width; + height_ = ev->height; + spdlog::info(BAR_SIZE_MSG, width_, height_, output_name_); + } +}; +#endif + +struct RawSurfaceImpl : public BarSurface, public sigc::trackable { + RawSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} { + output_ = gdk_wayland_monitor_get_wl_output(output.monitor->gobj()); + output_name_ = output.name; + + window.signal_realize().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::on_realize)); + window.signal_map_event().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::on_map)); + window.signal_configure_event().connect_notify( + sigc::mem_fun(*this, &RawSurfaceImpl::on_configure)); + + if (window.get_realized()) { + on_realize(); + } + } + + void set_exclusive_zone(bool enable) override { + exclusive_zone_ = enable; + if (layer_surface_) { + auto zone = 0; + if (enable) { + // exclusive zone already includes margin for anchored edge, + // only opposite margin should be added + if ((anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR) { + zone += width_; + zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) ? margins_.right : margins_.left; + } else { + zone += height_; + zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) ? margins_.bottom : margins_.top; + } + } + spdlog::debug("Set exclusive zone {} for output {}", zone, output_name_); + zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_, zone); + } + } + + void set_layer(const std::string_view& layer) override { + layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; + if (layer == "top") { + layer_ = ZWLR_LAYER_SHELL_V1_LAYER_TOP; + } else if (layer == "overlay") { + layer_ = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; + } + // updating already mapped window + if (layer_surface_) { + if (zwlr_layer_surface_v1_get_version(layer_surface_) >= + ZWLR_LAYER_SURFACE_V1_SET_LAYER_SINCE_VERSION) { + zwlr_layer_surface_v1_set_layer(layer_surface_, layer_); + commit(); + } else { + spdlog::warn("Unable to set layer: layer-shell interface version is too old"); + } + } + } + + void set_margins(const struct bar_margins& margins) override { + margins_ = margins; + // updating already mapped window + if (layer_surface_) { + zwlr_layer_surface_v1_set_margin( + layer_surface_, margins_.top, margins_.right, margins_.bottom, margins_.left); + commit(); + } + } + + void set_position(const std::string_view& position) override { + anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; + if (position == "bottom") { + anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; + } else if (position == "left") { + anchor_ = VERTICAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; + } else if (position == "right") { + anchor_ = VERTICAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + } + + // updating already mapped window + if (layer_surface_) { + zwlr_layer_surface_v1_set_anchor(layer_surface_, anchor_); + commit(); + } + } + + void set_size(uint32_t width, uint32_t height) override { + width_ = width; + height_ = height; + // layer_shell.configure handler should update exclusive zone if size changes + window_.set_size_request(width, height); + }; + + private: + constexpr static uint8_t VERTICAL_ANCHOR = + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; + constexpr static uint8_t HORIZONTAL_ANCHOR = + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + + Gtk::Window& window_; + std::string output_name_; + uint32_t width_; + uint32_t height_; + uint8_t anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; + bool exclusive_zone_ = true; + struct bar_margins margins_; + + zwlr_layer_shell_v1_layer layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; + struct wl_output* output_ = nullptr; + struct wl_surface* surface_ = nullptr; + struct zwlr_layer_surface_v1* layer_surface_ = nullptr; + + void on_realize() { + auto gdk_window = window_.get_window()->gobj(); + gdk_wayland_window_set_use_custom_surface(gdk_window); + } + + void on_map(GdkEventAny* ev) { + auto client = Client::inst(); + auto gdk_window = window_.get_window()->gobj(); + surface_ = gdk_wayland_window_get_wl_surface(gdk_window); + + layer_surface_ = zwlr_layer_shell_v1_get_layer_surface( + client->layer_shell, surface_, output_, layer_, "waybar"); + + zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_, false); + + zwlr_layer_surface_v1_set_anchor(layer_surface_, anchor_); + zwlr_layer_surface_v1_set_margin( + layer_surface_, margins_.top, margins_.right, margins_.bottom, margins_.left); + set_surface_size(width_, height_); + set_exclusive_zone(exclusive_zone_); + + static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { + .configure = on_surface_configure, + .closed = on_surface_closed, + }; + zwlr_layer_surface_v1_add_listener(layer_surface_, &layer_surface_listener, this); + + wl_surface_commit(surface_); + wl_display_roundtrip(client->wl_display); + } + + void on_configure(GdkEventConfigure* ev) { + /* + * GTK wants new size for the window. + * + * Prefer configured size if it's non-default. + * If the size is not set and the window is smaller than requested by GTK, request resize from + * layer surface. + */ + auto tmp_height = height_; + auto tmp_width = width_; + if (ev->height > static_cast(height_)) { + // Default minimal value + if (height_ > 1) { + spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height); + } + /* + if (config["height"].isUInt()) { + spdlog::info(SIZE_DEFINED, "Height"); + } else */ + tmp_height = ev->height; + } + if (ev->width > static_cast(width_)) { + // Default minimal value + if (width_ > 1) { + spdlog::warn(MIN_WIDTH_MSG, width_, ev->width); + } + /* + if (config["width"].isUInt()) { + spdlog::info(SIZE_DEFINED, "Width"); + } else */ + tmp_width = ev->width; + } + if (tmp_width != width_ || tmp_height != height_) { + set_surface_size(tmp_width, tmp_height); + } + } + + void commit() { + if (window_.get_mapped()) { + wl_surface_commit(surface_); + } + } + + void set_surface_size(uint32_t width, uint32_t height) { + /* If the client is anchored to two opposite edges, layer_surface.configure will return + * size without margins for the axis. + * layer_surface.set_size, however, expects size with margins for the anchored axis. + * This is not specified by wlr-layer-shell and based on actual behavior of sway. + */ + bool vertical = (anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR; + if (vertical && height > 1) { + height += margins_.top + margins_.bottom; + } + if (!vertical && width > 1) { + width += margins_.right + margins_.left; + } + spdlog::debug("Set surface size {}x{} for output {}", width, height, output_name_); + zwlr_layer_surface_v1_set_size(layer_surface_, width, height); + } + + static void on_surface_configure(void* data, struct zwlr_layer_surface_v1* surface, + uint32_t serial, uint32_t width, uint32_t height) { + auto o = static_cast(data); + if (width != o->width_ || height != o->height_) { + o->width_ = width; + o->height_ = height; + o->window_.set_size_request(o->width_, o->height_); + o->window_.resize(o->width_, o->height_); + o->set_exclusive_zone(o->exclusive_zone_); + spdlog::info(BAR_SIZE_MSG, + o->width_ == 1 ? "auto" : std::to_string(o->width_), + o->height_ == 1 ? "auto" : std::to_string(o->height_), + o->output_name_); + wl_surface_commit(o->surface_); + } + zwlr_layer_surface_v1_ack_configure(surface, serial); + } + + static void on_surface_closed(void* data, struct zwlr_layer_surface_v1* /* surface */) { + auto o = static_cast(data); + if (o->layer_surface_) { + zwlr_layer_surface_v1_destroy(o->layer_surface_); + o->layer_surface_ = nullptr; + } + } +}; + +}; // namespace waybar + waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) : output(w_output), config(w_config), - surface(nullptr), window{Gtk::WindowType::WINDOW_TOPLEVEL}, - layer_surface_(nullptr), - anchor_(ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP), left_(Gtk::ORIENTATION_HORIZONTAL, 0), center_(Gtk::ORIENTATION_HORIZONTAL, 0), right_(Gtk::ORIENTATION_HORIZONTAL, 0), @@ -29,32 +362,22 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) center_.get_style_context()->add_class("modules-center"); right_.get_style_context()->add_class("modules-right"); - if (config["position"] == "right" || config["position"] == "left") { + auto position = config["position"].asString(); + + if (position == "right" || position == "left") { + left_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); + center_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); + right_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); + box_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); + vertical = true; + height_ = 0; width_ = 1; } height_ = config["height"].isUInt() ? config["height"].asUInt() : height_; width_ = config["width"].isUInt() ? config["width"].asUInt() : width_; - if (config["position"] == "bottom") { - anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; - } else if (config["position"] == "left") { - anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; - } else if (config["position"] == "right") { - anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; - } - if (anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM || - anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) { - anchor_ |= ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; - } else if (anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT || - anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) { - anchor_ |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; - left_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); - center_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); - right_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); - box_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); - vertical = true; - } + struct bar_margins margins_; if (config["margin-top"].isInt() || config["margin-right"].isInt() || config["margin-bottom"].isInt() || config["margin-left"].isInt()) { @@ -102,210 +425,51 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) } #ifdef HAVE_GTK_LAYER_SHELL - use_gls_ = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true; - if (use_gls_) { - initGtkLayerShell(); - window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMapGLS)); - window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Bar::onConfigureGLS)); - } -#endif - - if (!use_gls_) { - window.signal_realize().connect_notify(sigc::mem_fun(*this, &Bar::onRealize)); - window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); - window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Bar::onConfigure)); - } - window.set_size_request(width_, height_); - setupWidgets(); - - if (!use_gls_ && window.get_realized()) { - onRealize(); - } - window.show_all(); -} - -#ifdef HAVE_GTK_LAYER_SHELL -void waybar::Bar::initGtkLayerShell() { - auto gtk_window = window.gobj(); - // this has to be executed before GtkWindow.realize - gtk_layer_init_for_window(gtk_window); - gtk_layer_set_keyboard_interactivity(gtk_window, FALSE); - auto layer = config["layer"] == "top" ? GTK_LAYER_SHELL_LAYER_TOP : GTK_LAYER_SHELL_LAYER_BOTTOM; - gtk_layer_set_layer(gtk_window, layer); - gtk_layer_set_monitor(gtk_window, output->monitor->gobj()); - gtk_layer_set_namespace(gtk_window, "waybar"); - - gtk_layer_set_anchor(gtk_window, - GTK_LAYER_SHELL_EDGE_LEFT, - (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) ? TRUE : FALSE); - gtk_layer_set_anchor(gtk_window, - GTK_LAYER_SHELL_EDGE_RIGHT, - (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) ? TRUE : FALSE); - gtk_layer_set_anchor(gtk_window, - GTK_LAYER_SHELL_EDGE_TOP, - (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) ? TRUE : FALSE); - gtk_layer_set_anchor(gtk_window, - GTK_LAYER_SHELL_EDGE_BOTTOM, - (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM) ? TRUE : FALSE); - - gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, margins_.left); - gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, margins_.right); - gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_TOP, margins_.top); - gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, margins_.bottom); - - setExclusiveZone(width_, height_); -} - -void waybar::Bar::onConfigureGLS(GdkEventConfigure* ev) { - /* - * GTK wants new size for the window. - * Actual resizing and management of the exclusve zone is handled within the gtk-layer-shell code. - * This event handler only updates stored size of the window and prints some warnings. - * - * Note: forced resizing to a window smaller than required by GTK would not work with - * gtk-layer-shell. - */ - if (vertical) { - if (width_ > 1 && ev->width > static_cast(width_)) { - spdlog::warn(MIN_WIDTH_MSG, width_, ev->width); - } - } else { - if (height_ > 1 && ev->height > static_cast(height_)) { - spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height); - } - } - width_ = ev->width; - height_ = ev->height; - spdlog::info(BAR_SIZE_MSG, width_, height_, output->name); -} - -void waybar::Bar::onMapGLS(GdkEventAny* ev) { - /* - * Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor). - */ - auto gdk_window = window.get_window(); - surface = gdk_wayland_window_get_wl_surface(gdk_window->gobj()); -} - -#endif - -void waybar::Bar::onConfigure(GdkEventConfigure* ev) { - /* - * GTK wants new size for the window. - * - * Prefer configured size if it's non-default. - * If the size is not set and the window is smaller than requested by GTK, request resize from - * layer surface. - */ - auto tmp_height = height_; - auto tmp_width = width_; - if (ev->height > static_cast(height_)) { - // Default minimal value - if (height_ > 1) { - spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height); - } - if (config["height"].isUInt()) { - spdlog::info(SIZE_DEFINED, "Height"); - } else { - tmp_height = ev->height; - } - } - if (ev->width > static_cast(width_)) { - // Default minimal value - if (width_ > 1) { - spdlog::warn(MIN_WIDTH_MSG, width_, ev->width); - } - if (config["width"].isUInt()) { - spdlog::info(SIZE_DEFINED, "Width"); - } else { - tmp_width = ev->width; - } - } - if (tmp_width != width_ || tmp_height != height_) { - setSurfaceSize(tmp_width, tmp_height); - } -} - -void waybar::Bar::onRealize() { - auto gdk_window = window.get_window()->gobj(); - gdk_wayland_window_set_use_custom_surface(gdk_window); -} - -void waybar::Bar::onMap(GdkEventAny* ev) { - auto gdk_window = window.get_window()->gobj(); - surface = gdk_wayland_window_get_wl_surface(gdk_window); - - auto client = waybar::Client::inst(); - // owned by output->monitor; no need to destroy - auto wl_output = gdk_wayland_monitor_get_wl_output(output->monitor->gobj()); - auto layer = - config["layer"] == "top" ? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; - layer_surface_ = zwlr_layer_shell_v1_get_layer_surface( - client->layer_shell, surface, wl_output, layer, "waybar"); - - zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_, false); - zwlr_layer_surface_v1_set_anchor(layer_surface_, anchor_); - zwlr_layer_surface_v1_set_margin( - layer_surface_, margins_.top, margins_.right, margins_.bottom, margins_.left); - setSurfaceSize(width_, height_); - setExclusiveZone(width_, height_); - - static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { - .configure = layerSurfaceHandleConfigure, - .closed = layerSurfaceHandleClosed, - }; - zwlr_layer_surface_v1_add_listener(layer_surface_, &layer_surface_listener, this); - - wl_surface_commit(surface); - wl_display_roundtrip(client->wl_display); -} - -void waybar::Bar::setExclusiveZone(uint32_t width, uint32_t height) { -#ifdef HAVE_GTK_LAYER_SHELL - if (use_gls_) { - if (visible) { - spdlog::debug("Enable auto exclusive zone for output {}", output->name); - gtk_layer_auto_exclusive_zone_enable(window.gobj()); - } else { - spdlog::debug("Disable exclusive zone for output {}", output->name); - gtk_layer_set_exclusive_zone(window.gobj(), 0); - } + bool use_gls = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true; + if (use_gls) { + surface_impl_ = std::make_unique(window, *output); } else #endif { - auto zone = 0; - if (visible) { - // exclusive zone already includes margin for anchored edge, - // only opposite margin should be added - if (vertical) { - zone += width; - zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) ? margins_.right : margins_.left; - } else { - zone += height; - zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) ? margins_.bottom : margins_.top; - } - } - spdlog::debug("Set exclusive zone {} for output {}", zone, output->name); - zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_, zone); + surface_impl_ = std::make_unique(window, *output); } + + if (config["layer"].isString()) { + surface_impl_->set_layer(config["layer"].asString()); + } + surface_impl_->set_exclusive_zone(true); + surface_impl_->set_margins(margins_); + surface_impl_->set_position(position); + surface_impl_->set_size(width_, height_); + + window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); + + setupWidgets(); + window.show_all(); } -void waybar::Bar::setSurfaceSize(uint32_t width, uint32_t height) { - /* If the client is anchored to two opposite edges, layer_surface.configure will return - * size without margins for the axis. - * layer_surface.set_size, however, expects size with margins for the anchored axis. - * This is not specified by wlr-layer-shell and based on actual behavior of sway. +void waybar::Bar::onMap(GdkEventAny*) { + /* + * Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor). */ - if (vertical && height > 1) { - height += margins_.top + margins_.bottom; - } - if (!vertical && width > 1) { - width += margins_.right + margins_.left; - } - spdlog::debug("Set surface size {}x{} for output {}", width, height, output->name); - zwlr_layer_surface_v1_set_size(layer_surface_, width, height); + auto gdk_window = window.get_window()->gobj(); + surface = gdk_wayland_window_get_wl_surface(gdk_window); } +void waybar::Bar::setVisible(bool value) { + visible = value; + if (!visible) { + window.get_style_context()->add_class("hidden"); + window.set_opacity(0); + } else { + window.get_style_context()->remove_class("hidden"); + window.set_opacity(1); + } + surface_impl_->set_exclusive_zone(visible); +} + +void waybar::Bar::toggle() { setVisible(!visible); } + // Converting string to button code rn as to avoid doing it later void waybar::Bar::setupAltFormatKeyForModule(const std::string& module_name) { if (config.isMember(module_name)) { @@ -367,50 +531,6 @@ void waybar::Bar::handleSignal(int signal) { } } -void waybar::Bar::layerSurfaceHandleConfigure(void* data, struct zwlr_layer_surface_v1* surface, - uint32_t serial, uint32_t width, uint32_t height) { - auto o = static_cast(data); - if (width != o->width_ || height != o->height_) { - o->width_ = width; - o->height_ = height; - o->window.set_size_request(o->width_, o->height_); - o->window.resize(o->width_, o->height_); - o->setExclusiveZone(width, height); - spdlog::info(BAR_SIZE_MSG, - o->width_ == 1 ? "auto" : std::to_string(o->width_), - o->height_ == 1 ? "auto" : std::to_string(o->height_), - o->output->name); - wl_surface_commit(o->surface); - } - zwlr_layer_surface_v1_ack_configure(surface, serial); -} - -void waybar::Bar::layerSurfaceHandleClosed(void* data, struct zwlr_layer_surface_v1* /*surface*/) { - auto o = static_cast(data); - if (o->layer_surface_) { - zwlr_layer_surface_v1_destroy(o->layer_surface_); - o->layer_surface_ = nullptr; - } - o->modules_left_.clear(); - o->modules_center_.clear(); - o->modules_right_.clear(); -} - -auto waybar::Bar::toggle() -> void { - visible = !visible; - if (!visible) { - window.get_style_context()->add_class("hidden"); - window.set_opacity(0); - } else { - window.get_style_context()->remove_class("hidden"); - window.set_opacity(1); - } - setExclusiveZone(width_, height_); - if (!use_gls_) { - wl_surface_commit(surface); - } -} - void waybar::Bar::getModules(const Factory& factory, const std::string& pos) { if (config[pos].isArray()) { for (const auto& name : config[pos]) { From f01996ae997d4d2aa8ce9904332d99f12f3e4e2c Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Wed, 28 Oct 2020 08:06:40 -0700 Subject: [PATCH 34/94] fix(bar): CamelCase SurfaceImpl method names --- include/bar.hpp | 10 +++---- src/bar.cpp | 70 ++++++++++++++++++++++++------------------------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/include/bar.hpp b/include/bar.hpp index df503275..1e3d37b0 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -36,11 +36,11 @@ class BarSurface { BarSurface() = default; public: - virtual void set_exclusive_zone(bool enable) = 0; - virtual void set_layer(const std::string_view &layer) = 0; - virtual void set_margins(const struct bar_margins &margins) = 0; - virtual void set_position(const std::string_view &position) = 0; - virtual void set_size(uint32_t width, uint32_t height) = 0; + virtual void setExclusiveZone(bool enable) = 0; + virtual void setLayer(const std::string_view &layer) = 0; + virtual void setMargins(const struct bar_margins &margins) = 0; + virtual void setPosition(const std::string_view &position) = 0; + virtual void setSize(uint32_t width, uint32_t height) = 0; virtual ~BarSurface() = default; }; diff --git a/src/bar.cpp b/src/bar.cpp index a684f12b..c7387e38 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -31,10 +31,10 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { gtk_layer_set_namespace(window_.gobj(), "waybar"); window.signal_configure_event().connect_notify( - sigc::mem_fun(*this, &GLSSurfaceImpl::on_configure)); + sigc::mem_fun(*this, &GLSSurfaceImpl::onConfigure)); } - void set_exclusive_zone(bool enable) override { + void setExclusiveZone(bool enable) override { if (enable) { gtk_layer_auto_exclusive_zone_enable(window_.gobj()); } else { @@ -42,14 +42,14 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { } } - void set_margins(const struct bar_margins& margins) override { + void setMargins(const struct bar_margins& margins) override { gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_LEFT, margins.left); gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, margins.right); gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_TOP, margins.top); gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, margins.bottom); } - void set_layer(const std::string_view& value) override { + void setLayer(const std::string_view& value) override { auto layer = GTK_LAYER_SHELL_LAYER_BOTTOM; if (value == "top") { layer = GTK_LAYER_SHELL_LAYER_TOP; @@ -59,7 +59,7 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { gtk_layer_set_layer(window_.gobj(), layer); } - void set_position(const std::string_view& position) override { + void setPosition(const std::string_view& position) override { auto unanchored = GTK_LAYER_SHELL_EDGE_BOTTOM; vertical_ = false; if (position == "bottom") { @@ -79,7 +79,7 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { } } - void set_size(uint32_t width, uint32_t height) override { + void setSize(uint32_t width, uint32_t height) override { width_ = width; height_ = height; window_.set_size_request(width_, height_); @@ -92,7 +92,7 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { uint32_t height_; bool vertical_ = false; - void on_configure(GdkEventConfigure* ev) { + void onConfigure(GdkEventConfigure* ev) { /* * GTK wants new size for the window. * Actual resizing and management of the exclusve zone is handled within the gtk-layer-shell @@ -122,17 +122,17 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { output_ = gdk_wayland_monitor_get_wl_output(output.monitor->gobj()); output_name_ = output.name; - window.signal_realize().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::on_realize)); - window.signal_map_event().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::on_map)); + window.signal_realize().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::onRealize)); + window.signal_map_event().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::onMap)); window.signal_configure_event().connect_notify( - sigc::mem_fun(*this, &RawSurfaceImpl::on_configure)); + sigc::mem_fun(*this, &RawSurfaceImpl::onConfigure)); if (window.get_realized()) { - on_realize(); + onRealize(); } } - void set_exclusive_zone(bool enable) override { + void setExclusiveZone(bool enable) override { exclusive_zone_ = enable; if (layer_surface_) { auto zone = 0; @@ -152,7 +152,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { } } - void set_layer(const std::string_view& layer) override { + void setLayer(const std::string_view& layer) override { layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; if (layer == "top") { layer_ = ZWLR_LAYER_SHELL_V1_LAYER_TOP; @@ -171,7 +171,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { } } - void set_margins(const struct bar_margins& margins) override { + void setMargins(const struct bar_margins& margins) override { margins_ = margins; // updating already mapped window if (layer_surface_) { @@ -181,7 +181,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { } } - void set_position(const std::string_view& position) override { + void setPosition(const std::string_view& position) override { anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; if (position == "bottom") { anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; @@ -198,7 +198,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { } } - void set_size(uint32_t width, uint32_t height) override { + void setSize(uint32_t width, uint32_t height) override { width_ = width; height_ = height; // layer_shell.configure handler should update exclusive zone if size changes @@ -224,12 +224,12 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { struct wl_surface* surface_ = nullptr; struct zwlr_layer_surface_v1* layer_surface_ = nullptr; - void on_realize() { + void onRealize() { auto gdk_window = window_.get_window()->gobj(); gdk_wayland_window_set_use_custom_surface(gdk_window); } - void on_map(GdkEventAny* ev) { + void onMap(GdkEventAny* ev) { auto client = Client::inst(); auto gdk_window = window_.get_window()->gobj(); surface_ = gdk_wayland_window_get_wl_surface(gdk_window); @@ -242,12 +242,12 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { zwlr_layer_surface_v1_set_anchor(layer_surface_, anchor_); zwlr_layer_surface_v1_set_margin( layer_surface_, margins_.top, margins_.right, margins_.bottom, margins_.left); - set_surface_size(width_, height_); - set_exclusive_zone(exclusive_zone_); + setSurfaceSize(width_, height_); + setExclusiveZone(exclusive_zone_); static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { - .configure = on_surface_configure, - .closed = on_surface_closed, + .configure = onSurfaceConfigure, + .closed = onSurfaceClosed, }; zwlr_layer_surface_v1_add_listener(layer_surface_, &layer_surface_listener, this); @@ -255,7 +255,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { wl_display_roundtrip(client->wl_display); } - void on_configure(GdkEventConfigure* ev) { + void onConfigure(GdkEventConfigure* ev) { /* * GTK wants new size for the window. * @@ -288,7 +288,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { tmp_width = ev->width; } if (tmp_width != width_ || tmp_height != height_) { - set_surface_size(tmp_width, tmp_height); + setSurfaceSize(tmp_width, tmp_height); } } @@ -298,7 +298,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { } } - void set_surface_size(uint32_t width, uint32_t height) { + void setSurfaceSize(uint32_t width, uint32_t height) { /* If the client is anchored to two opposite edges, layer_surface.configure will return * size without margins for the axis. * layer_surface.set_size, however, expects size with margins for the anchored axis. @@ -315,15 +315,15 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { zwlr_layer_surface_v1_set_size(layer_surface_, width, height); } - static void on_surface_configure(void* data, struct zwlr_layer_surface_v1* surface, - uint32_t serial, uint32_t width, uint32_t height) { + static void onSurfaceConfigure(void* data, struct zwlr_layer_surface_v1* surface, uint32_t serial, + uint32_t width, uint32_t height) { auto o = static_cast(data); if (width != o->width_ || height != o->height_) { o->width_ = width; o->height_ = height; o->window_.set_size_request(o->width_, o->height_); o->window_.resize(o->width_, o->height_); - o->set_exclusive_zone(o->exclusive_zone_); + o->setExclusiveZone(o->exclusive_zone_); spdlog::info(BAR_SIZE_MSG, o->width_ == 1 ? "auto" : std::to_string(o->width_), o->height_ == 1 ? "auto" : std::to_string(o->height_), @@ -333,7 +333,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { zwlr_layer_surface_v1_ack_configure(surface, serial); } - static void on_surface_closed(void* data, struct zwlr_layer_surface_v1* /* surface */) { + static void onSurfaceClosed(void* data, struct zwlr_layer_surface_v1* /* surface */) { auto o = static_cast(data); if (o->layer_surface_) { zwlr_layer_surface_v1_destroy(o->layer_surface_); @@ -435,12 +435,12 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) } if (config["layer"].isString()) { - surface_impl_->set_layer(config["layer"].asString()); + surface_impl_->setLayer(config["layer"].asString()); } - surface_impl_->set_exclusive_zone(true); - surface_impl_->set_margins(margins_); - surface_impl_->set_position(position); - surface_impl_->set_size(width_, height_); + surface_impl_->setExclusiveZone(true); + surface_impl_->setMargins(margins_); + surface_impl_->setPosition(position); + surface_impl_->setSize(width_, height_); window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); @@ -465,7 +465,7 @@ void waybar::Bar::setVisible(bool value) { window.get_style_context()->remove_class("hidden"); window.set_opacity(1); } - surface_impl_->set_exclusive_zone(visible); + surface_impl_->setExclusiveZone(visible); } void waybar::Bar::toggle() { setVisible(!visible); } From f97de599dde49943d2742db2903b83769601deac Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Wed, 21 Oct 2020 22:45:03 -0700 Subject: [PATCH 35/94] refactor: header cleanup Replace a couple of header includes with forward declarations. --- include/bar.hpp | 2 -- include/client.hpp | 4 ++++ src/bar.cpp | 1 + src/client.cpp | 3 +++ src/modules/idle_inhibitor.cpp | 2 ++ 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/include/bar.hpp b/include/bar.hpp index 1e3d37b0..d4748d5a 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -9,8 +9,6 @@ #include #include "AModule.hpp" -#include "idle-inhibit-unstable-v1-client-protocol.h" -#include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" namespace waybar { diff --git a/include/client.hpp b/include/client.hpp index 39b6ae3b..05215cc0 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -8,6 +8,10 @@ #include #include "bar.hpp" +struct zwlr_layer_shell_v1; +struct zwp_idle_inhibitor_v1; +struct zwp_idle_inhibit_manager_v1; + namespace waybar { class Client { diff --git a/src/bar.cpp b/src/bar.cpp index c7387e38..fb04f627 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -7,6 +7,7 @@ #include "bar.hpp" #include "client.hpp" #include "factory.hpp" +#include "wlr-layer-shell-unstable-v1-client-protocol.h" namespace waybar { static constexpr const char* MIN_HEIGHT_MSG = diff --git a/src/client.cpp b/src/client.cpp index 9dd93d81..005761ee 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -6,6 +6,9 @@ #include "util/clara.hpp" #include "util/json.hpp" +#include "idle-inhibit-unstable-v1-client-protocol.h" +#include "wlr-layer-shell-unstable-v1-client-protocol.h" + waybar::Client *waybar::Client::inst() { static auto c = new Client(); return c; diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index d94e9579..d2cfe905 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -1,4 +1,6 @@ #include "modules/idle_inhibitor.hpp" + +#include "idle-inhibit-unstable-v1-client-protocol.h" #include "util/command.hpp" waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar, From d4d35e6b2b6f142844e18c69164c38d6b02f185a Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Thu, 22 Oct 2020 08:04:57 -0700 Subject: [PATCH 36/94] chore(subprojects): update gtk-layer-shell to 0.4.0 --- subprojects/gtk-layer-shell.wrap | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/subprojects/gtk-layer-shell.wrap b/subprojects/gtk-layer-shell.wrap index 6fe68c3f..555fbcb6 100644 --- a/subprojects/gtk-layer-shell.wrap +++ b/subprojects/gtk-layer-shell.wrap @@ -1,5 +1,5 @@ [wrap-file] -directory = gtk-layer-shell-0.3.0 -source_filename = gtk-layer-shell-0.3.0.tar.gz -source_hash = edd5e31279d494df66da9e9190c219fa295da547f5538207685e98468dbc134d -source_url = https://github.com/wmww/gtk-layer-shell/archive/v0.3.0/gtk-layer-shell-0.3.0.tar.gz +directory = gtk-layer-shell-0.4.0 +source_filename = gtk-layer-shell-0.4.0.tar.gz +source_hash = 52fd74d3161fefa5528585ca5a523c3150934961f2284ad010ae54336dad097e +source_url = https://github.com/wmww/gtk-layer-shell/archive/v0.4.0/gtk-layer-shell-0.4.0.tar.gz From 591a417b7db973304e788ccf58bbbfdb289ed9a7 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Fri, 23 Oct 2020 00:13:23 -0700 Subject: [PATCH 37/94] fix(bar): rework surface commit calls for RawSurfaceImpl wayland log shows that some changes were committed twice and some weren't committed until the next redraw. --- include/bar.hpp | 1 + src/bar.cpp | 34 ++++++++++++++++------------------ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/include/bar.hpp b/include/bar.hpp index d4748d5a..8348070f 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -39,6 +39,7 @@ class BarSurface { virtual void setMargins(const struct bar_margins &margins) = 0; virtual void setPosition(const std::string_view &position) = 0; virtual void setSize(uint32_t width, uint32_t height) = 0; + virtual void commit(){}; virtual ~BarSurface() = default; }; diff --git a/src/bar.cpp b/src/bar.cpp index fb04f627..8e5d7b05 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -165,7 +165,6 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { if (zwlr_layer_surface_v1_get_version(layer_surface_) >= ZWLR_LAYER_SURFACE_V1_SET_LAYER_SINCE_VERSION) { zwlr_layer_surface_v1_set_layer(layer_surface_, layer_); - commit(); } else { spdlog::warn("Unable to set layer: layer-shell interface version is too old"); } @@ -178,7 +177,6 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { if (layer_surface_) { zwlr_layer_surface_v1_set_margin( layer_surface_, margins_.top, margins_.right, margins_.bottom, margins_.left); - commit(); } } @@ -195,7 +193,6 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { // updating already mapped window if (layer_surface_) { zwlr_layer_surface_v1_set_anchor(layer_surface_, anchor_); - commit(); } } @@ -206,6 +203,12 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { window_.set_size_request(width, height); }; + void commit() override { + if (surface_) { + wl_surface_commit(surface_); + } + } + private: constexpr static uint8_t VERTICAL_ANCHOR = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; @@ -231,6 +234,10 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { } void onMap(GdkEventAny* ev) { + static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { + .configure = onSurfaceConfigure, + .closed = onSurfaceClosed, + }; auto client = Client::inst(); auto gdk_window = window_.get_window()->gobj(); surface_ = gdk_wayland_window_get_wl_surface(gdk_window); @@ -238,21 +245,16 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { layer_surface_ = zwlr_layer_shell_v1_get_layer_surface( client->layer_shell, surface_, output_, layer_, "waybar"); + zwlr_layer_surface_v1_add_listener(layer_surface_, &layer_surface_listener, this); zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_, false); - zwlr_layer_surface_v1_set_anchor(layer_surface_, anchor_); zwlr_layer_surface_v1_set_margin( layer_surface_, margins_.top, margins_.right, margins_.bottom, margins_.left); + setSurfaceSize(width_, height_); setExclusiveZone(exclusive_zone_); - static const struct zwlr_layer_surface_v1_listener layer_surface_listener = { - .configure = onSurfaceConfigure, - .closed = onSurfaceClosed, - }; - zwlr_layer_surface_v1_add_listener(layer_surface_, &layer_surface_listener, this); - - wl_surface_commit(surface_); + commit(); wl_display_roundtrip(client->wl_display); } @@ -290,12 +292,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { } if (tmp_width != width_ || tmp_height != height_) { setSurfaceSize(tmp_width, tmp_height); - } - } - - void commit() { - if (window_.get_mapped()) { - wl_surface_commit(surface_); + commit(); } } @@ -329,7 +326,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { o->width_ == 1 ? "auto" : std::to_string(o->width_), o->height_ == 1 ? "auto" : std::to_string(o->height_), o->output_name_); - wl_surface_commit(o->surface_); + o->commit(); } zwlr_layer_surface_v1_ack_configure(surface, serial); } @@ -467,6 +464,7 @@ void waybar::Bar::setVisible(bool value) { window.set_opacity(1); } surface_impl_->setExclusiveZone(visible); + surface_impl_->commit(); } void waybar::Bar::toggle() { setVisible(!visible); } From fe3aeb36c51267b87c8a8b8fa2ea827047c25196 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Fri, 23 Oct 2020 00:49:09 -0700 Subject: [PATCH 38/94] refactor(bar): wrap layer_surface pointer in unique_ptr --- src/bar.cpp | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/bar.cpp b/src/bar.cpp index 8e5d7b05..c2cf6d34 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -4,6 +4,8 @@ #include +#include + #include "bar.hpp" #include "client.hpp" #include "factory.hpp" @@ -149,7 +151,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { } } spdlog::debug("Set exclusive zone {} for output {}", zone, output_name_); - zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_, zone); + zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_.get(), zone); } } @@ -162,9 +164,9 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { } // updating already mapped window if (layer_surface_) { - if (zwlr_layer_surface_v1_get_version(layer_surface_) >= + if (zwlr_layer_surface_v1_get_version(layer_surface_.get()) >= ZWLR_LAYER_SURFACE_V1_SET_LAYER_SINCE_VERSION) { - zwlr_layer_surface_v1_set_layer(layer_surface_, layer_); + zwlr_layer_surface_v1_set_layer(layer_surface_.get(), layer_); } else { spdlog::warn("Unable to set layer: layer-shell interface version is too old"); } @@ -176,7 +178,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { // updating already mapped window if (layer_surface_) { zwlr_layer_surface_v1_set_margin( - layer_surface_, margins_.top, margins_.right, margins_.bottom, margins_.left); + layer_surface_.get(), margins_.top, margins_.right, margins_.bottom, margins_.left); } } @@ -192,7 +194,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { // updating already mapped window if (layer_surface_) { - zwlr_layer_surface_v1_set_anchor(layer_surface_, anchor_); + zwlr_layer_surface_v1_set_anchor(layer_surface_.get(), anchor_); } } @@ -215,6 +217,11 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { constexpr static uint8_t HORIZONTAL_ANCHOR = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + template + using deleter_fn = std::integral_constant; + using layer_surface_ptr = + std::unique_ptr>; + Gtk::Window& window_; std::string output_name_; uint32_t width_; @@ -223,10 +230,10 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { bool exclusive_zone_ = true; struct bar_margins margins_; - zwlr_layer_shell_v1_layer layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; - struct wl_output* output_ = nullptr; - struct wl_surface* surface_ = nullptr; - struct zwlr_layer_surface_v1* layer_surface_ = nullptr; + zwlr_layer_shell_v1_layer layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; + struct wl_output* output_ = nullptr; // owned by GTK + struct wl_surface* surface_ = nullptr; // owned by GTK + layer_surface_ptr layer_surface_; void onRealize() { auto gdk_window = window_.get_window()->gobj(); @@ -242,14 +249,14 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { auto gdk_window = window_.get_window()->gobj(); surface_ = gdk_wayland_window_get_wl_surface(gdk_window); - layer_surface_ = zwlr_layer_shell_v1_get_layer_surface( - client->layer_shell, surface_, output_, layer_, "waybar"); + layer_surface_.reset(zwlr_layer_shell_v1_get_layer_surface( + client->layer_shell, surface_, output_, layer_, "waybar")); - zwlr_layer_surface_v1_add_listener(layer_surface_, &layer_surface_listener, this); - zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_, false); - zwlr_layer_surface_v1_set_anchor(layer_surface_, anchor_); + zwlr_layer_surface_v1_add_listener(layer_surface_.get(), &layer_surface_listener, this); + zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_.get(), false); + zwlr_layer_surface_v1_set_anchor(layer_surface_.get(), anchor_); zwlr_layer_surface_v1_set_margin( - layer_surface_, margins_.top, margins_.right, margins_.bottom, margins_.left); + layer_surface_.get(), margins_.top, margins_.right, margins_.bottom, margins_.left); setSurfaceSize(width_, height_); setExclusiveZone(exclusive_zone_); @@ -310,7 +317,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { width += margins_.right + margins_.left; } spdlog::debug("Set surface size {}x{} for output {}", width, height, output_name_); - zwlr_layer_surface_v1_set_size(layer_surface_, width, height); + zwlr_layer_surface_v1_set_size(layer_surface_.get(), width, height); } static void onSurfaceConfigure(void* data, struct zwlr_layer_surface_v1* surface, uint32_t serial, @@ -333,10 +340,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { static void onSurfaceClosed(void* data, struct zwlr_layer_surface_v1* /* surface */) { auto o = static_cast(data); - if (o->layer_surface_) { - zwlr_layer_surface_v1_destroy(o->layer_surface_); - o->layer_surface_ = nullptr; - } + o->layer_surface_.reset(); } }; From fc5906dbd4a9d026a6f746ffe37ec69ee5e2e7ea Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Thu, 22 Oct 2020 23:04:58 -0700 Subject: [PATCH 39/94] feat(bar): change layer to `bottom` when hidden Invisible bar on a `top` layer would still intercept pointer events and stop them from reaching windows below. Always changing the layer to to `bottom` along with making bar invisible would prevent that. --- include/bar.hpp | 9 ++++++++- src/bar.cpp | 27 +++++++++++++++++---------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/include/bar.hpp b/include/bar.hpp index 8348070f..88df8b71 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -22,6 +22,12 @@ struct waybar_output { nullptr, &zxdg_output_v1_destroy}; }; +enum class bar_layer : uint8_t { + BOTTOM, + TOP, + OVERLAY, +}; + struct bar_margins { int top = 0; int right = 0; @@ -35,7 +41,7 @@ class BarSurface { public: virtual void setExclusiveZone(bool enable) = 0; - virtual void setLayer(const std::string_view &layer) = 0; + virtual void setLayer(bar_layer layer) = 0; virtual void setMargins(const struct bar_margins &margins) = 0; virtual void setPosition(const std::string_view &position) = 0; virtual void setSize(uint32_t width, uint32_t height) = 0; @@ -71,6 +77,7 @@ class Bar { std::unique_ptr surface_impl_; uint32_t width_ = 0; uint32_t height_ = 1; + bar_layer layer_; Gtk::Box left_; Gtk::Box center_; Gtk::Box right_; diff --git a/src/bar.cpp b/src/bar.cpp index c2cf6d34..f0fe64a7 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -52,11 +52,11 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, margins.bottom); } - void setLayer(const std::string_view& value) override { + void setLayer(bar_layer value) override { auto layer = GTK_LAYER_SHELL_LAYER_BOTTOM; - if (value == "top") { + if (value == bar_layer::TOP) { layer = GTK_LAYER_SHELL_LAYER_TOP; - } else if (value == "overlay") { + } else if (value == bar_layer::OVERLAY) { layer = GTK_LAYER_SHELL_LAYER_OVERLAY; } gtk_layer_set_layer(window_.gobj(), layer); @@ -155,11 +155,11 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { } } - void setLayer(const std::string_view& layer) override { + void setLayer(bar_layer layer) override { layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; - if (layer == "top") { + if (layer == bar_layer::TOP) { layer_ = ZWLR_LAYER_SHELL_V1_LAYER_TOP; - } else if (layer == "overlay") { + } else if (layer == bar_layer::OVERLAY) { layer_ = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; } // updating already mapped window @@ -168,7 +168,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { ZWLR_LAYER_SURFACE_V1_SET_LAYER_SINCE_VERSION) { zwlr_layer_surface_v1_set_layer(layer_surface_.get(), layer_); } else { - spdlog::warn("Unable to set layer: layer-shell interface version is too old"); + spdlog::warn("Unable to change layer: layer-shell implementation is too old"); } } } @@ -350,6 +350,7 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) : output(w_output), config(w_config), window{Gtk::WindowType::WINDOW_TOPLEVEL}, + layer_{bar_layer::BOTTOM}, left_(Gtk::ORIENTATION_HORIZONTAL, 0), center_(Gtk::ORIENTATION_HORIZONTAL, 0), right_(Gtk::ORIENTATION_HORIZONTAL, 0), @@ -364,6 +365,12 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) center_.get_style_context()->add_class("modules-center"); right_.get_style_context()->add_class("modules-right"); + if (config["layer"] == "top") { + layer_ = bar_layer::TOP; + } else if (config["layer"] == "overlay") { + layer_ = bar_layer::OVERLAY; + } + auto position = config["position"].asString(); if (position == "right" || position == "left") { @@ -436,9 +443,7 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) surface_impl_ = std::make_unique(window, *output); } - if (config["layer"].isString()) { - surface_impl_->setLayer(config["layer"].asString()); - } + surface_impl_->setLayer(layer_); surface_impl_->setExclusiveZone(true); surface_impl_->setMargins(margins_); surface_impl_->setPosition(position); @@ -463,9 +468,11 @@ void waybar::Bar::setVisible(bool value) { if (!visible) { window.get_style_context()->add_class("hidden"); window.set_opacity(0); + surface_impl_->setLayer(bar_layer::BOTTOM); } else { window.get_style_context()->remove_class("hidden"); window.set_opacity(1); + surface_impl_->setLayer(layer_); } surface_impl_->setExclusiveZone(visible); surface_impl_->commit(); From 9c566564e1c892b4f681be3787cd8a589f95a0ec Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Fri, 23 Oct 2020 02:59:04 -0700 Subject: [PATCH 40/94] fix(bar): address some of RawSurfaceImpl resizing issues --- include/bar.hpp | 2 -- src/bar.cpp | 55 ++++++++++++++++++++++++++++--------------------- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/include/bar.hpp b/include/bar.hpp index 88df8b71..8aab8f7c 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -75,8 +75,6 @@ class Bar { void setupAltFormatKeyForModuleList(const char *module_list_name); std::unique_ptr surface_impl_; - uint32_t width_ = 0; - uint32_t height_ = 1; bar_layer layer_; Gtk::Box left_; Gtk::Box center_; diff --git a/src/bar.cpp b/src/bar.cpp index f0fe64a7..771adab5 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -199,8 +199,8 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { } void setSize(uint32_t width, uint32_t height) override { - width_ = width; - height_ = height; + configured_width_ = width_ = width; + configured_height_ = height_ = height; // layer_shell.configure handler should update exclusive zone if size changes window_.set_size_request(width, height); }; @@ -224,8 +224,10 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { Gtk::Window& window_; std::string output_name_; - uint32_t width_; - uint32_t height_; + uint32_t configured_width_ = 0; + uint32_t configured_height_ = 0; + uint32_t width_ = 0; + uint32_t height_ = 0; uint8_t anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; bool exclusive_zone_ = true; struct bar_margins margins_; @@ -280,22 +282,22 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { if (height_ > 1) { spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height); } - /* - if (config["height"].isUInt()) { + if (configured_height_ > 1) { spdlog::info(SIZE_DEFINED, "Height"); - } else */ - tmp_height = ev->height; + } else { + tmp_height = ev->height; + } } if (ev->width > static_cast(width_)) { // Default minimal value if (width_ > 1) { spdlog::warn(MIN_WIDTH_MSG, width_, ev->width); } - /* - if (config["width"].isUInt()) { + if (configured_width_ > 1) { spdlog::info(SIZE_DEFINED, "Width"); - } else */ - tmp_width = ev->width; + } else { + tmp_width = ev->width; + } } if (tmp_width != width_ || tmp_height != height_) { setSurfaceSize(tmp_width, tmp_height); @@ -308,13 +310,20 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable { * size without margins for the axis. * layer_surface.set_size, however, expects size with margins for the anchored axis. * This is not specified by wlr-layer-shell and based on actual behavior of sway. + * + * If the size for unanchored axis is not set (0), change request to 1 to avoid automatic + * assignment by the compositor. */ - bool vertical = (anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR; - if (vertical && height > 1) { - height += margins_.top + margins_.bottom; - } - if (!vertical && width > 1) { - width += margins_.right + margins_.left; + if ((anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR) { + width = width > 0 ? width : 1; + if (height > 1) { + height += margins_.top + margins_.bottom; + } + } else { + height = height > 0 ? height : 1; + if (width > 1) { + width += margins_.right + margins_.left; + } } spdlog::debug("Set surface size {}x{} for output {}", width, height, output_name_); zwlr_layer_surface_v1_set_size(layer_surface_.get(), width, height); @@ -379,12 +388,10 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) right_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); box_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); vertical = true; - - height_ = 0; - width_ = 1; } - height_ = config["height"].isUInt() ? config["height"].asUInt() : height_; - width_ = config["width"].isUInt() ? config["width"].asUInt() : width_; + + uint32_t height = config["height"].isUInt() ? config["height"].asUInt() : 0; + uint32_t width = config["width"].isUInt() ? config["width"].asUInt() : 0; struct bar_margins margins_; @@ -447,7 +454,7 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) surface_impl_->setExclusiveZone(true); surface_impl_->setMargins(margins_); surface_impl_->setPosition(position); - surface_impl_->setSize(width_, height_); + surface_impl_->setSize(width, height); window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); From 96d965fe04272c522a976cc5bd6a39dc7ff2631d Mon Sep 17 00:00:00 2001 From: Laurent Arnoud Date: Sat, 10 Oct 2020 14:09:55 +0200 Subject: [PATCH 41/94] Add simpleclock as fallback when hhdate is not available ref https://github.com/Alexays/Waybar/issues/668 --- include/factory.hpp | 4 ++++ include/modules/simpleclock.hpp | 24 ++++++++++++++++++++++++ meson.build | 14 ++++++++++++-- src/modules/simpleclock.cpp | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 include/modules/simpleclock.hpp create mode 100644 src/modules/simpleclock.cpp diff --git a/include/factory.hpp b/include/factory.hpp index 853e6b09..1cae68c9 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -1,7 +1,11 @@ #pragma once #include +#ifdef HAVE_LIBDATE #include "modules/clock.hpp" +#else +#include "modules/simpleclock.hpp" +#endif #ifdef HAVE_SWAY #include "modules/sway/mode.hpp" #include "modules/sway/window.hpp" diff --git a/include/modules/simpleclock.hpp b/include/modules/simpleclock.hpp new file mode 100644 index 00000000..aa9a0a22 --- /dev/null +++ b/include/modules/simpleclock.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#if FMT_VERSION < 60000 +#include +#else +#include +#endif +#include "ALabel.hpp" +#include "util/sleeper_thread.hpp" + +namespace waybar::modules { + +class Clock : public ALabel { + public: + Clock(const std::string&, const Json::Value&); + ~Clock() = default; + auto update() -> void; + + private: + util::SleeperThread thread_; +}; + +} // namespace waybar::modules diff --git a/meson.build b/meson.build index 7f4ffaca..48faedad 100644 --- a/meson.build +++ b/meson.build @@ -113,7 +113,11 @@ gtk_layer_shell = dependency('gtk-layer-shell-0', required: get_option('gtk-layer-shell'), fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep']) systemd = dependency('systemd', required: get_option('systemd')) -tz_dep = dependency('date', default_options : [ 'use_system_tzdb=true' ], modules : [ 'date::date', 'date::date-tz' ], fallback: [ 'date', 'tz_dep' ]) +tz_dep = dependency('date', + required: false, + default_options : [ 'use_system_tzdb=true' ], + modules : [ 'date::date', 'date::date-tz' ], + fallback: [ 'date', 'tz_dep' ]) prefix = get_option('prefix') sysconfdir = get_option('sysconfdir') @@ -137,7 +141,6 @@ src_files = files( 'src/factory.cpp', 'src/AModule.cpp', 'src/ALabel.cpp', - 'src/modules/clock.cpp', 'src/modules/custom.cpp', 'src/modules/disk.cpp', 'src/modules/idle_inhibitor.cpp', @@ -237,6 +240,13 @@ if get_option('rfkill').enabled() endif endif +if tz_dep.found() + add_project_arguments('-DHAVE_LIBDATE', language: 'cpp') + src_files += 'src/modules/clock.cpp' +else + src_files += 'src/modules/simpleclock.cpp' +endif + subdir('protocol') executable( diff --git a/src/modules/simpleclock.cpp b/src/modules/simpleclock.cpp new file mode 100644 index 00000000..3004fc2c --- /dev/null +++ b/src/modules/simpleclock.cpp @@ -0,0 +1,33 @@ +#include "modules/simpleclock.hpp" +#include + +waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) + : ALabel(config, "clock", id, "{:%H:%M}", 60) { + thread_ = [this] { + dp.emit(); + auto now = std::chrono::system_clock::now(); + auto timeout = std::chrono::floor(now + interval_); + auto diff = std::chrono::seconds(timeout.time_since_epoch().count() % interval_.count()); + thread_.sleep_until(timeout - diff); + }; +} + +auto waybar::modules::Clock::update() -> void { + tzset(); // Update timezone information + auto now = std::chrono::system_clock::now(); + auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); + auto text = fmt::format(format_, localtime); + label_.set_markup(text); + + if (tooltipEnabled()) { + if (config_["tooltip-format"].isString()) { + auto tooltip_format = config_["tooltip-format"].asString(); + auto tooltip_text = fmt::format(tooltip_format, localtime); + label_.set_tooltip_text(tooltip_text); + } else { + label_.set_tooltip_text(text); + } + } + // Call parent update + ALabel::update(); +} From abe1fa5bd41db67058a6a9606efcc9b4c1f99e14 Mon Sep 17 00:00:00 2001 From: Jordan Leppert Date: Sat, 31 Oct 2020 12:21:51 +0000 Subject: [PATCH 42/94] Custom module - only call label_.set_tooltip_markup if tooltip markup has actually changed - fixes tooltips not appearing at all if a custom module is updating too frequently. --- src/modules/custom.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 92eedaaa..ba55edd5 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -131,9 +131,13 @@ auto waybar::modules::Custom::update() -> void { label_.set_markup(str); if (tooltipEnabled()) { if (text_ == tooltip_) { - label_.set_tooltip_markup(str); + if (label_.get_tooltip_markup() != str) { + label_.set_tooltip_markup(str); + } } else { - label_.set_tooltip_markup(tooltip_); + if (label_.get_tooltip_markup() != tooltip_) { + label_.set_tooltip_markup(tooltip_); + } } } auto classes = label_.get_style_context()->list_classes(); From 4872091442e869769c5db05d96358d3b660e03ad Mon Sep 17 00:00:00 2001 From: Jordan Leppert Date: Sat, 31 Oct 2020 16:31:27 +0000 Subject: [PATCH 43/94] Draft fix for syncing idle inhibitor across outputs. The idle_inhibitor surface has been moved to Client, all instances of idle inhibitor module now use one surface between them. Any time an idle inhibitor is clicked, currently it force updates ALL modules on all outputs, this needs work. --- include/bar.hpp | 1 + include/client.hpp | 1 + include/modules/idle_inhibitor.hpp | 1 - src/bar.cpp | 12 ++++++++++++ src/modules/idle_inhibitor.cpp | 30 +++++++++++++++++++++--------- 5 files changed, 35 insertions(+), 10 deletions(-) diff --git a/include/bar.hpp b/include/bar.hpp index fdc5a739..d1077842 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -31,6 +31,7 @@ class Bar { auto toggle() -> void; void handleSignal(int); + void updateAll(); struct waybar_output *output; Json::Value config; diff --git a/include/client.hpp b/include/client.hpp index 39b6ae3b..b7631ccf 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -22,6 +22,7 @@ class Client { struct zwlr_layer_shell_v1 * layer_shell = nullptr; struct zxdg_output_manager_v1 * xdg_output_manager = nullptr; struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; + struct zwp_idle_inhibitor_v1* idle_inhibitor; std::vector> bars; private: diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index 5ce324da..f1c690df 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -18,7 +18,6 @@ class IdleInhibitor : public ALabel { const Bar& bar_; std::string status_; - struct zwp_idle_inhibitor_v1* idle_inhibitor_; int pid_; }; diff --git a/src/bar.cpp b/src/bar.cpp index 8af6b971..747f0bf8 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -461,3 +461,15 @@ auto waybar::Bar::setupWidgets() -> void { right_.pack_end(*module, false, false); } } + +void waybar::Bar::updateAll() { + for (auto const& module : modules_left_) { + module->update(); + } + for (auto const& module : modules_center_) { + module->update(); + } + for (auto const& module : modules_right_) { + module->update(); + } +} diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index d94e9579..c6a4c78b 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -6,7 +6,6 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& : ALabel(config, "idle_inhibitor", id, "{status}"), bar_(bar), status_("deactivated"), - idle_inhibitor_(nullptr), pid_(-1) { event_box_.add_events(Gdk::BUTTON_PRESS_MASK); event_box_.signal_button_press_event().connect( @@ -15,9 +14,9 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& } waybar::modules::IdleInhibitor::~IdleInhibitor() { - if (idle_inhibitor_ != nullptr) { - zwp_idle_inhibitor_v1_destroy(idle_inhibitor_); - idle_inhibitor_ = nullptr; + if (waybar::Client::inst()->idle_inhibitor != nullptr) { + zwp_idle_inhibitor_v1_destroy(waybar::Client::inst()->idle_inhibitor); + waybar::Client::inst()->idle_inhibitor = nullptr; } if (pid_ != -1) { kill(-pid_, 9); @@ -26,6 +25,13 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() { } auto waybar::modules::IdleInhibitor::update() -> void { + // Check status + if (waybar::Client::inst()->idle_inhibitor != nullptr) { + status_ = "activated"; + } else { + status_ = "deactivated"; + } + label_.set_markup( fmt::format(format_, fmt::arg("status", status_), fmt::arg("icon", getIcon(0, status_)))); label_.get_style_context()->add_class(status_); @@ -39,17 +45,23 @@ auto waybar::modules::IdleInhibitor::update() -> void { bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { if (e->button == 1) { label_.get_style_context()->remove_class(status_); - if (idle_inhibitor_ != nullptr) { - zwp_idle_inhibitor_v1_destroy(idle_inhibitor_); - idle_inhibitor_ = nullptr; + if (waybar::Client::inst()->idle_inhibitor != nullptr) { + zwp_idle_inhibitor_v1_destroy(waybar::Client::inst()->idle_inhibitor); + waybar::Client::inst()->idle_inhibitor = nullptr; status_ = "deactivated"; } else { - idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor( - waybar::Client::inst()->idle_inhibit_manager, bar_.surface); + waybar::Client::inst()->idle_inhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor( + waybar::Client::inst()->idle_inhibit_manager, bar_.surface); status_ = "activated"; } click_param = status_; } + + // Make all modules update + for (auto const& bar : waybar::Client::inst()->bars) { + bar->updateAll(); + } + ALabel::handleToggle(e); return true; } From aa4fc3dd29544586482525e20f55c8c5becb82f0 Mon Sep 17 00:00:00 2001 From: Jordan Leppert Date: Sat, 31 Oct 2020 17:30:25 +0000 Subject: [PATCH 44/94] Idle inhibitor toggle no longer update all modules - a list of idle inhibitors is maintained on the Client. --- include/bar.hpp | 1 - include/client.hpp | 3 +++ src/bar.cpp | 12 ------------ src/modules/idle_inhibitor.cpp | 9 ++++++--- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/include/bar.hpp b/include/bar.hpp index d1077842..fdc5a739 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -31,7 +31,6 @@ class Bar { auto toggle() -> void; void handleSignal(int); - void updateAll(); struct waybar_output *output; Json::Value config; diff --git a/include/client.hpp b/include/client.hpp index b7631ccf..b6762f63 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -21,8 +21,11 @@ class Client { struct wl_registry * registry = nullptr; struct zwlr_layer_shell_v1 * layer_shell = nullptr; struct zxdg_output_manager_v1 * xdg_output_manager = nullptr; + struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; struct zwp_idle_inhibitor_v1* idle_inhibitor; + std::vector idle_inhibitor_modules; + std::vector> bars; private: diff --git a/src/bar.cpp b/src/bar.cpp index 747f0bf8..8af6b971 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -461,15 +461,3 @@ auto waybar::Bar::setupWidgets() -> void { right_.pack_end(*module, false, false); } } - -void waybar::Bar::updateAll() { - for (auto const& module : modules_left_) { - module->update(); - } - for (auto const& module : modules_center_) { - module->update(); - } - for (auto const& module : modules_right_) { - module->update(); - } -} diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index c6a4c78b..7a0dd505 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -10,6 +10,7 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& event_box_.add_events(Gdk::BUTTON_PRESS_MASK); event_box_.signal_button_press_event().connect( sigc::mem_fun(*this, &IdleInhibitor::handleToggle)); + waybar::Client::inst()->idle_inhibitor_modules.push_back(this); dp.emit(); } @@ -57,9 +58,11 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { click_param = status_; } - // Make all modules update - for (auto const& bar : waybar::Client::inst()->bars) { - bar->updateAll(); + // Make all other idle inhibitor modules update + for (auto const& module : waybar::Client::inst()->idle_inhibitor_modules) { + if (module != this) { + module->update(); + } } ALabel::handleToggle(e); From 4889e655ebe5a58b18c294ef3b96d8dd6a46240c Mon Sep 17 00:00:00 2001 From: Jordan Leppert Date: Sun, 1 Nov 2020 13:33:28 +0000 Subject: [PATCH 45/94] Since idle_inhibitor's have a surface, we should have one for each inhibitor module. Therefore, the status is stored on the Client, and all modules create or destroy their inhibitors depending on Client's idle_inhibitor_status. Also, when modules are destroyed they remove themselves from Client's idle_inhibitor_modules. --- include/client.hpp | 4 +-- include/modules/idle_inhibitor.hpp | 2 +- src/modules/idle_inhibitor.cpp | 49 ++++++++++++++++++------------ 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/include/client.hpp b/include/client.hpp index b6762f63..592af8f3 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -23,8 +23,8 @@ class Client { struct zxdg_output_manager_v1 * xdg_output_manager = nullptr; struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; - struct zwp_idle_inhibitor_v1* idle_inhibitor; - std::vector idle_inhibitor_modules; + std::list idle_inhibitor_modules; + std::string idle_inhibitor_status = "deactivated"; std::vector> bars; diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index f1c690df..abfc9960 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -12,12 +12,12 @@ class IdleInhibitor : public ALabel { IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&); ~IdleInhibitor(); auto update() -> void; + struct zwp_idle_inhibitor_v1* idle_inhibitor_ = nullptr; private: bool handleToggle(GdkEventButton* const& e); const Bar& bar_; - std::string status_; int pid_; }; diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 7a0dd505..fe7ee16f 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -5,20 +5,26 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& const Json::Value& config) : ALabel(config, "idle_inhibitor", id, "{status}"), bar_(bar), - status_("deactivated"), pid_(-1) { event_box_.add_events(Gdk::BUTTON_PRESS_MASK); event_box_.signal_button_press_event().connect( sigc::mem_fun(*this, &IdleInhibitor::handleToggle)); + + // Add this to the Client's idle_inhibitor_modules waybar::Client::inst()->idle_inhibitor_modules.push_back(this); + dp.emit(); } waybar::modules::IdleInhibitor::~IdleInhibitor() { - if (waybar::Client::inst()->idle_inhibitor != nullptr) { - zwp_idle_inhibitor_v1_destroy(waybar::Client::inst()->idle_inhibitor); - waybar::Client::inst()->idle_inhibitor = nullptr; + if (idle_inhibitor_ != nullptr) { + zwp_idle_inhibitor_v1_destroy(idle_inhibitor_); + idle_inhibitor_ = nullptr; } + + // Remove this from the Client's idle_inhibitor_modules + waybar::Client::inst()->idle_inhibitor_modules.remove(this); + if (pid_ != -1) { kill(-pid_, 9); pid_ = -1; @@ -27,17 +33,24 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() { auto waybar::modules::IdleInhibitor::update() -> void { // Check status - if (waybar::Client::inst()->idle_inhibitor != nullptr) { - status_ = "activated"; + std::string status = waybar::Client::inst()->idle_inhibitor_status; + if (status == "activated") { + if (idle_inhibitor_ == nullptr) { + idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor( + waybar::Client::inst()->idle_inhibit_manager, bar_.surface); + } } else { - status_ = "deactivated"; + if (idle_inhibitor_ != nullptr) { + zwp_idle_inhibitor_v1_destroy(idle_inhibitor_); + idle_inhibitor_ = nullptr; + } } label_.set_markup( - fmt::format(format_, fmt::arg("status", status_), fmt::arg("icon", getIcon(0, status_)))); - label_.get_style_context()->add_class(status_); + fmt::format(format_, fmt::arg("status", status), fmt::arg("icon", getIcon(0, status)))); + label_.get_style_context()->add_class(status); if (tooltipEnabled()) { - label_.set_tooltip_text(status_); + label_.set_tooltip_text(status); } // Call parent update ALabel::update(); @@ -45,17 +58,15 @@ auto waybar::modules::IdleInhibitor::update() -> void { bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { if (e->button == 1) { - label_.get_style_context()->remove_class(status_); - if (waybar::Client::inst()->idle_inhibitor != nullptr) { - zwp_idle_inhibitor_v1_destroy(waybar::Client::inst()->idle_inhibitor); - waybar::Client::inst()->idle_inhibitor = nullptr; - status_ = "deactivated"; + std::string status = waybar::Client::inst()->idle_inhibitor_status; + label_.get_style_context()->remove_class(status); + if (status == "activated") { + status = "deactivated"; } else { - waybar::Client::inst()->idle_inhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor( - waybar::Client::inst()->idle_inhibit_manager, bar_.surface); - status_ = "activated"; + status = "activated"; } - click_param = status_; + waybar::Client::inst()->idle_inhibitor_status = status; + click_param = status; } // Make all other idle inhibitor modules update From bb33427f6532dfbf09d7bbc6755ca3e2f3946115 Mon Sep 17 00:00:00 2001 From: Jordan Leppert Date: Sun, 1 Nov 2020 13:38:58 +0000 Subject: [PATCH 46/94] Making idle_inhibitor_ private and initialised in constructor, as it was before. --- include/modules/idle_inhibitor.hpp | 2 +- src/modules/idle_inhibitor.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index abfc9960..a857011c 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -12,12 +12,12 @@ class IdleInhibitor : public ALabel { IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&); ~IdleInhibitor(); auto update() -> void; - struct zwp_idle_inhibitor_v1* idle_inhibitor_ = nullptr; private: bool handleToggle(GdkEventButton* const& e); const Bar& bar_; + struct zwp_idle_inhibitor_v1* idle_inhibitor_; int pid_; }; diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index fe7ee16f..990af2ca 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -5,6 +5,7 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& const Json::Value& config) : ALabel(config, "idle_inhibitor", id, "{status}"), bar_(bar), + idle_inhibitor_(nullptr), pid_(-1) { event_box_.add_events(Gdk::BUTTON_PRESS_MASK); event_box_.signal_button_press_event().connect( From c6743988d3f7afee8a4952b24817eaa59a08fe49 Mon Sep 17 00:00:00 2001 From: Jordan Leppert Date: Sun, 1 Nov 2020 16:03:39 +0000 Subject: [PATCH 47/94] Removing 'click_param' as it is no longer used. --- include/ALabel.hpp | 1 - src/modules/idle_inhibitor.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/include/ALabel.hpp b/include/ALabel.hpp index 6848d677..5b9ac541 100644 --- a/include/ALabel.hpp +++ b/include/ALabel.hpp @@ -19,7 +19,6 @@ class ALabel : public AModule { protected: Gtk::Label label_; std::string format_; - std::string click_param; const std::chrono::seconds interval_; bool alt_ = false; std::string default_format_; diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 990af2ca..5eb94eb5 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -67,7 +67,6 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { status = "activated"; } waybar::Client::inst()->idle_inhibitor_status = status; - click_param = status; } // Make all other idle inhibitor modules update From 071cb86b45d5a348430feac0516794836cf77721 Mon Sep 17 00:00:00 2001 From: Jordan Leppert Date: Sun, 1 Nov 2020 17:09:48 +0000 Subject: [PATCH 48/94] Moving idle inhibitor shared stuff out of Client and into idle_inhibitor module as static members. --- include/client.hpp | 2 -- include/modules/idle_inhibitor.hpp | 2 ++ src/modules/idle_inhibitor.cpp | 15 +++++++++------ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/include/client.hpp b/include/client.hpp index 592af8f3..902f6866 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -23,8 +23,6 @@ class Client { struct zxdg_output_manager_v1 * xdg_output_manager = nullptr; struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; - std::list idle_inhibitor_modules; - std::string idle_inhibitor_status = "deactivated"; std::vector> bars; diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index a857011c..f5926318 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -12,6 +12,8 @@ class IdleInhibitor : public ALabel { IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&); ~IdleInhibitor(); auto update() -> void; + static std::list idle_inhibitor_modules; + static std::string idle_inhibitor_status; private: bool handleToggle(GdkEventButton* const& e); diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 5eb94eb5..ce5f9914 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -1,6 +1,9 @@ #include "modules/idle_inhibitor.hpp" #include "util/command.hpp" +std::list waybar::modules::IdleInhibitor::idle_inhibitor_modules; +std::string waybar::modules::IdleInhibitor::idle_inhibitor_status = "deactivated"; + waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar, const Json::Value& config) : ALabel(config, "idle_inhibitor", id, "{status}"), @@ -12,7 +15,7 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& sigc::mem_fun(*this, &IdleInhibitor::handleToggle)); // Add this to the Client's idle_inhibitor_modules - waybar::Client::inst()->idle_inhibitor_modules.push_back(this); + waybar::modules::IdleInhibitor::idle_inhibitor_modules.push_back(this); dp.emit(); } @@ -24,7 +27,7 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() { } // Remove this from the Client's idle_inhibitor_modules - waybar::Client::inst()->idle_inhibitor_modules.remove(this); + waybar::modules::IdleInhibitor::idle_inhibitor_modules.remove(this); if (pid_ != -1) { kill(-pid_, 9); @@ -34,7 +37,7 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() { auto waybar::modules::IdleInhibitor::update() -> void { // Check status - std::string status = waybar::Client::inst()->idle_inhibitor_status; + std::string status = waybar::modules::IdleInhibitor::idle_inhibitor_status; if (status == "activated") { if (idle_inhibitor_ == nullptr) { idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor( @@ -59,18 +62,18 @@ auto waybar::modules::IdleInhibitor::update() -> void { bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { if (e->button == 1) { - std::string status = waybar::Client::inst()->idle_inhibitor_status; + std::string status = waybar::modules::IdleInhibitor::idle_inhibitor_status; label_.get_style_context()->remove_class(status); if (status == "activated") { status = "deactivated"; } else { status = "activated"; } - waybar::Client::inst()->idle_inhibitor_status = status; + waybar::modules::IdleInhibitor::idle_inhibitor_status = status; } // Make all other idle inhibitor modules update - for (auto const& module : waybar::Client::inst()->idle_inhibitor_modules) { + for (auto const& module : waybar::modules::IdleInhibitor::idle_inhibitor_modules) { if (module != this) { module->update(); } From a9dae931c73e44f6c18fa0fc8a3220fbea5f265b Mon Sep 17 00:00:00 2001 From: Jordan Leppert Date: Sun, 1 Nov 2020 17:14:05 +0000 Subject: [PATCH 49/94] Renaming idle_inhibitor_modules and idle_inhibitor_status to shorter, more convenient names. --- include/client.hpp | 2 -- include/modules/idle_inhibitor.hpp | 4 ++-- src/modules/idle_inhibitor.cpp | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/include/client.hpp b/include/client.hpp index 902f6866..39b6ae3b 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -21,9 +21,7 @@ class Client { struct wl_registry * registry = nullptr; struct zwlr_layer_shell_v1 * layer_shell = nullptr; struct zxdg_output_manager_v1 * xdg_output_manager = nullptr; - struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; - std::vector> bars; private: diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index f5926318..2cbc6f12 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -12,8 +12,8 @@ class IdleInhibitor : public ALabel { IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&); ~IdleInhibitor(); auto update() -> void; - static std::list idle_inhibitor_modules; - static std::string idle_inhibitor_status; + static std::list modules; + static std::string status; private: bool handleToggle(GdkEventButton* const& e); diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index ce5f9914..96db8799 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -1,8 +1,8 @@ #include "modules/idle_inhibitor.hpp" #include "util/command.hpp" -std::list waybar::modules::IdleInhibitor::idle_inhibitor_modules; -std::string waybar::modules::IdleInhibitor::idle_inhibitor_status = "deactivated"; +std::list waybar::modules::IdleInhibitor::modules; +std::string waybar::modules::IdleInhibitor::status = "deactivated"; waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar, const Json::Value& config) @@ -14,8 +14,8 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& event_box_.signal_button_press_event().connect( sigc::mem_fun(*this, &IdleInhibitor::handleToggle)); - // Add this to the Client's idle_inhibitor_modules - waybar::modules::IdleInhibitor::idle_inhibitor_modules.push_back(this); + // Add this to the modules list + waybar::modules::IdleInhibitor::modules.push_back(this); dp.emit(); } @@ -26,8 +26,8 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() { idle_inhibitor_ = nullptr; } - // Remove this from the Client's idle_inhibitor_modules - waybar::modules::IdleInhibitor::idle_inhibitor_modules.remove(this); + // Remove this from the modules list + waybar::modules::IdleInhibitor::modules.remove(this); if (pid_ != -1) { kill(-pid_, 9); @@ -37,7 +37,7 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() { auto waybar::modules::IdleInhibitor::update() -> void { // Check status - std::string status = waybar::modules::IdleInhibitor::idle_inhibitor_status; + std::string status = waybar::modules::IdleInhibitor::status; if (status == "activated") { if (idle_inhibitor_ == nullptr) { idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor( @@ -62,18 +62,18 @@ auto waybar::modules::IdleInhibitor::update() -> void { bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { if (e->button == 1) { - std::string status = waybar::modules::IdleInhibitor::idle_inhibitor_status; + std::string status = waybar::modules::IdleInhibitor::status; label_.get_style_context()->remove_class(status); if (status == "activated") { status = "deactivated"; } else { status = "activated"; } - waybar::modules::IdleInhibitor::idle_inhibitor_status = status; + waybar::modules::IdleInhibitor::status = status; } // Make all other idle inhibitor modules update - for (auto const& module : waybar::modules::IdleInhibitor::idle_inhibitor_modules) { + for (auto const& module : waybar::modules::IdleInhibitor::modules) { if (module != this) { module->update(); } From b015836e7b55def65d83cba9b3367ce2c1636417 Mon Sep 17 00:00:00 2001 From: Jordan Leppert Date: Sun, 1 Nov 2020 18:17:51 +0000 Subject: [PATCH 50/94] Ensure style class is removed from all IdleInhibitor instances by moving it to update(). --- src/modules/idle_inhibitor.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 96db8799..24d94f51 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -37,13 +37,14 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() { auto waybar::modules::IdleInhibitor::update() -> void { // Check status - std::string status = waybar::modules::IdleInhibitor::status; if (status == "activated") { + label_.get_style_context()->remove_class("deactivated"); if (idle_inhibitor_ == nullptr) { idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor( waybar::Client::inst()->idle_inhibit_manager, bar_.surface); } } else { + label_.get_style_context()->remove_class("activated"); if (idle_inhibitor_ != nullptr) { zwp_idle_inhibitor_v1_destroy(idle_inhibitor_); idle_inhibitor_ = nullptr; @@ -62,14 +63,11 @@ auto waybar::modules::IdleInhibitor::update() -> void { bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { if (e->button == 1) { - std::string status = waybar::modules::IdleInhibitor::status; - label_.get_style_context()->remove_class(status); if (status == "activated") { status = "deactivated"; } else { status = "activated"; } - waybar::modules::IdleInhibitor::status = status; } // Make all other idle inhibitor modules update From 9785a8901382278c1832320a2c67cfec75dcf45f Mon Sep 17 00:00:00 2001 From: Jordan Leppert Date: Sun, 1 Nov 2020 18:25:41 +0000 Subject: [PATCH 51/94] Making active a bool --- include/modules/idle_inhibitor.hpp | 4 ++-- src/modules/idle_inhibitor.cpp | 27 ++++++++++++--------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index 2cbc6f12..4b6c097f 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -12,8 +12,8 @@ class IdleInhibitor : public ALabel { IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&); ~IdleInhibitor(); auto update() -> void; - static std::list modules; - static std::string status; + static std::list modules; + static bool status; private: bool handleToggle(GdkEventButton* const& e); diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 24d94f51..ba5d6943 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -2,7 +2,7 @@ #include "util/command.hpp" std::list waybar::modules::IdleInhibitor::modules; -std::string waybar::modules::IdleInhibitor::status = "deactivated"; +bool waybar::modules::IdleInhibitor::status = false; waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar, const Json::Value& config) @@ -37,7 +37,7 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() { auto waybar::modules::IdleInhibitor::update() -> void { // Check status - if (status == "activated") { + if (status) { label_.get_style_context()->remove_class("deactivated"); if (idle_inhibitor_ == nullptr) { idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor( @@ -51,11 +51,12 @@ auto waybar::modules::IdleInhibitor::update() -> void { } } + std::string status_text = status ? "activated" : "deactivated"; label_.set_markup( - fmt::format(format_, fmt::arg("status", status), fmt::arg("icon", getIcon(0, status)))); - label_.get_style_context()->add_class(status); + fmt::format(format_, fmt::arg("status", status_text), fmt::arg("icon", getIcon(0, status_text)))); + label_.get_style_context()->add_class(status_text); if (tooltipEnabled()) { - label_.set_tooltip_text(status); + label_.set_tooltip_text(status_text); } // Call parent update ALabel::update(); @@ -63,17 +64,13 @@ auto waybar::modules::IdleInhibitor::update() -> void { bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { if (e->button == 1) { - if (status == "activated") { - status = "deactivated"; - } else { - status = "activated"; - } - } + status = !status; - // Make all other idle inhibitor modules update - for (auto const& module : waybar::modules::IdleInhibitor::modules) { - if (module != this) { - module->update(); + // Make all other idle inhibitor modules update + for (auto const& module : waybar::modules::IdleInhibitor::modules) { + if (module != this) { + module->update(); + } } } From d8dafa7ecc189dd203617e24441012e3ac54d9a0 Mon Sep 17 00:00:00 2001 From: Arnaud Vallette d'Osia Date: Wed, 18 Nov 2020 20:12:07 +0100 Subject: [PATCH 52/94] add minimize-raise() action --- src/modules/wlr/taskbar.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 8d6fa9b4..5c830e05 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -436,6 +436,14 @@ bool Task::handle_clicked(GdkEventButton *bt) activate(); else if (action == "minimize") minimize(!minimized()); + else if (action == "minimize-raise"){ + if (minimized()) + minimize(false); + else if (active()) + minimize(true); + else + activate(); + } else if (action == "maximize") maximize(!maximized()); else if (action == "fullscreen") From 82823850745d5ec367070e1a08f568c8b3a77eb8 Mon Sep 17 00:00:00 2001 From: Arnaud Vallette d'Osia Date: Sun, 22 Nov 2020 13:06:46 +0100 Subject: [PATCH 53/94] update actions on taskbar man page --- man/waybar-wlr-taskbar.5.scd | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/man/waybar-wlr-taskbar.5.scd b/man/waybar-wlr-taskbar.5.scd index 55d2afdc..a2bff265 100644 --- a/man/waybar-wlr-taskbar.5.scd +++ b/man/waybar-wlr-taskbar.5.scd @@ -83,9 +83,10 @@ Addressed by *wlr/taskbar* # CLICK ACTIONS *activate*: Bring the application into foreground. -*minimize*: Minimize the application. -*maximize*: Maximize the application. -*fullscreen*: Set the application to fullscreen. +*minimize*: Toggle application's minimized state. +*minimize-raise*: Bring the application into foreground or toggle its minimized state. +*maximize*: Toggle application's maximized state. +*fullscreen*: Toggle application's fullscreen state. *close*: Close the application. # EXAMPLES From 374d5ae5a198611011b8f78fc97a20b7ece5a049 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 26 Nov 2020 15:10:33 +0100 Subject: [PATCH 54/94] fix: check get_icon return non nullpt --- src/modules/wlr/taskbar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 5c830e05..a18c3e29 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -95,7 +95,7 @@ static std::string get_from_desktop_app_info(const std::string &app_id) if (!app_info) app_info = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix); - if (app_info) + if (app_info && app_info->get_icon() != nullptr) return app_info->get_icon()->to_string(); return ""; From 05b12602d4f1fde4c13309a0b3a70afb010d3231 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 26 Nov 2020 15:16:55 +0100 Subject: [PATCH 55/94] fix: don't check against nullptr --- src/modules/wlr/taskbar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index a18c3e29..bdc980c4 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -95,7 +95,7 @@ static std::string get_from_desktop_app_info(const std::string &app_id) if (!app_info) app_info = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix); - if (app_info && app_info->get_icon() != nullptr) + if (app_info && app_info->get_icon()) return app_info->get_icon()->to_string(); return ""; From 85e00b2aab89381feda50f683d79287ec9f13e86 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Thu, 26 Nov 2020 15:31:40 -0800 Subject: [PATCH 56/94] fix: build fails with meson < 0.53.0 meson.build:12:0: ERROR: Module "fs" does not exist Fixes #909 --- meson.build | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/meson.build b/meson.build index 7f4ffaca..1e7e22c2 100644 --- a/meson.build +++ b/meson.build @@ -2,6 +2,7 @@ project( 'waybar', 'cpp', 'c', version: '0.9.4', license: 'MIT', + meson_version: '>= 0.49.0', default_options : [ 'cpp_std=c++17', 'buildtype=release', @@ -9,8 +10,6 @@ project( ], ) -fs = import('fs') - compiler = meson.get_compiler('cpp') cpp_args = [] @@ -318,9 +317,9 @@ if scdoc.found() foreach file : man_files path = '@0@'.format(file) - basename = fs.name(path) + basename = path.split('/')[-1] - topic = basename.split('.')[-3].split('/')[-1] + topic = basename.split('.')[-3] section = basename.split('.')[-2] output = '@0@.@1@'.format(topic, section) From 2695985da04675a18f21d2ba4e09bb2c232b1eee Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Thu, 26 Nov 2020 15:38:41 -0800 Subject: [PATCH 57/94] fix: compilation error with gcc 11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ../src/modules/network.cpp:22:6: error: ‘optional’ in namespace ‘std’ does not name a template type 22 | std::optional read_netstat(std::string_view category, std::string_view key) { | ^~~~~~~~ ../src/modules/network.cpp:7:1: note: ‘std::optional’ is defined in header ‘’; did you forget to ‘#include ’? 6 | #include "util/format.hpp" +++ |+#include 7 | #ifdef WANT_RFKILL --- src/modules/network.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 74ae9130..7d9da8b5 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "util/format.hpp" #ifdef WANT_RFKILL #include "util/rfkill.hpp" From 3b576ae12dc1302e7c86d2e1d98d8e578debf694 Mon Sep 17 00:00:00 2001 From: Daniel De Graaf Date: Wed, 21 Oct 2020 13:10:10 -0400 Subject: [PATCH 58/94] Add "tooltip-format" to temperature module --- man/waybar-temperature.5.scd | 5 +++++ src/modules/temperature.cpp | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/man/waybar-temperature.5.scd b/man/waybar-temperature.5.scd index 86497367..7810a59b 100644 --- a/man/waybar-temperature.5.scd +++ b/man/waybar-temperature.5.scd @@ -50,6 +50,11 @@ Addressed by *temperature* typeof: array ++ Based on the current temperature (Celsius) and *critical-threshold* if available, the corresponding icon gets selected. The order is *low* to *high*. +*tooltip-format*: ++ + typeof: string ++ + default: {temperatureC}°C ++ + The format for the tooltip + *rotate*: ++ typeof: integer ++ Positive value to rotate the text label. diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index dc6b2d78..84560e8d 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -40,6 +40,16 @@ auto waybar::modules::Temperature::update() -> void { fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k), fmt::arg("icon", getIcon(temperature_c, "", max_temp)))); + if (tooltipEnabled()) { + std::string tooltip_format = "{temperatureC}°C"; + if (config_["tooltip-format"].isString()) { + tooltip_format = config_["tooltip-format"].asString(); + } + label_.set_tooltip_text(fmt::format(tooltip_format, + fmt::arg("temperatureC", temperature_c), + fmt::arg("temperatureF", temperature_f), + fmt::arg("temperatureK", temperature_k))); + } // Call parent update ALabel::update(); } From a7056f7ccec0126f3b2a1c86b09a4d6dacef8cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20C=C3=B4rte-Real?= Date: Thu, 26 Nov 2020 01:02:06 +0000 Subject: [PATCH 59/94] Calculate battery state from just energy values The energy values are all that's needed to calculate the battery state. Using other values for the total capacity results in broken results in some cases. This matches the output of TLP and i3status, while also being more straightforward. --- src/modules/battery.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index beb0554f..93fcc8bd 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -80,18 +80,15 @@ void waybar::modules::Battery::getBatteries() { const std::tuple waybar::modules::Battery::getInfos() const { try { - uint16_t total = 0; uint32_t total_power = 0; // μW uint32_t total_energy = 0; // μWh uint32_t total_energy_full = 0; std::string status = "Unknown"; for (auto const& bat : batteries_) { - uint16_t capacity; uint32_t power_now; uint32_t energy_full; uint32_t energy_now; std::string _status; - std::ifstream(bat / "capacity") >> capacity; std::ifstream(bat / "status") >> _status; auto rate_path = fs::exists(bat / "current_now") ? "current_now" : "power_now"; std::ifstream(bat / rate_path) >> power_now; @@ -102,7 +99,6 @@ const std::tuple waybar::modules::Battery::getInfos if (_status != "Unknown") { status = _status; } - total += capacity; total_power += power_now; total_energy += energy_now; total_energy_full += energy_full; @@ -120,7 +116,7 @@ const std::tuple waybar::modules::Battery::getInfos } else if (status == "Charging" && total_power != 0) { time_remaining = -(float)(total_energy_full - total_energy) / total_power; } - uint16_t capacity = total / batteries_.size(); + float capacity = ((float)total_energy * 100.0f / (float) total_energy_full); // Handle full-at if (config_["full-at"].isUInt()) { auto full_at = config_["full-at"].asUInt(); From e0cdcb6e30efbf56a862b0b4dfb7baa5677bf35d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20C=C3=B4rte-Real?= Date: Thu, 26 Nov 2020 02:16:57 +0000 Subject: [PATCH 60/94] Handle charging above 100% gracefully When calibrating a battery it's possible to go above 100%. Handle that gracefully by just presenting the battery as full and 100%. --- src/modules/battery.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 93fcc8bd..02e05b79 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -115,6 +115,11 @@ const std::tuple waybar::modules::Battery::getInfos time_remaining = (float)total_energy / total_power; } else if (status == "Charging" && total_power != 0) { time_remaining = -(float)(total_energy_full - total_energy) / total_power; + if (time_remaining > 0.0f) { + // If we've turned positive it means the battery is past 100% and so + // just report that as no time remaining + time_remaining = 0.0f; + } } float capacity = ((float)total_energy * 100.0f / (float) total_energy_full); // Handle full-at @@ -127,6 +132,12 @@ const std::tuple waybar::modules::Battery::getInfos } } } + if (capacity > 100.f) { + // This can happen when the battery is calibrating and goes above 100% + // Handle it gracefully by clamping at 100% and presenting it as full + capacity = 100.f; + status = "Full"; + } return {capacity, time_remaining, status}; } catch (const std::exception& e) { spdlog::error("Battery: {}", e.what()); From eb3f4216d402903b18c41b38cf4ddb8f0a72ad3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20C=C3=B4rte-Real?= Date: Thu, 26 Nov 2020 02:20:53 +0000 Subject: [PATCH 61/94] Show battery state as rounded number Round the battery charge state so that 99.9% shows as 100%. --- src/modules/battery.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 02e05b79..eece435a 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -138,7 +138,7 @@ const std::tuple waybar::modules::Battery::getInfos capacity = 100.f; status = "Full"; } - return {capacity, time_remaining, status}; + return {round(capacity), time_remaining, status}; } catch (const std::exception& e) { spdlog::error("Battery: {}", e.what()); return {0, 0, "Unknown"}; From f45d5829577d59eb9980a31793f2feb2f0c15eb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20C=C3=B4rte-Real?= Date: Thu, 26 Nov 2020 02:34:36 +0000 Subject: [PATCH 62/94] Always mark battery as full at 100% Since we're now clamping at 100% and rounding, mark as full at that point. Some batteries will stay in charging state for a long time while stuck at the same charge level. Just mark them as full to not be confusing. --- src/modules/battery.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index eece435a..2d553705 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -134,11 +134,17 @@ const std::tuple waybar::modules::Battery::getInfos } if (capacity > 100.f) { // This can happen when the battery is calibrating and goes above 100% - // Handle it gracefully by clamping at 100% and presenting it as full + // Handle it gracefully by clamping at 100% capacity = 100.f; + } + uint8_t cap = round(capacity); + if (cap == 100) { + // If we've reached 100% just mark as full as some batteries can stay + // stuck reporting they're still charging but not yet done status = "Full"; } - return {round(capacity), time_remaining, status}; + + return {cap, time_remaining, status}; } catch (const std::exception& e) { spdlog::error("Battery: {}", e.what()); return {0, 0, "Unknown"}; From 908fa2c6c239af7a27c53658940f1f20bc5fa5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20C=C3=B4rte-Real?= Date: Fri, 27 Nov 2020 10:55:27 +0000 Subject: [PATCH 63/94] Make the battery full-at go to 100% full-at was capped at the value instead of allowing the battery to show 100% when you were at the full-at value. Uncapping makes more sense as it makes the full-at value the new 100% and the scale goes down from there. Whereas before the battery would stay at the full-at value until it went down enough which is unrealistic. --- man/waybar-battery.5.scd | 2 +- src/modules/battery.cpp | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/man/waybar-battery.5.scd b/man/waybar-battery.5.scd index 917a03d7..c9e6e78a 100644 --- a/man/waybar-battery.5.scd +++ b/man/waybar-battery.5.scd @@ -20,7 +20,7 @@ The *battery* module displays the current capacity and state (eg. charging) of y *full-at*: ++ typeof: integer ++ - Define the max percentage of the battery, useful for an old battery, e.g. 96 + Define the max percentage of the battery, for when you've set the battery to stop charging at a lower level to save it. For example, if you've set the battery to stop at 80% that will become the new 100%. *interval*: ++ typeof: integer ++ diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 2d553705..031c949f 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -127,9 +127,6 @@ const std::tuple waybar::modules::Battery::getInfos auto full_at = config_["full-at"].asUInt(); if (full_at < 100) { capacity = 100.f * capacity / full_at; - if (capacity > full_at) { - capacity = full_at; - } } } if (capacity > 100.f) { From 89ca155c43eea73593cacfa4f58e687851a0989c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20C=C3=B4rte-Real?= Date: Fri, 27 Nov 2020 13:56:04 +0000 Subject: [PATCH 64/94] Support hotplugging of batteries Refresh the list of batteries on update to handle hotplug correctly. Probably fixes #490. --- include/modules/battery.hpp | 6 ++-- src/modules/battery.cpp | 69 +++++++++++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/include/modules/battery.hpp b/include/modules/battery.hpp index d4d20d1e..f0e3c39d 100644 --- a/include/modules/battery.hpp +++ b/include/modules/battery.hpp @@ -31,16 +31,16 @@ class Battery : public ALabel { private: static inline const fs::path data_dir_ = "/sys/class/power_supply/"; - void getBatteries(); + void refreshBatteries(); void worker(); const std::string getAdapterStatus(uint8_t capacity) const; const std::tuple getInfos() const; const std::string formatTimeRemaining(float hoursRemaining); - std::vector batteries_; + int global_watch; + std::map batteries_; fs::path adapter_; int fd_; - std::vector wds_; std::string old_status_; util::SleeperThread thread_; diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 031c949f..84bc973b 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -4,23 +4,31 @@ waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config) : ALabel(config, "battery", id, "{capacity}%", 60) { - getBatteries(); fd_ = inotify_init1(IN_CLOEXEC); if (fd_ == -1) { throw std::runtime_error("Unable to listen batteries."); } - for (auto const& bat : batteries_) { - auto wd = inotify_add_watch(fd_, (bat / "uevent").c_str(), IN_ACCESS); - if (wd != -1) { - wds_.push_back(wd); - } + + // Watch the directory for any added or removed batteries + global_watch = inotify_add_watch(fd_, data_dir_.c_str(), IN_CREATE | IN_DELETE); + if (global_watch < 0) { + throw std::runtime_error("Could not watch for battery plug/unplug"); } + + refreshBatteries(); worker(); } waybar::modules::Battery::~Battery() { - for (auto wd : wds_) { - inotify_rm_watch(fd_, wd); + if (global_watch >= 0) { + inotify_rm_watch(fd_, global_watch); + } + for (auto it = batteries_.cbegin(); it != batteries_.cend(); it++) { + auto watch_id = (*it).second; + if (watch_id >= 0) { + inotify_rm_watch(fd_, watch_id); + } + batteries_.erase(it); } close(fd_); } @@ -43,7 +51,13 @@ void waybar::modules::Battery::worker() { }; } -void waybar::modules::Battery::getBatteries() { +void waybar::modules::Battery::refreshBatteries() { + // Mark existing list of batteries as not necessarily found + std::map check_map; + for (auto const& bat : batteries_) { + check_map[bat.first] = false; + } + try { for (auto& node : fs::directory_iterator(data_dir_)) { if (!fs::is_directory(node)) { @@ -54,12 +68,22 @@ void waybar::modules::Battery::getBatteries() { if (((bat_defined && dir_name == config_["bat"].asString()) || !bat_defined) && fs::exists(node.path() / "capacity") && fs::exists(node.path() / "uevent") && fs::exists(node.path() / "status") && fs::exists(node.path() / "type")) { - std::string type; - std::ifstream(node.path() / "type") >> type; + std::string type; + std::ifstream(node.path() / "type") >> type; - if (!type.compare("Battery")){ - batteries_.push_back(node.path()); + if (!type.compare("Battery")){ + check_map[node.path()] = true; + auto search = batteries_.find(node.path()); + if (search == batteries_.end()) { + // We've found a new battery save it and start listening for events + auto event_path = (node.path() / "uevent"); + auto wd = inotify_add_watch(fd_, event_path.c_str(), IN_ACCESS); + if (wd < 0) { + throw std::runtime_error("Could not watch events for " + node.path().string()); } + batteries_[node.path()] = wd; + } + } } auto adap_defined = config_["adapter"].isString(); if (((adap_defined && dir_name == config_["adapter"].asString()) || !adap_defined) && @@ -76,6 +100,17 @@ void waybar::modules::Battery::getBatteries() { } throw std::runtime_error("No batteries."); } + + // Remove any batteries that are no longer present and unwatch them + for (auto const& check : check_map) { + if (!check.second) { + auto watch_id = batteries_[check.first]; + if (watch_id >= 0) { + inotify_rm_watch(fd_, watch_id); + } + batteries_.erase(check.first); + } + } } const std::tuple waybar::modules::Battery::getInfos() const { @@ -84,7 +119,8 @@ const std::tuple waybar::modules::Battery::getInfos uint32_t total_energy = 0; // μWh uint32_t total_energy_full = 0; std::string status = "Unknown"; - for (auto const& bat : batteries_) { + for (auto const& item : batteries_) { + auto bat = item.first; uint32_t power_now; uint32_t energy_full; uint32_t energy_now; @@ -175,6 +211,11 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai } auto waybar::modules::Battery::update() -> void { + // Make sure we have the correct set of batteries, in case of hotplug + // TODO: split the global watch into it's own event and only run the refresh + // when there's been a CREATE/DELETE event + refreshBatteries(); + auto [capacity, time_remaining, status] = getInfos(); if (status == "Unknown") { status = getAdapterStatus(capacity); From 31a4aff1f8328037ad8986e2ffd1a4a90af33114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20C=C3=B4rte-Real?= Date: Fri, 27 Nov 2020 14:23:37 +0000 Subject: [PATCH 65/94] Don't show battery estimate at 0 If we think we're done might as well not show 0h 0min as the estimate and just not show anything. --- src/modules/battery.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 84bc973b..c506171f 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -200,10 +200,14 @@ const std::string waybar::modules::Battery::getAdapterStatus(uint8_t capacity) c } const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemaining) { - hoursRemaining = std::fabs(hoursRemaining); + hoursRemaining = std::fabs(hoursRemaining); uint16_t full_hours = static_cast(hoursRemaining); uint16_t minutes = static_cast(60 * (hoursRemaining - full_hours)); auto format = std::string("{H} h {M} min"); + if (full_hours == 0 && minutes == 0) { + // Migh as well not show "0h 0min" + return ""; + } if (config_["format-time"].isString()) { format = config_["format-time"].asString(); } From c784e8170e61bc7cd36c64e0967372524cd0fd7e Mon Sep 17 00:00:00 2001 From: Thomas Hebb Date: Mon, 30 Nov 2020 12:49:48 -0800 Subject: [PATCH 66/94] clock: initialize cached date We are currently using this value once before it's initialized, since we check it before we set it in Clock::calendar_text(). This was caught by Valgrind, producing the following error: ==8962== Conditional jump or move depends on uninitialised value(s) ==8962== at 0x138285: date::operator==(date::year_month_day const&, date::year_month_day const&) (date.h:2793) ==8962== by 0x135F11: waybar::modules::Clock::calendar_text[abi:cxx11](waybar::modules::waybar_time const&) (clock.cpp:111) ==8962== by 0x13587C: waybar::modules::Clock::update() (clock.cpp:62) ==8962== by 0x156EFA: waybar::Bar::getModules(waybar::Factory const&, std::__cxx11::basic_string, std::allocator > const&)::{lambda()#1}::operator()() const (bar.cpp:577) ==8962== by 0x157F39: sigc::adaptor_functor, std::allocator > const&)::{lambda()#1}>::operator()() const (adaptor_trait.h:256) ==8962== by 0x157D94: sigc::internal::slot_call0, std::allocator > const&)::{lambda()#1}, void>::call_it(sigc::internal::slot_rep*) (slot.h:136) ==8962== by 0x5177B21: Glib::DispatchNotifier::pipe_io_handler(Glib::IOCondition) (in /usr/lib/libglibmm-2.4.so.1.3.0) ==8962== by 0x517DB5B: Glib::IOSource::dispatch(sigc::slot_base*) (in /usr/lib/libglibmm-2.4.so.1.3.0) ==8962== by 0x5188B96: Glib::Source::dispatch_vfunc(_GSource*, int (*)(void*), void*) (in /usr/lib/libglibmm-2.4.so.1.3.0) ==8962== by 0x5CBC913: g_main_context_dispatch (in /usr/lib/libglib-2.0.so.0.6600.2) ==8962== by 0x5D107D0: ??? (in /usr/lib/libglib-2.0.so.0.6600.2) ==8962== by 0x5CBB120: g_main_context_iteration (in /usr/lib/libglib-2.0.so.0.6600.2) Initialize the value to prevent the error. --- include/modules/clock.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index 643b7369..b3e2634f 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -29,7 +29,7 @@ class Clock : public ALabel { const date::time_zone* time_zone_; bool fixed_time_zone_; int time_zone_idx_; - date::year_month_day cached_calendar_ymd_; + date::year_month_day cached_calendar_ymd_ = date::January/1/0; std::string cached_calendar_text_; bool handleScroll(GdkEventScroll* e); From e8dbdee2385ee77c5b1f0e03dd3f3f912ee98a8e Mon Sep 17 00:00:00 2001 From: Thomas Hebb Date: Mon, 30 Nov 2020 17:01:58 -0800 Subject: [PATCH 67/94] Make spdlog use the same version of fmt we use spdlog bundles a version of fmt. However, we also depend on and use fmt directly. If we don't tell spdlog not to use its bundled version, we end up with two versions of fmt in our include path, since both libraries are header-only, meaning any slight API mismatches will cause build failures and undesired behavior. We seem to have gotten lucky so far, but I ran into all sorts of issues when I tried to update to a newer version of spdlog. This change prevents them. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 1e7e22c2..70067eed 100644 --- a/meson.build +++ b/meson.build @@ -80,7 +80,7 @@ is_openbsd = host_machine.system() == 'openbsd' thread_dep = dependency('threads') fmt = dependency('fmt', version : ['>=5.3.0'], fallback : ['fmt', 'fmt_dep']) -spdlog = dependency('spdlog', version : ['>=1.3.1'], fallback : ['spdlog', 'spdlog_dep']) +spdlog = dependency('spdlog', version : ['>=1.3.1'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true']) wayland_client = dependency('wayland-client') wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols') From ad40511358ef057c1a0a8ff9b0bcfcf61ede6ff0 Mon Sep 17 00:00:00 2001 From: Thomas Hebb Date: Mon, 30 Nov 2020 17:10:02 -0800 Subject: [PATCH 68/94] Update spdlog subproject to 1.8.1 Among other changes, this adds spdlog::should_log(), which lets us easily determine whether a log message will be printed so that we can avoid extra computation when unnecessary. New wrap file taken from https://wrapdb.mesonbuild.com/spdlog and modified to download from GitHub as per commit 99dde1aff8db ("Download patch files from Github instead of wrapdb"). --- meson.build | 2 +- subprojects/spdlog.wrap | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/meson.build b/meson.build index 70067eed..7f9db5f7 100644 --- a/meson.build +++ b/meson.build @@ -80,7 +80,7 @@ is_openbsd = host_machine.system() == 'openbsd' thread_dep = dependency('threads') fmt = dependency('fmt', version : ['>=5.3.0'], fallback : ['fmt', 'fmt_dep']) -spdlog = dependency('spdlog', version : ['>=1.3.1'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true']) +spdlog = dependency('spdlog', version : ['>=1.8.0'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true']) wayland_client = dependency('wayland-client') wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols') diff --git a/subprojects/spdlog.wrap b/subprojects/spdlog.wrap index 750036b9..c30450e2 100644 --- a/subprojects/spdlog.wrap +++ b/subprojects/spdlog.wrap @@ -1,10 +1,13 @@ [wrap-file] -directory = spdlog-1.3.1 +directory = spdlog-1.8.1 -source_url = https://github.com/gabime/spdlog/archive/v1.3.1.tar.gz -source_filename = v1.3.1.tar.gz -source_hash = 160845266e94db1d4922ef755637f6901266731c4cb3b30b45bf41efa0e6ab70 +source_url = https://github.com/gabime/spdlog/archive/v1.8.1.tar.gz +source_filename = v1.8.1.tar.gz +source_hash = 5197b3147cfcfaa67dd564db7b878e4a4b3d9f3443801722b3915cdeced656cb -patch_url = https://github.com/mesonbuild/spdlog/releases/download/1.3.1-1/spdlog.zip -patch_filename = spdlog-1.3.1-1-wrap.zip -patch_hash = 715a0229781019b853d409cc0bf891ee4b9d3a17bec0cf87f4ad30b28bbecc87 +patch_url = https://github.com/mesonbuild/spdlog/releases/download/1.8.1-1/spdlog.zip +patch_filename = spdlog-1.8.1-1-wrap.zip +patch_hash = 76844292a8e912aec78450618271a311841b33b17000988f215ddd6c64dd71b3 + +[provide] +spdlog = spdlog_dep From 85df7ce2da2caa8910151a28fcd42f46ea150a6b Mon Sep 17 00:00:00 2001 From: Thomas Hebb Date: Mon, 30 Nov 2020 12:05:18 -0800 Subject: [PATCH 69/94] Add debug log message to print each bar's widget tree This is very useful when writing CSS that affects more than just a single widget. Pass `-l debug` to enable debug logging and show this information. Example output: [2020-11-30 12:38:51.141] [debug] GTK widget tree: window#waybar.background.bottom.eDP-1.:dir(ltr) decoration:dir(ltr) box.horizontal:dir(ltr) box.horizontal.modules-left:dir(ltr) widget:dir(ltr) box#workspaces.horizontal:dir(ltr) widget:dir(ltr) label#mode:dir(ltr) widget:dir(ltr) label#window:dir(ltr) box.horizontal.modules-center:dir(ltr) box.horizontal.modules-right:dir(ltr) widget:dir(ltr) box#tray.horizontal:dir(ltr) widget:dir(ltr) label#idle_inhibitor:dir(ltr) widget:dir(ltr) label#pulseaudio:dir(ltr) widget:dir(ltr) label#network:dir(ltr) widget:dir(ltr) label#cpu:dir(ltr) widget:dir(ltr) label#memory:dir(ltr) widget:dir(ltr) label#temperature:dir(ltr) widget:dir(ltr) label#backlight:dir(ltr) widget:dir(ltr) label#battery:dir(ltr) widget:dir(ltr) label#clock:dir(ltr) --- src/bar.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/bar.cpp b/src/bar.cpp index 771adab5..7bd61727 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -460,6 +460,16 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) setupWidgets(); window.show_all(); + + if (spdlog::should_log(spdlog::level::debug)) { + // Unfortunately, this function isn't in the C++ bindings, so we have to call the C version. + char* gtk_tree = gtk_style_context_to_string( + window.get_style_context()->gobj(), + (GtkStyleContextPrintFlags)(GTK_STYLE_CONTEXT_PRINT_RECURSE | + GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE)); + spdlog::debug("GTK widget tree:\n{}", gtk_tree); + g_free(gtk_tree); + } } void waybar::Bar::onMap(GdkEventAny*) { From 407bf27401c25a96cefd332fd9a5d9f1fef12fce Mon Sep 17 00:00:00 2001 From: Thomas Hebb Date: Mon, 30 Nov 2020 17:32:06 -0800 Subject: [PATCH 70/94] Update fmt subproject to 7.1.3 There is no particular change in this update that we require. However, our previous version, 5.3.0, is nearly two years old, so it seems prudent to pull in all the upstream fixes that have been made since then. New wrap file taken from https://wrapdb.mesonbuild.com/fmt and modified to download from GitHub as per commit 99dde1aff8db ("Download patch files from Github instead of wrapdb"). --- subprojects/fmt.wrap | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/subprojects/fmt.wrap b/subprojects/fmt.wrap index eb79283c..71abc80b 100644 --- a/subprojects/fmt.wrap +++ b/subprojects/fmt.wrap @@ -1,10 +1,13 @@ [wrap-file] -directory = fmt-5.3.0 +directory = fmt-7.1.3 -source_url = https://github.com/fmtlib/fmt/archive/5.3.0.tar.gz -source_filename = fmt-5.3.0.tar.gz -source_hash = defa24a9af4c622a7134076602070b45721a43c51598c8456ec6f2c4dbb51c89 +source_url = https://github.com/fmtlib/fmt/archive/7.1.3.tar.gz +source_filename = fmt-7.1.3.tar.gz +source_hash = 5cae7072042b3043e12d53d50ef404bbb76949dad1de368d7f993a15c8c05ecc -patch_url = https://github.com/mesonbuild/fmt/releases/download/5.3.0-1/fmt.zip -patch_filename = fmt-5.3.0-1-wrap.zip -patch_hash = 18f21a3b8833949c35d4ac88a7059577d5fa24b98786e4b1b2d3d81bb811440f \ No newline at end of file +patch_url = https://github.com/mesonbuild/fmt/releases/download/7.1.3-1/fmt.zip +patch_filename = fmt-7.1.3-1-wrap.zip +patch_hash = 6eb951a51806fd6ffd596064825c39b844c1fe1799840ef507b61a53dba08213 + +[provide] +fmt = fmt_dep From 29f78e04267f726363d11a3fb135d60aa4ce1f41 Mon Sep 17 00:00:00 2001 From: Thomas Hebb Date: Mon, 30 Nov 2020 18:07:22 -0800 Subject: [PATCH 71/94] Fix a few compiler warnings There was one uninitialized value warning and two mismatched-sign compare warnings. They both appear valid, the first occurring when MPD's "format-stopped" contains {songPosition} or {queueLength} and the second occurring when the clock's "timezones" array is more than 2 billion items long (not likely, I admit). Fix both issues. --- src/modules/clock.cpp | 10 ++++------ src/modules/mpd/mpd.cpp | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index f3136067..5b2c3f43 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -85,13 +85,11 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) { return true; } auto nr_zones = config_["timezones"].size(); - int new_idx = time_zone_idx_ + ((dir == SCROLL_DIR::UP) ? 1 : -1); - if (new_idx < 0) { - time_zone_idx_ = nr_zones - 1; - } else if (new_idx >= nr_zones) { - time_zone_idx_ = 0; + if (dir == SCROLL_DIR::UP) { + size_t new_idx = time_zone_idx_ + 1; + time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx; } else { - time_zone_idx_ = new_idx; + time_zone_idx_ = time_zone_idx_ == 0 ? nr_zones - 1 : time_zone_idx_ - 1; } auto zone_name = config_["timezones"][time_zone_idx_]; if (!zone_name.isString() || zone_name.empty()) { diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 710f23ff..98332dc0 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -100,7 +100,7 @@ void waybar::modules::MPD::setLabel() { auto format = format_; std::string artist, album_artist, album, title, date; - int song_pos, queue_length; + int song_pos = 0, queue_length = 0; std::chrono::seconds elapsedTime, totalTime; std::string stateIcon = ""; From dd596a5c6c15cd7fa7e7033b1dccf3c9868f9d1c Mon Sep 17 00:00:00 2001 From: Jeremy Attali Date: Mon, 30 Nov 2020 23:11:51 -0500 Subject: [PATCH 72/94] fix(systemd): restart when service fails The current service doesn't play too nice with Sway when it is started from [sway service](https://github.com/xdbob/sway-services). Waybar is started before the system has a display. ``` Nov 30 22:11:23 ansan waybar[1352]: Unable to init server: Could not connect: Connection refused Nov 30 22:11:23 ansan waybar[1352]: cannot open display: Nov 30 22:11:23 ansan systemd[1306]: waybar.service: Main process exited, code=exited, status=1/FAILURE Nov 30 22:11:23 ansan systemd[1306]: waybar.service: Failed with result 'exit-code'. ``` Restarting the service after the system has been initialized works nicely, so this restart rule should do the trick without tinkering with the target. --- resources/waybar.service.in | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/waybar.service.in b/resources/waybar.service.in index 2c907e90..af5832d5 100644 --- a/resources/waybar.service.in +++ b/resources/waybar.service.in @@ -6,6 +6,7 @@ After=graphical-session.target [Service] ExecStart=@prefix@/bin/waybar +Restart=on-failure [Install] WantedBy=graphical-session.target From 1fe0bcacc0f30f10c9eabf3f01782b3557c4446a Mon Sep 17 00:00:00 2001 From: Thomas Hebb Date: Sat, 29 Aug 2020 23:18:26 -0700 Subject: [PATCH 73/94] style: add 4px margins to window and workspaces modules These modules, unlike others, have no horizontal margins by default. This means that they'll appear uncomfortably close together in any config that puts them side-by-side. In general, the default style should make configs with any module ordering look good. Add the same 4px horizontal margins that other module have to these. To preserve the current default appearance, exempt the workspace module from a margin on the appropriate side when it's the leftmost or rightmost module on the bar. --- resources/style.css | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/resources/style.css b/resources/style.css index 99fbbae2..920bb527 100644 --- a/resources/style.css +++ b/resources/style.css @@ -83,6 +83,21 @@ window#waybar.chromium { color: #ffffff; } +#window, +#workspaces { + margin: 0 4px; +} + +/* If workspaces is the leftmost module, omit left margin */ +.modules-left > widget:first-child > #workspaces { + margin-left: 0; +} + +/* If workspaces is the rightmost module, omit right margin */ +.modules-right > widget:last-child > #workspaces { + margin-right: 0; +} + #clock { background-color: #64727D; } From 09c89bcd2024b29903ae2acc9fe13ae019f49e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20C=C3=B4rte-Real?= Date: Thu, 3 Dec 2020 09:52:33 +0000 Subject: [PATCH 74/94] Don't update battery list on every update Speedup battery state update by only updating the battery list when we get a CREATE/DELETE event in the directory or whenever we do a full refresh on the interval. --- include/modules/battery.hpp | 7 +++-- src/modules/battery.cpp | 53 +++++++++++++++++++++++++------------ 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/include/modules/battery.hpp b/include/modules/battery.hpp index f0e3c39d..08dd79db 100644 --- a/include/modules/battery.hpp +++ b/include/modules/battery.hpp @@ -34,16 +34,19 @@ class Battery : public ALabel { void refreshBatteries(); void worker(); const std::string getAdapterStatus(uint8_t capacity) const; - const std::tuple getInfos() const; + const std::tuple getInfos(); const std::string formatTimeRemaining(float hoursRemaining); int global_watch; std::map batteries_; fs::path adapter_; - int fd_; + int battery_watch_fd_; + int global_watch_fd_; + std::mutex battery_list_mutex_; std::string old_status_; util::SleeperThread thread_; + util::SleeperThread thread_battery_update_; util::SleeperThread thread_timer_; }; diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index c506171f..0b54e9cb 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -4,13 +4,18 @@ waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config) : ALabel(config, "battery", id, "{capacity}%", 60) { - fd_ = inotify_init1(IN_CLOEXEC); - if (fd_ == -1) { + battery_watch_fd_ = inotify_init1(IN_CLOEXEC); + if (battery_watch_fd_ == -1) { + throw std::runtime_error("Unable to listen batteries."); + } + + global_watch_fd_ = inotify_init1(IN_CLOEXEC); + if (global_watch_fd_ == -1) { throw std::runtime_error("Unable to listen batteries."); } // Watch the directory for any added or removed batteries - global_watch = inotify_add_watch(fd_, data_dir_.c_str(), IN_CREATE | IN_DELETE); + global_watch = inotify_add_watch(global_watch_fd_, data_dir_.c_str(), IN_CREATE | IN_DELETE); if (global_watch < 0) { throw std::runtime_error("Could not watch for battery plug/unplug"); } @@ -20,38 +25,55 @@ waybar::modules::Battery::Battery(const std::string& id, const Json::Value& conf } waybar::modules::Battery::~Battery() { + std::lock_guard guard(battery_list_mutex_); + if (global_watch >= 0) { - inotify_rm_watch(fd_, global_watch); + inotify_rm_watch(global_watch_fd_, global_watch); } + close(global_watch_fd_); + for (auto it = batteries_.cbegin(); it != batteries_.cend(); it++) { auto watch_id = (*it).second; if (watch_id >= 0) { - inotify_rm_watch(fd_, watch_id); + inotify_rm_watch(battery_watch_fd_, watch_id); } batteries_.erase(it); } - close(fd_); + close(battery_watch_fd_); } void waybar::modules::Battery::worker() { thread_timer_ = [this] { + // Make sure we eventually update the list of batteries even if we miss an + // inotify event for some reason + refreshBatteries(); dp.emit(); thread_timer_.sleep_for(interval_); }; thread_ = [this] { struct inotify_event event = {0}; - int nbytes = read(fd_, &event, sizeof(event)); + int nbytes = read(battery_watch_fd_, &event, sizeof(event)); if (nbytes != sizeof(event) || event.mask & IN_IGNORED) { thread_.stop(); return; } - // TODO: don't stop timer for now since there is some bugs :? - // thread_timer_.stop(); + dp.emit(); + }; + thread_battery_update_ = [this] { + struct inotify_event event = {0}; + int nbytes = read(global_watch_fd_, &event, sizeof(event)); + if (nbytes != sizeof(event) || event.mask & IN_IGNORED) { + thread_.stop(); + return; + } + refreshBatteries(); dp.emit(); }; } void waybar::modules::Battery::refreshBatteries() { + std::lock_guard guard(battery_list_mutex_); + // Mark existing list of batteries as not necessarily found std::map check_map; for (auto const& bat : batteries_) { @@ -77,7 +99,7 @@ void waybar::modules::Battery::refreshBatteries() { if (search == batteries_.end()) { // We've found a new battery save it and start listening for events auto event_path = (node.path() / "uevent"); - auto wd = inotify_add_watch(fd_, event_path.c_str(), IN_ACCESS); + auto wd = inotify_add_watch(battery_watch_fd_, event_path.c_str(), IN_ACCESS); if (wd < 0) { throw std::runtime_error("Could not watch events for " + node.path().string()); } @@ -106,14 +128,16 @@ void waybar::modules::Battery::refreshBatteries() { if (!check.second) { auto watch_id = batteries_[check.first]; if (watch_id >= 0) { - inotify_rm_watch(fd_, watch_id); + inotify_rm_watch(battery_watch_fd_, watch_id); } batteries_.erase(check.first); } } } -const std::tuple waybar::modules::Battery::getInfos() const { +const std::tuple waybar::modules::Battery::getInfos() { + std::lock_guard guard(battery_list_mutex_); + try { uint32_t total_power = 0; // μW uint32_t total_energy = 0; // μWh @@ -215,11 +239,6 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai } auto waybar::modules::Battery::update() -> void { - // Make sure we have the correct set of batteries, in case of hotplug - // TODO: split the global watch into it's own event and only run the refresh - // when there's been a CREATE/DELETE event - refreshBatteries(); - auto [capacity, time_remaining, status] = getInfos(); if (status == "Unknown") { status = getAdapterStatus(capacity); From bb60e68b9d39cb21b195cbd156d05b0987fc653f Mon Sep 17 00:00:00 2001 From: Till Smejkal Date: Thu, 3 Dec 2020 21:52:20 +0100 Subject: [PATCH 75/94] Update to the latest version of the foreign toplevel manager protocol There was an update the of the toplevel manager protocol. Unfortunately, there are no new interesting updates with regard to the taskbar implementation. Nonetheless, update the protocol xml files to the latest version so that the implementation is up-to-date. While being there, also change the debug warning that is shown when there is a version mismatch between the server and client version of the protocol. --- ...lr-foreign-toplevel-management-unstable-v1.xml | 15 +++++++++++++-- src/modules/wlr/taskbar.cpp | 13 +++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/protocol/wlr-foreign-toplevel-management-unstable-v1.xml b/protocol/wlr-foreign-toplevel-management-unstable-v1.xml index a97738f8..10813371 100644 --- a/protocol/wlr-foreign-toplevel-management-unstable-v1.xml +++ b/protocol/wlr-foreign-toplevel-management-unstable-v1.xml @@ -25,7 +25,7 @@ THIS SOFTWARE. - + The purpose of this protocol is to enable the creation of taskbars and docks by providing them with a list of opened applications and @@ -68,7 +68,7 @@ - + A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel window. Each app may have multiple opened toplevels. @@ -255,5 +255,16 @@ actually changes, this will be indicated by the state event. + + + + + + This event is emitted whenever the parent of the toplevel changes. + + No event is emitted when the parent handle is destroyed by the client. + + + diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index bdc980c4..0a42ca15 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -187,6 +187,12 @@ static void tl_handle_done(void *data, struct zwlr_foreign_toplevel_handle_v1 *h return static_cast(data)->handle_done(); } +static void tl_handle_parent(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, + struct zwlr_foreign_toplevel_handle_v1 *parent) +{ + /* This is explicitly left blank */ +} + static void tl_handle_closed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle) { return static_cast(data)->handle_closed(); @@ -200,6 +206,7 @@ static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_handle_imp .state = tl_handle_state, .done = tl_handle_done, .closed = tl_handle_closed, + .parent = tl_handle_parent, }; Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, @@ -661,9 +668,11 @@ void Taskbar::register_manager(struct wl_registry *registry, uint32_t name, uint spdlog::warn("Register foreign toplevel manager again although already existing!"); return; } - if (version < ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN_SINCE_VERSION) { - spdlog::warn("Using different foreign toplevel manager protocol version: {}", version); + if (version < ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_FULLSCREEN_SINCE_VERSION) { + spdlog::warn("Foreign toplevel manager server does not have the appropriate version." + " To be able to use all features, you need at least version 2, but server is version {}", version); } + // limit version to a highest supported by the client protocol file version = std::min(version, zwlr_foreign_toplevel_manager_v1_interface.version); From 18f129a7128ee7a3991fba316fe956c455177360 Mon Sep 17 00:00:00 2001 From: Till Smejkal Date: Fri, 4 Dec 2020 08:04:02 +0100 Subject: [PATCH 76/94] Spit out a warning when trying to set/unset fullscreen without server supporting it Previously we only checked when connecting to the server whether it had the minimum required version but didn't act accordingly in the various functions that use the functionality of later versions. If there were a server in the wild, that actually would not have this functionality, there would have been a crash. Fix this by checking the version before using the functionality and gracefully abort it. --- src/modules/wlr/taskbar.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 0a42ca15..e70ad9c3 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -546,6 +546,11 @@ void Task::activate() void Task::fullscreen(bool set) { + if (zwlr_foreign_toplevel_handle_v1_get_version(handle_) < ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_SET_FULLSCREEN_SINCE_VERSION) { + spdlog::warn("Foreign toplevel manager server does not support for set/unset fullscreen."); + return; + } + if (set) zwlr_foreign_toplevel_handle_v1_set_fullscreen(handle_, nullptr); else From 68b6136989bf102b30a05b7b1964c44bca59a57b Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Fri, 4 Dec 2020 00:38:18 -0800 Subject: [PATCH 77/94] fix(sway/workspaces): ignore emulated scroll events GDK Wayland backend can emit two events for mouse scroll: one is a GDK_SCROLL_SMOOTH and the other one is an emulated scroll event with direction. We only receive emulated events on a window, thus it is not possible to handle these in a module and stop propagation. Ignoring emulated events should be safe since those are duplicates of smooth scroll events anyways. Fixes #386 --- src/modules/sway/workspaces.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 8d78bf50..d0c24632 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -291,6 +291,12 @@ std::string Workspaces::getIcon(const std::string &name, const Json::Value &node } bool Workspaces::handleScroll(GdkEventScroll *e) { + if (gdk_event_get_pointer_emulated((GdkEvent *)e)) { + /** + * Ignore emulated scroll events on window + */ + return false; + } auto dir = AModule::getScrollDir(e); if (dir == SCROLL_DIR::NONE) { return true; From 0d03c1d4da50ef248b197c29301e7cd879f98336 Mon Sep 17 00:00:00 2001 From: Andrea Scarpino Date: Fri, 4 Dec 2020 23:44:43 +0100 Subject: [PATCH 78/94] Fix waybar-pulseaudio with pipewire-pulse --- src/modules/pulseaudio.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index 571a78e5..b5193f51 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -178,8 +178,8 @@ void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, const pa_ser pa->default_sink_name_ = i->default_sink_name; pa->default_source_name_ = i->default_source_name; - pa_context_get_sink_info_by_name(context, i->default_sink_name, sinkInfoCb, data); - pa_context_get_source_info_by_name(context, i->default_source_name, sourceInfoCb, data); + pa_context_get_sink_info_by_name(context, "@DEFAULT_SINK@", sinkInfoCb, data); + pa_context_get_source_info_by_name(context, "@DEFAULT_SOURCE@", sourceInfoCb, data); } static const std::array ports = { From 50ecc972843fd6dcc514775349a8b733e1dd2468 Mon Sep 17 00:00:00 2001 From: danielrainer <34983953+danielrainer@users.noreply.github.com> Date: Sat, 12 Dec 2020 23:21:17 +0100 Subject: [PATCH 79/94] Fix typo --- man/waybar-states.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/waybar-states.5.scd b/man/waybar-states.5.scd index fe6a7307..ae2df1b9 100644 --- a/man/waybar-states.5.scd +++ b/man/waybar-states.5.scd @@ -13,7 +13,7 @@ apply a class when the value matches the declared state value. Each class gets activated when the current capacity is equal or below the configured **. - Also each state can have its own *format*. - Those con be configured via *format-*. + Those can be configured via *format-*. Or if you want to differentiate a bit more even as *format--*. # EXAMPLE From 85ca5027f41b4c15a7c90a5ffeef3c1f5a2d2340 Mon Sep 17 00:00:00 2001 From: Harit Kapadia Date: Fri, 18 Dec 2020 18:14:14 -0500 Subject: [PATCH 80/94] Fix Sway #waybar.solo CSS rule applying on split This error occurs because of an incorrect assumption that the size of the list of nodes that contains the focused window is the number of windows in a workspace. The windows in a workspace are stored as a tree by Sway, rather than a list, so the number of windows has to be found by counting the leaves of a workspace tree. --- src/modules/sway/window.cpp | 47 +++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/modules/sway/window.cpp b/src/modules/sway/window.cpp index f10bf1ce..64f72521 100644 --- a/src/modules/sway/window.cpp +++ b/src/modules/sway/window.cpp @@ -64,29 +64,51 @@ auto Window::update() -> void { ALabel::update(); } -std::tuple Window::getFocusedNode( - const Json::Value& nodes, std::string& output) { - for (auto const& node : nodes) { +int leafNodesInWorkspace(const Json::Value& node) { + auto const& nodes = node["nodes"]; + if(nodes.empty()) { + if(node["type"] == "workspace") + return 0; + else + return 1; + } + int sum = 0; + for(auto const& node : nodes) + sum += leafNodesInWorkspace(node); + return sum; +} + +std::tuple gfnWithWorkspace( + const Json::Value& nodes, std::string& output, const Json::Value& config_, + const Bar& bar_, Json::Value& parentWorkspace) { + for(auto const& node : nodes) { if (node["output"].isString()) { output = node["output"].asString(); } + // found node if (node["focused"].asBool() && (node["type"] == "con" || node["type"] == "floating_con")) { if ((!config_["all-outputs"].asBool() && output == bar_.output->name) || config_["all-outputs"].asBool()) { auto app_id = node["app_id"].isString() ? node["app_id"].asString() - : node["window_properties"]["instance"].asString(); - return {nodes.size(), - node["id"].asInt(), - Glib::Markup::escape_text(node["name"].asString()), - app_id}; + : node["window_properties"]["instance"].asString(); + int nb = node.size(); + if(parentWorkspace != 0) + nb = leafNodesInWorkspace(parentWorkspace); + return {nb, + node["id"].asInt(), + Glib::Markup::escape_text(node["name"].asString()), + app_id}; } } - auto [nb, id, name, app_id] = getFocusedNode(node["nodes"], output); + // iterate + if(node["type"] == "workspace") + parentWorkspace = node; + auto [nb, id, name, app_id] = gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace); if (id > -1 && !name.empty()) { return {nb, id, name, app_id}; } // Search for floating node - std::tie(nb, id, name, app_id) = getFocusedNode(node["floating_nodes"], output); + std::tie(nb, id, name, app_id) = gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace); if (id > -1 && !name.empty()) { return {nb, id, name, app_id}; } @@ -94,6 +116,11 @@ std::tuple Window::getFocusedNode( return {0, -1, "", ""}; } +std::tuple Window::getFocusedNode( + const Json::Value& nodes, std::string& output) { + return gfnWithWorkspace(nodes, output, config_, bar_, placeholder); +} + void Window::getTree() { try { ipc_.sendCmd(IPC_GET_TREE); From cb7baee045d41d32bd4d90f7e0237f1ec925b528 Mon Sep 17 00:00:00 2001 From: Harit Kapadia Date: Fri, 18 Dec 2020 18:17:17 -0500 Subject: [PATCH 81/94] Fixed compile error --- src/modules/sway/window.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/sway/window.cpp b/src/modules/sway/window.cpp index 64f72521..e9203451 100644 --- a/src/modules/sway/window.cpp +++ b/src/modules/sway/window.cpp @@ -118,6 +118,7 @@ std::tuple gfnWithWorkspace( std::tuple Window::getFocusedNode( const Json::Value& nodes, std::string& output) { + Json::Value placeholder = 0; return gfnWithWorkspace(nodes, output, config_, bar_, placeholder); } From 4b29aef048b640f162cd4d0b7ad082fc20b1c0b8 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 23 Dec 2020 21:33:40 +0100 Subject: [PATCH 82/94] chore: v0.9.5 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 7f9db5f7..839c1dc4 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'waybar', 'cpp', 'c', - version: '0.9.4', + version: '0.9.5', license: 'MIT', meson_version: '>= 0.49.0', default_options : [ From e4340a7536d31332d8e455714d160d343d462932 Mon Sep 17 00:00:00 2001 From: Jan Beich Date: Sun, 29 Nov 2020 20:43:04 +0000 Subject: [PATCH 83/94] CI: add FreeBSD to Actions --- .github/workflows/freebsd.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/freebsd.yml diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml new file mode 100644 index 00000000..7072c493 --- /dev/null +++ b/.github/workflows/freebsd.yml @@ -0,0 +1,22 @@ +name: freebsd + +on: [ push, pull_request ] + +jobs: + clang: + runs-on: macos-latest # until https://github.com/actions/runner/issues/385 + steps: + - uses: actions/checkout@v2 + - name: Test in FreeBSD VM + uses: vmactions/freebsd-vm@v0.0.9 # aka FreeBSD 12.2 + with: + usesh: true + prepare: | + export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio + sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf + pkg install -y git # subprojects/date + pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu sndio \ + libfmt libmpdclient libudev-devd meson pkgconf pulseaudio scdoc spdlog + run: | + meson build -Dman-pages=enabled + ninja -C build From f3911867496d4d3f119e5a5f464aa2ff30f90750 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 25 Dec 2020 09:28:05 +0100 Subject: [PATCH 84/94] Revert "Replace lowercase "k" with uppercase "K" to make it look more consistent" --- include/util/format.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/util/format.hpp b/include/util/format.hpp index d7d16090..288d8f0c 100644 --- a/include/util/format.hpp +++ b/include/util/format.hpp @@ -42,7 +42,7 @@ namespace fmt { template auto format(const pow_format& s, FormatContext &ctx) -> decltype (ctx.out()) { - const char* units[] = { "", "K", "M", "G", "T", "P", nullptr}; + const char* units[] = { "", "k", "M", "G", "T", "P", nullptr}; auto base = s.binary_ ? 1024ull : 1000ll; auto fraction = (double) s.val_; From 005af7f7b743731229d6e7d7866a5628650bce68 Mon Sep 17 00:00:00 2001 From: Andrea Scarpino Date: Fri, 25 Dec 2020 17:37:21 +0100 Subject: [PATCH 85/94] Revert "Fix waybar-pulseaudio with pipewire-pulse" This reverts commit 0d03c1d4da50ef248b197c29301e7cd879f98336. --- src/modules/pulseaudio.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index b5193f51..571a78e5 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -178,8 +178,8 @@ void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, const pa_ser pa->default_sink_name_ = i->default_sink_name; pa->default_source_name_ = i->default_source_name; - pa_context_get_sink_info_by_name(context, "@DEFAULT_SINK@", sinkInfoCb, data); - pa_context_get_source_info_by_name(context, "@DEFAULT_SOURCE@", sourceInfoCb, data); + pa_context_get_sink_info_by_name(context, i->default_sink_name, sinkInfoCb, data); + pa_context_get_source_info_by_name(context, i->default_source_name, sourceInfoCb, data); } static const std::array ports = { From 0233e0eeec03f9eb00b98bbf04301852431c0cdd Mon Sep 17 00:00:00 2001 From: Andreas Backx Date: Fri, 25 Dec 2020 20:54:38 +0000 Subject: [PATCH 86/94] Added waybar_output.identifier support. Resolves #602. --- include/bar.hpp | 1 + include/client.hpp | 15 ++++++++------- src/client.cpp | 48 ++++++++++++++++++++++++++++------------------ 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/include/bar.hpp b/include/bar.hpp index 8aab8f7c..d6cd895f 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -17,6 +17,7 @@ class Factory; struct waybar_output { Glib::RefPtr monitor; std::string name; + std::string identifier; std::unique_ptr xdg_output = { nullptr, &zxdg_output_v1_destroy}; diff --git a/include/client.hpp b/include/client.hpp index 05215cc0..f5339420 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -6,6 +6,7 @@ #include #include #include + #include "bar.hpp" struct zwlr_layer_shell_v1; @@ -33,18 +34,18 @@ class Client { std::tuple getConfigs(const std::string &config, const std::string &style) const; void bindInterfaces(); - const std::string getValidPath(const std::vector &paths) const; - void handleOutput(struct waybar_output &output); - bool isValidOutput(const Json::Value &config, struct waybar_output &output); - auto setupConfig(const std::string &config_file) -> void; - auto setupCss(const std::string &css_file) -> void; - struct waybar_output &getOutput(void *); + const std::string getValidPath(const std::vector &paths) const; + void handleOutput(struct waybar_output &output); + bool isValidOutput(const Json::Value &config, struct waybar_output &output); + auto setupConfig(const std::string &config_file) -> void; + auto setupCss(const std::string &css_file) -> void; + struct waybar_output & getOutput(void *); std::vector getOutputConfigs(struct waybar_output &output); static void handleGlobal(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version); static void handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name); - static void handleOutputName(void *, struct zxdg_output_v1 *, const char *); + static void handleOutputDescription(void *, struct zxdg_output_v1 *, const char *); void handleMonitorAdded(Glib::RefPtr monitor); void handleMonitorRemoved(Glib::RefPtr monitor); diff --git a/src/client.cpp b/src/client.cpp index 005761ee..627904c5 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -1,12 +1,14 @@ #include "client.hpp" + #include #include + #include #include -#include "util/clara.hpp" -#include "util/json.hpp" #include "idle-inhibit-unstable-v1-client-protocol.h" +#include "util/clara.hpp" +#include "util/json.hpp" #include "wlr-layer-shell-unstable-v1-client-protocol.h" waybar::Client *waybar::Client::inst() { @@ -59,8 +61,8 @@ void waybar::Client::handleOutput(struct waybar_output &output) { .logical_position = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {}, .logical_size = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {}, .done = [](void *, struct zxdg_output_v1 *) {}, - .name = &handleOutputName, - .description = [](void *, struct zxdg_output_v1 *, const char *) {}, + .name = [](void *, struct zxdg_output_v1 *, const char *) {}, + .description = &handleOutputDescription, }; // owned by output->monitor; no need to destroy auto wl_output = gdk_wayland_monitor_get_wl_output(output.monitor->gobj()); @@ -71,18 +73,21 @@ void waybar::Client::handleOutput(struct waybar_output &output) { bool waybar::Client::isValidOutput(const Json::Value &config, struct waybar_output &output) { if (config["output"].isArray()) { for (auto const &output_conf : config["output"]) { - if (output_conf.isString() && output_conf.asString() == output.name) { + if (output_conf.isString() && + (output_conf.asString() == output.name || output_conf.asString() == output.identifier)) { + std::cout << output_conf.asString() << std::endl; return true; } } return false; } else if (config["output"].isString()) { - auto config_output_name = config["output"].asString(); - if (!config_output_name.empty()) { - if (config_output_name.substr(0, 1) == "!") { - return config_output_name.substr(1) != output.name; + auto config_output = config["output"].asString(); + if (!config_output.empty()) { + if (config_output.substr(0, 1) == "!") { + return config_output.substr(1) != output.name || + config_output.substr(1) != output.identifier; } - return config_output_name == output.name; + return config_output == output.name || config_output == output.identifier; } } @@ -112,16 +117,20 @@ std::vector waybar::Client::getOutputConfigs(struct waybar_output & return configs; } -void waybar::Client::handleOutputName(void * data, struct zxdg_output_v1 * /*xdg_output*/, - const char *name) { +void waybar::Client::handleOutputDescription(void *data, struct zxdg_output_v1 * /*xdg_output*/, + const char *description) { auto client = waybar::Client::inst(); try { - auto &output = client->getOutput(data); - output.name = name; - spdlog::debug("Output detected: {} ({} {})", - name, - output.monitor->get_manufacturer(), - output.monitor->get_model()); + auto & output = client->getOutput(data); + const char *open_paren = strrchr(description, '('); + const char *close_paren = strrchr(description, ')'); + + // Description format: "identifier (name)" + size_t identifier_length = open_paren - description; + output.identifier = std::string(description, identifier_length - 1); + output.name = std::string(description + identifier_length + 1, close_paren - open_paren - 1); + + spdlog::debug("Output detected: {}", description); auto configs = client->getOutputConfigs(output); if (configs.empty()) { output.xdg_output.reset(); @@ -260,7 +269,8 @@ int waybar::Client::main(int argc, char *argv[]) { if (!log_level.empty()) { spdlog::set_level(spdlog::level::from_str(log_level)); } - gtk_app = Gtk::Application::create(argc, argv, "fr.arouillard.waybar", Gio::APPLICATION_HANDLES_COMMAND_LINE); + gtk_app = Gtk::Application::create( + argc, argv, "fr.arouillard.waybar", Gio::APPLICATION_HANDLES_COMMAND_LINE); gdk_display = Gdk::Display::get_default(); if (!gdk_display) { throw std::runtime_error("Can't find display"); From e5684c6127d2fb063a7171a35cd52e03d5187ab3 Mon Sep 17 00:00:00 2001 From: Andreas Backx Date: Fri, 25 Dec 2020 23:03:01 +0000 Subject: [PATCH 87/94] Separated name and description setup and moved bar creation to done callback. --- include/client.hpp | 2 ++ src/client.cpp | 51 ++++++++++++++++++++++++++++++++-------------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/include/client.hpp b/include/client.hpp index f5339420..f2eafb15 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -45,6 +45,8 @@ class Client { static void handleGlobal(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version); static void handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name); + static void handleOutputDone(void *, struct zxdg_output_v1 *); + static void handleOutputName(void *, struct zxdg_output_v1 *, const char *); static void handleOutputDescription(void *, struct zxdg_output_v1 *, const char *); void handleMonitorAdded(Glib::RefPtr monitor); void handleMonitorRemoved(Glib::RefPtr monitor); diff --git a/src/client.cpp b/src/client.cpp index 627904c5..4240f096 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -60,8 +60,8 @@ void waybar::Client::handleOutput(struct waybar_output &output) { static const struct zxdg_output_v1_listener xdgOutputListener = { .logical_position = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {}, .logical_size = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {}, - .done = [](void *, struct zxdg_output_v1 *) {}, - .name = [](void *, struct zxdg_output_v1 *, const char *) {}, + .done = &handleOutputDone, + .name = &handleOutputName, .description = &handleOutputDescription, }; // owned by output->monitor; no need to destroy @@ -75,7 +75,6 @@ bool waybar::Client::isValidOutput(const Json::Value &config, struct waybar_outp for (auto const &output_conf : config["output"]) { if (output_conf.isString() && (output_conf.asString() == output.name || output_conf.asString() == output.identifier)) { - std::cout << output_conf.asString() << std::endl; return true; } } @@ -84,7 +83,7 @@ bool waybar::Client::isValidOutput(const Json::Value &config, struct waybar_outp auto config_output = config["output"].asString(); if (!config_output.empty()) { if (config_output.substr(0, 1) == "!") { - return config_output.substr(1) != output.name || + return config_output.substr(1) != output.name && config_output.substr(1) != output.identifier; } return config_output == output.name || config_output == output.identifier; @@ -117,20 +116,12 @@ std::vector waybar::Client::getOutputConfigs(struct waybar_output & return configs; } -void waybar::Client::handleOutputDescription(void *data, struct zxdg_output_v1 * /*xdg_output*/, - const char *description) { +void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_output*/) { auto client = waybar::Client::inst(); try { - auto & output = client->getOutput(data); - const char *open_paren = strrchr(description, '('); - const char *close_paren = strrchr(description, ')'); + auto &output = client->getOutput(data); + spdlog::debug("Output detection done: {} ({})", output.name, output.identifier); - // Description format: "identifier (name)" - size_t identifier_length = open_paren - description; - output.identifier = std::string(description, identifier_length - 1); - output.name = std::string(description + identifier_length + 1, close_paren - open_paren - 1); - - spdlog::debug("Output detected: {}", description); auto configs = client->getOutputConfigs(output); if (configs.empty()) { output.xdg_output.reset(); @@ -148,6 +139,36 @@ void waybar::Client::handleOutputDescription(void *data, struct zxdg_output_v1 * } } +void waybar::Client::handleOutputName(void * data, struct zxdg_output_v1 * /*xdg_output*/, + const char *name) { + auto client = waybar::Client::inst(); + try { + auto &output = client->getOutput(data); + spdlog::debug("Output detected with name: {} ({} {})", + name, + output.monitor->get_manufacturer(), + output.monitor->get_model()); + output.name = name; + } catch (const std::exception &e) { + std::cerr << e.what() << std::endl; + } +} + +void waybar::Client::handleOutputDescription(void *data, struct zxdg_output_v1 * /*xdg_output*/, + const char *description) { + auto client = waybar::Client::inst(); + try { + auto & output = client->getOutput(data); + const char *open_paren = strrchr(description, '('); + + // Description format: "identifier (name)" + size_t identifier_length = open_paren - description; + output.identifier = std::string(description, identifier_length - 1); + } catch (const std::exception &e) { + std::cerr << e.what() << std::endl; + } +} + void waybar::Client::handleMonitorAdded(Glib::RefPtr monitor) { auto &output = outputs_.emplace_back(); output.monitor = monitor; From 3fbbbf85416cb6755cd0b6fb81f84e8938b58658 Mon Sep 17 00:00:00 2001 From: Andreas Backx Date: Fri, 25 Dec 2020 23:31:29 +0000 Subject: [PATCH 88/94] Removed redundant log line. --- src/client.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index 4240f096..ad281d59 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -144,10 +144,6 @@ void waybar::Client::handleOutputName(void * data, struct zxdg_output_v1 * auto client = waybar::Client::inst(); try { auto &output = client->getOutput(data); - spdlog::debug("Output detected with name: {} ({} {})", - name, - output.monitor->get_manufacturer(), - output.monitor->get_model()); output.name = name; } catch (const std::exception &e) { std::cerr << e.what() << std::endl; From c0361e8546213d8d4653859329a7d02da4f56191 Mon Sep 17 00:00:00 2001 From: dorgnarg Date: Mon, 28 Dec 2020 13:34:59 -0700 Subject: [PATCH 89/94] A hopeful fix to the module section classes when the bar is vertical --- src/bar.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bar.cpp b/src/bar.cpp index 7bd61727..10cf0fc8 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -387,6 +387,9 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) center_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); right_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); box_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); + left_.get_style_context()->add_class("modules-left"); + center_.get_style_context()->add_class("modules-center"); + right_.get_style_context()->add_class("modules-right"); vertical = true; } From 42e86677739a62abaeb078cfdaed773e6ce03eea Mon Sep 17 00:00:00 2001 From: dorgnarg Date: Mon, 28 Dec 2020 13:44:16 -0700 Subject: [PATCH 90/94] Better way of doing it by just moving the original delcarations after everything's for sure been set --- src/bar.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/bar.cpp b/src/bar.cpp index 10cf0fc8..1dbd69a0 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -370,9 +370,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) window.get_style_context()->add_class(output->name); window.get_style_context()->add_class(config["name"].asString()); window.get_style_context()->add_class(config["position"].asString()); - left_.get_style_context()->add_class("modules-left"); - center_.get_style_context()->add_class("modules-center"); - right_.get_style_context()->add_class("modules-right"); if (config["layer"] == "top") { layer_ = bar_layer::TOP; @@ -387,11 +384,12 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) center_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); right_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); box_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); - left_.get_style_context()->add_class("modules-left"); - center_.get_style_context()->add_class("modules-center"); - right_.get_style_context()->add_class("modules-right"); vertical = true; } + + left_.get_style_context()->add_class("modules-left"); + center_.get_style_context()->add_class("modules-center"); + right_.get_style_context()->add_class("modules-right"); uint32_t height = config["height"].isUInt() ? config["height"].asUInt() : 0; uint32_t width = config["width"].isUInt() ? config["width"].asUInt() : 0; From f20dbbbd74316917b0541c7b64aa29f3d58d66ee Mon Sep 17 00:00:00 2001 From: Johannes Christenson Date: Sun, 3 Jan 2021 19:08:06 +0100 Subject: [PATCH 91/94] Fixing logic in getIcon --- src/ALabel.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ALabel.cpp b/src/ALabel.cpp index 3a4063d9..9371a0e7 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -60,14 +60,14 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_ std::string ALabel::getIcon(uint16_t percentage, std::vector& alts, uint16_t max) { auto format_icons = config_["format-icons"]; if (format_icons.isObject()) { + std::string _alt = "default"; for (const auto& alt : alts) { if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) { - format_icons = format_icons[alt]; + _alt = alt; break; - } else { - format_icons = format_icons["default"]; } } + format_icons = format_icons[_alt]; } if (format_icons.isArray()) { auto size = format_icons.size(); From ef9c3ef1cbb57afa2f64a70282fffe33e5519dfe Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Wed, 6 Jan 2021 07:03:14 -0800 Subject: [PATCH 92/94] fix(wlr/taskbar): fix wl_array out-of-bounds access wl_array->size contains the number of bytes in the array instead of the number of elements. --- src/modules/wlr/taskbar.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index e70ad9c3..46147bd0 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -367,16 +367,16 @@ void Task::handle_output_leave(struct wl_output *output) void Task::handle_state(struct wl_array *state) { state_ = 0; - for (auto* entry = static_cast(state->data); - entry < static_cast(state->data) + state->size; - entry++) { - if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED) + size_t size = state->size / sizeof(uint32_t); + for (size_t i = 0; i < size; ++i) { + auto entry = static_cast(state->data)[i]; + if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED) state_ |= MAXIMIZED; - if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED) + if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED) state_ |= MINIMIZED; - if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) + if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) state_ |= ACTIVE; - if (*entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN) + if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN) state_ |= FULLSCREEN; } } From b79301a5bdafb2d0a7764990530898996a97c386 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Fri, 8 Jan 2021 15:07:04 -0800 Subject: [PATCH 93/94] fix(wlr/taskbar): protocol error when reconnecting outputs Destroy request is not specified for foreign toplevel manager and it does not prevent the compositor from sending more events. Libwayland would ignore events to a destroyed objects, but that could indirectly cause a gap in the sequence of new object ids and trigger error condition in the library. With this commit waybar sends a `stop` request to notify the compositor about the destruction of a toplevel manager. That fixes abnormal termination of the bar with following errors: ``` (waybar:11791): Gdk-DEBUG: 20:04:19.778: not a valid new object id (4278190088), message toplevel(n) Gdk-Message: 20:04:19.778: Error reading events from display: Invalid argument ``` --- src/modules/wlr/taskbar.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 46147bd0..4cbb8ce6 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -637,8 +637,20 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu Taskbar::~Taskbar() { if (manager_) { - zwlr_foreign_toplevel_manager_v1_destroy(manager_); - manager_ = nullptr; + struct wl_display *display = Client::inst()->wl_display; + /* + * Send `stop` request and wait for one roundtrip. + * This is not quite correct as the protocol encourages us to wait for the .finished event, + * but it should work with wlroots foreign toplevel manager implementation. + */ + zwlr_foreign_toplevel_manager_v1_stop(manager_); + wl_display_roundtrip(display); + + if (manager_) { + spdlog::warn("Foreign toplevel manager destroyed before .finished event"); + zwlr_foreign_toplevel_manager_v1_destroy(manager_); + manager_ = nullptr; + } } } From 9d5ce45f3b7571d10d19ee122f9739c6b3d04e40 Mon Sep 17 00:00:00 2001 From: sjtio Date: Fri, 15 Jan 2021 01:07:56 +0000 Subject: [PATCH 94/94] add option tag-labels to river/tags --- man/waybar-river-tags.5.scd | 4 ++++ src/modules/river/tags.cpp | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/man/waybar-river-tags.5.scd b/man/waybar-river-tags.5.scd index a02ddeb3..0f027249 100644 --- a/man/waybar-river-tags.5.scd +++ b/man/waybar-river-tags.5.scd @@ -17,6 +17,10 @@ Addressed by *river/tags* default: 9 ++ The number of tags that should be displayed. +*tag-labels*: ++ + typeof: array ++ + The label to display for each tag. + # EXAMPLE ``` diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index 804ea090..e96b2016 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include "client.hpp" #include "modules/river/tags.hpp" #include "river-status-unstable-v1-client-protocol.h" @@ -64,8 +66,20 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con // Default to 9 tags const uint32_t num_tags = config["num-tags"].isUInt() ? config_["num-tags"].asUInt() : 9; - for (uint32_t tag = 1; tag <= num_tags; ++tag) { - Gtk::Button &button = buttons_.emplace_back(std::to_string(tag)); + + std::vector tag_labels(num_tags); + for (uint32_t tag = 0; tag < num_tags; ++tag) { + tag_labels[tag] = std::to_string(tag+1); + } + const Json::Value custom_labels = config["tag-labels"]; + if (custom_labels.isArray() && !custom_labels.empty()) { + for (uint32_t tag = 0; tag < std::min(num_tags, custom_labels.size()); ++tag) { + tag_labels[tag] = custom_labels[tag].asString(); + } + } + + for (const auto &tag_label : tag_labels) { + Gtk::Button &button = buttons_.emplace_back(tag_label); button.set_relief(Gtk::RELIEF_NONE); box_.pack_start(button, false, false, 0); button.show();