From 649a98a67952cb6f63547a504bff83d40daae220 Mon Sep 17 00:00:00 2001 From: Emiliano Deustua Date: Sat, 28 Jun 2025 16:33:14 -0500 Subject: [PATCH 01/45] feat: Add ISO 8601 calendar to clock module --- include/modules/clock.hpp | 25 +++++++++++++------------ man/waybar-clock.5.scd | 6 ++++++ src/modules/clock.cpp | 35 +++++++++++++++++++++++++---------- 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index e34b7a8e..741996aa 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -38,18 +38,19 @@ class Clock final : public ALabel { 5 - tooltip-format */ std::map fmtMap_; - uint cldMonCols_{3}; // calendar count month columns - int cldWnLen_{3}; // calendar week number length - const int cldMonColLen_{20}; // calendar month column length - WS cldWPos_{WS::HIDDEN}; // calendar week side to print - date::months cldCurrShift_{0}; // calendar months shift - int cldShift_{1}; // calendar months shift factor - date::year_month_day cldYearShift_; // calendar Year mode. Cached ymd - std::string cldYearCached_; // calendar Year mode. Cached calendar - date::year_month cldMonShift_; // calendar Month mode. Cached ym - std::string cldMonCached_; // calendar Month mode. Cached calendar - date::day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight) - std::string cldText_{""}; // calendar text to print + uint cldMonCols_{3}; // calendar count month columns + int cldWnLen_{3}; // calendar week number length + const int cldMonColLen_{20}; // calendar month column length + WS cldWPos_{WS::HIDDEN}; // calendar week side to print + date::months cldCurrShift_{0}; // calendar months shift + int cldShift_{1}; // calendar months shift factor + date::year_month_day cldYearShift_; // calendar Year mode. Cached ymd + std::string cldYearCached_; // calendar Year mode. Cached calendar + date::year_month cldMonShift_; // calendar Month mode. Cached ym + std::string cldMonCached_; // calendar Month mode. Cached calendar + date::day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight) + std::string cldText_{""}; // calendar text to print + bool iso8601Calendar_{false}; // whether the calendar is in ISO8601 CldMode cldMode_{CldMode::MONTH}; auto get_calendar(const date::year_month_day& today, const date::year_month_day& ymd, const date::time_zone* tz) -> const std::string; diff --git a/man/waybar-clock.5.scd b/man/waybar-clock.5.scd index 50a5fc07..0006171e 100644 --- a/man/waybar-clock.5.scd +++ b/man/waybar-clock.5.scd @@ -126,6 +126,12 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe :[ 1 :[ Value to scroll months/years forward/backward. Can be negative. Is configured under *on-scroll* option +|[ *iso8601* +:[ bool +:[ false +:[ When enabled, the calendar follows the ISO 8601 standard: weeks begin on + Monday, and the first week of the year is numbered 1. The default week format is + '{:%V}'. 3. Addressed by *clock: calendar: format* [- *Option* diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index a7d57437..84e4a63c 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -73,6 +73,11 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) "using instead", cfgMode); } + + if (config_[kCldPlaceholder]["iso"].isBool()) { + iso8601Calendar_ = config_[kCldPlaceholder]["iso"].asBool(); + } + if (config_[kCldPlaceholder]["weeks-pos"].isString()) { if (config_[kCldPlaceholder]["weeks-pos"].asString() == "left") cldWPos_ = WS::LEFT; if (config_[kCldPlaceholder]["weeks-pos"].asString() == "right") cldWPos_ = WS::RIGHT; @@ -99,16 +104,20 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) } else fmtMap_.insert({3, "{}"}); if (config_[kCldPlaceholder]["format"]["weeks"].isString() && cldWPos_ != WS::HIDDEN) { + const auto defaultFmt = + iso8601Calendar_ ? "{:%V}" : ((first_day_of_week() == Monday) ? "{:%W}" : "{:%U}"); fmtMap_.insert({4, std::regex_replace(config_[kCldPlaceholder]["format"]["weeks"].asString(), - std::regex("\\{\\}"), - (first_day_of_week() == Monday) ? "{:%W}" : "{:%U}")}); + std::regex("\\{\\}"), defaultFmt)}); Glib::ustring tmp{std::regex_replace(fmtMap_[4], std::regex("]+>|\\{.*\\}"), "")}; cldWnLen_ += tmp.size(); } else { - if (cldWPos_ != WS::HIDDEN) - fmtMap_.insert({4, (first_day_of_week() == Monday) ? "{:%W}" : "{:%U}"}); - else + if (cldWPos_ != WS::HIDDEN) { + const auto defaultFmt = + iso8601Calendar_ ? "{:%V}" : ((first_day_of_week() == Monday) ? "{:%W}" : "{:%U}"); + fmtMap_.insert({4, defaultFmt}); + } else { cldWnLen_ = 0; + } } if (config_[kCldPlaceholder]["mode-mon-col"].isInt()) { cldMonCols_ = config_[kCldPlaceholder]["mode-mon-col"].asInt(); @@ -204,9 +213,11 @@ const unsigned cldRowsInMonth(const year_month& ym, const weekday& firstdow) { auto cldGetWeekForLine(const year_month& ym, const weekday& firstdow, const unsigned line) -> const year_month_weekday { - unsigned index{line - 2}; - if (weekday{ym / 1} == firstdow) ++index; - return ym / firstdow[index]; + const unsigned idx = line - 2; + const std::chrono::weekday_indexed indexed_first_day_of_week = + weekday{ym / 1} == firstdow ? firstdow[idx + 1] : firstdow[idx]; + + return ym / indexed_first_day_of_week; } auto getCalendarLine(const year_month_day& currDate, const year_month ym, const unsigned line, @@ -265,7 +276,7 @@ auto getCalendarLine(const year_month_day& currDate, const year_month ym, const } // Print non-first week default: { - auto ymdTmp{cldGetWeekForLine(ym, firstdow, line)}; + const auto ymdTmp{cldGetWeekForLine(ym, firstdow, line)}; if (ymdTmp.ok()) { auto d{year_month_day{ymdTmp}.day()}; const auto dlast{(ym / last).day()}; @@ -356,8 +367,9 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea : static_cast(zoned_seconds{ tz, local_days{cldGetWeekForLine(ymTmp, firstdow, line)}}))) << ' '; - } else + } else { os << pads; + } } } @@ -481,6 +493,9 @@ using deleting_unique_ptr = std::unique_ptr>; // Computations done similarly to Linux cal utility. auto waybar::modules::Clock::first_day_of_week() -> weekday { + if (iso8601Calendar_) { + return Monday; + } #ifdef HAVE_LANGINFO_1STDAY deleting_unique_ptr::type, freelocale> posix_locale{ newlocale(LC_ALL, m_locale_.name().c_str(), nullptr)}; From 08a39dd9eb7661b5e2f2cea5970c483821dced1f Mon Sep 17 00:00:00 2001 From: Emiliano Deustua Date: Mon, 30 Jun 2025 10:44:46 -0500 Subject: [PATCH 02/45] refactor: Change config key name --- src/modules/clock.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 84e4a63c..fdccef54 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -74,8 +74,8 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) cfgMode); } - if (config_[kCldPlaceholder]["iso"].isBool()) { - iso8601Calendar_ = config_[kCldPlaceholder]["iso"].asBool(); + if (config_[kCldPlaceholder]["iso8601"].isBool()) { + iso8601Calendar_ = config_[kCldPlaceholder]["iso8601"].asBool(); } if (config_[kCldPlaceholder]["weeks-pos"].isString()) { From be819be8bd622db9ba6b76e19852c74b3479d131 Mon Sep 17 00:00:00 2001 From: Emiliano Deustua Date: Mon, 7 Jul 2025 22:10:09 -0500 Subject: [PATCH 03/45] fix: Revert formatting to clang --- include/modules/clock.hpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index 741996aa..72e03638 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -38,19 +38,19 @@ class Clock final : public ALabel { 5 - tooltip-format */ std::map fmtMap_; - uint cldMonCols_{3}; // calendar count month columns - int cldWnLen_{3}; // calendar week number length - const int cldMonColLen_{20}; // calendar month column length - WS cldWPos_{WS::HIDDEN}; // calendar week side to print - date::months cldCurrShift_{0}; // calendar months shift - int cldShift_{1}; // calendar months shift factor - date::year_month_day cldYearShift_; // calendar Year mode. Cached ymd - std::string cldYearCached_; // calendar Year mode. Cached calendar - date::year_month cldMonShift_; // calendar Month mode. Cached ym - std::string cldMonCached_; // calendar Month mode. Cached calendar - date::day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight) - std::string cldText_{""}; // calendar text to print - bool iso8601Calendar_{false}; // whether the calendar is in ISO8601 + uint cldMonCols_{3}; // calendar count month columns + int cldWnLen_{3}; // calendar week number length + const int cldMonColLen_{20}; // calendar month column length + WS cldWPos_{WS::HIDDEN}; // calendar week side to print + date::months cldCurrShift_{0}; // calendar months shift + int cldShift_{1}; // calendar months shift factor + date::year_month_day cldYearShift_; // calendar Year mode. Cached ymd + std::string cldYearCached_; // calendar Year mode. Cached calendar + date::year_month cldMonShift_; // calendar Month mode. Cached ym + std::string cldMonCached_; // calendar Month mode. Cached calendar + date::day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight) + std::string cldText_{""}; // calendar text to print + bool iso8601Calendar_{false}; // whether the calendar is in ISO8601 CldMode cldMode_{CldMode::MONTH}; auto get_calendar(const date::year_month_day& today, const date::year_month_day& ymd, const date::time_zone* tz) -> const std::string; From 64b64d03160a4552fcc4555802924c154d744302 Mon Sep 17 00:00:00 2001 From: Skylar Abruzese Date: Sat, 9 Aug 2025 18:33:35 -0400 Subject: [PATCH 04/45] Moved workspace id failing to parse from an error to part of the trace. With named persistent workspaces it is expected behavior that they have no id since their workspace may not have been created by hyprland yet. --- src/modules/hyprland/workspaces.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 5d2903dc..32f88c02 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -1129,9 +1129,9 @@ std::optional Workspaces::parseWorkspaceId(std::string const &workspaceIdSt try { return workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr); } catch (std::exception const &e) { - spdlog::error("Failed to parse workspace ID: {}", e.what()); + spdlog::debug("Workspace \"{}\" is not bound to an id: {}", workspaceIdStr, e.what()); return std::nullopt; } } -} // namespace waybar::modules::hyprland \ No newline at end of file +} // namespace waybar::modules::hyprland From 83f16a2092a899c35c696029b2e1bddbdffe3361 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Tue, 12 Aug 2025 16:43:31 +0200 Subject: [PATCH 05/45] Document newer config options of workspace-taskbar Adds some configs that were only documented in the GitHub wiki to the manpage. --- man/waybar-hyprland-workspaces.5.scd | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index 430a5134..e280ac25 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -50,6 +50,11 @@ This setting is ignored if *workspace-taskbar.enable* is set to true. default: false ++ Enables the workspace taskbar mode. + *update-active-window*: ++ + typeof: bool ++ + default: false ++ + If true, the active/focused window will have an 'active' class. Could cause higher CPU usage due to more frequent redraws. + *format*: ++ typeof: string ++ default: {icon} ++ @@ -70,6 +75,19 @@ This setting is ignored if *workspace-taskbar.enable* is set to true. default: horizontal ++ Direction in which the workspace taskbar is displayed. + *ignore-list*: ++ + typeof: array ++ + default: [] ++ + Regex patterns to match against window class or window title. If a window's class OR title matches any of the patterns, it will not be shown. + + *on-click-window*: ++ + typeof: string ++ + default: "" ++ + Command to run when a window is clicked. Available placeholders are: ++ + - {address} Hyprland address of the clicked window. ++ + - {button} Pressed button number, see https://api.gtkd.org/gdk.c.types.GdkEventButton.button.html. ++ + See https://github.com/Alexays/Waybar/wiki/Module:-Hyprland#workspace-taskbars-example for a full example. + *show-special*: ++ typeof: bool ++ default: false ++ @@ -216,4 +234,6 @@ Additional to workspace name matching, the following *format-icons* can be set. - *#workspaces button.special* - *#workspaces button.urgent* - *#workspaces button.hosting-monitor* (gets applied if workspace-monitor == waybar-monitor) -- *#workspaces .taskbar-window* (each window in the taskbar) +- *#workspaces .workspace-label* +- *#workspaces .taskbar-window* (each window in the taskbar, only if 'workspace-taskbar.enable' is true) +- *#workspaces .taskbar-window.active* (applied to the focused window, only if 'workspace-taskbar.update-active-window' is true) From be48f6bff202a5589a3f169c91b1c394cfff12d3 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Tue, 12 Aug 2025 13:25:50 -0500 Subject: [PATCH 06/45] fix(flake): fix formatter configuration Signed-off-by: Austin Horstman --- flake.nix | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/flake.nix b/flake.nix index 7c7a2281..1dd3e8d3 100644 --- a/flake.nix +++ b/flake.nix @@ -75,28 +75,26 @@ formatter = genSystems ( pkgs: pkgs.treefmt.withConfig { - settings = [ - { - formatter = { - clang-format = { - options = [ "-i" ]; - command = lib.getExe' pkgs.clang-tools "clang-format"; - excludes = [ ]; - includes = [ - "*.c" - "*.cpp" - "*.h" - "*.hpp" - ]; - }; - nixfmt = { - command = lib.getExe pkgs.nixfmt-rfc-style; - includes = [ "*.nix" ]; - }; + settings = { + formatter = { + clang-format = { + options = [ "-i" ]; + command = lib.getExe' pkgs.clang-tools "clang-format"; + excludes = [ ]; + includes = [ + "*.c" + "*.cpp" + "*.h" + "*.hpp" + ]; }; - tree-root-file = ".git/index"; - } - ]; + nixfmt = { + command = lib.getExe pkgs.nixfmt-rfc-style; + includes = [ "*.nix" ]; + }; + }; + tree-root-file = ".git/index"; + }; } ); From a0c21318f90d79ada4efdd7505c929f49c74288c Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Tue, 12 Aug 2025 13:26:02 -0500 Subject: [PATCH 07/45] chore(format): run treefmt Signed-off-by: Austin Horstman --- nix/default.nix | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/nix/default.nix b/nix/default.nix index 2c97c20d..a96d0b3f 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -30,13 +30,12 @@ in # nixpkgs checks version, no need when building locally nativeInstallCheckInputs = [ ]; - buildInputs = (builtins.filter (p: - p.pname != "wireplumber" && - p.pname != "gps" - ) oldAttrs.buildInputs) ++ [ - pkgs.wireplumber - pkgs.gpsd - ]; + buildInputs = + (builtins.filter (p: p.pname != "wireplumber" && p.pname != "gps") oldAttrs.buildInputs) + ++ [ + pkgs.wireplumber + pkgs.gpsd + ]; postUnpack = '' pushd "$sourceRoot" From d09a4072e52d4823eac29aca2be890d26f4a6314 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Tue, 12 Aug 2025 13:28:07 -0500 Subject: [PATCH 08/45] chore(flake): nixfmt-rfc-style -> nixfmt Marked stable and uses new name after replacing classic. Signed-off-by: Austin Horstman --- flake.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 1dd3e8d3..b2f96731 100644 --- a/flake.nix +++ b/flake.nix @@ -65,7 +65,7 @@ nativeBuildInputs = pkgs.waybar.nativeBuildInputs ++ (with pkgs; [ - nixfmt-rfc-style + nixfmt clang-tools gdb ]); @@ -89,7 +89,7 @@ ]; }; nixfmt = { - command = lib.getExe pkgs.nixfmt-rfc-style; + command = lib.getExe pkgs.nixfmt; includes = [ "*.nix" ]; }; }; From 089b9a20fc3ce21bf306a8686f18f9f7904d4aff Mon Sep 17 00:00:00 2001 From: rellimn <66418892+rellimn@users.noreply.github.com> Date: Tue, 12 Aug 2025 20:51:35 +0200 Subject: [PATCH 09/45] Fix module and tray item staying in hover state after opening menu Fixes #3980 --- src/AModule.cpp | 4 ++++ src/modules/sni/item.cpp | 3 +++ 2 files changed, 7 insertions(+) diff --git a/src/AModule.cpp b/src/AModule.cpp index 259c6a39..c6fdff3e 100644 --- a/src/AModule.cpp +++ b/src/AModule.cpp @@ -172,6 +172,10 @@ bool AModule::handleUserEvent(GdkEventButton* const& e) { // Popup the menu gtk_widget_show_all(GTK_WIDGET(menu_)); gtk_menu_popup_at_pointer(GTK_MENU(menu_), reinterpret_cast(e)); + // Manually reset prelight to make sure the module doesn't stay in a hover state + if (auto* module = event_box_.get_child(); module != nullptr) { + module->unset_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT); + } } } // Second call user scripts diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 4e80eba7..83be42a3 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -443,6 +443,9 @@ void Item::makeMenu() { gtk_menu->attach_to_widget(event_box); } } + // Manually reset prelight to make sure the tray item doesn't stay in a hover state even though + // the menu is focused + event_box.unset_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT); } bool Item::handleClick(GdkEventButton* const& ev) { From 5ac28f39475a9e0bcd64042fb0b38fdad754f72c Mon Sep 17 00:00:00 2001 From: Arkoniak Date: Tue, 12 Aug 2025 22:23:18 +0300 Subject: [PATCH 10/45] bugfix: expanding wildcards in config includes (#4354) --- include/config.hpp | 3 ++- src/config.cpp | 34 +++++++++++++++++++++++++--------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/include/config.hpp b/include/config.hpp index 3490c3f1..bf653c2a 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -35,7 +35,8 @@ class Config { void setupConfig(Json::Value &dst, const std::string &config_file, int depth); void resolveConfigIncludes(Json::Value &config, int depth); void mergeConfig(Json::Value &a_config_, Json::Value &b_config_); - static std::optional findIncludePath(const std::string &name); + static std::vector findIncludePath( + const std::string &name, const std::vector &dirs = CONFIG_DIRS); std::string config_file_; diff --git a/src/config.cpp b/src/config.cpp index 2618e679..145056dd 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -106,12 +106,24 @@ void Config::setupConfig(Json::Value &dst, const std::string &config_file, int d mergeConfig(dst, tmp_config); } -std::optional Config::findIncludePath(const std::string &name) { +std::vector Config::findIncludePath(const std::string &name, + const std::vector &dirs) { auto match1 = tryExpandPath(name, ""); if (!match1.empty()) { - return match1.front(); + return match1; } - return findConfigPath({name}); + if (const char *dir = std::getenv(Config::CONFIG_PATH_ENV)) { + if (auto res = tryExpandPath(dir, name); !res.empty()) { + return res; + } + } + for (const auto &dir : dirs) { + if (auto res = tryExpandPath(dir, name); !res.empty()) { + return res; + } + } + + return {}; } void Config::resolveConfigIncludes(Json::Value &config, int depth) { @@ -119,18 +131,22 @@ void Config::resolveConfigIncludes(Json::Value &config, int depth) { if (includes.isArray()) { for (const auto &include : includes) { spdlog::info("Including resource file: {}", include.asString()); - auto match = findIncludePath(include.asString()); - if (match.has_value()) { - setupConfig(config, match.value(), depth + 1); + auto matches = findIncludePath(include.asString()); + if (!matches.empty()) { + for (const auto &match : matches) { + setupConfig(config, match, depth + 1); + } } else { spdlog::warn("Unable to find resource file: {}", include.asString()); } } } else if (includes.isString()) { spdlog::info("Including resource file: {}", includes.asString()); - auto match = findIncludePath(includes.asString()); - if (match.has_value()) { - setupConfig(config, match.value(), depth + 1); + auto matches = findIncludePath(includes.asString()); + if (!matches.empty()) { + for (const auto &match : matches) { + setupConfig(config, match, depth + 1); + } } else { spdlog::warn("Unable to find resource file: {}", includes.asString()); } From 691e66a7fdedccb8f6b9a30f7792a8101fb46e41 Mon Sep 17 00:00:00 2001 From: Arkoniak Date: Wed, 13 Aug 2025 10:13:46 +0300 Subject: [PATCH 11/45] tests: additional tests for bugfix (#4354) --- test/config.cpp | 27 ++++++++++++++++++++++++++ test/config/include-relative-path.json | 5 +++++ test/config/include-wildcard.json | 5 +++++ test/config/modules/cpu.jsonc | 6 ++++++ test/config/modules/memory.jsonc | 6 ++++++ 5 files changed, 49 insertions(+) create mode 100644 test/config/include-relative-path.json create mode 100644 test/config/include-wildcard.json create mode 100644 test/config/modules/cpu.jsonc create mode 100644 test/config/modules/memory.jsonc diff --git a/test/config.cpp b/test/config.cpp index c60519ce..638d9663 100644 --- a/test/config.cpp +++ b/test/config.cpp @@ -84,6 +84,33 @@ TEST_CASE("Load simple config with include", "[config]") { } } +TEST_CASE("Load simple config with wildcard include", "[config]") { + waybar::Config conf; + conf.load("test/config/include-wildcard.json"); + + auto& data = conf.getConfig(); + SECTION("validate cpu include file") { REQUIRE(data["cpu"]["format"].asString() == "goo"); } + SECTION("validate memory include file") { REQUIRE(data["memory"]["format"].asString() == "foo"); } +} + +TEST_CASE("Load config using relative paths and wildcards", "[config]") { + waybar::Config conf; + + const char* old_config_path = std::getenv(waybar::Config::CONFIG_PATH_ENV); + setenv(waybar::Config::CONFIG_PATH_ENV, "test/config", 1); + + conf.load("test/config/include-relative-path.json"); + + auto& data = conf.getConfig(); + SECTION("validate cpu include file") { REQUIRE(data["cpu"]["format"].asString() == "goo"); } + SECTION("validate memory include file") { REQUIRE(data["memory"]["format"].asString() == "foo"); } + + if (old_config_path) + setenv(waybar::Config::CONFIG_PATH_ENV, old_config_path, 1); + else + unsetenv(waybar::Config::CONFIG_PATH_ENV); +} + TEST_CASE("Load multiple bar config with include", "[config]") { waybar::Config conf; conf.load("test/config/include-multi.json"); diff --git a/test/config/include-relative-path.json b/test/config/include-relative-path.json new file mode 100644 index 00000000..82214b37 --- /dev/null +++ b/test/config/include-relative-path.json @@ -0,0 +1,5 @@ +{ + "include": ["modules/*.jsonc"], + "position": "top", + "nullOption": null +} diff --git a/test/config/include-wildcard.json b/test/config/include-wildcard.json new file mode 100644 index 00000000..75c9f3a1 --- /dev/null +++ b/test/config/include-wildcard.json @@ -0,0 +1,5 @@ +{ + "include": ["test/config/modules/*.jsonc"], + "position": "top", + "nullOption": null +} diff --git a/test/config/modules/cpu.jsonc b/test/config/modules/cpu.jsonc new file mode 100644 index 00000000..393fd786 --- /dev/null +++ b/test/config/modules/cpu.jsonc @@ -0,0 +1,6 @@ +{ + "cpu": { + "interval": 2, + "format": "goo" + } +} diff --git a/test/config/modules/memory.jsonc b/test/config/modules/memory.jsonc new file mode 100644 index 00000000..6085d43a --- /dev/null +++ b/test/config/modules/memory.jsonc @@ -0,0 +1,6 @@ +{ + "memory": { + "interval": 2, + "format": "foo", + } +} From 8fe76317fb02fcc959954724b86c554c7745d62f Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Tue, 12 Aug 2025 09:47:28 -0500 Subject: [PATCH 12/45] feat(makefile): support more detailed test output Add a command to show a prettier test output so you can see what's going on easier. Signed-off-by: Austin Horstman --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 3bb11199..c362ee69 100644 --- a/Makefile +++ b/Makefile @@ -23,5 +23,9 @@ test: meson test -C build --no-rebuild --verbose --suite waybar .PHONY: test +test-detailed: + meson test -C build --verbose --print-errorlogs --test-args='--reporter console -s' +.PHONY: test-detailed + clean: rm -rf build From 25ac6b7a801eefb30b4f9263f28311dd09939c92 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Tue, 12 Aug 2025 09:59:45 -0500 Subject: [PATCH 13/45] refactor(makefile): allow build on test command meson will skip building, if not needed, but we want to make sure we're actually testing what we expect. Signed-off-by: Austin Horstman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c362ee69..12af8e0e 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ debug-run: build-debug ./build/waybar --log-level debug test: - meson test -C build --no-rebuild --verbose --suite waybar + meson test -C build --verbose --suite waybar .PHONY: test test-detailed: From e8755b1a7f5c28180877e0a14793466d0654a8d2 Mon Sep 17 00:00:00 2001 From: Sebastian Schwarz Date: Sat, 16 Aug 2025 14:14:59 +0200 Subject: [PATCH 14/45] fix: display icons for userwide installed applications Previously Waybar only displayed icons for applications which were installed systemwide. Icons were resolved via `.desktop` files in directories specified by the environment variable `XDG_DATA_DIRS`. However the [XDG specification](https://specifications.freedesktop.org/basedir-spec/0.8/#variables) notes that this variable should only consulted **in addition** to `XDG_DATA_HOME`: > `$XDG_DATA_DIRS` defines the preference-ordered set of base > directories to search for data files in addition to the > `$XDG_DATA_HOME` base directory. This because `XDG_DATA_DIRS` contains only systemwide directories, whereas `XDG_DATA_HOME` contains the userwide directory. Also including the latter when looking up `.desktop` files allows Waybar to display icons for applications which where installed userwide. --- src/AAppIconLabel.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AAppIconLabel.cpp b/src/AAppIconLabel.cpp index 3f47eff1..a309a6e0 100644 --- a/src/AAppIconLabel.cpp +++ b/src/AAppIconLabel.cpp @@ -63,7 +63,8 @@ std::optional getDesktopFilePath(const std::string& app_identifier, return {}; } - const auto data_dirs = Glib::get_system_data_dirs(); + auto data_dirs = Glib::get_system_data_dirs(); + data_dirs.insert(data_dirs.begin(), Glib::get_user_data_dir()); for (const auto& data_dir : data_dirs) { const auto data_app_dir = data_dir + "/applications/"; auto desktop_file_suffix = app_identifier + ".desktop"; From 6dc5a73a02af12fccf8eac8494da7d91db23400b Mon Sep 17 00:00:00 2001 From: aidansunbury Date: Sat, 16 Aug 2025 15:29:59 -0700 Subject: [PATCH 15/45] initial changes --- include/ALabel.hpp | 2 +- man/waybar-custom.5.scd | 4 ++-- src/ALabel.cpp | 7 ++++--- src/modules/custom.cpp | 5 +++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/include/ALabel.hpp b/include/ALabel.hpp index a1aae9da..92fc2e0f 100644 --- a/include/ALabel.hpp +++ b/include/ALabel.hpp @@ -21,7 +21,7 @@ class ALabel : public AModule { protected: Gtk::Label label_; std::string format_; - const std::chrono::seconds interval_; + const std::chrono::milliseconds interval_; bool alt_ = false; std::string default_format_; diff --git a/man/waybar-custom.5.scd b/man/waybar-custom.5.scd index 309fc184..5707010c 100644 --- a/man/waybar-custom.5.scd +++ b/man/waybar-custom.5.scd @@ -35,14 +35,14 @@ Addressed by *custom/* See *return-type* *interval*: ++ - typeof: integer ++ + typeof: integer or float ++ The interval (in seconds) in which the information gets polled. ++ Use *once* if you want to execute the module only on startup. ++ You can update it manually with a signal. If no *interval* or *signal* is defined, it is assumed that the out script loops itself. ++ If a *signal* is defined then the script will run once on startup and will only update with a signal. *restart-interval*: ++ - typeof: integer ++ + typeof: integer or float ++ The restart interval (in seconds). ++ Can't be used with the *interval* option, so only with continuous scripts. ++ Once the script exits, it'll be re-executed after the *restart-interval*. diff --git a/src/ALabel.cpp b/src/ALabel.cpp index 6df80e46..2262ad1f 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -18,9 +18,10 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st enable_scroll), format_(config_["format"].isString() ? config_["format"].asString() : format), interval_(config_["interval"] == "once" - ? std::chrono::seconds::max() - : std::chrono::seconds( - config_["interval"].isUInt() ? config_["interval"].asUInt() : interval)), + ? std::chrono::milliseconds::max() + : std::chrono::milliseconds( + static_cast( + (config_["interval"].isNumeric() ? config_["interval"].asDouble() : interval) * 1000))), default_format_(format_) { label_.set_name(name); if (!id.empty()) { diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index f9fd621e..0a7b7dd4 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -89,9 +89,10 @@ void waybar::modules::Custom::continuousWorker() { dp.emit(); spdlog::error("{} stopped unexpectedly, is it endless?", name_); } - if (config_["restart-interval"].isUInt()) { + if (config_["restart-interval"].isNumeric()) { pid_ = -1; - thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt())); + thread_.sleep_for(std::chrono::milliseconds( + static_cast(config_["restart-interval"].asDouble() * 1000))); fp_ = util::command::open(cmd, pid_, output_name_); if (!fp_) { throw std::runtime_error("Unable to open " + cmd); From 2b552f7fb679f1677121b263ac485fef5e040ceb Mon Sep 17 00:00:00 2001 From: aidansunbury Date: Sat, 16 Aug 2025 15:34:43 -0700 Subject: [PATCH 16/45] compat --- include/modules/image.hpp | 2 +- man/waybar-cpu.5.scd | 2 +- man/waybar-image.5.scd | 2 +- man/waybar-jack.5.scd | 2 +- man/waybar-temperature.5.scd | 2 +- src/ALabel.cpp | 3 ++- src/modules/custom.cpp | 4 +++- src/modules/image.cpp | 14 +++++++++----- 8 files changed, 19 insertions(+), 12 deletions(-) diff --git a/include/modules/image.hpp b/include/modules/image.hpp index 7c0d014f..510dad94 100644 --- a/include/modules/image.hpp +++ b/include/modules/image.hpp @@ -31,7 +31,7 @@ class Image : public AModule { std::string path_; std::string tooltip_; int size_; - int interval_; + std::chrono::milliseconds interval_; util::command::res output_; util::SleeperThread thread_; diff --git a/man/waybar-cpu.5.scd b/man/waybar-cpu.5.scd index 287bf123..b9c1ca6a 100644 --- a/man/waybar-cpu.5.scd +++ b/man/waybar-cpu.5.scd @@ -11,7 +11,7 @@ The *cpu* module displays the current CPU utilization. # CONFIGURATION *interval*: ++ - typeof: integer ++ + typeof: integer or float ++ default: 10 ++ The interval in which the information gets polled. diff --git a/man/waybar-image.5.scd b/man/waybar-image.5.scd index a2dcc938..0fe0deff 100644 --- a/man/waybar-image.5.scd +++ b/man/waybar-image.5.scd @@ -24,7 +24,7 @@ The *image* module displays an image from a path. The width/height to render the image. *interval*: ++ - typeof: integer ++ + typeof: integer or float ++ The interval (in seconds) to re-render the image. ++ This is useful if the contents of *path* changes. ++ If no *interval* is defined, the image will only be rendered once. diff --git a/man/waybar-jack.5.scd b/man/waybar-jack.5.scd index 85ce7180..98adc8db 100644 --- a/man/waybar-jack.5.scd +++ b/man/waybar-jack.5.scd @@ -45,7 +45,7 @@ Addressed by *jack* The format of information displayed in the tooltip. *interval*: ++ - typeof: integer ++ + typeof: integer or float ++ default: 1 ++ The interval in which the information gets polled. diff --git a/man/waybar-temperature.5.scd b/man/waybar-temperature.5.scd index 923d643d..554ead19 100644 --- a/man/waybar-temperature.5.scd +++ b/man/waybar-temperature.5.scd @@ -40,7 +40,7 @@ Addressed by *temperature* The threshold before it is considered critical (Celsius). *interval*: ++ - typeof: integer ++ + typeof: integer or float ++ default: 10 ++ The interval in which the information gets polled. diff --git a/src/ALabel.cpp b/src/ALabel.cpp index 2262ad1f..ddd737b1 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -21,7 +21,8 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st ? std::chrono::milliseconds::max() : std::chrono::milliseconds( static_cast( - (config_["interval"].isNumeric() ? config_["interval"].asDouble() : interval) * 1000))), + std::max(0.001, // Minimum 1ms to prevent performance issues + config_["interval"].isNumeric() ? config_["interval"].asDouble() : interval) * 1000))), default_format_(format_) { label_.set_name(name); if (!id.empty()) { diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 0a7b7dd4..426b97fd 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -92,7 +92,9 @@ void waybar::modules::Custom::continuousWorker() { if (config_["restart-interval"].isNumeric()) { pid_ = -1; thread_.sleep_for(std::chrono::milliseconds( - static_cast(config_["restart-interval"].asDouble() * 1000))); + static_cast( + std::max(0.001, // Minimum 1ms to prevent performance issues + config_["restart-interval"].asDouble()) * 1000))); fp_ = util::command::open(cmd, pid_, output_name_); if (!fp_) { throw std::runtime_error("Unable to open " + cmd); diff --git a/src/modules/image.cpp b/src/modules/image.cpp index 71e93b94..0c5590e0 100644 --- a/src/modules/image.cpp +++ b/src/modules/image.cpp @@ -14,14 +14,19 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config) size_ = config["size"].asInt(); - interval_ = config_["interval"].asInt(); + interval_ = config_["interval"] == "once" + ? std::chrono::milliseconds::max() + : std::chrono::milliseconds( + static_cast( + std::max(0.001, // Minimum 1ms to prevent performance issues + config_["interval"].isNumeric() ? config_["interval"].asDouble() : 0) * 1000)); if (size_ == 0) { size_ = 16; } - if (interval_ == 0) { - interval_ = INT_MAX; + if (interval_.count() == 0) { + interval_ = std::chrono::milliseconds::max(); } delayWorker(); @@ -30,8 +35,7 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config) void waybar::modules::Image::delayWorker() { thread_ = [this] { dp.emit(); - auto interval = std::chrono::seconds(interval_); - thread_.sleep_for(interval); + thread_.sleep_for(interval_); }; } From 2b81782fa9122802e1ffe4bf1a723f427c6341e5 Mon Sep 17 00:00:00 2001 From: aidansunbury Date: Sat, 16 Aug 2025 16:01:41 -0700 Subject: [PATCH 17/45] more changes --- man/waybar-cpu.5.scd | 3 ++- man/waybar-custom.5.scd | 2 ++ man/waybar-image.5.scd | 1 + src/ALabel.cpp | 6 +++--- src/modules/custom.cpp | 5 ++--- src/modules/image.cpp | 6 +++--- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/man/waybar-cpu.5.scd b/man/waybar-cpu.5.scd index b9c1ca6a..40682372 100644 --- a/man/waybar-cpu.5.scd +++ b/man/waybar-cpu.5.scd @@ -13,7 +13,8 @@ The *cpu* module displays the current CPU utilization. *interval*: ++ typeof: integer or float ++ default: 10 ++ - The interval in which the information gets polled. + The interval in which the information gets polled. ++ + Minimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. *format*: ++ typeof: string ++ diff --git a/man/waybar-custom.5.scd b/man/waybar-custom.5.scd index 5707010c..37b4c42c 100644 --- a/man/waybar-custom.5.scd +++ b/man/waybar-custom.5.scd @@ -37,6 +37,7 @@ Addressed by *custom/* *interval*: ++ typeof: integer or float ++ The interval (in seconds) in which the information gets polled. ++ + Minimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++ Use *once* if you want to execute the module only on startup. ++ You can update it manually with a signal. If no *interval* or *signal* is defined, it is assumed that the out script loops itself. ++ If a *signal* is defined then the script will run once on startup and will only update with a signal. @@ -44,6 +45,7 @@ Addressed by *custom/* *restart-interval*: ++ typeof: integer or float ++ The restart interval (in seconds). ++ + Minimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++ Can't be used with the *interval* option, so only with continuous scripts. ++ Once the script exits, it'll be re-executed after the *restart-interval*. diff --git a/man/waybar-image.5.scd b/man/waybar-image.5.scd index 0fe0deff..8c991265 100644 --- a/man/waybar-image.5.scd +++ b/man/waybar-image.5.scd @@ -26,6 +26,7 @@ The *image* module displays an image from a path. *interval*: ++ typeof: integer or float ++ The interval (in seconds) to re-render the image. ++ + Minimum value is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++ This is useful if the contents of *path* changes. ++ If no *interval* is defined, the image will only be rendered once. diff --git a/src/ALabel.cpp b/src/ALabel.cpp index ddd737b1..7f9143b3 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -20,9 +20,9 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st interval_(config_["interval"] == "once" ? std::chrono::milliseconds::max() : std::chrono::milliseconds( - static_cast( - std::max(0.001, // Minimum 1ms to prevent performance issues - config_["interval"].isNumeric() ? config_["interval"].asDouble() : interval) * 1000))), + std::max(1L, // Minimum 1ms due to millisecond precision + static_cast( + (config_["interval"].isNumeric() ? config_["interval"].asDouble() : interval) * 1000)))), default_format_(format_) { label_.set_name(name); if (!id.empty()) { diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 426b97fd..2fbc9a16 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -92,9 +92,8 @@ void waybar::modules::Custom::continuousWorker() { if (config_["restart-interval"].isNumeric()) { pid_ = -1; thread_.sleep_for(std::chrono::milliseconds( - static_cast( - std::max(0.001, // Minimum 1ms to prevent performance issues - config_["restart-interval"].asDouble()) * 1000))); + std::max(1L, // Minimum 1ms due to millisecond precision + static_cast(config_["restart-interval"].asDouble() * 1000)))); fp_ = util::command::open(cmd, pid_, output_name_); if (!fp_) { throw std::runtime_error("Unable to open " + cmd); diff --git a/src/modules/image.cpp b/src/modules/image.cpp index 0c5590e0..9529ee35 100644 --- a/src/modules/image.cpp +++ b/src/modules/image.cpp @@ -17,9 +17,9 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config) interval_ = config_["interval"] == "once" ? std::chrono::milliseconds::max() : std::chrono::milliseconds( - static_cast( - std::max(0.001, // Minimum 1ms to prevent performance issues - config_["interval"].isNumeric() ? config_["interval"].asDouble() : 0) * 1000)); + std::max(1L, // Minimum 1ms due to millisecond precision + static_cast( + (config_["interval"].isNumeric() ? config_["interval"].asDouble() : 0) * 1000))); if (size_ == 0) { size_ = 16; From 691b7d427b7b6980442bc149646a955b29646089 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Mon, 18 Aug 2025 20:46:02 +0200 Subject: [PATCH 18/45] Implement "reverse-direction" --- include/modules/hyprland/workspaces.hpp | 2 ++ man/waybar-hyprland-workspaces.5.scd | 5 +++++ src/modules/hyprland/workspace.cpp | 15 +++++++++++++-- src/modules/hyprland/workspaces.cpp | 1 + 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 76b3462d..ef35639d 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -51,6 +51,7 @@ class Workspaces : public AModule, public EventHandler { auto taskbarFormatAfter() const -> std::string { return m_taskbarFormatAfter; } auto taskbarIconSize() const -> int { return m_taskbarIconSize; } auto taskbarOrientation() const -> Gtk::Orientation { return m_taskbarOrientation; } + auto taskbarReverseDirection() const -> bool { return m_taskbarReverseDirection; } auto onClickWindow() const -> std::string { return m_onClickWindow; } auto getIgnoredWindows() const -> std::vector { return m_ignoreWindows; } @@ -183,6 +184,7 @@ class Workspaces : public AModule, public EventHandler { std::string m_taskbarFormatAfter; int m_taskbarIconSize = 16; Gtk::Orientation m_taskbarOrientation = Gtk::ORIENTATION_HORIZONTAL; + bool m_taskbarReverseDirection = false; std::string m_onClickWindow; std::string m_currentActiveWindowAddress; diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index e280ac25..f2b3fb6b 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -55,6 +55,11 @@ This setting is ignored if *workspace-taskbar.enable* is set to true. default: false ++ If true, the active/focused window will have an 'active' class. Could cause higher CPU usage due to more frequent redraws. + *reverse-direction*: ++ + typeof: bool ++ + default: false ++ + If true, the taskbar windows will be added in reverse order (right to left if orientation is horizontal, bottom to top if vertical). + *format*: ++ typeof: string ++ default: {icon} ++ diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 2c8a7b09..febb70c2 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -259,9 +259,9 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { } bool isFirst = true; - for (const auto &window_repr : m_windowMap) { + auto processWindow = [&](const WindowRepr &window_repr) { if (shouldSkipWindow(window_repr)) { - continue; + return; // skip } if (isFirst) { isFirst = false; @@ -270,6 +270,7 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { m_content.pack_start(*windowSeparator, false, false); windowSeparator->show(); } + auto window_box = Gtk::make_managed(Gtk::ORIENTATION_HORIZONTAL); window_box->set_tooltip_text(window_repr.window_title); window_box->get_style_context()->add_class("taskbar-window"); @@ -307,6 +308,16 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { m_content.pack_start(*event_box, true, false); event_box->show_all(); + }; + + if (m_workspaceManager.taskbarReverseDirection()) { + for (auto it = m_windowMap.rbegin(); it != m_windowMap.rend(); ++it) { + processWindow(*it); + } + } else { + for (const auto &window_repr : m_windowMap) { + processWindow(window_repr); + } } auto formatAfter = m_workspaceManager.formatAfter(); diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 5d2903dc..abfa03d3 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -728,6 +728,7 @@ auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> vo populateBoolConfig(workspaceTaskbar, "enable", m_enableTaskbar); populateBoolConfig(workspaceTaskbar, "update-active-window", m_updateActiveWindow); + populateBoolConfig(workspaceTaskbar, "reverse-direction", m_taskbarReverseDirection); if (workspaceTaskbar["format"].isString()) { /* The user defined a format string, use it */ From 45d01ce6e5e0b5d30dfe339c084e16954ae64179 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Mon, 18 Aug 2025 21:17:26 +0200 Subject: [PATCH 19/45] Implement "active-window-position" --- include/modules/hyprland/workspaces.hpp | 10 ++++++++++ man/waybar-hyprland-workspaces.5.scd | 5 +++++ src/modules/hyprland/workspace.cpp | 21 +++++++++++++++++++-- src/modules/hyprland/workspaces.cpp | 12 ++++++++++++ src/util/enum.cpp | 1 + 5 files changed, 47 insertions(+), 2 deletions(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index ef35639d..a5d94bbf 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -55,6 +55,9 @@ class Workspaces : public AModule, public EventHandler { auto onClickWindow() const -> std::string { return m_onClickWindow; } auto getIgnoredWindows() const -> std::vector { return m_ignoreWindows; } + enum class ActiveWindowPosition { NONE, FIRST, LAST }; + auto activeWindowPosition() const -> ActiveWindowPosition { return m_activeWindowPosition; } + std::string getRewrite(std::string window_class, std::string window_title); std::string& getWindowSeparator() { return m_formatWindowSeparator; } bool isWorkspaceIgnored(std::string const& workspace_name); @@ -185,6 +188,13 @@ class Workspaces : public AModule, public EventHandler { int m_taskbarIconSize = 16; Gtk::Orientation m_taskbarOrientation = Gtk::ORIENTATION_HORIZONTAL; bool m_taskbarReverseDirection = false; + util::EnumParser m_activeWindowEnumParser; + ActiveWindowPosition m_activeWindowPosition = ActiveWindowPosition::NONE; + std::map m_activeWindowPositionMap = { + {"NONE", ActiveWindowPosition::NONE}, + {"FIRST", ActiveWindowPosition::FIRST}, + {"LAST", ActiveWindowPosition::LAST}, + }; std::string m_onClickWindow; std::string m_currentActiveWindowAddress; diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index f2b3fb6b..1d04157b 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -60,6 +60,11 @@ This setting is ignored if *workspace-taskbar.enable* is set to true. default: false ++ If true, the taskbar windows will be added in reverse order (right to left if orientation is horizontal, bottom to top if vertical). + *active-window-position*: ++ + typeof: "none" | "first" | "last" ++ + default: "none" ++ + If set to "first", the active window will be moved at the beginning of the taskbar. If set to "last", it will be moved at the end. It will only work if *update-active-window* is set to true. + *format*: ++ typeof: string ++ default: {icon} ++ diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index febb70c2..850b0397 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -104,8 +104,25 @@ void Workspace::initializeWindowMap(const Json::Value &clients_data) { } void Workspace::setActiveWindow(WindowAddress const &addr) { - for (auto &window : m_windowMap) { - window.setActive(window.address == addr); + std::optional activeIdx; + for (size_t i = 0; i < m_windowMap.size(); ++i) { + auto &window = m_windowMap[i]; + bool isActive = (window.address == addr); + window.setActive(isActive); + if (isActive) { + activeIdx = i; + } + } + + auto activeWindowPos = m_workspaceManager.activeWindowPosition(); + if (activeIdx.has_value() && activeWindowPos != Workspaces::ActiveWindowPosition::NONE) { + auto window = std::move(m_windowMap[*activeIdx]); + m_windowMap.erase(m_windowMap.begin() + *activeIdx); + if (activeWindowPos == Workspaces::ActiveWindowPosition::FIRST) { + m_windowMap.insert(m_windowMap.begin(), std::move(window)); + } else if (activeWindowPos == Workspaces::ActiveWindowPosition::LAST) { + m_windowMap.emplace_back(std::move(window)); + } } } diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index abfa03d3..ca37084a 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -776,6 +776,18 @@ auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> vo } } } + + if (workspaceTaskbar["active-window-position"].isString()) { + auto posStr = workspaceTaskbar["active-window-position"].asString(); + try { + m_activeWindowPosition = + m_activeWindowEnumParser.parseStringToEnum(posStr, m_activeWindowPositionMap); + } catch (const std::invalid_argument &e) { + spdlog::warn( + "Invalid string representation for active-window-position. Falling back to 'none'."); + m_activeWindowPosition = ActiveWindowPosition::NONE; + } + } } void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payload) { diff --git a/src/util/enum.cpp b/src/util/enum.cpp index 1e28d66e..6b5d5562 100644 --- a/src/util/enum.cpp +++ b/src/util/enum.cpp @@ -41,6 +41,7 @@ EnumType EnumParser::parseStringToEnum(const std::string& str, // Explicit instantiations for specific EnumType types you intend to use // Add explicit instantiations for all relevant EnumType types template struct EnumParser; +template struct EnumParser; template struct EnumParser; } // namespace waybar::util From 5a29473080cb63b372204d94e70cc9bd28f94fe4 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Fri, 22 Aug 2025 18:44:20 -0400 Subject: [PATCH 20/45] Prevent child zombie process from tearing down Hyprland IPC In rare circumstances, we may fork(), e.g., as part of a custom module, and the child process may fail to exec() and exit. In those cases, the IPC destructor will be called in the child process. Prior to this commit, this call would then result in the shared socket being closed. Prevent this by only closing the socket from the original process. Fixes #3975 and #4152. Signed-off-by: Lukas Fleischer --- include/modules/hyprland/backend.hpp | 1 + src/modules/hyprland/backend.cpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp index d9f16526..c94c4705 100644 --- a/include/modules/hyprland/backend.hpp +++ b/include/modules/hyprland/backend.hpp @@ -42,6 +42,7 @@ class IPC { util::JsonParser parser_; std::list> callbacks_; int socketfd_; // the hyprland socket file descriptor + pid_t socketOwnerPid_; bool running_ = true; }; diff --git a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp index 2bd3b509..2c7684be 100644 --- a/src/modules/hyprland/backend.cpp +++ b/src/modules/hyprland/backend.cpp @@ -46,9 +46,14 @@ std::filesystem::path IPC::getSocketFolder(const char* instanceSig) { IPC::IPC() { // will start IPC and relay events to parseIPC ipcThread_ = std::thread([this]() { socketListener(); }); + socketOwnerPid_ = getpid(); } IPC::~IPC() { + // Do no stop Hyprland IPC if a child process (with successful fork() but + // failed exec()) exits. + if (getpid() != socketOwnerPid_) return; + running_ = false; spdlog::info("Hyprland IPC stopping..."); if (socketfd_ != -1) { From 8e488b4d5e8ed69e75117dc072d8572be7328c50 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Sat, 23 Aug 2025 00:24:31 -0400 Subject: [PATCH 21/45] clock: fix use after scope Signed-off-by: Lukas Fleischer --- src/modules/clock.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index a7d57437..1ee74043 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -63,8 +63,8 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) if (cldInTooltip_) { if (config_[kCldPlaceholder]["mode"].isString()) { const std::string cfgMode{config_[kCldPlaceholder]["mode"].asString()}; - const std::map monthModes{{"month", CldMode::MONTH}, - {"year", CldMode::YEAR}}; + const std::map monthModes{{"month", CldMode::MONTH}, + {"year", CldMode::YEAR}}; if (monthModes.find(cfgMode) != monthModes.end()) cldMode_ = monthModes.at(cfgMode); else @@ -92,10 +92,8 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) fmtMap_.insert({2, "{}"}); if (config_[kCldPlaceholder]["format"]["today"].isString()) { fmtMap_.insert({3, config_[kCldPlaceholder]["format"]["today"].asString()}); - cldBaseDay_ = - year_month_day{ - floor(zoned_time{local_zone(), system_clock::now()}.get_local_time())} - .day(); + auto local_time = zoned_time{local_zone(), system_clock::now()}.get_local_time(); + cldBaseDay_ = year_month_day{floor(local_time)}.day(); } else fmtMap_.insert({3, "{}"}); if (config_[kCldPlaceholder]["format"]["weeks"].isString() && cldWPos_ != WS::HIDDEN) { From 19468d4365bd8578b8d2c0925c89085ca6db6212 Mon Sep 17 00:00:00 2001 From: Manse Date: Mon, 25 Aug 2025 10:50:51 -0300 Subject: [PATCH 22/45] docs: fix waybar menu example and formatting fix example's hibernate id typo fix xml and json formatting --- man/waybar-menu.5.scd | 50 +++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/man/waybar-menu.5.scd b/man/waybar-menu.5.scd index 47e10432..798917f0 100644 --- a/man/waybar-menu.5.scd +++ b/man/waybar-menu.5.scd @@ -87,15 +87,15 @@ Module config : ``` "custom/power": { "format" : "⏻ ", - "tooltip": false, - "menu": "on-click", - "menu-file": "~/.config/waybar/power_menu.xml", - "menu-actions": { - "shutdown": "shutdown", - "reboot": "reboot", - "suspend": "systemctl suspend", - "hibernate": "systemctl hibernate", - }, + "tooltip": false, + "menu": "on-click", + "menu-file": "~/.config/waybar/power_menu.xml", + "menu-actions": { + "shutdown": "shutdown", + "reboot": "reboot", + "suspend": "systemctl suspend", + "hibernate": "systemctl hibernate", + }, }, ``` @@ -104,28 +104,28 @@ Module config : - - - Suspend - - - - - Hibernate - - + + + Suspend + + + + + Hibernate + + - - Shutdown - + + Shutdown + - - Reboot - + + Reboot + From af9c31ccd392be7e542b851599d78dcc6aea1e30 Mon Sep 17 00:00:00 2001 From: Jens Peters Date: Wed, 3 Sep 2025 06:48:32 +0200 Subject: [PATCH 23/45] ext/workspaces: fix appearing of hidden workspaces Do this by removing the needs-update tracking. The gain was questionable to begin with and turns out it doesn't work correctly with multiple outputs. --- include/modules/ext/workspace_manager.hpp | 2 -- src/modules/ext/workspace_manager.cpp | 28 +++++++++-------------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/include/modules/ext/workspace_manager.hpp b/include/modules/ext/workspace_manager.hpp index 0607b5ba..686c7fc7 100644 --- a/include/modules/ext/workspace_manager.hpp +++ b/include/modules/ext/workspace_manager.hpp @@ -136,8 +136,6 @@ class Workspace { Gtk::Button button_; Gtk::Box content_; Gtk::Label label_; - - bool needs_updating_ = false; }; } // namespace waybar::modules::ext diff --git a/src/modules/ext/workspace_manager.cpp b/src/modules/ext/workspace_manager.cpp index 5fec3cdb..9a539c29 100644 --- a/src/modules/ext/workspace_manager.cpp +++ b/src/modules/ext/workspace_manager.cpp @@ -377,16 +377,19 @@ Workspace::~Workspace() { } void Workspace::update() { - if (!needs_updating_) { - return; - } + const auto style_context = button_.get_style_context(); // update style and visibility - const auto style_context = button_.get_style_context(); - style_context->remove_class("active"); - style_context->remove_class("urgent"); - style_context->remove_class("hidden"); + if (!has_state(EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) { + style_context->remove_class("active"); + } + if (!has_state(EXT_WORKSPACE_HANDLE_V1_STATE_URGENT)) { + style_context->remove_class("urgent"); + } + if (!has_state(EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN)) { + style_context->remove_class("hidden"); + } if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) { button_.set_visible(true); @@ -408,34 +411,26 @@ void Workspace::update() { label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("name", name_), fmt::arg("id", workspace_id_), fmt::arg("icon", with_icon_ ? icon() : ""))); - - needs_updating_ = false; } void Workspace::handle_id(const std::string &id) { spdlog::debug("[ext/workspaces]: ID for workspace {}: {}", id_, id); workspace_id_ = id; - needs_updating_ = true; workspace_manager_.set_needs_sorting(); } void Workspace::handle_name(const std::string &name) { spdlog::debug("[ext/workspaces]: Name for workspace {}: {}", id_, name); name_ = name; - needs_updating_ = true; workspace_manager_.set_needs_sorting(); } void Workspace::handle_coordinates(const std::vector &coordinates) { coordinates_ = coordinates; - needs_updating_ = true; workspace_manager_.set_needs_sorting(); } -void Workspace::handle_state(uint32_t state) { - state_ = state; - needs_updating_ = true; -} +void Workspace::handle_state(uint32_t state) { state_ = state; } void Workspace::handle_capabilities(uint32_t capabilities) { spdlog::debug("[ext/workspaces]: Capabilities for workspace {}:", id_); @@ -451,7 +446,6 @@ void Workspace::handle_capabilities(uint32_t capabilities) { if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ASSIGN) == capabilities) { spdlog::debug("[ext/workspaces]: - assign"); } - needs_updating_ = true; } void Workspace::handle_removed() { From 97fa6aa4dd2aaa8155d9711f3021da8f2a4bbb82 Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 5 Sep 2025 21:01:23 -0500 Subject: [PATCH 24/45] Added option to show timezones in tooltip only and removed the extra blank line at the bottom of the timezone list. --- include/modules/clock.hpp | 1 + man/waybar-clock.5.scd | 25 +++++++++++++++++++++++++ src/modules/clock.cpp | 17 +++++++++++++++-- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index e34b7a8e..f86247a0 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -62,6 +62,7 @@ class Clock final : public ALabel { std::vector tzList_; // time zones list int tzCurrIdx_; // current time zone index for tzList_ std::string tzText_{""}; // time zones text to print + std::string tzTooltipFormat_{""}; // optional timezone tooltip format util::SleeperThread thread_; // ordinal date in tooltip diff --git a/man/waybar-clock.5.scd b/man/waybar-clock.5.scd index 50a5fc07..9fbad875 100644 --- a/man/waybar-clock.5.scd +++ b/man/waybar-clock.5.scd @@ -39,6 +39,12 @@ $XDG_CONFIG_HOME/waybar/config ++ :[ A list of timezones (as in *timezone*) to use for time display, changed using the scroll wheel. Do not specify *timezone* option when *timezones* is specified. "" represents the system's local timezone +|[ *timezone-tooltip-format* +:[ string +:[ +:[ Format to use for displaying timezones in the tooltip. When set, this allows showing + timezone information (like timezone abbreviations) in the tooltip while keeping the + main display clean. Uses the same format options as *format* |[ *locale* :[ string :[ @@ -229,6 +235,25 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe } ``` +4. Show timezone in tooltip only + +``` +"clock": { + "interval": 60, + "format": "{:%H:%M}", + "timezone-tooltip-format": "{:%H:%M %Z}", + "timezones": [ + "", + "America/Chicago", + "America/Los_Angeles", + "Europe/Paris", + "UTC" + ], + "tooltip": true, + "tooltip-format": "{tz_list}" +} +``` + # STYLE - *#clock* diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index a7d57437..ddd4c887 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -30,6 +30,7 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) cldMonShift_{year(1900) / January}, tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos}, tzCurrIdx_{0}, + tzTooltipFormat_{config_["timezone-tooltip-format"].isString() ? config_["timezone-tooltip-format"].asString() : ""}, ordInTooltip_{m_tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} { m_tlpText_ = m_tlpFmt_; @@ -188,11 +189,23 @@ auto waybar::modules::Clock::getTZtext(sys_seconds now) -> std::string { if (tzList_.size() == 1) return ""; std::stringstream os; + bool first = true; for (size_t tz_idx{0}; tz_idx < tzList_.size(); ++tz_idx) { - if (static_cast(tz_idx) == tzCurrIdx_) continue; + // Skip current timezone unless timezone-tooltip-format is specified + if (static_cast(tz_idx) == tzCurrIdx_ && tzTooltipFormat_.empty()) continue; + const auto* tz = tzList_[tz_idx] != nullptr ? tzList_[tz_idx] : local_zone(); auto zt{zoned_time{tz, now}}; - os << fmt_lib::vformat(m_locale_, format_, fmt_lib::make_format_args(zt)) << '\n'; + + // Add newline before each entry except the first + if (!first) { + os << '\n'; + } + first = false; + + // Use timezone-tooltip-format if specified, otherwise use format_ + const std::string& fmt = tzTooltipFormat_.empty() ? format_ : tzTooltipFormat_; + os << fmt_lib::vformat(m_locale_, fmt, fmt_lib::make_format_args(zt)); } return os.str(); From d45ce841e3fb6aad6cd803a159aef4f94c57ca0e Mon Sep 17 00:00:00 2001 From: guttermonk Date: Sat, 6 Sep 2025 08:40:09 -0500 Subject: [PATCH 25/45] Keep the local timezone from being in the tooltip. --- src/modules/clock.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index ddd4c887..928fcc6c 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -191,10 +191,13 @@ auto waybar::modules::Clock::getTZtext(sys_seconds now) -> std::string { std::stringstream os; bool first = true; for (size_t tz_idx{0}; tz_idx < tzList_.size(); ++tz_idx) { + // Skip local timezone (nullptr) - never show it in tooltip + if (tzList_[tz_idx] == nullptr) continue; + // Skip current timezone unless timezone-tooltip-format is specified if (static_cast(tz_idx) == tzCurrIdx_ && tzTooltipFormat_.empty()) continue; - const auto* tz = tzList_[tz_idx] != nullptr ? tzList_[tz_idx] : local_zone(); + const auto* tz = tzList_[tz_idx]; auto zt{zoned_time{tz, now}}; // Add newline before each entry except the first From e8a2e6c66f5cda182eab764a5763037c010cd2b3 Mon Sep 17 00:00:00 2001 From: dmyTRUEk <25669613+dmyTRUEk@users.noreply.github.com> Date: Wed, 10 Sep 2025 13:21:04 +0300 Subject: [PATCH 26/45] docs(temperature): add missed `warning` style --- man/waybar-temperature.5.scd | 1 + 1 file changed, 1 insertion(+) diff --git a/man/waybar-temperature.5.scd b/man/waybar-temperature.5.scd index 923d643d..41f705c8 100644 --- a/man/waybar-temperature.5.scd +++ b/man/waybar-temperature.5.scd @@ -160,4 +160,5 @@ Addressed by *temperature* # STYLE - *#temperature* +- *#temperature.warning* - *#temperature.critical* From 09a07cd429b9628f5495e476574754513be930d9 Mon Sep 17 00:00:00 2001 From: dmyTRUEk <25669613+dmyTRUEk@users.noreply.github.com> Date: Fri, 12 Sep 2025 20:00:43 +0300 Subject: [PATCH 27/45] fix(temperature): `critical` style not being removed (#4455) --- src/modules/temperature.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index b1241ba3..fa23ef56 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -74,12 +74,14 @@ auto waybar::modules::Temperature::update() -> void { if (critical) { format = config_["format-critical"].isString() ? config_["format-critical"].asString() : format; label_.get_style_context()->add_class("critical"); - } else if (warning) { - format = config_["format-warning"].isString() ? config_["format-warning"].asString() : format; - label_.get_style_context()->add_class("warning"); } else { label_.get_style_context()->remove_class("critical"); - label_.get_style_context()->remove_class("warning"); + if (warning) { + format = config_["format-warning"].isString() ? config_["format-warning"].asString() : format; + label_.get_style_context()->add_class("warning"); + } else { + label_.get_style_context()->remove_class("warning"); + } } if (format.empty()) { From 3435e338451c4f0c5f8f7ac3157e0f0f030200f6 Mon Sep 17 00:00:00 2001 From: Nikolai Nechaev Date: Sat, 13 Sep 2025 08:40:30 +0900 Subject: [PATCH 28/45] Fix: style: show active workspace on hyprland The `hyprland/workspaces` module has the CSS class `active` rather than `focused` for the current workspace. Update the default CSS selector for the current workspace to match both `button.focused` and `button.active`. --- resources/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/style.css b/resources/style.css index 7e830285..bcfcb5c8 100644 --- a/resources/style.css +++ b/resources/style.css @@ -63,7 +63,7 @@ button:hover { background: rgba(0, 0, 0, 0.2); } -#workspaces button.focused { +#workspaces button.focused, #workspaces button.active { background-color: #64727D; box-shadow: inset 0 -3px #ffffff; } From e87ece4462f29ec4601c57c48518d037253aa66f Mon Sep 17 00:00:00 2001 From: winkelnp <68015877+winkelnp@users.noreply.github.com> Date: Mon, 15 Sep 2025 22:03:23 +0200 Subject: [PATCH 29/45] set gamemode icon size and spacing to 0 if use-icon is false --- src/modules/gamemode.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/modules/gamemode.cpp b/src/modules/gamemode.cpp index 811f13ca..72ef9503 100644 --- a/src/modules/gamemode.cpp +++ b/src/modules/gamemode.cpp @@ -53,7 +53,6 @@ Gamemode::Gamemode(const std::string& id, const Json::Value& config) if (config_["icon-spacing"].isUInt()) { iconSpacing = config_["icon-spacing"].asUInt(); } - box_.set_spacing(iconSpacing); // Whether to use icon or not if (config_["use-icon"].isBool()) { @@ -64,7 +63,6 @@ Gamemode::Gamemode(const std::string& id, const Json::Value& config) if (config_["icon-size"].isUInt()) { iconSize = config_["icon-size"].asUInt(); } - icon_.set_pixel_size(iconSize); // Format if (config_["format"].isString()) { @@ -228,6 +226,11 @@ auto Gamemode::update() -> void { iconName = DEFAULT_ICON_NAME; } icon_.set_from_icon_name(iconName, Gtk::ICON_SIZE_INVALID); + box_.set_spacing(iconSpacing); + icon_.set_pixel_size(iconSize); + } else { + box_.set_spacing(0); + icon_.set_pixel_size(0); } // Call parent update From fd601801b46ce49374651fafc9c82e15273ae099 Mon Sep 17 00:00:00 2001 From: LordMZTE Date: Tue, 16 Sep 2025 16:04:25 +0200 Subject: [PATCH 30/45] fix(river): hide vacant tags on initial startup Before this, vacant tags would show with `hide-vacant` set on initial startup, because we receive initial tag events from River before we show the bar. In that case, we won't call `set_visible(false)` on the respective buttons because they're not shown yet. This registers the output status listener after we show the bar so we won't miss any events. --- include/modules/river/tags.hpp | 1 + src/modules/river/tags.cpp | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/include/modules/river/tags.hpp b/include/modules/river/tags.hpp index fb3eefaa..fd867346 100644 --- a/include/modules/river/tags.hpp +++ b/include/modules/river/tags.hpp @@ -21,6 +21,7 @@ class Tags : public waybar::AModule { void handle_view_tags(struct wl_array *tags); void handle_urgent_tags(uint32_t tags); + void handle_show(); void handle_primary_clicked(uint32_t tag); bool handle_button_press(GdkEventButton *event_button, uint32_t tag); diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index 359e5a23..33be0e6f 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -150,11 +150,7 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con button.show(); } - struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); - output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output); - zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this); - - zriver_status_manager_v1_destroy(status_manager_); + box_.signal_show().connect(sigc::mem_fun(*this, &Tags::handle_show)); } Tags::~Tags() { @@ -165,6 +161,19 @@ Tags::~Tags() { if (control_) { zriver_control_v1_destroy(control_); } + + if (status_manager_) { + zriver_status_manager_v1_destroy(status_manager_); + } +} + +void Tags::handle_show() { + struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output); + zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this); + + zriver_status_manager_v1_destroy(status_manager_); + status_manager_ = nullptr; } void Tags::handle_primary_clicked(uint32_t tag) { From 0c3e82219f2ef1217b8c9a4a6092fe2ff01a7b78 Mon Sep 17 00:00:00 2001 From: peelz Date: Tue, 16 Sep 2025 14:03:45 -0400 Subject: [PATCH 31/45] fix: close sni menu on item destruction --- include/modules/sni/item.hpp | 2 +- src/modules/sni/item.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/modules/sni/item.hpp b/include/modules/sni/item.hpp index c5e86d37..503ab637 100644 --- a/include/modules/sni/item.hpp +++ b/include/modules/sni/item.hpp @@ -26,7 +26,7 @@ struct ToolTip { class Item : public sigc::trackable { public: Item(const std::string&, const std::string&, const Json::Value&, const Bar&); - ~Item() = default; + ~Item(); std::string bus_name; std::string object_path; diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 4e80eba7..30a6b834 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -74,6 +74,13 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf cancellable_, interface); } +Item::~Item() { + if (this->gtk_menu != nullptr) { + this->gtk_menu->popdown(); + this->gtk_menu->detach(); + } +} + bool Item::handleMouseEnter(GdkEventCrossing* const& e) { event_box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT); return false; From 4a5358e8c58dd608c892514da5f7b51a63789411 Mon Sep 17 00:00:00 2001 From: Cole Leavitt Date: Wed, 17 Sep 2025 21:17:26 -0700 Subject: [PATCH 32/45] fix: handle null GDK window reference in surface initialization --- src/bar.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/bar.cpp b/src/bar.cpp index f3468ff4..70029a2a 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -433,7 +433,18 @@ void waybar::Bar::onMap(GdkEventAny* /*unused*/) { /* * Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor). */ - auto* gdk_window = window.get_window()->gobj(); + auto gdk_window_ref = window.get_window(); + if (!gdk_window_ref) { + spdlog::warn("Failed to get GDK window during onMap, deferring surface initialization"); + return; + } + + auto* gdk_window = gdk_window_ref->gobj(); + if (!gdk_window) { + spdlog::warn("GDK window object is null during onMap, deferring surface initialization"); + return; + } + surface = gdk_wayland_window_get_wl_surface(gdk_window); configureGlobalOffset(gdk_window_get_width(gdk_window), gdk_window_get_height(gdk_window)); From 45cfaf4a0bb50b8eecb96a9f85016cd129e5fc80 Mon Sep 17 00:00:00 2001 From: Cole Leavitt Date: Wed, 17 Sep 2025 21:20:43 -0700 Subject: [PATCH 33/45] fix: validate 'swap-icon-label' configuration type and log warnings --- src/AIconLabel.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/AIconLabel.cpp b/src/AIconLabel.cpp index 79cd5fe1..a20c22e9 100644 --- a/src/AIconLabel.cpp +++ b/src/AIconLabel.cpp @@ -36,10 +36,13 @@ AIconLabel::AIconLabel(const Json::Value &config, const std::string &name, const box_.set_spacing(spacing); bool swap_icon_label = false; - if (not config_["swap-icon-label"].isBool()) - spdlog::warn("'swap-icon-label' must be a bool."); - else + if (config_["swap-icon-label"].isNull()) { + } else if (config_["swap-icon-label"].isBool()) { swap_icon_label = config_["swap-icon-label"].asBool(); + } else { + spdlog::warn("'swap-icon-label' must be a bool, found '{}'. Using default value (false).", + config_["swap-icon-label"].asString()); + } if ((rot == 0 || rot == 3) ^ swap_icon_label) { box_.add(image_); From 9e3a9f7d35c73ded1e30caff3e15219379d7f798 Mon Sep 17 00:00:00 2001 From: RQuarx Date: Sat, 20 Sep 2025 15:05:16 +0700 Subject: [PATCH 34/45] fix: changed *waybar-styles(5)" to *waybar-styles(5)* in the last line --- man/waybar.5.scd.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 566f7dc5..1b799275 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -437,4 +437,4 @@ A group may hide all but one element, showing them only on mouse hover. In order # SEE ALSO *sway-output(5)* -*waybar-styles(5)" +*waybar-styles(5)* From b08d0c21f32054feb85752bbf1cce70f5660ebd6 Mon Sep 17 00:00:00 2001 From: Jens Peters Date: Sat, 20 Sep 2025 14:11:46 +0200 Subject: [PATCH 35/45] ext/workspaces: add deactivate action --- man/waybar-ext-workspaces.5.scd | 2 ++ src/modules/ext/workspace_manager.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/man/waybar-ext-workspaces.5.scd b/man/waybar-ext-workspaces.5.scd index 54c67be2..3fdae606 100644 --- a/man/waybar-ext-workspaces.5.scd +++ b/man/waybar-ext-workspaces.5.scd @@ -64,6 +64,8 @@ Addressed by *ext/workspaces* *activate*: Switch to workspace. +*deactivate*: Deactivate the workspace. + *close*: Close the workspace. # ICONS diff --git a/src/modules/ext/workspace_manager.cpp b/src/modules/ext/workspace_manager.cpp index 9a539c29..555e3771 100644 --- a/src/modules/ext/workspace_manager.cpp +++ b/src/modules/ext/workspace_manager.cpp @@ -469,6 +469,8 @@ bool Workspace::handle_clicked(const GdkEventButton *button) const { if (action == "activate") { ext_workspace_handle_v1_activate(ext_handle_); + } else if (action == "deactivate") { + ext_workspace_handle_v1_deactivate(ext_handle_); } else if (action == "close") { ext_workspace_handle_v1_remove(ext_handle_); } else { From cbd8930e220831c1350d5e81f2c121de93046588 Mon Sep 17 00:00:00 2001 From: Standreas Date: Tue, 23 Sep 2025 16:32:45 +0200 Subject: [PATCH 36/45] Fix example in waybar-niri-language.5.scd --- man/waybar-niri-language.5.scd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/man/waybar-niri-language.5.scd b/man/waybar-niri-language.5.scd index 44876fd9..8e4e26fc 100644 --- a/man/waybar-niri-language.5.scd +++ b/man/waybar-niri-language.5.scd @@ -52,8 +52,8 @@ Addressed by *niri/language* ``` "niri/language": { - "format": "Lang: {long}" - "format-en": "AMERICA, HELL YEAH!" + "format": "Lang: {long}", + "format-en": "AMERICA, HELL YEAH!", "format-tr": "As bayrakları" } ``` From cbab9c9713b79e636d313819fd2f6dc55a2e9e31 Mon Sep 17 00:00:00 2001 From: Viktar Lukashonak Date: Thu, 25 Sep 2025 21:46:13 +0300 Subject: [PATCH 37/45] spdlog bump --- meson.build | 2 +- subprojects/spdlog.wrap | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/meson.build b/meson.build index b3e6ca0f..b3cfb5fa 100644 --- a/meson.build +++ b/meson.build @@ -69,7 +69,7 @@ is_openbsd = host_machine.system() == 'openbsd' thread_dep = dependency('threads') fmt = dependency('fmt', version : ['>=8.1.1'], fallback : ['fmt', 'fmt_dep']) -spdlog = dependency('spdlog', version : ['>=1.10.0'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=enabled', 'std_format=disabled', 'tests=disabled']) +spdlog = dependency('spdlog', version : ['>=1.15.2'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['std_format=disabled', 'tests=disabled']) 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 af00d5a7..f7a43600 100644 --- a/subprojects/spdlog.wrap +++ b/subprojects/spdlog.wrap @@ -1,13 +1,13 @@ [wrap-file] -directory = spdlog-1.14.1 -source_url = https://github.com/gabime/spdlog/archive/refs/tags/v1.14.1.tar.gz -source_filename = spdlog-1.14.1.tar.gz -source_hash = 1586508029a7d0670dfcb2d97575dcdc242d3868a259742b69f100801ab4e16b -patch_filename = spdlog_1.14.1-1_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/spdlog_1.14.1-1/get_patch -patch_hash = ae878e732330ea1048f90d7e117c40c0cd2a6fb8ae5492c7955818ce3aaade6c -source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/spdlog_1.14.1-1/spdlog-1.14.1.tar.gz -wrapdb_version = 1.14.1-1 +directory = spdlog-1.15.2 +source_url = https://github.com/gabime/spdlog/archive/refs/tags/v1.15.2.tar.gz +source_filename = spdlog-1.15.2.tar.gz +source_hash = 7a80896357f3e8e920e85e92633b14ba0f229c506e6f978578bdc35ba09e9a5d +patch_filename = spdlog_1.15.2-3_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/spdlog_1.15.2-3/get_patch +patch_hash = d5ab078661f571ef5113a8e4bc5c4121e16c044e7772a24b44b1ca8f3ee7c6cb +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/spdlog_1.15.2-3/spdlog-1.15.2.tar.gz +wrapdb_version = 1.15.2-3 [provide] spdlog = spdlog_dep From 76d3b47ffdbdbc41e4a28645c5f7cb07e9b1d63d Mon Sep 17 00:00:00 2001 From: Viktar Lukashonak Date: Fri, 26 Sep 2025 23:34:11 +0300 Subject: [PATCH 38/45] Cava back/front end transformation --- include/modules/cava.hpp | 59 ------- include/modules/cava/cava.hpp | 30 ++++ include/modules/cava/cava_backend.hpp | 74 +++++++++ meson.build | 2 +- src/factory.cpp | 4 +- src/modules/cava.cpp | 211 ------------------------ src/modules/cava/cava.cpp | 51 ++++++ src/modules/cava/cava_backend.cpp | 223 ++++++++++++++++++++++++++ 8 files changed, 381 insertions(+), 273 deletions(-) delete mode 100644 include/modules/cava.hpp create mode 100644 include/modules/cava/cava.hpp create mode 100644 include/modules/cava/cava_backend.hpp delete mode 100644 src/modules/cava.cpp create mode 100644 src/modules/cava/cava.cpp create mode 100644 src/modules/cava/cava_backend.cpp diff --git a/include/modules/cava.hpp b/include/modules/cava.hpp deleted file mode 100644 index 1a88c7b7..00000000 --- a/include/modules/cava.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include "ALabel.hpp" -#include "util/sleeper_thread.hpp" - -namespace cava { -extern "C" { -// Need sdl_glsl output feature to be enabled on libcava -#ifndef SDL_GLSL -#define SDL_GLSL -#endif - -#include - -#ifdef SDL_GLSL -#undef SDL_GLSL -#endif -} -} // namespace cava - -namespace waybar::modules { -using namespace std::literals::chrono_literals; - -class Cava final : public ALabel { - public: - Cava(const std::string&, const Json::Value&); - virtual ~Cava(); - auto update() -> void override; - auto doAction(const std::string& name) -> void override; - - private: - util::SleeperThread thread_; - util::SleeperThread thread_fetch_input_; - - struct cava::error_s error_{}; // cava errors - struct cava::config_params prm_{}; // cava parameters - struct cava::audio_raw audio_raw_{}; // cava handled raw audio data(is based on audio_data) - struct cava::audio_data audio_data_{}; // cava audio data - struct cava::cava_plan* plan_; //{new cava_plan{}}; - // Cava API to read audio source - cava::ptr input_source_; - // Delay to handle audio source - std::chrono::milliseconds frame_time_milsec_{1s}; - // Text to display - std::string text_{""}; - int rePaint_{1}; - std::chrono::seconds fetch_input_delay_{4}; - std::chrono::seconds suspend_silence_delay_{0}; - bool silence_{false}; - bool hide_on_silence_{false}; - std::string format_silent_{""}; - int sleep_counter_{0}; - // Cava method - void pause_resume(); - // ModuleActionMap - static inline std::map actionMap_{ - {"mode", &waybar::modules::Cava::pause_resume}}; -}; -} // namespace waybar::modules diff --git a/include/modules/cava/cava.hpp b/include/modules/cava/cava.hpp new file mode 100644 index 00000000..6b13c4bd --- /dev/null +++ b/include/modules/cava/cava.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "ALabel.hpp" +#include "cava_backend.hpp" + +namespace waybar::modules::cava { + +class Cava final : public ALabel, public sigc::trackable { + public: + Cava(const std::string&, const Json::Value&); + ~Cava() = default; + auto onUpdate(const std::string& input) -> void; + auto onSilence() -> void; + auto doAction(const std::string& name) -> void override; + + private: + std::shared_ptr backend_; + // Text to display + std::string label_text_{""}; + bool hide_on_silence_{false}; + std::string format_silent_{""}; + int ascii_range_{0}; + bool silence_{false}; + // Cava method + void pause_resume(); + // ModuleActionMap + static inline std::map + actionMap_{{"mode", &waybar::modules::cava::Cava::pause_resume}}; +}; +} // namespace waybar::modules::cava diff --git a/include/modules/cava/cava_backend.hpp b/include/modules/cava/cava_backend.hpp new file mode 100644 index 00000000..d8a2ce06 --- /dev/null +++ b/include/modules/cava/cava_backend.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include +#include + +#include "util/sleeper_thread.hpp" + +namespace cava { +extern "C" { +// Need sdl_glsl output feature to be enabled on libcava +#ifndef SDL_GLSL +#define SDL_GLSL +#endif + +#include + +#ifdef SDL_GLSL +#undef SDL_GLSL +#endif +} +} // namespace cava + +namespace waybar::modules::cava { +using namespace std::literals::chrono_literals; + +class CavaBackend final { + public: + static std::shared_ptr inst(const Json::Value& config); + + virtual ~CavaBackend(); + // Methods + int getAsciiRange(); + void doPauseResume(); + void Update(); + // Signal accessor + using type_signal_update = sigc::signal; + type_signal_update signal_update(); + using type_signal_silence = sigc::signal; + type_signal_silence signal_silence(); + + private: + CavaBackend(const Json::Value& config); + util::SleeperThread thread_; + util::SleeperThread read_thread_; + // Cava API to read audio source + ::cava::ptr input_source_; + + struct ::cava::error_s error_{}; // cava errors + struct ::cava::config_params prm_{}; // cava parameters + struct ::cava::audio_raw audio_raw_{}; // cava handled raw audio data(is based on audio_data) + struct ::cava::audio_data audio_data_{}; // cava audio data + struct ::cava::cava_plan* plan_; //{new cava_plan{}}; + + std::chrono::seconds fetch_input_delay_{4}; + // Delay to handle audio source + std::chrono::milliseconds frame_time_milsec_{1s}; + + int re_paint_{0}; + bool silence_{false}; + bool silence_prev_{false}; + std::chrono::seconds suspend_silence_delay_{0}; + int sleep_counter_{0}; + std::string output_{}; + // Methods + void invoke(); + void execute(); + bool isSilence(); + void doUpdate(bool force = false); + + // Signal + type_signal_update m_signal_update_; + type_signal_silence m_signal_silence_; +}; +} // namespace waybar::modules::cava diff --git a/meson.build b/meson.build index b3cfb5fa..822c566b 100644 --- a/meson.build +++ b/meson.build @@ -505,7 +505,7 @@ cava = dependency('cava', if cava.found() add_project_arguments('-DHAVE_LIBCAVA', language: 'cpp') - src_files += files('src/modules/cava.cpp') + src_files += files('src/modules/cava/cava.cpp', 'src/modules/cava/cava_backend.cpp') man_files += files('man/waybar-cava.5.scd') endif diff --git a/src/factory.cpp b/src/factory.cpp index 20408106..7828ce75 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -109,7 +109,7 @@ #include "modules/wireplumber.hpp" #endif #ifdef HAVE_LIBCAVA -#include "modules/cava.hpp" +#include "modules/cava/cava.hpp" #endif #ifdef HAVE_SYSTEMD_MONITOR #include "modules/systemd_failed_units.hpp" @@ -343,7 +343,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, #endif #ifdef HAVE_LIBCAVA if (ref == "cava") { - return new waybar::modules::Cava(id, config_[name]); + return new waybar::modules::cava::Cava(id, config_[name]); } #endif #ifdef HAVE_SYSTEMD_MONITOR diff --git a/src/modules/cava.cpp b/src/modules/cava.cpp deleted file mode 100644 index 405a351a..00000000 --- a/src/modules/cava.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#include "modules/cava.hpp" - -#include - -waybar::modules::Cava::Cava(const std::string& id, const Json::Value& config) - : ALabel(config, "cava", id, "{}", 60, false, false, false) { - // Load waybar module config - char cfgPath[PATH_MAX]; - cfgPath[0] = '\0'; - - if (config_["cava_config"].isString()) strcpy(cfgPath, config_["cava_config"].asString().data()); - // Load cava config - error_.length = 0; - - if (!load_config(cfgPath, &prm_, false, &error_)) { - spdlog::error("Error loading config. {0}", error_.message); - exit(EXIT_FAILURE); - } - - // Override cava parameters by the user config - prm_.inAtty = 0; - prm_.output = cava::output_method::OUTPUT_RAW; - strcpy(prm_.data_format, "ascii"); - strcpy(prm_.raw_target, "/dev/stdout"); - prm_.ascii_range = config_["format-icons"].size() - 1; - - prm_.bar_width = 2; - prm_.bar_spacing = 0; - prm_.bar_height = 32; - prm_.bar_width = 1; - prm_.orientation = cava::ORIENT_TOP; - prm_.xaxis = cava::xaxis_scale::NONE; - prm_.mono_opt = cava::AVERAGE; - prm_.autobars = 0; - prm_.gravity = 0; - prm_.integral = 1; - - if (config_["framerate"].isInt()) prm_.framerate = config_["framerate"].asInt(); - if (config_["autosens"].isInt()) prm_.autosens = config_["autosens"].asInt(); - if (config_["sensitivity"].isInt()) prm_.sens = config_["sensitivity"].asInt(); - if (config_["bars"].isInt()) prm_.fixedbars = config_["bars"].asInt(); - if (config_["lower_cutoff_freq"].isNumeric()) - prm_.lower_cut_off = config_["lower_cutoff_freq"].asLargestInt(); - if (config_["higher_cutoff_freq"].isNumeric()) - prm_.upper_cut_off = config_["higher_cutoff_freq"].asLargestInt(); - if (config_["sleep_timer"].isInt()) prm_.sleep_timer = config_["sleep_timer"].asInt(); - if (config_["method"].isString()) - prm_.input = cava::input_method_by_name(config_["method"].asString().c_str()); - if (config_["source"].isString()) prm_.audio_source = config_["source"].asString().data(); - if (config_["sample_rate"].isNumeric()) prm_.samplerate = config_["sample_rate"].asLargestInt(); - if (config_["sample_bits"].isInt()) prm_.samplebits = config_["sample_bits"].asInt(); - if (config_["stereo"].isBool()) prm_.stereo = config_["stereo"].asBool(); - if (config_["reverse"].isBool()) prm_.reverse = config_["reverse"].asBool(); - if (config_["bar_delimiter"].isInt()) prm_.bar_delim = config_["bar_delimiter"].asInt(); - if (config_["monstercat"].isBool()) prm_.monstercat = config_["monstercat"].asBool(); - if (config_["waves"].isBool()) prm_.waves = config_["waves"].asBool(); - if (config_["noise_reduction"].isDouble()) - prm_.noise_reduction = config_["noise_reduction"].asDouble(); - if (config_["input_delay"].isInt()) - fetch_input_delay_ = std::chrono::seconds(config_["input_delay"].asInt()); - if (config_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool(); - if (config_["format_silent"].isString()) format_silent_ = config_["format_silent"].asString(); - // Make cava parameters configuration - plan_ = new cava::cava_plan{}; - - audio_raw_.height = prm_.ascii_range; - audio_data_.format = -1; - audio_data_.source = new char[1 + strlen(prm_.audio_source)]; - audio_data_.source[0] = '\0'; - strcpy(audio_data_.source, prm_.audio_source); - - audio_data_.rate = 0; - audio_data_.samples_counter = 0; - audio_data_.channels = 2; - audio_data_.IEEE_FLOAT = 0; - - audio_data_.input_buffer_size = BUFFER_SIZE * audio_data_.channels; - audio_data_.cava_buffer_size = audio_data_.input_buffer_size * 8; - - audio_data_.cava_in = new double[audio_data_.cava_buffer_size]{0.0}; - - audio_data_.terminate = 0; - audio_data_.suspendFlag = false; - input_source_ = get_input(&audio_data_, &prm_); - - if (!input_source_) { - spdlog::error("cava API didn't provide input audio source method"); - exit(EXIT_FAILURE); - } - // Calculate delay for Update() thread - frame_time_milsec_ = std::chrono::milliseconds((int)(1e3 / prm_.framerate)); - - // Init cava plan, audio_raw structure - audio_raw_init(&audio_data_, &audio_raw_, &prm_, plan_); - if (!plan_) spdlog::error("cava plan is not provided"); - audio_raw_.previous_frame[0] = -1; // For first Update() call need to rePaint text message - // Read audio source trough cava API. Cava orginizes this process via infinity loop - thread_fetch_input_ = [this] { - thread_fetch_input_.sleep_for(fetch_input_delay_); - input_source_(&audio_data_); - }; - - thread_ = [this] { - dp.emit(); - thread_.sleep_for(frame_time_milsec_); - }; -} - -waybar::modules::Cava::~Cava() { - thread_fetch_input_.stop(); - thread_.stop(); - delete plan_; - plan_ = nullptr; -} - -void upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { - if (delta == std::chrono::seconds{0}) { - delta += std::chrono::seconds{1}; - delay += delta; - } -} - -void downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { - if (delta > std::chrono::seconds{0}) { - delay -= delta; - delta -= std::chrono::seconds{1}; - } -} - -auto waybar::modules::Cava::update() -> void { - if (audio_data_.suspendFlag) return; - silence_ = true; - - for (int i{0}; i < audio_data_.input_buffer_size; ++i) { - if (audio_data_.cava_in[i]) { - silence_ = false; - sleep_counter_ = 0; - break; - } - } - - if (silence_ && prm_.sleep_timer != 0) { - if (sleep_counter_ <= - (int)(std::chrono::milliseconds(prm_.sleep_timer * 1s) / frame_time_milsec_)) { - ++sleep_counter_; - silence_ = false; - } - } - - if (!silence_ || prm_.sleep_timer == 0) { - downThreadDelay(frame_time_milsec_, suspend_silence_delay_); - // Process: execute cava - pthread_mutex_lock(&audio_data_.lock); - cava::cava_execute(audio_data_.cava_in, audio_data_.samples_counter, audio_raw_.cava_out, - plan_); - if (audio_data_.samples_counter > 0) audio_data_.samples_counter = 0; - pthread_mutex_unlock(&audio_data_.lock); - - // Do transformation under raw data - audio_raw_fetch(&audio_raw_, &prm_, &rePaint_, plan_); - - if (rePaint_ == 1) { - text_.clear(); - - for (int i{0}; i < audio_raw_.number_of_bars; ++i) { - audio_raw_.previous_frame[i] = audio_raw_.bars[i]; - text_.append( - getIcon((audio_raw_.bars[i] > prm_.ascii_range) ? prm_.ascii_range : audio_raw_.bars[i], - "", prm_.ascii_range + 1)); - if (prm_.bar_delim != 0) text_.push_back(prm_.bar_delim); - } - - label_.set_markup(text_); - label_.show(); - ALabel::update(); - label_.get_style_context()->add_class("updated"); - } - - label_.get_style_context()->remove_class("silent"); - } else { - upThreadDelay(frame_time_milsec_, suspend_silence_delay_); - if (hide_on_silence_) - label_.hide(); - else if (config_["format_silent"].isString()) - label_.set_markup(format_silent_); - - label_.get_style_context()->add_class("silent"); - label_.get_style_context()->remove_class("updated"); - } -} - -auto waybar::modules::Cava::doAction(const std::string& name) -> void { - if ((actionMap_[name])) { - (this->*actionMap_[name])(); - } else - spdlog::error("Cava. Unsupported action \"{0}\"", name); -} - -// Cava actions -void waybar::modules::Cava::pause_resume() { - pthread_mutex_lock(&audio_data_.lock); - if (audio_data_.suspendFlag) { - audio_data_.suspendFlag = false; - pthread_cond_broadcast(&audio_data_.resumeCond); - downThreadDelay(frame_time_milsec_, suspend_silence_delay_); - } else { - audio_data_.suspendFlag = true; - upThreadDelay(frame_time_milsec_, suspend_silence_delay_); - } - pthread_mutex_unlock(&audio_data_.lock); -} diff --git a/src/modules/cava/cava.cpp b/src/modules/cava/cava.cpp new file mode 100644 index 00000000..a2a74606 --- /dev/null +++ b/src/modules/cava/cava.cpp @@ -0,0 +1,51 @@ +#include "modules/cava/cava.hpp" + +#include + +waybar::modules::cava::Cava::Cava(const std::string& id, const Json::Value& config) + : ALabel(config, "cava", id, "{}", 60, false, false, false), + backend_{waybar::modules::cava::CavaBackend::inst(config)} { + if (config_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool(); + if (config_["format_silent"].isString()) format_silent_ = config_["format_silent"].asString(); + + ascii_range_ = backend_->getAsciiRange(); + backend_->signal_update().connect(sigc::mem_fun(*this, &Cava::onUpdate)); + backend_->signal_silence().connect(sigc::mem_fun(*this, &Cava::onSilence)); + backend_->Update(); +} + +auto waybar::modules::cava::Cava::doAction(const std::string& name) -> void { + if ((actionMap_[name])) { + (this->*actionMap_[name])(); + } else + spdlog::error("Cava. Unsupported action \"{0}\"", name); +} + +// Cava actions +void waybar::modules::cava::Cava::pause_resume() { backend_->doPauseResume(); } +auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void { + if (silence_) { + label_.get_style_context()->remove_class("silent"); + label_.get_style_context()->add_class("updated"); + } + label_text_.clear(); + for (auto& ch : input) + label_text_.append(getIcon((ch > ascii_range_) ? ascii_range_ : ch, "", ascii_range_ + 1)); + + label_.set_markup(label_text_); + label_.show(); + ALabel::update(); + silence_ = false; +} +auto waybar::modules::cava::Cava::onSilence() -> void { + if (!silence_) { + label_.get_style_context()->remove_class("updated"); + + if (hide_on_silence_) + label_.hide(); + else if (config_["format_silent"].isString()) + label_.set_markup(format_silent_); + silence_ = true; + label_.get_style_context()->add_class("silent"); + } +} diff --git a/src/modules/cava/cava_backend.cpp b/src/modules/cava/cava_backend.cpp new file mode 100644 index 00000000..ec32261c --- /dev/null +++ b/src/modules/cava/cava_backend.cpp @@ -0,0 +1,223 @@ +#include "modules/cava/cava_backend.hpp" + +#include + +std::shared_ptr waybar::modules::cava::CavaBackend::inst( + const Json::Value& config) { + static auto* backend = new CavaBackend(config); + static std::shared_ptr backend_ptr{backend}; + return backend_ptr; +} + +waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) { + // Load waybar module config + char cfgPath[PATH_MAX]; + cfgPath[0] = '\0'; + + if (config["cava_config"].isString()) strcpy(cfgPath, config["cava_config"].asString().data()); + // Load cava config + error_.length = 0; + + if (!load_config(cfgPath, &prm_, false, &error_)) { + spdlog::error("cava backend. Error loading config. {0}", error_.message); + exit(EXIT_FAILURE); + } + + // Override cava parameters by the user config + prm_.inAtty = 0; + prm_.output = ::cava::output_method::OUTPUT_RAW; + strcpy(prm_.data_format, "ascii"); + strcpy(prm_.raw_target, "/dev/stdout"); + prm_.ascii_range = config["format-icons"].size() - 1; + + prm_.bar_width = 2; + prm_.bar_spacing = 0; + prm_.bar_height = 32; + prm_.bar_width = 1; + prm_.orientation = ::cava::ORIENT_TOP; + prm_.xaxis = ::cava::xaxis_scale::NONE; + prm_.mono_opt = ::cava::AVERAGE; + prm_.autobars = 0; + prm_.gravity = 0; + prm_.integral = 1; + + if (config["framerate"].isInt()) prm_.framerate = config["framerate"].asInt(); + // Calculate delay for Update() thread + frame_time_milsec_ = std::chrono::milliseconds((int)(1e3 / prm_.framerate)); + if (config["autosens"].isInt()) prm_.autosens = config["autosens"].asInt(); + if (config["sensitivity"].isInt()) prm_.sens = config["sensitivity"].asInt(); + if (config["bars"].isInt()) prm_.fixedbars = config["bars"].asInt(); + if (config["lower_cutoff_freq"].isNumeric()) + prm_.lower_cut_off = config["lower_cutoff_freq"].asLargestInt(); + if (config["higher_cutoff_freq"].isNumeric()) + prm_.upper_cut_off = config["higher_cutoff_freq"].asLargestInt(); + if (config["sleep_timer"].isInt()) prm_.sleep_timer = config["sleep_timer"].asInt(); + if (config["method"].isString()) + prm_.input = ::cava::input_method_by_name(config["method"].asString().c_str()); + if (config["source"].isString()) prm_.audio_source = config["source"].asString().data(); + if (config["sample_rate"].isNumeric()) prm_.samplerate = config["sample_rate"].asLargestInt(); + if (config["sample_bits"].isInt()) prm_.samplebits = config["sample_bits"].asInt(); + if (config["stereo"].isBool()) prm_.stereo = config["stereo"].asBool(); + if (config["reverse"].isBool()) prm_.reverse = config["reverse"].asBool(); + if (config["bar_delimiter"].isInt()) prm_.bar_delim = config["bar_delimiter"].asInt(); + if (config["monstercat"].isBool()) prm_.monstercat = config["monstercat"].asBool(); + if (config["waves"].isBool()) prm_.waves = config["waves"].asBool(); + if (config["noise_reduction"].isDouble()) + prm_.noise_reduction = config["noise_reduction"].asDouble(); + if (config["input_delay"].isInt()) + fetch_input_delay_ = std::chrono::seconds(config["input_delay"].asInt()); + + // Make cava parameters configuration + plan_ = new ::cava::cava_plan{}; + + audio_raw_.height = prm_.ascii_range; + audio_data_.format = -1; + audio_data_.source = new char[1 + strlen(prm_.audio_source)]; + audio_data_.source[0] = '\0'; + strcpy(audio_data_.source, prm_.audio_source); + + audio_data_.rate = 0; + audio_data_.samples_counter = 0; + audio_data_.channels = 2; + audio_data_.IEEE_FLOAT = 0; + + audio_data_.input_buffer_size = BUFFER_SIZE * audio_data_.channels; + audio_data_.cava_buffer_size = audio_data_.input_buffer_size * 8; + + audio_data_.cava_in = new double[audio_data_.cava_buffer_size]{0.0}; + + audio_data_.terminate = 0; + audio_data_.suspendFlag = false; + input_source_ = get_input(&audio_data_, &prm_); + + if (!input_source_) { + spdlog::error("cava backend API didn't provide input audio source method"); + exit(EXIT_FAILURE); + } + + // Init cava plan, audio_raw structure + audio_raw_init(&audio_data_, &audio_raw_, &prm_, plan_); + if (!plan_) spdlog::error("cava backend plan is not provided"); + audio_raw_.previous_frame[0] = -1; // For first Update() call need to rePaint text message + // Read audio source trough cava API. Cava orginizes this process via infinity loop + read_thread_ = [this] { + try { + input_source_(&audio_data_); + } catch (const std::runtime_error& e) { + spdlog::warn("Cava backend. Read source error: {0}", e.what()); + } + read_thread_.sleep_for(fetch_input_delay_); + }; + + thread_ = [this] { + doUpdate(); + thread_.sleep_for(frame_time_milsec_); + }; +} + +waybar::modules::cava::CavaBackend::~CavaBackend() { + thread_.stop(); + read_thread_.stop(); + delete plan_; + plan_ = nullptr; +} + +static void upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { + if (delta == std::chrono::seconds{0}) { + delta += std::chrono::seconds{1}; + delay += delta; + } +} + +static void downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { + if (delta > std::chrono::seconds{0}) { + delay -= delta; + delta -= std::chrono::seconds{1}; + } +} + +bool waybar::modules::cava::CavaBackend::isSilence() { + for (int i{0}; i < audio_data_.input_buffer_size; ++i) { + if (audio_data_.cava_in[i]) { + return false; + } + } + + return true; +} + +int waybar::modules::cava::CavaBackend::getAsciiRange() { return prm_.ascii_range; } + +// Process: execute cava +void waybar::modules::cava::CavaBackend::invoke() { + pthread_mutex_lock(&audio_data_.lock); + ::cava::cava_execute(audio_data_.cava_in, audio_data_.samples_counter, audio_raw_.cava_out, + plan_); + if (audio_data_.samples_counter > 0) audio_data_.samples_counter = 0; + pthread_mutex_unlock(&audio_data_.lock); +} + +// Do transformation under raw data +void waybar::modules::cava::CavaBackend::execute() { + invoke(); + audio_raw_fetch(&audio_raw_, &prm_, &re_paint_, plan_); + + if (re_paint_ == 1) { + output_.clear(); + for (int i{0}; i < audio_raw_.number_of_bars; ++i) { + audio_raw_.previous_frame[i] = audio_raw_.bars[i]; + output_.push_back(audio_raw_.bars[i]); + if (prm_.bar_delim != 0) output_.push_back(prm_.bar_delim); + } + } +} + +void waybar::modules::cava::CavaBackend::doPauseResume() { + pthread_mutex_lock(&audio_data_.lock); + if (audio_data_.suspendFlag) { + audio_data_.suspendFlag = false; + pthread_cond_broadcast(&audio_data_.resumeCond); + downThreadDelay(frame_time_milsec_, suspend_silence_delay_); + } else { + audio_data_.suspendFlag = true; + upThreadDelay(frame_time_milsec_, suspend_silence_delay_); + } + pthread_mutex_unlock(&audio_data_.lock); +} + +waybar::modules::cava::CavaBackend::type_signal_update +waybar::modules::cava::CavaBackend::signal_update() { + return m_signal_update_; +} + +waybar::modules::cava::CavaBackend::type_signal_silence +waybar::modules::cava::CavaBackend::signal_silence() { + return m_signal_silence_; +} + +void waybar::modules::cava::CavaBackend::Update() { doUpdate(true); } + +void waybar::modules::cava::CavaBackend::doUpdate(bool force) { + if (audio_data_.suspendFlag && !force) return; + + silence_ = isSilence(); + if (!silence_) sleep_counter_ = 0; + + if (silence_ && prm_.sleep_timer != 0) { + if (sleep_counter_ <= + (int)(std::chrono::milliseconds(prm_.sleep_timer * 1s) / frame_time_milsec_)) { + ++sleep_counter_; + silence_ = false; + } + } + + if (!silence_ || prm_.sleep_timer == 0) { + downThreadDelay(frame_time_milsec_, suspend_silence_delay_); + execute(); + if (re_paint_ == 1 || force) m_signal_update_.emit(output_); + } else { + upThreadDelay(frame_time_milsec_, suspend_silence_delay_); + if (silence_ != silence_prev_ || force) m_signal_silence_.emit(); + } + silence_prev_ = silence_; +} From 862ba2f568f121a5a9145c5fd3c92b27e6a144bf Mon Sep 17 00:00:00 2001 From: workflow <4farlion@gmail.com> Date: Mon, 29 Sep 2025 17:35:06 +0300 Subject: [PATCH 39/45] feat(niri/language): add CSS classes --- include/modules/niri/language.hpp | 1 + man/waybar-niri-language.5.scd | 9 +++++++++ src/modules/niri/language.cpp | 10 ++++++++++ 3 files changed, 20 insertions(+) diff --git a/include/modules/niri/language.hpp b/include/modules/niri/language.hpp index 42b90ac4..77f0a624 100644 --- a/include/modules/niri/language.hpp +++ b/include/modules/niri/language.hpp @@ -33,6 +33,7 @@ class Language : public ALabel, public EventHandler { std::vector layouts_; unsigned current_idx_; + std::string last_short_name_; }; } // namespace waybar::modules::niri diff --git a/man/waybar-niri-language.5.scd b/man/waybar-niri-language.5.scd index 44876fd9..7c3af59d 100644 --- a/man/waybar-niri-language.5.scd +++ b/man/waybar-niri-language.5.scd @@ -61,3 +61,12 @@ Addressed by *niri/language* # STYLE - *#language* + +Additionally, a CSS class matching the current layout's short name is added to the widget. This +allows per-language styling, for example: + +``` +#language.us { color: #00ff00; } +#language.de { color: #ff0000; } +#language.fr { color: #0000ff; } +``` diff --git a/src/modules/niri/language.cpp b/src/modules/niri/language.cpp index 3b55ff24..496e5404 100644 --- a/src/modules/niri/language.cpp +++ b/src/modules/niri/language.cpp @@ -58,6 +58,16 @@ void Language::doUpdate() { spdlog::debug("niri language update with short description {}", layout.short_description); spdlog::debug("niri language update with variant {}", layout.variant); + if (!last_short_name_.empty()) { + label_.get_style_context()->remove_class(last_short_name_); + } + if (!layout.short_name.empty()) { + label_.get_style_context()->add_class(layout.short_name); + last_short_name_ = layout.short_name; + } else { + last_short_name_.clear(); + } + std::string layoutName = std::string{}; if (config_.isMember("format-" + layout.short_description + "-" + layout.variant)) { const auto propName = "format-" + layout.short_description + "-" + layout.variant; From db2dfb6f3c7bf5503c4a3029a0b127c9e0f37a02 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 1 Oct 2025 00:13:30 +0000 Subject: [PATCH 40/45] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/dc9637876d0dcc8c9e5e22986b857632effeb727?narHash=sha256-cKgvtz6fKuK1Xr5LQW/zOUiAC0oSQoA9nOISB0pJZqM%3D' (2025-07-28) → 'github:NixOS/nixpkgs/e9f00bd893984bc8ce46c895c3bf7cac95331127?narHash=sha256-0m27AKv6ka%2Bq270dw48KflE0LwQYrO7Fm4/2//KCVWg%3D' (2025-09-28) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 0818b622..9b8db656 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1753694789, - "narHash": "sha256-cKgvtz6fKuK1Xr5LQW/zOUiAC0oSQoA9nOISB0pJZqM=", + "lastModified": 1759036355, + "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "dc9637876d0dcc8c9e5e22986b857632effeb727", + "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", "type": "github" }, "original": { From 5f1db15c2e76b368ea693afb2b1458b8ac3b1cbf Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Wed, 1 Oct 2025 08:06:11 +0200 Subject: [PATCH 41/45] Workspaces containing only ignored windows should be considered empty This fixes #4479 --- include/modules/hyprland/workspace.hpp | 2 +- src/modules/hyprland/workspace.cpp | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/include/modules/hyprland/workspace.hpp b/include/modules/hyprland/workspace.hpp index a06e3816..1d80d331 100644 --- a/include/modules/hyprland/workspace.hpp +++ b/include/modules/hyprland/workspace.hpp @@ -42,7 +42,6 @@ class Workspace { bool isPersistentConfig() const { return m_isPersistentConfig; }; bool isPersistentRule() const { return m_isPersistentRule; }; bool isVisible() const { return m_isVisible; }; - bool isEmpty() const { return m_windows == 0; }; bool isUrgent() const { return m_isUrgent; }; bool handleClicked(GdkEventButton* bt) const; @@ -88,6 +87,7 @@ class Workspace { Gtk::Label m_labelBefore; Gtk::Label m_labelAfter; + bool isEmpty() const; void updateTaskbar(const std::string& workspace_icon); bool handleClick(const GdkEventButton* event_button, WindowAddress const& addr) const; bool shouldSkipWindow(const WindowRepr& window_repr) const; diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 850b0397..ae66bc84 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -268,6 +268,17 @@ void Workspace::update(const std::string &workspace_icon) { } } +bool Workspace::isEmpty() const { + auto ignore_list = m_workspaceManager.getIgnoredWindows(); + if (ignore_list.empty()) { + return m_windows == 0; + } + // If there are windows but they are all ignored, consider the workspace empty + return std::all_of( + m_windowMap.begin(), m_windowMap.end(), + [this, &ignore_list](const auto &window_repr) { return shouldSkipWindow(window_repr); }); +} + void Workspace::updateTaskbar(const std::string &workspace_icon) { for (auto child : m_content.get_children()) { if (child != &m_labelBefore) { From 6f308d8ea17adaf73aba69ac15dacb0c97be1a06 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Wed, 1 Oct 2025 22:30:23 +0800 Subject: [PATCH 42/45] fix: right and middle button not work in ext/workspace module --- src/modules/ext/workspace_manager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/ext/workspace_manager.cpp b/src/modules/ext/workspace_manager.cpp index 555e3771..64c4be23 100644 --- a/src/modules/ext/workspace_manager.cpp +++ b/src/modules/ext/workspace_manager.cpp @@ -349,11 +349,11 @@ Workspace::Workspace(const Json::Value &config, WorkspaceManager &manager, } const bool config_on_click_middle = config["on-click-middle"].isString(); if (config_on_click_middle) { - on_click_middle_action_ = config["on-click"].asString(); + on_click_middle_action_ = config["on-click-middle"].asString(); } const bool config_on_click_right = config["on-click-right"].isString(); if (config_on_click_right) { - on_click_right_action_ = config["on-click"].asString(); + on_click_right_action_ = config["on-click-right"].asString(); } // setup UI From 801319f024736f927cb79ecd255f05fe15cf0071 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 2 Oct 2025 08:55:40 +0800 Subject: [PATCH 43/45] fix: Correct the error in converting network speed units --- src/modules/network.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index b0e01364..d0b7970c 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -333,18 +333,23 @@ auto waybar::modules::Network::update() -> void { fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("icon", getIcon(signal_strength_, state_)), - fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), - fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), - fmt::arg("bandwidthTotalBits", - pow_format((bandwidth_up + bandwidth_down) * 8ull / interval_.count(), "b/s")), - fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")), - fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s")), + fmt::arg("bandwidthDownBits", + pow_format(bandwidth_down * 8ull / (interval_.count() / 1000.0), "b/s")), + fmt::arg("bandwidthUpBits", + pow_format(bandwidth_up * 8ull / (interval_.count() / 1000.0), "b/s")), + fmt::arg( + "bandwidthTotalBits", + pow_format((bandwidth_up + bandwidth_down) * 8ull / (interval_.count() / 1000.0), "b/s")), + fmt::arg("bandwidthDownOctets", + pow_format(bandwidth_down / (interval_.count() / 1000.0), "o/s")), + fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / (interval_.count() / 1000.0), "o/s")), fmt::arg("bandwidthTotalOctets", - pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "o/s")), - fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / interval_.count(), "B/s")), - fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")), + pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "o/s")), + fmt::arg("bandwidthDownBytes", + pow_format(bandwidth_down / (interval_.count() / 1000.0), "B/s")), + fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / (interval_.count() / 1000.0), "B/s")), fmt::arg("bandwidthTotalBytes", - pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s"))); + pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "B/s"))); if (text.compare(label_.get_label()) != 0) { label_.set_markup(text); if (text.empty()) { From d8e23924106c790c11a6b8d64367fa759a20c0c1 Mon Sep 17 00:00:00 2001 From: Pierre Lairez Date: Fri, 3 Oct 2025 11:24:18 +0200 Subject: [PATCH 44/45] Fixes #4521 and #4522 The problem is commit 2b552f7 which introduces a minimum interval time of 1ms. But then, in modules/custom.cpp, the constructor tests if the interval is nonzero to distinguish continuous workers from delay workers. --- src/ALabel.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ALabel.cpp b/src/ALabel.cpp index 7f9143b3..4e6d3349 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -17,12 +17,17 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st config["format-alt"].isString() || config["menu"].isString() || enable_click, enable_scroll), format_(config_["format"].isString() ? config_["format"].asString() : format), + + // Leave the default option outside of the std::max(1L, ...), because the zero value + // (default) is used in modules/custom.cpp to make the difference between + // two types of custom scripts. Fixes #4521. interval_(config_["interval"] == "once" ? std::chrono::milliseconds::max() : std::chrono::milliseconds( - std::max(1L, // Minimum 1ms due to millisecond precision - static_cast( - (config_["interval"].isNumeric() ? config_["interval"].asDouble() : interval) * 1000)))), + (config_["interval"].isNumeric() + ? std::max(1L, // Minimum 1ms due to millisecond precision + static_cast(config_["interval"].asDouble()) * 1000) + : 1000 * (long)interval))), default_format_(format_) { label_.set_name(name); if (!id.empty()) { From 151cf545320cce6b49215e9d59c0e01f05d103be Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 5 Oct 2025 09:58:34 +0200 Subject: [PATCH 45/45] fix: lint --- src/modules/clock.cpp | 12 +++++++----- src/modules/custom.cpp | 2 +- src/modules/image.cpp | 11 ++++++----- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 2329f97f..62706944 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -30,7 +30,9 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) cldMonShift_{year(1900) / January}, tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos}, tzCurrIdx_{0}, - tzTooltipFormat_{config_["timezone-tooltip-format"].isString() ? config_["timezone-tooltip-format"].asString() : ""}, + tzTooltipFormat_{config_["timezone-tooltip-format"].isString() + ? config_["timezone-tooltip-format"].asString() + : ""}, ordInTooltip_{m_tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} { m_tlpText_ = m_tlpFmt_; @@ -200,19 +202,19 @@ auto waybar::modules::Clock::getTZtext(sys_seconds now) -> std::string { for (size_t tz_idx{0}; tz_idx < tzList_.size(); ++tz_idx) { // Skip local timezone (nullptr) - never show it in tooltip if (tzList_[tz_idx] == nullptr) continue; - + // Skip current timezone unless timezone-tooltip-format is specified if (static_cast(tz_idx) == tzCurrIdx_ && tzTooltipFormat_.empty()) continue; - + const auto* tz = tzList_[tz_idx]; auto zt{zoned_time{tz, now}}; - + // Add newline before each entry except the first if (!first) { os << '\n'; } first = false; - + // Use timezone-tooltip-format if specified, otherwise use format_ const std::string& fmt = tzTooltipFormat_.empty() ? format_ : tzTooltipFormat_; os << fmt_lib::vformat(m_locale_, fmt, fmt_lib::make_format_args(zt)); diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 2fbc9a16..d75633e9 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -92,7 +92,7 @@ void waybar::modules::Custom::continuousWorker() { if (config_["restart-interval"].isNumeric()) { pid_ = -1; thread_.sleep_for(std::chrono::milliseconds( - std::max(1L, // Minimum 1ms due to millisecond precision + std::max(1L, // Minimum 1ms due to millisecond precision static_cast(config_["restart-interval"].asDouble() * 1000)))); fp_ = util::command::open(cmd, pid_, output_name_); if (!fp_) { diff --git a/src/modules/image.cpp b/src/modules/image.cpp index 9529ee35..173aabd3 100644 --- a/src/modules/image.cpp +++ b/src/modules/image.cpp @@ -15,11 +15,12 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config) size_ = config["size"].asInt(); interval_ = config_["interval"] == "once" - ? std::chrono::milliseconds::max() - : std::chrono::milliseconds( - std::max(1L, // Minimum 1ms due to millisecond precision - static_cast( - (config_["interval"].isNumeric() ? config_["interval"].asDouble() : 0) * 1000))); + ? std::chrono::milliseconds::max() + : std::chrono::milliseconds(std::max( + 1L, // Minimum 1ms due to millisecond precision + static_cast( + (config_["interval"].isNumeric() ? config_["interval"].asDouble() : 0) * + 1000))); if (size_ == 0) { size_ = 16;