From 38af4a6f16bb24086a5e50f73dea3b589aad15b3 Mon Sep 17 00:00:00 2001 From: "Ruan E. Formigoni" Date: Thu, 10 Nov 2022 02:36:54 -0300 Subject: [PATCH 001/780] exec runs after on-* events --- include/AModule.hpp | 2 +- src/AModule.cpp | 10 +++++----- src/modules/custom.cpp | 3 +++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/include/AModule.hpp b/include/AModule.hpp index 357f70ec..d3009af1 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -25,6 +25,7 @@ class AModule : public IModule { SCROLL_DIR getScrollDir(GdkEventScroll *e); bool tooltipEnabled(); + std::vector pid_children_; const std::string name_; const Json::Value &config_; Gtk::EventBox event_box_; @@ -33,7 +34,6 @@ class AModule : public IModule { virtual bool handleScroll(GdkEventScroll *); private: - std::vector pid_; gdouble distance_scrolled_y_; gdouble distance_scrolled_x_; static const inline std::map, std::string> eventMap_{ diff --git a/src/AModule.cpp b/src/AModule.cpp index b19594a1..79f25654 100644 --- a/src/AModule.cpp +++ b/src/AModule.cpp @@ -35,7 +35,7 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std:: } AModule::~AModule() { - for (const auto& pid : pid_) { + for (const auto& pid : pid_children_) { if (pid != -1) { killpg(pid, SIGTERM); } @@ -45,7 +45,7 @@ AModule::~AModule() { auto AModule::update() -> void { // Run user-provided update handler if configured if (config_["on-update"].isString()) { - pid_.push_back(util::command::forkExec(config_["on-update"].asString())); + pid_children_.push_back(util::command::forkExec(config_["on-update"].asString())); } } @@ -62,7 +62,7 @@ bool AModule::handleToggle(GdkEventButton* const& e) { } if (!format.empty()) { - pid_.push_back(util::command::forkExec(format)); + pid_children_.push_back(util::command::forkExec(format)); } dp.emit(); return true; @@ -123,9 +123,9 @@ AModule::SCROLL_DIR AModule::getScrollDir(GdkEventScroll* e) { bool AModule::handleScroll(GdkEventScroll* e) { auto dir = getScrollDir(e); if (dir == SCROLL_DIR::UP && config_["on-scroll-up"].isString()) { - pid_.push_back(util::command::forkExec(config_["on-scroll-up"].asString())); + pid_children_.push_back(util::command::forkExec(config_["on-scroll-up"].asString())); } else if (dir == SCROLL_DIR::DOWN && config_["on-scroll-down"].isString()) { - pid_.push_back(util::command::forkExec(config_["on-scroll-down"].asString())); + pid_children_.push_back(util::command::forkExec(config_["on-scroll-down"].asString())); } dp.emit(); return true; diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 397298b2..cf66e44b 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -27,6 +27,9 @@ waybar::modules::Custom::~Custom() { void waybar::modules::Custom::delayWorker() { thread_ = [this] { + std::for_each(this->pid_children_.cbegin(), this->pid_children_.cend(), + [](int i){ wait(&i); }); + bool can_update = true; if (config_["exec-if"].isString()) { output_ = util::command::execNoRead(config_["exec-if"].asString()); From 15132aeec304bf85a27f0d8cd55079671bcb1013 Mon Sep 17 00:00:00 2001 From: "Ruan E. Formigoni" Date: Tue, 15 Nov 2022 01:19:51 -0300 Subject: [PATCH 002/780] Fix for leftover pids --- src/modules/custom.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index cf66e44b..458228c5 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -27,8 +27,13 @@ waybar::modules::Custom::~Custom() { void waybar::modules::Custom::delayWorker() { thread_ = [this] { - std::for_each(this->pid_children_.cbegin(), this->pid_children_.cend(), - [](int i){ wait(&i); }); + for( int i : this->pid_children_ ) + { + int status; + waitpid(i, &status, 0); + } + + this->pid_children_.clear(); bool can_update = true; if (config_["exec-if"].isString()) { From 0fdc4c11f3eab5b97c42116f69ac7656d8d3e194 Mon Sep 17 00:00:00 2001 From: Narice Date: Tue, 6 Dec 2022 18:40:38 +0100 Subject: [PATCH 003/780] feat(backlight): added percentexp as format option Humans don't perceive light linearly and so an exponential curve is more appropriate to gauge the perceived brightness of the screen. --- src/modules/backlight.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/modules/backlight.cpp b/src/modules/backlight.cpp index aa734a4f..bf3ae226 100644 --- a/src/modules/backlight.cpp +++ b/src/modules/backlight.cpp @@ -179,9 +179,17 @@ auto waybar::modules::Backlight::update() -> void { if (best->get_powered()) { event_box_.show(); + const uint8_t percent = best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max()); + + const uint8_t percent_exp = + best->get_max() == 0 + ? 100 + : roundf(powf((float)best->get_actual() / best->get_max(), 1.0f / 2.718f) * 100); + label_.set_markup(fmt::format(format_, fmt::arg("percent", std::to_string(percent)), + fmt::arg("percentexp", std::to_string(percent_exp)), fmt::arg("icon", getIcon(percent)))); getState(percent); } else { From dbb8a881be2b44845a3dbff8cb3e10a66ecbc902 Mon Sep 17 00:00:00 2001 From: Narice Date: Tue, 6 Dec 2022 19:22:21 +0100 Subject: [PATCH 004/780] feat(backlight): Added icon_exp and changed syntax changed syntax of percentexp to percent_exp for readability --- src/modules/backlight.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/backlight.cpp b/src/modules/backlight.cpp index bf3ae226..2df0dc1f 100644 --- a/src/modules/backlight.cpp +++ b/src/modules/backlight.cpp @@ -189,8 +189,9 @@ auto waybar::modules::Backlight::update() -> void { : roundf(powf((float)best->get_actual() / best->get_max(), 1.0f / 2.718f) * 100); label_.set_markup(fmt::format(format_, fmt::arg("percent", std::to_string(percent)), - fmt::arg("percentexp", std::to_string(percent_exp)), - fmt::arg("icon", getIcon(percent)))); + fmt::arg("percent_exp", std::to_string(percent_exp)), + fmt::arg("icon", getIcon(percent)), + fmt::arg("icon_exp", getIcon(percent_exp)))); getState(percent); } else { event_box_.hide(); From 2de8a83d845c97bf6bb4848cf9d592bbb2439c62 Mon Sep 17 00:00:00 2001 From: Alessio Molinari Date: Sun, 4 Feb 2024 17:20:45 +0100 Subject: [PATCH 005/780] feat: fetch battery percentage from upower if not found from bluez --- include/modules/bluetooth.hpp | 2 +- src/modules/bluetooth.cpp | 29 +++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/include/modules/bluetooth.hpp b/include/modules/bluetooth.hpp index 89658dcf..b0ed10ba 100644 --- a/include/modules/bluetooth.hpp +++ b/include/modules/bluetooth.hpp @@ -55,7 +55,7 @@ class Bluetooth : public ALabel { GDBusProxy*, GVariant*, const gchar* const*, gpointer) -> void; - auto getDeviceBatteryPercentage(GDBusObject*) -> std::optional; + auto getDeviceBatteryPercentage(GDBusObject*, GDBusProxy*) -> std::optional; auto getDeviceProperties(GDBusObject*, DeviceInfo&) -> bool; auto getControllerProperties(GDBusObject*, ControllerInfo&) -> bool; diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp index 80e4731b..7b94e59e 100644 --- a/src/modules/bluetooth.cpp +++ b/src/modules/bluetooth.cpp @@ -1,6 +1,7 @@ #include "modules/bluetooth.hpp" #include +#include #include #include @@ -335,7 +336,8 @@ auto waybar::modules::Bluetooth::onInterfaceProxyPropertiesChanged( } } -auto waybar::modules::Bluetooth::getDeviceBatteryPercentage(GDBusObject* object) +auto waybar::modules::Bluetooth::getDeviceBatteryPercentage(GDBusObject* object, + GDBusProxy* proxy_device) -> std::optional { GDBusProxy* proxy_device_bat = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Battery1")); @@ -345,6 +347,29 @@ auto waybar::modules::Bluetooth::getDeviceBatteryPercentage(GDBusObject* object) return battery_percentage; } + if (proxy_device != nullptr) { + auto serial = getStringProperty(proxy_device, "Address"); + std::transform(serial.begin(), serial.end(), serial.begin(), + [](unsigned char c) { return std::tolower(c); }); + + auto* client = up_client_new(); + if (client == nullptr) return std::nullopt; + + auto* devices = up_client_get_devices2(client); + UpDevice* dev; + char* udev_serial; + double percentage; + for (int i = 0; i < devices->len; i++) { + dev = (UpDevice*)g_ptr_array_index(devices, i); + g_object_get(dev, "serial", &udev_serial, nullptr); + if (serial == udev_serial) { + g_object_get(dev, "percentage", &percentage, nullptr); + g_ptr_array_unref(devices); + g_object_unref(client); + return percentage; + } + } + } return std::nullopt; } @@ -367,7 +392,7 @@ auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, Device g_object_unref(proxy_device); - device_info.battery_percentage = getDeviceBatteryPercentage(object); + device_info.battery_percentage = getDeviceBatteryPercentage(object, proxy_device); return true; } From d831a45622d8497b292188291d8dfa4c67fce52a Mon Sep 17 00:00:00 2001 From: Alessio Molinari Date: Sun, 4 Feb 2024 18:01:26 +0100 Subject: [PATCH 006/780] fix: restore signature --- include/modules/bluetooth.hpp | 2 +- src/modules/bluetooth.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/modules/bluetooth.hpp b/include/modules/bluetooth.hpp index b0ed10ba..89658dcf 100644 --- a/include/modules/bluetooth.hpp +++ b/include/modules/bluetooth.hpp @@ -55,7 +55,7 @@ class Bluetooth : public ALabel { GDBusProxy*, GVariant*, const gchar* const*, gpointer) -> void; - auto getDeviceBatteryPercentage(GDBusObject*, GDBusProxy*) -> std::optional; + auto getDeviceBatteryPercentage(GDBusObject*) -> std::optional; auto getDeviceProperties(GDBusObject*, DeviceInfo&) -> bool; auto getControllerProperties(GDBusObject*, ControllerInfo&) -> bool; diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp index 7b94e59e..0bfc4ac3 100644 --- a/src/modules/bluetooth.cpp +++ b/src/modules/bluetooth.cpp @@ -336,11 +336,11 @@ auto waybar::modules::Bluetooth::onInterfaceProxyPropertiesChanged( } } -auto waybar::modules::Bluetooth::getDeviceBatteryPercentage(GDBusObject* object, - GDBusProxy* proxy_device) +auto waybar::modules::Bluetooth::getDeviceBatteryPercentage(GDBusObject* object) -> std::optional { GDBusProxy* proxy_device_bat = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Battery1")); + GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Device1")); if (proxy_device_bat != NULL) { unsigned char battery_percentage = getUcharProperty(proxy_device_bat, "Percentage"); g_object_unref(proxy_device_bat); @@ -392,7 +392,7 @@ auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, Device g_object_unref(proxy_device); - device_info.battery_percentage = getDeviceBatteryPercentage(object, proxy_device); + device_info.battery_percentage = getDeviceBatteryPercentage(object); return true; } From 0cbb280de635aa3ddcdbb416d8ebd55a1fb82b15 Mon Sep 17 00:00:00 2001 From: Jannik Date: Sat, 24 Feb 2024 15:00:39 +0100 Subject: [PATCH 007/780] fix title not showing for special workspaces (seperate outputs: true) --- src/modules/hyprland/window.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index 1af02b55..eb659b13 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -99,7 +99,8 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace { spdlog::warn("Monitor not found: {}", monitorName); return Workspace{-1, 0, "", ""}; } - const int id = (*monitor)["activeWorkspace"]["id"].asInt(); + const int special_id = (*monitor)["specialWorkspace"]["id"].asInt(); + const int id = special_id != 0 ? special_id : (*monitor)["activeWorkspace"]["id"].asInt(); const auto workspaces = gIPC->getSocket1JsonReply("workspaces"); assert(workspaces.isArray()); From 9f5c27c6e06e51853b55f68043521d1677e4d048 Mon Sep 17 00:00:00 2001 From: Jannik Date: Sat, 24 Feb 2024 18:16:43 +0100 Subject: [PATCH 008/780] fix title not showing for special workspaces (seperate outputs: false --- include/modules/hyprland/window.hpp | 1 - src/modules/hyprland/window.cpp | 13 ++++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/include/modules/hyprland/window.hpp b/include/modules/hyprland/window.hpp index ea4d83b2..4cc16ffb 100644 --- a/include/modules/hyprland/window.hpp +++ b/include/modules/hyprland/window.hpp @@ -42,7 +42,6 @@ class Window : public waybar::AAppIconLabel, public EventHandler { }; auto getActiveWorkspace(const std::string&) -> Workspace; - auto getActiveWorkspace() -> Workspace; void onEvent(const std::string&) override; void queryActiveWorkspace(); void setClass(const std::string&, bool enable); diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index eb659b13..fca8b9ab 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -84,17 +84,12 @@ auto Window::update() -> void { AAppIconLabel::update(); } -auto Window::getActiveWorkspace() -> Workspace { - const auto workspace = gIPC->getSocket1JsonReply("activeworkspace"); - assert(workspace.isObject()); - return Workspace::parse(workspace); -} - -auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace { +auto Window::getActiveWorkspace(const std::string& monitorName = "") -> Workspace { const auto monitors = gIPC->getSocket1JsonReply("monitors"); assert(monitors.isArray()); - auto monitor = std::find_if(monitors.begin(), monitors.end(), - [&](Json::Value monitor) { return monitor["name"] == monitorName; }); + auto monitor = std::find_if(monitors.begin(), monitors.end(), [&](Json::Value monitor) { + return monitorName == "" ? monitor["focused"].asBool() : monitor["name"] == monitorName; + }); if (monitor == std::end(monitors)) { spdlog::warn("Monitor not found: {}", monitorName); return Workspace{-1, 0, "", ""}; From ea4b95fdd2434ea8cffcf24e9ff36e990815af15 Mon Sep 17 00:00:00 2001 From: Alessio Molinari Date: Mon, 4 Mar 2024 16:34:45 +0100 Subject: [PATCH 009/780] Fix: move init gdbusproxy after proxy_device_bat nullcheck Co-authored-by: Alexis Rouillard --- src/modules/bluetooth.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp index 0bfc4ac3..735866d8 100644 --- a/src/modules/bluetooth.cpp +++ b/src/modules/bluetooth.cpp @@ -340,13 +340,13 @@ auto waybar::modules::Bluetooth::getDeviceBatteryPercentage(GDBusObject* object) -> std::optional { GDBusProxy* proxy_device_bat = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Battery1")); - GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Device1")); if (proxy_device_bat != NULL) { unsigned char battery_percentage = getUcharProperty(proxy_device_bat, "Percentage"); g_object_unref(proxy_device_bat); return battery_percentage; } + GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Device1")); if (proxy_device != nullptr) { auto serial = getStringProperty(proxy_device, "Address"); std::transform(serial.begin(), serial.end(), serial.begin(), From 626c52cc79f4aa4798e026f6250e8c61943a95cc Mon Sep 17 00:00:00 2001 From: lilly-lizard Date: Sat, 13 Jul 2024 14:08:48 +1200 Subject: [PATCH 010/780] add "output" class to river tags module when monitor is focused --- include/modules/river/tags.hpp | 4 +++ src/modules/river/tags.cpp | 58 ++++++++++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/include/modules/river/tags.hpp b/include/modules/river/tags.hpp index fb3eefaa..5927ac59 100644 --- a/include/modules/river/tags.hpp +++ b/include/modules/river/tags.hpp @@ -20,6 +20,8 @@ class Tags : public waybar::AModule { void handle_focused_tags(uint32_t tags); void handle_view_tags(struct wl_array *tags); void handle_urgent_tags(uint32_t tags); + void handle_focused_output(struct wl_output *output); + void handle_unfocused_output(struct wl_output *output); void handle_primary_clicked(uint32_t tag); bool handle_button_press(GdkEventButton *event_button, uint32_t tag); @@ -30,9 +32,11 @@ class Tags : public waybar::AModule { private: const waybar::Bar &bar_; + struct wl_output *output_; // stores the output this module belongs to Gtk::Box box_; std::vector buttons_; struct zriver_output_status_v1 *output_status_; + struct zriver_seat_status_v1 *seat_status_; }; } /* namespace waybar::modules::river */ diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index 9e7cd5aa..cf0adbae 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -27,12 +27,39 @@ static void listen_urgent_tags(void *data, struct zriver_output_status_v1 *zrive static_cast(data)->handle_urgent_tags(tags); } +static void listen_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, + const char *title) { + // This module doesn't care +} + +static void listen_mode(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, + const char *mode) { + // This module doesn't care +} + +static void listen_focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, + struct wl_output *output) { + static_cast(data)->handle_focused_output(output); +} + +static void listen_unfocused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, + struct wl_output *output) { + static_cast(data)->handle_unfocused_output(output); +} + static const zriver_output_status_v1_listener output_status_listener_impl{ .focused_tags = listen_focused_tags, .view_tags = listen_view_tags, .urgent_tags = listen_urgent_tags, }; +static const zriver_seat_status_v1_listener seat_status_listener_impl{ + .focused_output = listen_focused_output, + .unfocused_output = listen_unfocused_output, + .focused_view = listen_focused_view, + .mode = listen_mode, +}; + static void listen_command_success(void *data, struct zriver_command_callback_v1 *zriver_command_callback_v1, const char *output) { @@ -88,12 +115,15 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con seat_{nullptr}, bar_(bar), box_{bar.orientation, 0}, - output_status_{nullptr} { + output_status_{nullptr}, + seat_status_{nullptr} { struct wl_display *display = Client::inst()->wl_display; struct wl_registry *registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener_impl, this); wl_display_roundtrip(display); + output_ = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + if (!status_manager_) { spdlog::error("river_status_manager_v1 not advertised"); return; @@ -150,10 +180,12 @@ 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); + 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); + seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_); + zriver_seat_status_v1_add_listener(seat_status_, &seat_status_listener_impl, this); + zriver_status_manager_v1_destroy(status_manager_); } @@ -162,6 +194,10 @@ Tags::~Tags() { zriver_output_status_v1_destroy(output_status_); } + if (seat_status_) { + zriver_seat_status_v1_destroy(seat_status_); + } + if (control_) { zriver_control_v1_destroy(control_); } @@ -224,4 +260,20 @@ void Tags::handle_urgent_tags(uint32_t tags) { } } +void Tags::handle_focused_output(struct wl_output *output) { + if (output_ == output) { + for (size_t i = 0; i < buttons_.size(); ++i) { + buttons_[i].get_style_context()->add_class("output"); + } + } +} + +void Tags::handle_unfocused_output(struct wl_output *output) { + if (output_ == output) { + for (size_t i = 0; i < buttons_.size(); ++i) { + buttons_[i].get_style_context()->remove_class("output"); + } + } +} + } /* namespace waybar::modules::river */ From 79ce23a9a77f874d14fd14070a46cf9d2dba9499 Mon Sep 17 00:00:00 2001 From: lilly-lizard Date: Sat, 13 Jul 2024 14:12:54 +1200 Subject: [PATCH 011/780] docs for river tags output style --- man/waybar-river-tags.5.scd | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/man/waybar-river-tags.5.scd b/man/waybar-river-tags.5.scd index f0b2b84e..d629f73d 100644 --- a/man/waybar-river-tags.5.scd +++ b/man/waybar-river-tags.5.scd @@ -40,10 +40,13 @@ Addressed by *river/tags* - *#tags button.occupied* - *#tags button.focused* - *#tags button.urgent* +- *#tags button.output* -Note that occupied/focused/urgent status may overlap. That is, a tag may be +Note that occupied/focused/urgent/output status may overlap. That is, a tag may be both occupied and focused at the same time. +The *output* style is applied when the river output (e.g. monitor) of the current bar is focused. + # SEE ALSO waybar(5), river(1) From f4608b3e312448b37a8f9d6351154026e67c680a Mon Sep 17 00:00:00 2001 From: schmop Date: Thu, 25 Jul 2024 01:40:49 +0200 Subject: [PATCH 012/780] Fix battery status changes not being detected Historically we listened to /sys/class/poewr_supply inotify events, which does not seem to work anymore. We switched now to udev netlink kernel events. --- include/modules/battery.hpp | 8 +++++-- include/util/udev_deleter.hpp | 21 ++++++++++++++++++ src/modules/battery.cpp | 40 +++++++++++++++++++--------------- src/util/backlight_backend.cpp | 17 +-------------- 4 files changed, 51 insertions(+), 35 deletions(-) create mode 100644 include/util/udev_deleter.hpp diff --git a/include/modules/battery.hpp b/include/modules/battery.hpp index 8e1a2ad2..fc403be0 100644 --- a/include/modules/battery.hpp +++ b/include/modules/battery.hpp @@ -5,8 +5,11 @@ #include #if defined(__linux__) #include +#include "util/udev_deleter.hpp" #endif +#include + #include #include #include @@ -36,11 +39,12 @@ class Battery : public ALabel { const std::string formatTimeRemaining(float hoursRemaining); void setBarClass(std::string&); - int global_watch; std::map batteries_; + std::unique_ptr udev_; + std::array poll_fds_; + std::unique_ptr mon_; fs::path adapter_; int battery_watch_fd_; - int global_watch_fd_; std::mutex battery_list_mutex_; std::string old_status_; bool warnFirstTime_{true}; diff --git a/include/util/udev_deleter.hpp b/include/util/udev_deleter.hpp new file mode 100644 index 00000000..b2f1e538 --- /dev/null +++ b/include/util/udev_deleter.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace waybar::util { +struct UdevDeleter { + void operator()(udev *ptr) const { udev_unref(ptr); } +}; + +struct UdevDeviceDeleter { + void operator()(udev_device *ptr) const { udev_device_unref(ptr); } +}; + +struct UdevEnumerateDeleter { + void operator()(udev_enumerate *ptr) const { udev_enumerate_unref(ptr); } +}; + +struct UdevMonitorDeleter { + void operator()(udev_monitor *ptr) const { udev_monitor_unref(ptr); } +}; +} // namespace waybar::util \ No newline at end of file diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index d87cc612..bad72e6b 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -5,6 +5,9 @@ #include #endif #include +#include +#include +#include #include waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const Json::Value& config) @@ -14,17 +17,18 @@ waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const J if (battery_watch_fd_ == -1) { throw std::runtime_error("Unable to listen batteries."); } - - global_watch_fd_ = inotify_init1(IN_CLOEXEC); - if (global_watch_fd_ == -1) { - throw std::runtime_error("Unable to listen batteries."); + udev_ = std::unique_ptr(udev_new()); + if (udev_ == nullptr) { + throw std::runtime_error("udev_new failed"); } - - // Watch the directory for any added or removed batteries - global_watch = inotify_add_watch(global_watch_fd_, data_dir_.c_str(), IN_CREATE | IN_DELETE); - if (global_watch < 0) { - throw std::runtime_error("Could not watch for battery plug/unplug"); + mon_ = std::unique_ptr(udev_monitor_new_from_netlink(udev_.get(), "kernel")); + if (mon_ == nullptr) { + throw std::runtime_error("udev monitor new failed"); } + if (udev_monitor_filter_add_match_subsystem_devtype(mon_.get(), "power_supply", nullptr) < 0) { + throw std::runtime_error("udev failed to add monitor filter"); + } + udev_monitor_enable_receiving(mon_.get()); #endif worker(); } @@ -33,11 +37,6 @@ waybar::modules::Battery::~Battery() { #if defined(__linux__) std::lock_guard guard(battery_list_mutex_); - if (global_watch >= 0) { - inotify_rm_watch(global_watch_fd_, global_watch); - } - close(global_watch_fd_); - for (auto it = batteries_.cbegin(), next_it = it; it != batteries_.cend(); it = next_it) { ++next_it; auto watch_id = (*it).second; @@ -74,12 +73,18 @@ void waybar::modules::Battery::worker() { dp.emit(); }; thread_battery_update_ = [this] { - struct inotify_event event = {0}; - int nbytes = read(global_watch_fd_, &event, sizeof(event)); - if (nbytes != sizeof(event) || event.mask & IN_IGNORED) { + poll_fds_[0].revents = 0; + poll_fds_[0].events = POLLIN; + poll_fds_[0].fd = udev_monitor_get_fd(mon_.get()); + int ret = poll(poll_fds_.data(), poll_fds_.size(), -1); + if (ret < 0) { thread_.stop(); return; } + if ((poll_fds_[0].revents & POLLIN) != 0) { + signalfd_siginfo signal_info; + read(poll_fds_[0].fd, &signal_info, sizeof(signal_info)); + } refreshBatteries(); dp.emit(); }; @@ -668,6 +673,7 @@ auto waybar::modules::Battery::update() -> void { status = getAdapterStatus(capacity); } auto status_pretty = status; + puts(status.c_str()); // Transform to lowercase and replace space with dash std::transform(status.begin(), status.end(), status.begin(), [](char ch) { return ch == ' ' ? '-' : std::tolower(ch); }); diff --git a/src/util/backlight_backend.cpp b/src/util/backlight_backend.cpp index bb102cd9..df6afd56 100644 --- a/src/util/backlight_backend.cpp +++ b/src/util/backlight_backend.cpp @@ -1,4 +1,5 @@ #include "util/backlight_backend.hpp" +#include "util/udev_deleter.hpp" #include #include @@ -29,22 +30,6 @@ class FileDescriptor { int fd_; }; -struct UdevDeleter { - void operator()(udev *ptr) { udev_unref(ptr); } -}; - -struct UdevDeviceDeleter { - void operator()(udev_device *ptr) { udev_device_unref(ptr); } -}; - -struct UdevEnumerateDeleter { - void operator()(udev_enumerate *ptr) { udev_enumerate_unref(ptr); } -}; - -struct UdevMonitorDeleter { - void operator()(udev_monitor *ptr) { udev_monitor_unref(ptr); } -}; - void check_eq(int rc, int expected, const char *message = "eq, rc was: ") { if (rc != expected) { throw std::runtime_error(fmt::format(fmt::runtime(message), rc)); From 62cb61c670876f192976a366b3fc4175cbe72406 Mon Sep 17 00:00:00 2001 From: Sonter Date: Wed, 14 Aug 2024 11:34:28 +0300 Subject: [PATCH 013/780] Add format_silent to cava module --- include/modules/cava.hpp | 1 + src/modules/cava.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/include/modules/cava.hpp b/include/modules/cava.hpp index 430c71b7..219d9302 100644 --- a/include/modules/cava.hpp +++ b/include/modules/cava.hpp @@ -39,6 +39,7 @@ class Cava final : public ALabel { 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(); diff --git a/src/modules/cava.cpp b/src/modules/cava.cpp index 431ce5f1..1841e276 100644 --- a/src/modules/cava.cpp +++ b/src/modules/cava.cpp @@ -59,6 +59,7 @@ waybar::modules::Cava::Cava(const std::string& id, const Json::Value& config) 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{}; @@ -176,6 +177,7 @@ auto waybar::modules::Cava::update() -> void { } 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_); } } From ed40168d89085d30538422252016018f152a2d06 Mon Sep 17 00:00:00 2001 From: Sonter <108224581+S0nter@users.noreply.github.com> Date: Wed, 14 Aug 2024 14:43:17 +0000 Subject: [PATCH 014/780] Add cava.silence to css --- src/modules/cava.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/cava.cpp b/src/modules/cava.cpp index 1841e276..26ad4fd1 100644 --- a/src/modules/cava.cpp +++ b/src/modules/cava.cpp @@ -174,10 +174,14 @@ auto waybar::modules::Cava::update() -> void { label_.show(); ALabel::update(); } + + label_.get_style_context()->remove_class("silence"); } 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("silence"); } } From 367f156eb0f956b848739c94139ea0311e8f1890 Mon Sep 17 00:00:00 2001 From: Sonter Date: Wed, 14 Aug 2024 19:25:07 +0300 Subject: [PATCH 015/780] Add cava.update to css --- src/modules/cava.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/cava.cpp b/src/modules/cava.cpp index 26ad4fd1..1e6a97f7 100644 --- a/src/modules/cava.cpp +++ b/src/modules/cava.cpp @@ -173,6 +173,7 @@ auto waybar::modules::Cava::update() -> void { label_.set_markup(text_); label_.show(); ALabel::update(); + label_.get_style_context()->add_class("update"); } label_.get_style_context()->remove_class("silence"); @@ -182,6 +183,7 @@ auto waybar::modules::Cava::update() -> void { else if (config_["format_silent"].isString()) label_.set_markup(format_silent_); label_.get_style_context()->add_class("silence"); + label_.get_style_context()->remove_class("update"); } } From 36497cd41a599d8c5f9da8f11a8dd4ea5efe169e Mon Sep 17 00:00:00 2001 From: Sonter Date: Wed, 14 Aug 2024 19:27:12 +0300 Subject: [PATCH 016/780] Rename cava css values --- src/modules/cava.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/cava.cpp b/src/modules/cava.cpp index 1e6a97f7..f81bf799 100644 --- a/src/modules/cava.cpp +++ b/src/modules/cava.cpp @@ -173,17 +173,17 @@ auto waybar::modules::Cava::update() -> void { label_.set_markup(text_); label_.show(); ALabel::update(); - label_.get_style_context()->add_class("update"); + label_.get_style_context()->add_class("updated"); } - label_.get_style_context()->remove_class("silence"); + 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("silence"); - label_.get_style_context()->remove_class("update"); + label_.get_style_context()->add_class("silent"); + label_.get_style_context()->remove_class("updated"); } } From 5c859bf520fe91d37127937e9a0af534b487d4ea Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sat, 24 Aug 2024 00:06:24 +1000 Subject: [PATCH 017/780] temp: changed window -> windowcount --- include/modules/hyprland/windowcount.hpp | 65 +++++++ man/waybar-hyprland-windowcount.5.scd | 89 +++++++++ meson.build | 1 + src/factory.cpp | 4 + src/modules/hyprland/windowcount.cpp | 233 +++++++++++++++++++++++ 5 files changed, 392 insertions(+) create mode 100644 include/modules/hyprland/windowcount.hpp create mode 100644 man/waybar-hyprland-windowcount.5.scd create mode 100644 src/modules/hyprland/windowcount.cpp diff --git a/include/modules/hyprland/windowcount.hpp b/include/modules/hyprland/windowcount.hpp new file mode 100644 index 00000000..3972c66a --- /dev/null +++ b/include/modules/hyprland/windowcount.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include + +#include + +#include "AAppIconLabel.hpp" +#include "bar.hpp" +#include "modules/hyprland/backend.hpp" +#include "util/json.hpp" + +namespace waybar::modules::hyprland { + +class WindowCount : public waybar::AAppIconLabel, public EventHandler { + public: + WindowCount(const std::string&, const waybar::Bar&, const Json::Value&); + ~WindowCount() override; + + auto update() -> void override; + + private: + struct Workspace { + int id; + int windows; + std::string last_window; + std::string last_window_title; + + static auto parse(const Json::Value& value) -> Workspace; + }; + + struct WindowCountData { + bool floating; + int monitor = -1; + std::string class_name; + std::string initial_class_name; + std::string title; + std::string initial_title; + bool fullscreen; + bool grouped; + + static auto parse(const Json::Value&) -> WindowCountData; + }; + + static auto getActiveWorkspace(const std::string&) -> Workspace; + static auto getActiveWorkspace() -> Workspace; + void onEvent(const std::string& ev) override; + void queryActiveWorkspace(); + void setClass(const std::string&, bool enable); + + bool separateOutputs_; + std::mutex mutex_; + const Bar& bar_; + util::JsonParser parser_; + WindowCountData windowData_; + Workspace workspace_; + std::string soloClass_; + std::string lastSoloClass_; + bool solo_; + bool allFloating_; + bool swallowing_; + bool fullscreen_; + bool focused_; +}; + +} // namespace waybar::modules::hyprland diff --git a/man/waybar-hyprland-windowcount.5.scd b/man/waybar-hyprland-windowcount.5.scd new file mode 100644 index 00000000..4e9c5d18 --- /dev/null +++ b/man/waybar-hyprland-windowcount.5.scd @@ -0,0 +1,89 @@ +waybar-hyprland-window(5) + +# NAME + +waybar - hyprland window module + +# DESCRIPTION + +The *window* module displays the title of the currently focused window in Hyprland. + +# CONFIGURATION + +Addressed by *hyprland/window* + +*format*: ++ + typeof: string ++ + default: {title} ++ + The format, how information should be displayed. On {} the current window title is displayed. + +*rewrite*: ++ + typeof: object ++ + Rules to rewrite window title. See *rewrite rules*. + +*separate-outputs*: ++ + typeof: bool ++ + Show the active window of the monitor the bar belongs to, instead of the focused window. + +*icon*: ++ + typeof: bool ++ + default: false ++ + Option to hide the application icon. + +*icon-size*: ++ + typeof: integer ++ + default: 24 ++ + Option to change the size of the application icon. + +# FORMAT REPLACEMENTS +See the output of "hyprctl clients" for examples + +*{title}*: The current title of the focused window. + +*{initialTitle}*: The initial title of the focused window. + +*{class}*: The current class of the focused window. + +*{initialClass}*: The initial class of the focused window. + +# REWRITE RULES + +*rewrite* is an object where keys are regular expressions and values are +rewrite rules if the expression matches. Rules may contain references to +captures of the expression. + +Regular expression and replacement follow ECMA-script rules. + +If no expression matches, the title is left unchanged. + +Invalid expressions (e.g., mismatched parentheses) are skipped. + +# EXAMPLES + +``` +"hyprland/window": { + "format": "{}", + "rewrite": { + "(.*) - Mozilla Firefox": "🌎 $1", + "(.*) - zsh": "> [$1]" + } +} +``` + +# STYLE + +- *#window* +- *window#waybar.empty #window* When no windows are in the workspace + +The following classes are applied to the entire Waybar rather than just the +window widget: + +- *window#waybar.empty* When no windows are in the workspace +- *window#waybar.solo* When one tiled window is visible in the workspace + (floating windows may be present) +- *window#waybar.* Where ** is the *class* (e.g. *chromium*) of + the solo tiled window in the workspace (use *hyprctl clients* to see classes) +- *window#waybar.floating* When there are only floating windows in the workspace +- *window#waybar.fullscreen* When there is a fullscreen window in the workspace; + useful with Hyprland's *fullscreen, 1* mode +- *window#waybar.swallowing* When there is a swallowed window in the workspace diff --git a/meson.build b/meson.build index 8daa6c9c..1a7a94df 100644 --- a/meson.build +++ b/meson.build @@ -306,6 +306,7 @@ if true 'src/modules/hyprland/language.cpp', 'src/modules/hyprland/submap.cpp', 'src/modules/hyprland/window.cpp', + 'src/modules/hyprland/windowcount.cpp', 'src/modules/hyprland/workspace.cpp', 'src/modules/hyprland/workspaces.cpp', 'src/modules/hyprland/windowcreationpayload.cpp', diff --git a/src/factory.cpp b/src/factory.cpp index ca10ef95..2344160f 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -34,6 +34,7 @@ #include "modules/hyprland/language.hpp" #include "modules/hyprland/submap.hpp" #include "modules/hyprland/window.hpp" +#include "modules/hyprland/windowcount.hpp" #include "modules/hyprland/workspaces.hpp" #endif #if defined(__FreeBSD__) || defined(__linux__) @@ -196,6 +197,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, if (ref == "hyprland/window") { return new waybar::modules::hyprland::Window(id, bar_, config_[name]); } + if (ref == "hyprland/windowcount") { + return new waybar::modules::hyprland::WindowCount(id, bar_, config_[name]); + } if (ref == "hyprland/language") { return new waybar::modules::hyprland::Language(id, bar_, config_[name]); } diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp new file mode 100644 index 00000000..52c25978 --- /dev/null +++ b/src/modules/hyprland/windowcount.cpp @@ -0,0 +1,233 @@ +#include "modules/hyprland/windowcount.hpp" + +#include +#include +#include +#include + +#include +#include + +#include "modules/hyprland/backend.hpp" +#include "util/rewrite_string.hpp" +#include "util/sanitize_str.hpp" + +namespace waybar::modules::hyprland { + +WindowCount::WindowCount(const std::string& id, const Bar& bar, const Json::Value& config) + : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) { + modulesReady = true; + separateOutputs_ = config["separate-outputs"].asBool(); + + if (!gIPC) { + gIPC = std::make_unique(); + } + + queryActiveWorkspace(); + update(); + dp.emit(); + + // register for hyprland ipc + gIPC->registerForIPC("activewindow", this); + gIPC->registerForIPC("closewindow", this); + gIPC->registerForIPC("movewindow", this); + gIPC->registerForIPC("changefloatingmode", this); + gIPC->registerForIPC("fullscreen", this); +} + +WindowCount::~WindowCount() { + gIPC->unregisterForIPC(this); + // wait for possible event handler to finish + std::lock_guard lg(mutex_); +} + +auto WindowCount::update() -> void { + // fix ampersands + std::lock_guard lg(mutex_); + + std::string windowName = waybar::util::sanitize_string(workspace_.last_window_title); + std::string windowAddress = workspace_.last_window; + + windowData_.title = windowName; + + if (!format_.empty()) { + label_.show(); + label_.set_markup(waybar::util::rewriteString( + fmt::format(fmt::runtime(format_), fmt::arg("title", windowName), + fmt::arg("initialTitle", windowData_.initial_title), + fmt::arg("class", windowData_.class_name), + fmt::arg("initialClass", windowData_.initial_class_name)), + config_["rewrite"])); + } else { + label_.hide(); + } + + if (focused_) { + image_.show(); + } else { + image_.hide(); + } + + setClass("empty", workspace_.windows == 0); + setClass("solo", solo_); + setClass("floating", allFloating_); + setClass("swallowing", swallowing_); + setClass("fullscreen", fullscreen_); + + if (!lastSoloClass_.empty() && soloClass_ != lastSoloClass_) { + if (bar_.window.get_style_context()->has_class(lastSoloClass_)) { + bar_.window.get_style_context()->remove_class(lastSoloClass_); + spdlog::trace("Removing solo class: {}", lastSoloClass_); + } + } + + if (!soloClass_.empty() && soloClass_ != lastSoloClass_) { + bar_.window.get_style_context()->add_class(soloClass_); + spdlog::trace("Adding solo class: {}", soloClass_); + } + lastSoloClass_ = soloClass_; + + AAppIconLabel::update(); +} + +auto WindowCount::getActiveWorkspace() -> Workspace { + const auto workspace = gIPC->getSocket1JsonReply("activeworkspace"); + + if (workspace.isObject()) { + return Workspace::parse(workspace); + } + + return {}; +} + +auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspace { + const auto monitors = gIPC->getSocket1JsonReply("monitors"); + if (monitors.isArray()) { + auto monitor = std::find_if(monitors.begin(), monitors.end(), [&](Json::Value monitor) { + return monitor["name"] == monitorName; + }); + if (monitor == std::end(monitors)) { + spdlog::warn("Monitor not found: {}", monitorName); + return Workspace{-1, 0, "", ""}; + } + const int id = (*monitor)["activeWorkspace"]["id"].asInt(); + + const auto workspaces = gIPC->getSocket1JsonReply("workspaces"); + if (workspaces.isArray()) { + auto workspace = std::find_if(workspaces.begin(), workspaces.end(), + [&](Json::Value workspace) { return workspace["id"] == id; }); + if (workspace == std::end(workspaces)) { + spdlog::warn("No workspace with id {}", id); + return Workspace{-1, 0, "", ""}; + } + return Workspace::parse(*workspace); + }; + }; + + return {}; +} + +auto WindowCount::Workspace::parse(const Json::Value& value) -> WindowCount::Workspace { + return Workspace{ + value["id"].asInt(), + value["windows"].asInt(), + value["lastwindow"].asString(), + value["lastwindowtitle"].asString(), + }; +} + +auto WindowCount::WindowCountData::parse(const Json::Value& value) -> WindowCount::WindowCountData { + return WindowCountData{value["floating"].asBool(), value["monitor"].asInt(), + value["class"].asString(), value["initialClass"].asString(), + value["title"].asString(), value["initialTitle"].asString(), + value["fullscreen"].asBool(), !value["grouped"].empty()}; +} + +void WindowCount::queryActiveWorkspace() { + std::lock_guard lg(mutex_); + + if (separateOutputs_) { + workspace_ = getActiveWorkspace(this->bar_.output->name); + } else { + workspace_ = getActiveWorkspace(); + } + + focused_ = true; + if (workspace_.windows > 0) { + const auto clients = gIPC->getSocket1JsonReply("clients"); + if (clients.isArray()) { + auto activeWindowCount = std::find_if(clients.begin(), clients.end(), [&](Json::Value window) { + return window["address"] == workspace_.last_window; + }); + + if (activeWindowCount == std::end(clients)) { + focused_ = false; + return; + } + + windowData_ = WindowCountData::parse(*activeWindowCount); + updateAppIconName(windowData_.class_name, windowData_.initial_class_name); + std::vector workspaceWindowCounts; + std::copy_if(clients.begin(), clients.end(), std::back_inserter(workspaceWindowCounts), + [&](Json::Value window) { + return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool(); + }); + swallowing_ = + std::any_of(workspaceWindowCounts.begin(), workspaceWindowCounts.end(), [&](Json::Value window) { + return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0"; + }); + std::vector visibleWindowCounts; + std::copy_if(workspaceWindowCounts.begin(), workspaceWindowCounts.end(), + std::back_inserter(visibleWindowCounts), + [&](Json::Value window) { return !window["hidden"].asBool(); }); + solo_ = 1 == std::count_if(visibleWindowCounts.begin(), visibleWindowCounts.end(), + [&](Json::Value window) { return !window["floating"].asBool(); }); + allFloating_ = std::all_of(visibleWindowCounts.begin(), visibleWindowCounts.end(), + [&](Json::Value window) { return window["floating"].asBool(); }); + fullscreen_ = windowData_.fullscreen; + + // Fullscreen windows look like they are solo + if (fullscreen_) { + solo_ = true; + } + + // Grouped windows have a tab bar and therefore don't look fullscreen or solo + if (windowData_.grouped) { + fullscreen_ = false; + solo_ = false; + } + + if (solo_) { + soloClass_ = windowData_.class_name; + } else { + soloClass_ = ""; + } + }; + } else { + focused_ = false; + windowData_ = WindowCountData{}; + allFloating_ = false; + swallowing_ = false; + fullscreen_ = false; + solo_ = false; + soloClass_ = ""; + } +} + +void WindowCount::onEvent(const std::string& ev) { + queryActiveWorkspace(); + + dp.emit(); +} + +void WindowCount::setClass(const std::string& classname, bool enable) { + if (enable) { + if (!bar_.window.get_style_context()->has_class(classname)) { + bar_.window.get_style_context()->add_class(classname); + } + } else { + bar_.window.get_style_context()->remove_class(classname); + } +} + +} // namespace waybar::modules::hyprland From d64c80e234e69872239d3a2d4c24e8077771014f Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sat, 24 Aug 2024 00:14:30 +1000 Subject: [PATCH 018/780] temp: working implementation --- include/modules/hyprland/windowcount.hpp | 24 +---- src/modules/hyprland/windowcount.cpp | 115 +++-------------------- 2 files changed, 14 insertions(+), 125 deletions(-) diff --git a/include/modules/hyprland/windowcount.hpp b/include/modules/hyprland/windowcount.hpp index 3972c66a..d5fd9565 100644 --- a/include/modules/hyprland/windowcount.hpp +++ b/include/modules/hyprland/windowcount.hpp @@ -28,19 +28,6 @@ class WindowCount : public waybar::AAppIconLabel, public EventHandler { static auto parse(const Json::Value& value) -> Workspace; }; - struct WindowCountData { - bool floating; - int monitor = -1; - std::string class_name; - std::string initial_class_name; - std::string title; - std::string initial_title; - bool fullscreen; - bool grouped; - - static auto parse(const Json::Value&) -> WindowCountData; - }; - static auto getActiveWorkspace(const std::string&) -> Workspace; static auto getActiveWorkspace() -> Workspace; void onEvent(const std::string& ev) override; @@ -50,16 +37,9 @@ class WindowCount : public waybar::AAppIconLabel, public EventHandler { bool separateOutputs_; std::mutex mutex_; const Bar& bar_; - util::JsonParser parser_; - WindowCountData windowData_; Workspace workspace_; - std::string soloClass_; - std::string lastSoloClass_; - bool solo_; - bool allFloating_; - bool swallowing_; - bool fullscreen_; bool focused_; + int windowCount_; }; -} // namespace waybar::modules::hyprland +} // namespace waybar::modules::hyprland \ No newline at end of file diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index 52c25978..da600379 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -15,7 +15,7 @@ namespace waybar::modules::hyprland { WindowCount::WindowCount(const std::string& id, const Bar& bar, const Json::Value& config) - : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) { + : AAppIconLabel(config, "windowcount", id, "{count}", 0, true), bar_(bar) { modulesReady = true; separateOutputs_ = config["separate-outputs"].asBool(); @@ -28,11 +28,12 @@ WindowCount::WindowCount(const std::string& id, const Bar& bar, const Json::Valu dp.emit(); // register for hyprland ipc - gIPC->registerForIPC("activewindow", this); + gIPC->registerForIPC("fullscreen", this); + gIPC->registerForIPC("workspace", this); + gIPC->registerForIPC("focusedmon", this); + gIPC->registerForIPC("openwindow", this); gIPC->registerForIPC("closewindow", this); gIPC->registerForIPC("movewindow", this); - gIPC->registerForIPC("changefloatingmode", this); - gIPC->registerForIPC("fullscreen", this); } WindowCount::~WindowCount() { @@ -42,50 +43,19 @@ WindowCount::~WindowCount() { } auto WindowCount::update() -> void { - // fix ampersands std::lock_guard lg(mutex_); - std::string windowName = waybar::util::sanitize_string(workspace_.last_window_title); - std::string windowAddress = workspace_.last_window; - - windowData_.title = windowName; - if (!format_.empty()) { label_.show(); label_.set_markup(waybar::util::rewriteString( - fmt::format(fmt::runtime(format_), fmt::arg("title", windowName), - fmt::arg("initialTitle", windowData_.initial_title), - fmt::arg("class", windowData_.class_name), - fmt::arg("initialClass", windowData_.initial_class_name)), + fmt::format(fmt::runtime(format_), fmt::arg("count", workspace_.windows)), config_["rewrite"])); } else { label_.hide(); } - if (focused_) { - image_.show(); - } else { - image_.hide(); - } - - setClass("empty", workspace_.windows == 0); - setClass("solo", solo_); - setClass("floating", allFloating_); - setClass("swallowing", swallowing_); - setClass("fullscreen", fullscreen_); - - if (!lastSoloClass_.empty() && soloClass_ != lastSoloClass_) { - if (bar_.window.get_style_context()->has_class(lastSoloClass_)) { - bar_.window.get_style_context()->remove_class(lastSoloClass_); - spdlog::trace("Removing solo class: {}", lastSoloClass_); - } - } - - if (!soloClass_.empty() && soloClass_ != lastSoloClass_) { - bar_.window.get_style_context()->add_class(soloClass_); - spdlog::trace("Adding solo class: {}", soloClass_); - } - lastSoloClass_ = soloClass_; + // Display the count as the label text + label_.set_text(fmt::format("{}", workspace_.windows)); AAppIconLabel::update(); } @@ -136,13 +106,6 @@ auto WindowCount::Workspace::parse(const Json::Value& value) -> WindowCount::Wor }; } -auto WindowCount::WindowCountData::parse(const Json::Value& value) -> WindowCount::WindowCountData { - return WindowCountData{value["floating"].asBool(), value["monitor"].asInt(), - value["class"].asString(), value["initialClass"].asString(), - value["title"].asString(), value["initialTitle"].asString(), - value["fullscreen"].asBool(), !value["grouped"].empty()}; -} - void WindowCount::queryActiveWorkspace() { std::lock_guard lg(mutex_); @@ -153,70 +116,16 @@ void WindowCount::queryActiveWorkspace() { } focused_ = true; - if (workspace_.windows > 0) { - const auto clients = gIPC->getSocket1JsonReply("clients"); - if (clients.isArray()) { - auto activeWindowCount = std::find_if(clients.begin(), clients.end(), [&](Json::Value window) { - return window["address"] == workspace_.last_window; - }); + windowCount_ = workspace_.windows; - if (activeWindowCount == std::end(clients)) { - focused_ = false; - return; - } - - windowData_ = WindowCountData::parse(*activeWindowCount); - updateAppIconName(windowData_.class_name, windowData_.initial_class_name); - std::vector workspaceWindowCounts; - std::copy_if(clients.begin(), clients.end(), std::back_inserter(workspaceWindowCounts), - [&](Json::Value window) { - return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool(); - }); - swallowing_ = - std::any_of(workspaceWindowCounts.begin(), workspaceWindowCounts.end(), [&](Json::Value window) { - return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0"; - }); - std::vector visibleWindowCounts; - std::copy_if(workspaceWindowCounts.begin(), workspaceWindowCounts.end(), - std::back_inserter(visibleWindowCounts), - [&](Json::Value window) { return !window["hidden"].asBool(); }); - solo_ = 1 == std::count_if(visibleWindowCounts.begin(), visibleWindowCounts.end(), - [&](Json::Value window) { return !window["floating"].asBool(); }); - allFloating_ = std::all_of(visibleWindowCounts.begin(), visibleWindowCounts.end(), - [&](Json::Value window) { return window["floating"].asBool(); }); - fullscreen_ = windowData_.fullscreen; - - // Fullscreen windows look like they are solo - if (fullscreen_) { - solo_ = true; - } - - // Grouped windows have a tab bar and therefore don't look fullscreen or solo - if (windowData_.grouped) { - fullscreen_ = false; - solo_ = false; - } - - if (solo_) { - soloClass_ = windowData_.class_name; - } else { - soloClass_ = ""; - } - }; - } else { + if (workspace_.windows == 0) { focused_ = false; - windowData_ = WindowCountData{}; - allFloating_ = false; - swallowing_ = false; - fullscreen_ = false; - solo_ = false; - soloClass_ = ""; } } void WindowCount::onEvent(const std::string& ev) { queryActiveWorkspace(); - + update(); dp.emit(); } @@ -230,4 +139,4 @@ void WindowCount::setClass(const std::string& classname, bool enable) { } } -} // namespace waybar::modules::hyprland +} // namespace waybar::modules::hyprland \ No newline at end of file From 58e4f89a82057612ea12b688dc099203899f1d32 Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sat, 24 Aug 2024 00:40:22 +1000 Subject: [PATCH 019/780] fix: allow custom format --- src/modules/hyprland/windowcount.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index da600379..f5b180a0 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -45,18 +45,21 @@ WindowCount::~WindowCount() { auto WindowCount::update() -> void { std::lock_guard lg(mutex_); - if (!format_.empty()) { - label_.show(); + std::string format = config_["format"].asString(); + std::string formattedText; + + if (!format.empty()) { + formattedText = fmt::format(fmt::runtime(format), workspace_.windows); label_.set_markup(waybar::util::rewriteString( - fmt::format(fmt::runtime(format_), fmt::arg("count", workspace_.windows)), + formattedText, config_["rewrite"])); + label_.show(); } else { + // Default display + label_.set_text(fmt::format("{}", workspace_.windows)); label_.hide(); } - // Display the count as the label text - label_.set_text(fmt::format("{}", workspace_.windows)); - AAppIconLabel::update(); } @@ -125,7 +128,6 @@ void WindowCount::queryActiveWorkspace() { void WindowCount::onEvent(const std::string& ev) { queryActiveWorkspace(); - update(); dp.emit(); } From e40bc27257d1c9699e3291b8406c68e3da575e8b Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sat, 24 Aug 2024 00:40:41 +1000 Subject: [PATCH 020/780] fix: default separate-outputs to true --- src/modules/hyprland/windowcount.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index f5b180a0..04c62153 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -17,7 +17,7 @@ namespace waybar::modules::hyprland { WindowCount::WindowCount(const std::string& id, const Bar& bar, const Json::Value& config) : AAppIconLabel(config, "windowcount", id, "{count}", 0, true), bar_(bar) { modulesReady = true; - separateOutputs_ = config["separate-outputs"].asBool(); + separateOutputs_ = config.isMember("separate-outputs") ? config["separate-outputs"].asBool() : true; if (!gIPC) { gIPC = std::make_unique(); From 1806edcb06c167a5e75993ecbb36b7a5867d6920 Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sat, 24 Aug 2024 01:04:58 +1000 Subject: [PATCH 021/780] fix: remove unused variable --- include/modules/hyprland/windowcount.hpp | 1 - src/modules/hyprland/windowcount.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/include/modules/hyprland/windowcount.hpp b/include/modules/hyprland/windowcount.hpp index d5fd9565..f9644f5a 100644 --- a/include/modules/hyprland/windowcount.hpp +++ b/include/modules/hyprland/windowcount.hpp @@ -39,7 +39,6 @@ class WindowCount : public waybar::AAppIconLabel, public EventHandler { const Bar& bar_; Workspace workspace_; bool focused_; - int windowCount_; }; } // namespace waybar::modules::hyprland \ No newline at end of file diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index 04c62153..d1429c25 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -119,7 +119,6 @@ void WindowCount::queryActiveWorkspace() { } focused_ = true; - windowCount_ = workspace_.windows; if (workspace_.windows == 0) { focused_ = false; From 1b282e67a7fd2ef1abad474574e13efcdd1b5ffa Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sat, 24 Aug 2024 01:06:00 +1000 Subject: [PATCH 022/780] fix: remove unused attributes --- include/modules/hyprland/windowcount.hpp | 3 --- src/modules/hyprland/windowcount.cpp | 6 ++---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/include/modules/hyprland/windowcount.hpp b/include/modules/hyprland/windowcount.hpp index f9644f5a..1d283b67 100644 --- a/include/modules/hyprland/windowcount.hpp +++ b/include/modules/hyprland/windowcount.hpp @@ -22,9 +22,6 @@ class WindowCount : public waybar::AAppIconLabel, public EventHandler { struct Workspace { int id; int windows; - std::string last_window; - std::string last_window_title; - static auto parse(const Json::Value& value) -> Workspace; }; diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index d1429c25..e6e36238 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -81,7 +81,7 @@ auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspac }); if (monitor == std::end(monitors)) { spdlog::warn("Monitor not found: {}", monitorName); - return Workspace{-1, 0, "", ""}; + return Workspace{-1, 0}; } const int id = (*monitor)["activeWorkspace"]["id"].asInt(); @@ -91,7 +91,7 @@ auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspac [&](Json::Value workspace) { return workspace["id"] == id; }); if (workspace == std::end(workspaces)) { spdlog::warn("No workspace with id {}", id); - return Workspace{-1, 0, "", ""}; + return Workspace{-1, 0}; } return Workspace::parse(*workspace); }; @@ -104,8 +104,6 @@ auto WindowCount::Workspace::parse(const Json::Value& value) -> WindowCount::Wor return Workspace{ value["id"].asInt(), value["windows"].asInt(), - value["lastwindow"].asString(), - value["lastwindowtitle"].asString(), }; } From 38ffb24c526bb9cd88dbd078d9e4618a5bea9e84 Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sat, 24 Aug 2024 01:25:50 +1000 Subject: [PATCH 023/780] feat: format-fullscreen and format-windowed override added --- include/modules/hyprland/windowcount.hpp | 1 + src/modules/hyprland/windowcount.cpp | 24 ++++++++++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/include/modules/hyprland/windowcount.hpp b/include/modules/hyprland/windowcount.hpp index 1d283b67..c8cfcf6d 100644 --- a/include/modules/hyprland/windowcount.hpp +++ b/include/modules/hyprland/windowcount.hpp @@ -22,6 +22,7 @@ class WindowCount : public waybar::AAppIconLabel, public EventHandler { struct Workspace { int id; int windows; + bool hasfullscreen; static auto parse(const Json::Value& value) -> Workspace; }; diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index e6e36238..8e9e77bc 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -46,20 +46,27 @@ auto WindowCount::update() -> void { std::lock_guard lg(mutex_); std::string format = config_["format"].asString(); + std::string formatFullscreen = config_["format-fullscreen"].asString(); + std::string formatWindowed = config_["format-windowed"].asString(); std::string formattedText; - if (!format.empty()) { - formattedText = fmt::format(fmt::runtime(format), workspace_.windows); + if (workspace_.hasfullscreen && !formatFullscreen.empty()) { label_.set_markup(waybar::util::rewriteString( - formattedText, + fmt::format(fmt::runtime(formatFullscreen), workspace_.windows), + config_["rewrite"])); + } else if (!workspace_.hasfullscreen && !formatWindowed.empty()) { + label_.set_markup(waybar::util::rewriteString( + fmt::format(fmt::runtime(formatWindowed), workspace_.windows), + config_["rewrite"])); + } else if (!format.empty()) { + label_.set_markup(waybar::util::rewriteString( + fmt::format(fmt::runtime(format), workspace_.windows), config_["rewrite"])); - label_.show(); } else { - // Default display label_.set_text(fmt::format("{}", workspace_.windows)); - label_.hide(); } + label_.show(); AAppIconLabel::update(); } @@ -81,7 +88,7 @@ auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspac }); if (monitor == std::end(monitors)) { spdlog::warn("Monitor not found: {}", monitorName); - return Workspace{-1, 0}; + return Workspace{-1, 0, false}; } const int id = (*monitor)["activeWorkspace"]["id"].asInt(); @@ -91,7 +98,7 @@ auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspac [&](Json::Value workspace) { return workspace["id"] == id; }); if (workspace == std::end(workspaces)) { spdlog::warn("No workspace with id {}", id); - return Workspace{-1, 0}; + return Workspace{-1, 0, false}; } return Workspace::parse(*workspace); }; @@ -104,6 +111,7 @@ auto WindowCount::Workspace::parse(const Json::Value& value) -> WindowCount::Wor return Workspace{ value["id"].asInt(), value["windows"].asInt(), + value["hasfullscreen"].asBool(), }; } From f7e1d3425153feb99981af66962df3745bc1b045 Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sat, 24 Aug 2024 01:33:07 +1000 Subject: [PATCH 024/780] feat: added empty and fullscreen style classes --- src/modules/hyprland/windowcount.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index 8e9e77bc..d6fff2c1 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -48,7 +48,9 @@ auto WindowCount::update() -> void { std::string format = config_["format"].asString(); std::string formatFullscreen = config_["format-fullscreen"].asString(); std::string formatWindowed = config_["format-windowed"].asString(); - std::string formattedText; + + setClass("empty", workspace_.windows == 0); + setClass("fullscreen", workspace_.hasfullscreen); if (workspace_.hasfullscreen && !formatFullscreen.empty()) { label_.set_markup(waybar::util::rewriteString( From 6aa8aa3b2279fce790b68766b60b38a2905594f4 Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sat, 24 Aug 2024 12:57:10 +1000 Subject: [PATCH 025/780] fix: remove focused_ --- include/modules/hyprland/windowcount.hpp | 1 - src/modules/hyprland/windowcount.cpp | 6 ------ 2 files changed, 7 deletions(-) diff --git a/include/modules/hyprland/windowcount.hpp b/include/modules/hyprland/windowcount.hpp index c8cfcf6d..1b89d1a3 100644 --- a/include/modules/hyprland/windowcount.hpp +++ b/include/modules/hyprland/windowcount.hpp @@ -36,7 +36,6 @@ class WindowCount : public waybar::AAppIconLabel, public EventHandler { std::mutex mutex_; const Bar& bar_; Workspace workspace_; - bool focused_; }; } // namespace waybar::modules::hyprland \ No newline at end of file diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index d6fff2c1..b8142f72 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -125,12 +125,6 @@ void WindowCount::queryActiveWorkspace() { } else { workspace_ = getActiveWorkspace(); } - - focused_ = true; - - if (workspace_.windows == 0) { - focused_ = false; - } } void WindowCount::onEvent(const std::string& ev) { From a5e322ee66752307996c2fa8e6ca9e2c9add7830 Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sat, 24 Aug 2024 12:59:57 +1000 Subject: [PATCH 026/780] fix: remove rewrite --- src/modules/hyprland/windowcount.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index b8142f72..c9fc3050 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -9,7 +9,6 @@ #include #include "modules/hyprland/backend.hpp" -#include "util/rewrite_string.hpp" #include "util/sanitize_str.hpp" namespace waybar::modules::hyprland { @@ -53,17 +52,11 @@ auto WindowCount::update() -> void { setClass("fullscreen", workspace_.hasfullscreen); if (workspace_.hasfullscreen && !formatFullscreen.empty()) { - label_.set_markup(waybar::util::rewriteString( - fmt::format(fmt::runtime(formatFullscreen), workspace_.windows), - config_["rewrite"])); + label_.set_markup(fmt::format(fmt::runtime(formatFullscreen), workspace_.windows)); } else if (!workspace_.hasfullscreen && !formatWindowed.empty()) { - label_.set_markup(waybar::util::rewriteString( - fmt::format(fmt::runtime(formatWindowed), workspace_.windows), - config_["rewrite"])); + label_.set_markup(fmt::format(fmt::runtime(formatWindowed), workspace_.windows)); } else if (!format.empty()) { - label_.set_markup(waybar::util::rewriteString( - fmt::format(fmt::runtime(format), workspace_.windows), - config_["rewrite"])); + label_.set_markup(fmt::format(fmt::runtime(format), workspace_.windows)); } else { label_.set_text(fmt::format("{}", workspace_.windows)); } From 9254ef6f2f909130d3ad2dabbe9ebc479a662484 Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sat, 24 Aug 2024 13:29:46 +1000 Subject: [PATCH 027/780] docs: updated scd man pages --- man/waybar-hyprland-windowcount.5.scd | 75 ++++----------------------- 1 file changed, 10 insertions(+), 65 deletions(-) diff --git a/man/waybar-hyprland-windowcount.5.scd b/man/waybar-hyprland-windowcount.5.scd index 4e9c5d18..0b6b2171 100644 --- a/man/waybar-hyprland-windowcount.5.scd +++ b/man/waybar-hyprland-windowcount.5.scd @@ -1,89 +1,34 @@ -waybar-hyprland-window(5) +waybar-hyprland-windowcount(5) # NAME -waybar - hyprland window module +waybar - hyprland window count module # DESCRIPTION -The *window* module displays the title of the currently focused window in Hyprland. +The *windowcount* module displays the number of windows in the current Hyprland workspace. # CONFIGURATION -Addressed by *hyprland/window* +Addressed by *hyprland/windowcount* *format*: ++ typeof: string ++ - default: {title} ++ - The format, how information should be displayed. On {} the current window title is displayed. - -*rewrite*: ++ - typeof: object ++ - Rules to rewrite window title. See *rewrite rules*. + default: {} ++ + The format, how information should be displayed. On {} the current window count is displayed. *separate-outputs*: ++ typeof: bool ++ - Show the active window of the monitor the bar belongs to, instead of the focused window. - -*icon*: ++ - typeof: bool ++ - default: false ++ - Option to hide the application icon. - -*icon-size*: ++ - typeof: integer ++ - default: 24 ++ - Option to change the size of the application icon. - -# FORMAT REPLACEMENTS -See the output of "hyprctl clients" for examples - -*{title}*: The current title of the focused window. - -*{initialTitle}*: The initial title of the focused window. - -*{class}*: The current class of the focused window. - -*{initialClass}*: The initial class of the focused window. - -# REWRITE RULES - -*rewrite* is an object where keys are regular expressions and values are -rewrite rules if the expression matches. Rules may contain references to -captures of the expression. - -Regular expression and replacement follow ECMA-script rules. - -If no expression matches, the title is left unchanged. - -Invalid expressions (e.g., mismatched parentheses) are skipped. - -# EXAMPLES - -``` -"hyprland/window": { - "format": "{}", - "rewrite": { - "(.*) - Mozilla Firefox": "🌎 $1", - "(.*) - zsh": "> [$1]" - } -} -``` + default: true ++ + Show the active window count of the monitor the bar belongs to, instead of the focused window. # STYLE -- *#window* -- *window#waybar.empty #window* When no windows are in the workspace +- *#windowcount* The following classes are applied to the entire Waybar rather than just the -window widget: +windowcount widget: - *window#waybar.empty* When no windows are in the workspace -- *window#waybar.solo* When one tiled window is visible in the workspace - (floating windows may be present) -- *window#waybar.* Where ** is the *class* (e.g. *chromium*) of - the solo tiled window in the workspace (use *hyprctl clients* to see classes) -- *window#waybar.floating* When there are only floating windows in the workspace - *window#waybar.fullscreen* When there is a fullscreen window in the workspace; useful with Hyprland's *fullscreen, 1* mode -- *window#waybar.swallowing* When there is a swallowed window in the workspace From 8254bd72b72f16c37d1dad11a0e5d9045e1c423a Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sat, 24 Aug 2024 13:36:00 +1000 Subject: [PATCH 028/780] style: applied clang-format on windowcount.cpp --- src/modules/hyprland/windowcount.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index c9fc3050..001cdf6c 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -16,7 +16,8 @@ namespace waybar::modules::hyprland { WindowCount::WindowCount(const std::string& id, const Bar& bar, const Json::Value& config) : AAppIconLabel(config, "windowcount", id, "{count}", 0, true), bar_(bar) { modulesReady = true; - separateOutputs_ = config.isMember("separate-outputs") ? config["separate-outputs"].asBool() : true; + separateOutputs_ = + config.isMember("separate-outputs") ? config["separate-outputs"].asBool() : true; if (!gIPC) { gIPC = std::make_unique(); From 13bc497abd9bad3e46c4f3832d1d57b201e2b5d3 Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sat, 24 Aug 2024 15:21:04 +1000 Subject: [PATCH 029/780] style: clang-format --- include/modules/hyprland/windowcount.hpp | 2 +- src/modules/hyprland/windowcount.cpp | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/include/modules/hyprland/windowcount.hpp b/include/modules/hyprland/windowcount.hpp index 1b89d1a3..195e6a34 100644 --- a/include/modules/hyprland/windowcount.hpp +++ b/include/modules/hyprland/windowcount.hpp @@ -38,4 +38,4 @@ class WindowCount : public waybar::AAppIconLabel, public EventHandler { Workspace workspace_; }; -} // namespace waybar::modules::hyprland \ No newline at end of file +} // namespace waybar::modules::hyprland diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index 001cdf6c..68f7c3b4 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -46,16 +46,19 @@ auto WindowCount::update() -> void { std::lock_guard lg(mutex_); std::string format = config_["format"].asString(); - std::string formatFullscreen = config_["format-fullscreen"].asString(); + std::string formatEmpty = config_["format-empty"].asString(); std::string formatWindowed = config_["format-windowed"].asString(); + std::string formatFullscreen = config_["format-fullscreen"].asString(); setClass("empty", workspace_.windows == 0); setClass("fullscreen", workspace_.hasfullscreen); - if (workspace_.hasfullscreen && !formatFullscreen.empty()) { - label_.set_markup(fmt::format(fmt::runtime(formatFullscreen), workspace_.windows)); + if (workspace_.windows == 0 && !formatEmpty.empty()) { + label_.set_markup(fmt::format(fmt::runtime(formatEmpty), workspace_.windows)); } else if (!workspace_.hasfullscreen && !formatWindowed.empty()) { label_.set_markup(fmt::format(fmt::runtime(formatWindowed), workspace_.windows)); + } else if (workspace_.hasfullscreen && !formatFullscreen.empty()) { + label_.set_markup(fmt::format(fmt::runtime(formatFullscreen), workspace_.windows)); } else if (!format.empty()) { label_.set_markup(fmt::format(fmt::runtime(format), workspace_.windows)); } else { @@ -136,4 +139,4 @@ void WindowCount::setClass(const std::string& classname, bool enable) { } } -} // namespace waybar::modules::hyprland \ No newline at end of file +} // namespace waybar::modules::hyprland From b82bcdb515b9b74cdd5f69745eac151e97439c39 Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sat, 24 Aug 2024 15:21:33 +1000 Subject: [PATCH 030/780] docs: updated documentation for windowcount.5.scd --- man/waybar-hyprland-windowcount.5.scd | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/man/waybar-hyprland-windowcount.5.scd b/man/waybar-hyprland-windowcount.5.scd index 0b6b2171..0ea7de09 100644 --- a/man/waybar-hyprland-windowcount.5.scd +++ b/man/waybar-hyprland-windowcount.5.scd @@ -15,7 +15,19 @@ Addressed by *hyprland/windowcount* *format*: ++ typeof: string ++ default: {} ++ - The format, how information should be displayed. On {} the current window count is displayed. + The format for how information should be displayed. On {} the current workspace window count is displayed. + +*format-empty*: ++ + typeof: string ++ + Override the format when the workspace contains no windows window + +*format-windowed*: ++ + typeof: string ++ + Override the format when the workspace contains no fullscreen windows + +*format-fullscreen*: ++ + typeof: string ++ + Override the format when the workspace contains a fullscreen window *separate-outputs*: ++ typeof: bool ++ From fd67c6e9157464207a22bcd4021d5c3d81234c75 Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sun, 25 Aug 2024 00:05:41 +1000 Subject: [PATCH 031/780] docs: rewording of separate-outputs in man page --- man/waybar-hyprland-windowcount.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/waybar-hyprland-windowcount.5.scd b/man/waybar-hyprland-windowcount.5.scd index 0ea7de09..6a4f87d1 100644 --- a/man/waybar-hyprland-windowcount.5.scd +++ b/man/waybar-hyprland-windowcount.5.scd @@ -32,7 +32,7 @@ Addressed by *hyprland/windowcount* *separate-outputs*: ++ typeof: bool ++ default: true ++ - Show the active window count of the monitor the bar belongs to, instead of the focused window. + Show the active workspace window count of the monitor the bar belongs to, instead of the focused workspace. # STYLE From f1acea521bbacb034ae5bb7380e78c51853faa34 Mon Sep 17 00:00:00 2001 From: Lauri Niskanen Date: Sun, 7 Jul 2024 06:47:06 +0300 Subject: [PATCH 032/780] pulseaudio: Add 'sink-mapping' config option This commit adds a new optional config to the pulseaudio modules. It maps sink names to other sink names so that if the current sink is a key, the sink named by the value is considered to be the current sink instead of it. E.g. "sink-mapping": { "easyeffects_sink": "speakers_sink" } --- include/util/audio_backend.hpp | 4 +++- man/waybar-pulseaudio.5.scd | 4 ++++ src/modules/pulseaudio.cpp | 1 + src/modules/pulseaudio_slider.cpp | 3 ++- src/util/audio_backend.cpp | 17 +++++++++++++++++ 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/include/util/audio_backend.hpp b/include/util/audio_backend.hpp index 2f53103e..b9a9e0ff 100644 --- a/include/util/audio_backend.hpp +++ b/include/util/audio_backend.hpp @@ -48,6 +48,7 @@ class AudioBackend { std::string default_source_name_; std::vector ignored_sinks_; + std::map sink_mapping_; std::function on_updated_cb_ = NOOP; @@ -69,6 +70,7 @@ class AudioBackend { void changeVolume(ChangeType change_type, double step = 1, uint16_t max_volume = 100); void setIgnoredSinks(const Json::Value& config); + void setSinkMapping(const Json::Value& config); std::string getSinkPortName() const { return port_name_; } std::string getFormFactor() const { return form_factor_; } @@ -93,4 +95,4 @@ class AudioBackend { bool isBluetooth(); }; -} // namespace waybar::util \ No newline at end of file +} // namespace waybar::util diff --git a/man/waybar-pulseaudio.5.scd b/man/waybar-pulseaudio.5.scd index 232e84a0..4f585d4e 100644 --- a/man/waybar-pulseaudio.5.scd +++ b/man/waybar-pulseaudio.5.scd @@ -113,6 +113,10 @@ Additionally, you can control the volume by scrolling *up* or *down* while the c typeof: array ++ Sinks in this list will not be shown as active sink by Waybar. Entries should be the sink's description field. +*sink-mapping*: ++ + typeof: object ++ + Sinks named by the values of this mapping will be considered to be the current sink instead of the sinks named by the respective keys. + *menu*: ++ typeof: string ++ Action that popups the menu. diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index 255ca571..0189daaa 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -7,6 +7,7 @@ waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value backend = util::AudioBackend::getInstance([this] { this->dp.emit(); }); backend->setIgnoredSinks(config_["ignored-sinks"]); + backend->setSinkMapping(config_["sink-mapping"]); } bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) { diff --git a/src/modules/pulseaudio_slider.cpp b/src/modules/pulseaudio_slider.cpp index bf85584e..7e642d29 100644 --- a/src/modules/pulseaudio_slider.cpp +++ b/src/modules/pulseaudio_slider.cpp @@ -6,6 +6,7 @@ PulseaudioSlider::PulseaudioSlider(const std::string& id, const Json::Value& con : ASlider(config, "pulseaudio-slider", id) { backend = util::AudioBackend::getInstance([this] { this->dp.emit(); }); backend->setIgnoredSinks(config_["ignored-sinks"]); + backend->setSinkMapping(config_["sink-mapping"]); if (config_["target"].isString()) { std::string target = config_["target"].asString(); @@ -79,4 +80,4 @@ void PulseaudioSlider::onValueChanged() { backend->changeVolume(volume, min_, max_); } -} // namespace waybar::modules \ No newline at end of file +} // namespace waybar::modules diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index 3d90b6d5..c267816f 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -155,6 +155,13 @@ void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, i } } + if (const auto mapping = backend->sink_mapping_.find(backend->current_sink_name_); + mapping != backend->sink_mapping_.end()) { + if (i->name == mapping->second) { + backend->current_sink_name_ = i->name; + } + } + if (backend->current_sink_name_ == i->name) { backend->current_sink_running_ = i->state == PA_SINK_RUNNING; } @@ -292,4 +299,14 @@ void AudioBackend::setIgnoredSinks(const Json::Value &config) { } } +void AudioBackend::setSinkMapping(const Json::Value &config) { + if (config.isObject()) { + for (auto it = config.begin(); it != config.end(); ++it) { + if (it.key().isString() && it->isString()) { + sink_mapping_.emplace(it.key().asString(), it->asString()); + } + } + } +} + } // namespace waybar::util From 0ee519753cf832c723623fbe8ba307acb1707503 Mon Sep 17 00:00:00 2001 From: Antoine Bolvy Date: Tue, 27 Aug 2024 15:43:19 +0200 Subject: [PATCH 033/780] feat: hidpi support for image module --- src/modules/image.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/modules/image.cpp b/src/modules/image.cpp index 8274d323..5e6c1493 100644 --- a/src/modules/image.cpp +++ b/src/modules/image.cpp @@ -42,7 +42,6 @@ void waybar::modules::Image::refresh(int sig) { } auto waybar::modules::Image::update() -> void { - Glib::RefPtr pixbuf; if (config_["path"].isString()) { path_ = config_["path"].asString(); } else if (config_["exec"].isString()) { @@ -51,19 +50,24 @@ auto waybar::modules::Image::update() -> void { } else { path_ = ""; } - if (Glib::file_test(path_, Glib::FILE_TEST_EXISTS)) - pixbuf = Gdk::Pixbuf::create_from_file(path_, size_, size_); - else - pixbuf = {}; - if (pixbuf) { + if (Glib::file_test(path_, Glib::FILE_TEST_EXISTS)) { + Glib::RefPtr pixbuf; + + int scaled_icon_size = size_ * image_.get_scale_factor(); + pixbuf = Gdk::Pixbuf::create_from_file(path_, scaled_icon_size, scaled_icon_size); + + auto surface = + Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), image_.get_window()); + image_.set(surface); + image_.show(); + if (tooltipEnabled() && !tooltip_.empty()) { if (box_.get_tooltip_markup() != tooltip_) { box_.set_tooltip_markup(tooltip_); } } - image_.set(pixbuf); - image_.show(); + box_.get_style_context()->remove_class("empty"); } else { image_.clear(); From 9b5c2dc7ed059ca5304fcbe8b23b9697cc853491 Mon Sep 17 00:00:00 2001 From: Leonard Cohnen Date: Sat, 31 Aug 2024 20:33:52 +0200 Subject: [PATCH 034/780] fix: upower module selection with multiple devices While looping over all the upower devices, the currently set device that will be rendered in the waybar, is overridden. Since the loop doesn't end when the device is found, the upDevice_ is overridden with NULL in the iteration for the next device. Now we only override upDevice_ if the current device matches the constraints. Fixes d2a719d67c5427dffc3e431c41b96b60399576e6 ("Redo to minimize code duplication.") Fixes #3267 --- src/modules/upower.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/modules/upower.cpp b/src/modules/upower.cpp index 552495f8..5ee6d64c 100644 --- a/src/modules/upower.cpp +++ b/src/modules/upower.cpp @@ -384,10 +384,11 @@ void UPower::setDisplayDevice() { displayDevice = upDevice; } } - // Unref current upDevice - if (displayDevice.upDevice != NULL) g_object_unref(thisPtr->upDevice_.upDevice); - // Reassign new upDevice - thisPtr->upDevice_ = displayDevice; + // Unref current upDevice if it exists + if (displayDevice.upDevice != NULL) { + if (thisPtr->upDevice_.upDevice != NULL) g_object_unref(thisPtr->upDevice_.upDevice); + thisPtr->upDevice_ = displayDevice; + } }, this); } From 7a6960842aac3cabc8256489631fe36e219f2576 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 1 Sep 2024 00:11:30 +0000 Subject: [PATCH 035/780] 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/52ec9ac3b12395ad677e8b62106f0b98c1f8569d?narHash=sha256-veKR07psFoJjINLC8RK4DiLniGGMgF3QMlS4tb74S6k%3D' (2024-07-28) → 'github:NixOS/nixpkgs/71e91c409d1e654808b2621f28a327acfdad8dc2?narHash=sha256-GnR7/ibgIH1vhoy8cYdmXE6iyZqKqFxQSVkFgosBh6w%3D' (2024-08-28) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index b8f68f4b..9bd73acc 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1722185531, - "narHash": "sha256-veKR07psFoJjINLC8RK4DiLniGGMgF3QMlS4tb74S6k=", + "lastModified": 1724819573, + "narHash": "sha256-GnR7/ibgIH1vhoy8cYdmXE6iyZqKqFxQSVkFgosBh6w=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "52ec9ac3b12395ad677e8b62106f0b98c1f8569d", + "rev": "71e91c409d1e654808b2621f28a327acfdad8dc2", "type": "github" }, "original": { From 1fa8019ad5de59f7a9077a0d928618576581a9b8 Mon Sep 17 00:00:00 2001 From: Michal Suchanek Date: Sun, 8 Sep 2024 13:33:52 +0200 Subject: [PATCH 036/780] man: Clarify rotate option fixes: #3576 --- man/waybar-backlight.5.scd | 2 +- man/waybar-battery.5.scd | 2 +- man/waybar-bluetooth.5.scd | 2 +- man/waybar-clock.5.scd | 2 +- man/waybar-cpu.5.scd | 2 +- man/waybar-custom.5.scd | 2 +- man/waybar-disk.5.scd | 2 +- man/waybar-dwl-window.5.scd | 2 +- man/waybar-hyprland-submap.5.scd | 2 +- man/waybar-idle-inhibitor.5.scd | 2 +- man/waybar-inhibitor.5.scd | 2 +- man/waybar-jack.5.scd | 2 +- man/waybar-memory.5.scd | 2 +- man/waybar-mpd.5.scd | 2 +- man/waybar-mpris.5.scd | 2 +- man/waybar-network.5.scd | 2 +- man/waybar-pulseaudio.5.scd | 2 +- man/waybar-river-layout.5.scd | 2 +- man/waybar-river-mode.5.scd | 2 +- man/waybar-river-window.5.scd | 2 +- man/waybar-sndio.5.scd | 2 +- man/waybar-sway-mode.5.scd | 2 +- man/waybar-sway-window.5.scd | 2 +- man/waybar-temperature.5.scd | 2 +- man/waybar-wireplumber.5.scd | 2 +- 25 files changed, 25 insertions(+), 25 deletions(-) diff --git a/man/waybar-backlight.5.scd b/man/waybar-backlight.5.scd index 1f674fc0..5286c2ed 100644 --- a/man/waybar-backlight.5.scd +++ b/man/waybar-backlight.5.scd @@ -38,7 +38,7 @@ The *backlight* module displays the current backlight level. *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *states*: ++ typeof: object ++ diff --git a/man/waybar-battery.5.scd b/man/waybar-battery.5.scd index 4fe9650a..29cb7d6d 100644 --- a/man/waybar-battery.5.scd +++ b/man/waybar-battery.5.scd @@ -69,7 +69,7 @@ The *battery* module displays the current capacity and state (eg. charging) of y *rotate*: ++ typeof: integer++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *on-click*: ++ typeof: string ++ diff --git a/man/waybar-bluetooth.5.scd b/man/waybar-bluetooth.5.scd index 1783dab3..bd64f457 100644 --- a/man/waybar-bluetooth.5.scd +++ b/man/waybar-bluetooth.5.scd @@ -54,7 +54,7 @@ Addressed by *bluetooth* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-clock.5.scd b/man/waybar-clock.5.scd index 40aedd15..3947266d 100644 --- a/man/waybar-clock.5.scd +++ b/man/waybar-clock.5.scd @@ -51,7 +51,7 @@ $XDG_CONFIG_HOME/waybar/config ++ |[ *rotate* :[ integer :[ -:[ Positive value to rotate the text label +:[ Positive value to rotate the text label (in 90 degree increments) |[ *on-click* :[ string :[ diff --git a/man/waybar-cpu.5.scd b/man/waybar-cpu.5.scd index fcbd1265..6b13a563 100644 --- a/man/waybar-cpu.5.scd +++ b/man/waybar-cpu.5.scd @@ -43,7 +43,7 @@ The *cpu* module displays the current CPU utilization. *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *states*: ++ typeof: object ++ diff --git a/man/waybar-custom.5.scd b/man/waybar-custom.5.scd index 4cf3c33d..aba1c18f 100644 --- a/man/waybar-custom.5.scd +++ b/man/waybar-custom.5.scd @@ -64,7 +64,7 @@ Addressed by *custom/* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-disk.5.scd b/man/waybar-disk.5.scd index df9ca4e5..1699a511 100644 --- a/man/waybar-disk.5.scd +++ b/man/waybar-disk.5.scd @@ -29,7 +29,7 @@ Addressed by *disk* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *states*: ++ typeof: object ++ diff --git a/man/waybar-dwl-window.5.scd b/man/waybar-dwl-window.5.scd index c2f5b93e..f185c82c 100644 --- a/man/waybar-dwl-window.5.scd +++ b/man/waybar-dwl-window.5.scd @@ -19,7 +19,7 @@ Addressed by *dwl/window* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-hyprland-submap.5.scd b/man/waybar-hyprland-submap.5.scd index 64398e61..e27138e7 100644 --- a/man/waybar-hyprland-submap.5.scd +++ b/man/waybar-hyprland-submap.5.scd @@ -19,7 +19,7 @@ Addressed by *hyprland/submap* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-idle-inhibitor.5.scd b/man/waybar-idle-inhibitor.5.scd index f7677634..81a097a7 100644 --- a/man/waybar-idle-inhibitor.5.scd +++ b/man/waybar-idle-inhibitor.5.scd @@ -21,7 +21,7 @@ screensaver, also known as "presentation mode". *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-inhibitor.5.scd b/man/waybar-inhibitor.5.scd index 679a5c4b..5513cc49 100644 --- a/man/waybar-inhibitor.5.scd +++ b/man/waybar-inhibitor.5.scd @@ -25,7 +25,7 @@ See *systemd-inhibit*(1) for more information. *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-jack.5.scd b/man/waybar-jack.5.scd index 573b36c2..180143b7 100644 --- a/man/waybar-jack.5.scd +++ b/man/waybar-jack.5.scd @@ -51,7 +51,7 @@ Addressed by *jack* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-memory.5.scd b/man/waybar-memory.5.scd index 7738c576..5c368ae8 100644 --- a/man/waybar-memory.5.scd +++ b/man/waybar-memory.5.scd @@ -29,7 +29,7 @@ Addressed by *memory* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *states*: ++ typeof: object ++ diff --git a/man/waybar-mpd.5.scd b/man/waybar-mpd.5.scd index 2f1bdf20..c576a5c0 100644 --- a/man/waybar-mpd.5.scd +++ b/man/waybar-mpd.5.scd @@ -91,7 +91,7 @@ Addressed by *mpd* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-mpris.5.scd b/man/waybar-mpris.5.scd index 455fcb17..380a1a19 100644 --- a/man/waybar-mpris.5.scd +++ b/man/waybar-mpris.5.scd @@ -107,7 +107,7 @@ The *mpris* module displays currently playing media via libplayerctl. *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-network.5.scd b/man/waybar-network.5.scd index bd546916..e1cf810d 100644 --- a/man/waybar-network.5.scd +++ b/man/waybar-network.5.scd @@ -58,7 +58,7 @@ Addressed by *network* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-pulseaudio.5.scd b/man/waybar-pulseaudio.5.scd index 232e84a0..5b38e8b7 100644 --- a/man/waybar-pulseaudio.5.scd +++ b/man/waybar-pulseaudio.5.scd @@ -40,7 +40,7 @@ Additionally, you can control the volume by scrolling *up* or *down* while the c *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *states*: ++ typeof: object ++ diff --git a/man/waybar-river-layout.5.scd b/man/waybar-river-layout.5.scd index 4fb23085..1368bda9 100644 --- a/man/waybar-river-layout.5.scd +++ b/man/waybar-river-layout.5.scd @@ -21,7 +21,7 @@ Addressed by *river/layout* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-river-mode.5.scd b/man/waybar-river-mode.5.scd index 5769a9a2..b992fdaf 100644 --- a/man/waybar-river-mode.5.scd +++ b/man/waybar-river-mode.5.scd @@ -19,7 +19,7 @@ Addressed by *river/mode* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-river-window.5.scd b/man/waybar-river-window.5.scd index 7e661f43..6db9a2fa 100644 --- a/man/waybar-river-window.5.scd +++ b/man/waybar-river-window.5.scd @@ -19,7 +19,7 @@ Addressed by *river/window* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-sndio.5.scd b/man/waybar-sndio.5.scd index f8d1615d..03dfe0af 100644 --- a/man/waybar-sndio.5.scd +++ b/man/waybar-sndio.5.scd @@ -20,7 +20,7 @@ cursor is over the module, and clicking on the module toggles mute. *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-sway-mode.5.scd b/man/waybar-sway-mode.5.scd index 1fcf3cf8..8d5d7c2c 100644 --- a/man/waybar-sway-mode.5.scd +++ b/man/waybar-sway-mode.5.scd @@ -19,7 +19,7 @@ Addressed by *sway/mode* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-sway-window.5.scd b/man/waybar-sway-window.5.scd index 037e6b55..6d1e3196 100644 --- a/man/waybar-sway-window.5.scd +++ b/man/waybar-sway-window.5.scd @@ -19,7 +19,7 @@ Addressed by *sway/window* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-temperature.5.scd b/man/waybar-temperature.5.scd index eab4cbb3..541bf3af 100644 --- a/man/waybar-temperature.5.scd +++ b/man/waybar-temperature.5.scd @@ -60,7 +60,7 @@ Addressed by *temperature* *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *max-length*: ++ typeof: integer ++ diff --git a/man/waybar-wireplumber.5.scd b/man/waybar-wireplumber.5.scd index 770ff0d5..9c26ebaf 100644 --- a/man/waybar-wireplumber.5.scd +++ b/man/waybar-wireplumber.5.scd @@ -31,7 +31,7 @@ The *wireplumber* module displays the current volume reported by WirePlumber. *rotate*: ++ typeof: integer ++ - Positive value to rotate the text label. + Positive value to rotate the text label (in 90 degree increments). *states*: ++ typeof: object ++ From 5b1826d2f66b836e73643998cc1d82af125c218f Mon Sep 17 00:00:00 2001 From: Michal Suchanek Date: Sun, 8 Sep 2024 13:54:50 +0200 Subject: [PATCH 037/780] label: Add warning for invalid rotate property --- src/ALabel.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ALabel.cpp b/src/ALabel.cpp index ecb1b7ce..3cb2c590 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -45,6 +45,8 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st if (config_["rotate"].isUInt()) { rotate = config["rotate"].asUInt(); + if (not (rotate == 0 || rotate == 90 || rotate == 180 || rotate == 270)) + spdlog::warn("'rotate' is only supported in 90 degree increments {} is not valid.", rotate); label_.set_angle(rotate); } From 70f3c1d9e95e1886181604298e71993888844ff3 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Tue, 10 Sep 2024 01:16:42 +0900 Subject: [PATCH 038/780] chore: update power_profiles_daemon.cpp minor fix --- src/modules/power_profiles_daemon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/power_profiles_daemon.cpp b/src/modules/power_profiles_daemon.cpp index eaa47023..3ae3ae83 100644 --- a/src/modules/power_profiles_daemon.cpp +++ b/src/modules/power_profiles_daemon.cpp @@ -204,7 +204,7 @@ void PowerProfilesDaemon::setPropCb(Glib::RefPtr& r) { auto _ = powerProfilesProxy_->call_finish(r); dp.emit(); } catch (const std::exception& e) { - spdlog::error("Failed to set the the active power profile: {}", e.what()); + spdlog::error("Failed to set the active power profile: {}", e.what()); } catch (const Glib::Error& e) { spdlog::error("Failed to set the active power profile: {}", std::string(e.what())); } From c2f1a7894b0b99779a482fcc85d30861f461e21a Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 13 Sep 2024 08:40:19 +0200 Subject: [PATCH 039/780] chore: update deps --- subprojects/catch2.wrap | 12 ++++++------ subprojects/fmt.wrap | 18 +++++++++--------- subprojects/spdlog.wrap | 18 +++++++++--------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/subprojects/catch2.wrap b/subprojects/catch2.wrap index f2dfd57c..489db6c6 100644 --- a/subprojects/catch2.wrap +++ b/subprojects/catch2.wrap @@ -1,10 +1,10 @@ [wrap-file] -directory = Catch2-3.5.1 -source_url = https://github.com/catchorg/Catch2/archive/v3.5.1.tar.gz -source_filename = Catch2-3.5.1.tar.gz -source_hash = 49c3ca7a68f1c8ec71307736bc6ed14fec21631707e1be9af45daf4037e75a08 -# source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/catch2_3.4.0-1/Catch2-3.4.0.tar.gz -# wrapdb_version = 3.4.0-1 +directory = Catch2-3.7.0 +source_url = https://github.com/catchorg/Catch2/archive/v3.7.0.tar.gz +source_filename = Catch2-3.7.0.tar.gz +source_hash = 5b10cd536fa3818112a82820ce0787bd9f2a906c618429e7c4dea639983c8e88 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/catch2_3.7.0-1/Catch2-3.7.0.tar.gz +wrapdb_version = 3.7.0-1 [provide] catch2 = catch2_dep diff --git a/subprojects/fmt.wrap b/subprojects/fmt.wrap index 9efe101e..42b61596 100644 --- a/subprojects/fmt.wrap +++ b/subprojects/fmt.wrap @@ -1,13 +1,13 @@ [wrap-file] -directory = fmt-9.1.0 -source_url = https://github.com/fmtlib/fmt/archive/9.1.0.tar.gz -source_filename = fmt-9.1.0.tar.gz -source_hash = 5dea48d1fcddc3ec571ce2058e13910a0d4a6bab4cc09a809d8b1dd1c88ae6f2 -patch_filename = fmt_9.1.0-2_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/fmt_9.1.0-2/get_patch -patch_hash = 23e8c4829f3e63f509b5643fe6bb87cbed39eae9594c451b338475d14d051967 -source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_9.1.0-2/fmt-9.1.0.tar.gz -wrapdb_version = 9.1.0-2 +directory = fmt-11.0.1 +source_url = https://github.com/fmtlib/fmt/archive/11.0.1.tar.gz +source_filename = fmt-11.0.1.tar.gz +source_hash = 7d009f7f89ac84c0a83f79ed602463d092fbf66763766a907c97fd02b100f5e9 +patch_filename = fmt_11.0.1-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/fmt_11.0.1-1/get_patch +patch_hash = 0a8b93d1ee6d84a82d3872a9bfb4c3977d8a53f7f484d42d1f7ed63ed496d549 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_11.0.1-1/fmt-11.0.1.tar.gz +wrapdb_version = 11.0.1-1 [provide] fmt = fmt_dep diff --git a/subprojects/spdlog.wrap b/subprojects/spdlog.wrap index 08004c90..af00d5a7 100644 --- a/subprojects/spdlog.wrap +++ b/subprojects/spdlog.wrap @@ -1,13 +1,13 @@ [wrap-file] -directory = spdlog-1.12.0 -source_url = https://github.com/gabime/spdlog/archive/refs/tags/v1.12.0.tar.gz -source_filename = spdlog-1.12.0.tar.gz -source_hash = 4dccf2d10f410c1e2feaff89966bfc49a1abb29ef6f08246335b110e001e09a9 -patch_filename = spdlog_1.12.0-2_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/spdlog_1.12.0-2/get_patch -patch_hash = 9596972d1eb2e0a69cea4a53273ca7bbbcb9b2fa872cd734864fc7232dc2d573 -source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/spdlog_1.12.0-2/spdlog-1.12.0.tar.gz -wrapdb_version = 1.12.0-2 +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 [provide] spdlog = spdlog_dep From 64d99a588406e5bba88472f25f814e58406fc836 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 13 Sep 2024 08:50:07 +0200 Subject: [PATCH 040/780] chore(fmt): std format --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 8daa6c9c..834ceb75 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']) +spdlog = dependency('spdlog', version : ['>=1.10.0'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=enabled', 'std_format=enabled']) wayland_client = dependency('wayland-client') wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols') From 6417782af6a88a7d2dd8f355d10d755f32e1d402 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 13 Sep 2024 08:55:14 +0200 Subject: [PATCH 041/780] chore: lint --- .github/workflows/clang-format.yml | 1 + include/AModule.hpp | 2 +- include/modules/clock.hpp | 4 ++-- include/modules/hyprland/workspaces.hpp | 4 ++-- include/util/clara.hpp | 12 ++++++------ include/util/format.hpp | 2 +- src/modules/bluetooth.cpp | 17 ++++++++--------- src/modules/clock.cpp | 4 ++-- src/modules/dwl/tags.cpp | 4 ++-- src/modules/hyprland/workspaces.cpp | 4 ++-- src/modules/memory/bsd.cpp | 8 ++++---- src/modules/wlr/workspace_manager.cpp | 4 ++-- test/utils/SafeSignal.cpp | 2 +- 13 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 40fd3126..a7b5d896 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -16,4 +16,5 @@ jobs: with: source: "." extensions: "hpp,h,cpp,c" + style: "file:.clang-format" clangFormatVersion: 16 diff --git a/include/AModule.hpp b/include/AModule.hpp index facb3130..94a88371 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -16,7 +16,7 @@ class AModule : public IModule { ~AModule() override; auto update() -> void override; - virtual auto refresh(int shouldRefresh) -> void{}; + virtual auto refresh(int shouldRefresh) -> void {}; operator Gtk::Widget &() override; auto doAction(const std::string &name) -> void override; diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index c212ec8b..0c62b676 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -51,8 +51,8 @@ class Clock final : public ALabel { day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight) std::string cldText_{""}; // calendar text to print CldMode cldMode_{CldMode::MONTH}; - auto get_calendar(const year_month_day& today, const year_month_day& ymd, const time_zone* tz) - -> const std::string; + auto get_calendar(const year_month_day& today, const year_month_day& ymd, + const time_zone* tz) -> const std::string; // get local time zone auto local_zone() -> const time_zone*; diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index f5c20f69..a9d56b79 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -61,8 +61,8 @@ class Workspaces : public AModule, public EventHandler { // Config void parseConfig(const Json::Value& config); auto populateIconsMap(const Json::Value& formatIcons) -> void; - static auto populateBoolConfig(const Json::Value& config, const std::string& key, bool& member) - -> void; + static auto populateBoolConfig(const Json::Value& config, const std::string& key, + bool& member) -> void; auto populateSortByConfig(const Json::Value& config) -> void; auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void; auto populateFormatWindowSeparatorConfig(const Json::Value& config) -> void; diff --git a/include/util/clara.hpp b/include/util/clara.hpp index 73fa5415..da7151fe 100644 --- a/include/util/clara.hpp +++ b/include/util/clara.hpp @@ -622,8 +622,8 @@ inline auto convertInto(std::string const &source, bool &target) -> ParserResult } #ifdef CLARA_CONFIG_OPTIONAL_TYPE template -inline auto convertInto(std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE &target) - -> ParserResult { +inline auto convertInto(std::string const &source, + CLARA_CONFIG_OPTIONAL_TYPE &target) -> ParserResult { T temp; auto result = convertInto(source, temp); if (result) target = std::move(temp); @@ -751,8 +751,8 @@ class ParserBase { public: virtual ~ParserBase() = default; virtual auto validate() const -> Result { return Result::ok(); } - virtual auto parse(std::string const &exeName, TokenStream const &tokens) const - -> InternalParseResult = 0; + virtual auto parse(std::string const &exeName, + TokenStream const &tokens) const -> InternalParseResult = 0; virtual auto cardinality() const -> size_t { return 1; } auto parse(Args const &args) const -> InternalParseResult { @@ -1098,8 +1098,8 @@ struct Parser : ParserBase { using ParserBase::parse; - auto parse(std::string const &exeName, TokenStream const &tokens) const - -> InternalParseResult override { + auto parse(std::string const &exeName, + TokenStream const &tokens) const -> InternalParseResult override { struct ParserInfo { ParserBase const *parser = nullptr; size_t count = 0; diff --git a/include/util/format.hpp b/include/util/format.hpp index cf8d706b..c8ed837a 100644 --- a/include/util/format.hpp +++ b/include/util/format.hpp @@ -6,7 +6,7 @@ class pow_format { public: pow_format(long long val, std::string&& unit, bool binary = false) - : val_(val), unit_(unit), binary_(binary){}; + : val_(val), unit_(unit), binary_(binary) {}; long long val_; std::string unit_; diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp index 06475a2e..c8f1f996 100644 --- a/src/modules/bluetooth.cpp +++ b/src/modules/bluetooth.cpp @@ -49,8 +49,8 @@ auto getBoolProperty(GDBusProxy* proxy, const char* property_name) -> bool { return false; } -auto getOptionalStringProperty(GDBusProxy* proxy, const char* property_name) - -> std::optional { +auto getOptionalStringProperty(GDBusProxy* proxy, + const char* property_name) -> std::optional { auto gvar = g_dbus_proxy_get_cached_property(proxy, property_name); if (gvar) { std::string property_value = g_variant_get_string(gvar, NULL); @@ -345,8 +345,8 @@ auto waybar::modules::Bluetooth::onInterfaceAddedOrRemoved(GDBusObjectManager* m auto waybar::modules::Bluetooth::onInterfaceProxyPropertiesChanged( GDBusObjectManagerClient* manager, GDBusObjectProxy* object_proxy, GDBusProxy* interface_proxy, - GVariant* changed_properties, const gchar* const* invalidated_properties, gpointer user_data) - -> void { + GVariant* changed_properties, const gchar* const* invalidated_properties, + gpointer user_data) -> void { std::string interface_name = g_dbus_proxy_get_interface_name(interface_proxy); std::string object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object_proxy)); @@ -395,8 +395,8 @@ auto waybar::modules::Bluetooth::getDeviceBatteryPercentage(GDBusObject* object) return std::nullopt; } -auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, DeviceInfo& device_info) - -> bool { +auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, + DeviceInfo& device_info) -> bool { GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Device1")); if (proxy_device != NULL) { @@ -462,9 +462,8 @@ auto waybar::modules::Bluetooth::findCurController() -> std::optional& connected_devices) - -> void { +auto waybar::modules::Bluetooth::findConnectedDevices( + const std::string& cur_controller_path, std::vector& connected_devices) -> void { GList* objects = g_dbus_object_manager_get_objects(manager_.get()); for (GList* l = objects; l != NULL; l = l->next) { GDBusObject* object = G_DBUS_OBJECT(l->data); diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 7a4cb9c2..db2979eb 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -198,8 +198,8 @@ const unsigned cldRowsInMonth(const year_month& ym, const weekday& firstdow) { return 2u + ceil((weekday{ym / 1} - firstdow) + ((ym / last).day() - day{0})).count(); } -auto cldGetWeekForLine(const year_month& ym, const weekday& firstdow, const unsigned line) - -> const year_month_weekday { +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]; diff --git a/src/modules/dwl/tags.cpp b/src/modules/dwl/tags.cpp index 085b8224..f8b250c8 100644 --- a/src/modules/dwl/tags.cpp +++ b/src/modules/dwl/tags.cpp @@ -53,8 +53,8 @@ static void set_layout(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t // Intentionally empty } -static void appid(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *appid){ - // Intentionally empty +static void appid(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *appid) { + // Intentionally empty }; static const zdwl_ipc_output_v2_listener output_status_listener_impl{ diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 047703cc..13364f3f 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -590,8 +590,8 @@ auto Workspaces::populateIconsMap(const Json::Value &formatIcons) -> void { m_iconsMap.emplace("", ""); } -auto Workspaces::populateBoolConfig(const Json::Value &config, const std::string &key, bool &member) - -> void { +auto Workspaces::populateBoolConfig(const Json::Value &config, const std::string &key, + bool &member) -> void { const auto &configValue = config[key]; if (configValue.isBool()) { member = configValue.asBool(); diff --git a/src/modules/memory/bsd.cpp b/src/modules/memory/bsd.cpp index 67f9fed7..1d970e8a 100644 --- a/src/modules/memory/bsd.cpp +++ b/src/modules/memory/bsd.cpp @@ -21,13 +21,13 @@ static uint64_t get_total_memory() { u_long physmem; #endif int mib[] = { - CTL_HW, + CTL_HW, #if defined(HW_MEMSIZE) - HW_MEMSIZE, + HW_MEMSIZE, #elif defined(HW_PHYSMEM64) - HW_PHYSMEM64, + HW_PHYSMEM64, #else - HW_PHYSMEM, + HW_PHYSMEM, #endif }; u_int miblen = sizeof(mib) / sizeof(mib[0]); diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index f556a161..3c630d81 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -118,8 +118,8 @@ auto WorkspaceManager::sort_workspaces() -> void { } } -auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version) - -> void { +auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, + uint32_t version) -> void { if (workspace_manager_) { spdlog::warn("Register workspace manager again although already registered!"); return; diff --git a/test/utils/SafeSignal.cpp b/test/utils/SafeSignal.cpp index 341e8e2e..e7e096b0 100644 --- a/test/utils/SafeSignal.cpp +++ b/test/utils/SafeSignal.cpp @@ -71,7 +71,7 @@ struct TestObject { unsigned copied = 0; unsigned moved = 0; - TestObject(const T& v) : value(v){}; + TestObject(const T& v) : value(v) {}; ~TestObject() = default; TestObject(const TestObject& other) From 4354da284969097f5d7ab2382cb9fe60a7f1a016 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 13 Sep 2024 08:59:36 +0200 Subject: [PATCH 042/780] chore: disable fmt tests --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 834ceb75..ed5ec7b8 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=enabled']) +spdlog = dependency('spdlog', version : ['>=1.10.0'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=enabled', 'std_format=disabled', 'tests=disabled']) wayland_client = dependency('wayland-client') wayland_cursor = dependency('wayland-cursor') wayland_protos = dependency('wayland-protocols') From 3ade275d100a4e53e6ac75912ea299d2965df250 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 13 Sep 2024 09:02:23 +0200 Subject: [PATCH 043/780] fix: version --- .github/workflows/clang-format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index a7b5d896..4a774dbd 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -17,4 +17,4 @@ jobs: source: "." extensions: "hpp,h,cpp,c" style: "file:.clang-format" - clangFormatVersion: 16 + clangFormatVersion: 18 From d623a89cd17b89d8e69d8185170c9a05413fac30 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 13 Sep 2024 09:33:40 +0200 Subject: [PATCH 044/780] fix: sni item fmt --- src/modules/image.cpp | 4 ++-- src/modules/sni/item.cpp | 21 +++++++++++++-------- subprojects/gtk-layer-shell.wrap | 8 ++++---- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/modules/image.cpp b/src/modules/image.cpp index 5e6c1493..71e93b94 100644 --- a/src/modules/image.cpp +++ b/src/modules/image.cpp @@ -57,8 +57,8 @@ auto waybar::modules::Image::update() -> void { int scaled_icon_size = size_ * image_.get_scale_factor(); pixbuf = Gdk::Pixbuf::create_from_file(path_, scaled_icon_size, scaled_icon_size); - auto surface = - Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), image_.get_window()); + auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), + image_.get_window()); image_.set(surface); image_.show(); diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 6c4ec8c0..126545b1 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -104,9 +104,11 @@ void Item::proxyReady(Glib::RefPtr& result) { this->updateImage(); } catch (const Glib::Error& err) { - spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); + spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, + std::string(err.what())); } catch (const std::exception& err) { - spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); + spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, + std::string(err.what())); } } @@ -124,14 +126,15 @@ ToolTip get_variant(const Glib::VariantBase& value) { result.text = get_variant(container.get_child(2)); auto description = get_variant(container.get_child(3)); if (!description.empty()) { - result.text = fmt::format("{}\n{}", result.text, description); + result.text = fmt::format("{}\n{}", std::string(result.text), std::string(description)); } return result; } void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { try { - spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, name, value); + spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, + std::string(name), get_variant(value)); if (name == "Category") { category = get_variant(value); @@ -176,10 +179,12 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { } } catch (const Glib::Error& err) { spdlog::warn("Failed to set tray item property: {}.{}, value = {}, err = {}", - id.empty() ? bus_name : id, name, value, err.what()); + id.empty() ? bus_name : id, std::string(name), get_variant(value), + std::string(err.what())); } catch (const std::exception& err) { spdlog::warn("Failed to set tray item property: {}.{}, value = {}, err = {}", - id.empty() ? bus_name : id, name, value, err.what()); + id.empty() ? bus_name : id, std::string(name), get_variant(value), + std::string(err.what())); } } @@ -221,9 +226,9 @@ void Item::processUpdatedProperties(Glib::RefPtr& _result) { this->updateImage(); } catch (const Glib::Error& err) { - spdlog::warn("Failed to update properties: {}", err.what()); + spdlog::warn("Failed to update properties: {}", std::string(err.what())); } catch (const std::exception& err) { - spdlog::warn("Failed to update properties: {}", err.what()); + spdlog::warn("Failed to update properties: {}", std::string(err.what())); } update_pending_.clear(); } diff --git a/subprojects/gtk-layer-shell.wrap b/subprojects/gtk-layer-shell.wrap index cb730345..fc0ddf74 100644 --- a/subprojects/gtk-layer-shell.wrap +++ b/subprojects/gtk-layer-shell.wrap @@ -1,5 +1,5 @@ [wrap-file] -directory = gtk-layer-shell-0.8.2 -source_filename = gtk-layer-shell-0.8.2.tar.gz -source_hash = 254dd246303127c5d5236ea640f01a82e35d2d652a48d139dd669c832a0f0dce -source_url = https://github.com/wmww/gtk-layer-shell/archive/v0.8.2/gtk-layer-shell-0.8.2.tar.gz +directory = gtk-layer-shell-0.9.0 +source_filename = gtk-layer-shell-0.9.0.tar.gz +source_hash = 3809e5565d9ed02e44bb73787ff218523e8760fef65830afe60ea7322e22da1c +source_url = https://github.com/wmww/gtk-layer-shell/archive/v0.9.0/gtk-layer-shell-0.9.0.tar.gz From 46e7ed35de6d105d7f65b5172a69250a57af4462 Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Thu, 29 Aug 2024 11:45:17 +0300 Subject: [PATCH 045/780] Add niri/workspaces, niri/window, niri/language --- README.md | 1 + include/modules/niri/backend.hpp | 52 ++++++ include/modules/niri/language.hpp | 38 +++++ include/modules/niri/window.hpp | 28 ++++ include/modules/niri/workspaces.hpp | 30 ++++ man/waybar-niri-language.5.scd | 58 +++++++ man/waybar-niri-window.5.scd | 81 +++++++++ man/waybar-niri-workspaces.5.scd | 97 +++++++++++ man/waybar.5.scd.in | 3 + meson.build | 15 ++ src/factory.cpp | 16 ++ src/modules/niri/backend.cpp | 249 ++++++++++++++++++++++++++++ src/modules/niri/language.cpp | 138 +++++++++++++++ src/modules/niri/window.cpp | 121 ++++++++++++++ src/modules/niri/workspaces.cpp | 204 +++++++++++++++++++++++ 15 files changed, 1131 insertions(+) create mode 100644 include/modules/niri/backend.hpp create mode 100644 include/modules/niri/language.hpp create mode 100644 include/modules/niri/window.hpp create mode 100644 include/modules/niri/workspaces.hpp create mode 100644 man/waybar-niri-language.5.scd create mode 100644 man/waybar-niri-window.5.scd create mode 100644 man/waybar-niri-workspaces.5.scd create mode 100644 src/modules/niri/backend.cpp create mode 100644 src/modules/niri/language.cpp create mode 100644 src/modules/niri/window.cpp create mode 100644 src/modules/niri/workspaces.cpp diff --git a/README.md b/README.md index a019eb6f..55a6c7d9 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ - Sway (Workspaces, Binding mode, Focused window name) - River (Mapping mode, Tags, Focused window name) - Hyprland (Window Icons, Workspaces, Focused window name) +- Niri (Workspaces, Focused window name, Language) - DWL (Tags, Focused window name) [requires dwl ipc patch](https://github.com/djpohly/dwl/wiki/ipc) - Tray [#21](https://github.com/Alexays/Waybar/issues/21) - Local time diff --git a/include/modules/niri/backend.hpp b/include/modules/niri/backend.hpp new file mode 100644 index 00000000..01af5017 --- /dev/null +++ b/include/modules/niri/backend.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include + +#include "util/json.hpp" + +namespace waybar::modules::niri { + +class EventHandler { + public: + virtual void onEvent(const Json::Value& ev) = 0; + virtual ~EventHandler() = default; +}; + +class IPC { + public: + IPC() { startIPC(); } + + void registerForIPC(const std::string& ev, EventHandler* ev_handler); + void unregisterForIPC(EventHandler* handler); + + static Json::Value send(const Json::Value& request); + + // The data members are only safe to access while dataMutex_ is locked. + std::lock_guard lockData() { return std::lock_guard(dataMutex_); } + const std::vector &workspaces() const { return workspaces_; } + const std::vector &windows() const { return windows_; } + const std::vector &keyboardLayoutNames() const { return keyboardLayoutNames_; } + unsigned keyboardLayoutCurrent() const { return keyboardLayoutCurrent_; } + + private: + void startIPC(); + static int connectToSocket(); + void parseIPC(const std::string&); + + std::mutex dataMutex_; + std::vector workspaces_; + std::vector windows_; + std::vector keyboardLayoutNames_; + unsigned keyboardLayoutCurrent_; + + util::JsonParser parser_; + std::mutex callbackMutex_; + std::list> callbacks_; +}; + +inline std::unique_ptr gIPC; + +}; // namespace waybar::modules::niri diff --git a/include/modules/niri/language.hpp b/include/modules/niri/language.hpp new file mode 100644 index 00000000..1cecd206 --- /dev/null +++ b/include/modules/niri/language.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include "ALabel.hpp" +#include "bar.hpp" +#include "modules/niri/backend.hpp" + +namespace waybar::modules::niri { + +class Language : public ALabel, public EventHandler { + public: + Language(const std::string&, const Bar&, const Json::Value&); + ~Language() override; + void update() override; + + private: + void updateFromIPC(); + void onEvent(const Json::Value &ev) override; + void doUpdate(); + + struct Layout { + std::string full_name; + std::string short_name; + std::string variant; + std::string short_description; + }; + + static Layout getLayout(const std::string &fullName); + + std::mutex mutex_; + const Bar &bar_; + + std::vector layouts_; + unsigned current_idx_; +}; + +} // namespace waybar::modules::niri diff --git a/include/modules/niri/window.hpp b/include/modules/niri/window.hpp new file mode 100644 index 00000000..909ae6f0 --- /dev/null +++ b/include/modules/niri/window.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include "AAppIconLabel.hpp" +#include "bar.hpp" +#include "modules/niri/backend.hpp" + +namespace waybar::modules::niri { + +class Window : public AAppIconLabel, public EventHandler { + public: + Window(const std::string &, const Bar &, const Json::Value &); + ~Window() override; + void update() override; + + private: + void onEvent(const Json::Value &ev) override; + void doUpdate(); + void setClass(const std::string &className, bool enable); + + const Bar &bar_; + + std::string oldAppId_; +}; + +} // namespace waybar::modules::niri diff --git a/include/modules/niri/workspaces.hpp b/include/modules/niri/workspaces.hpp new file mode 100644 index 00000000..a6850ed1 --- /dev/null +++ b/include/modules/niri/workspaces.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include "AModule.hpp" +#include "bar.hpp" +#include "modules/niri/backend.hpp" + +namespace waybar::modules::niri { + +class Workspaces : public AModule, public EventHandler { + public: + Workspaces(const std::string &, const Bar &, const Json::Value &); + ~Workspaces() override; + void update() override; + + private: + void onEvent(const Json::Value &ev) override; + void doUpdate(); + Gtk::Button &addButton(const Json::Value &ws); + std::string getIcon(const std::string &value, const Json::Value &ws); + + const Bar &bar_; + Gtk::Box box_; + // Map from niri workspace id to button. + std::unordered_map buttons_; +}; + +} // namespace waybar::modules::niri diff --git a/man/waybar-niri-language.5.scd b/man/waybar-niri-language.5.scd new file mode 100644 index 00000000..6895d25c --- /dev/null +++ b/man/waybar-niri-language.5.scd @@ -0,0 +1,58 @@ +waybar-niri-language(5) + +# NAME + +waybar - niri language module + +# DESCRIPTION + +The *language* module displays the currently selected language in niri. + +# CONFIGURATION + +Addressed by *niri/language* + +*format*: ++ + typeof: string ++ + default: {} ++ + The format, how information should be displayed. + +*format-* ++ + typeof: string++ + Provide an alternative name to display per language where is the language of your choosing. Can be passed multiple times with multiple languages as shown by the example below. + +*menu*: ++ + typeof: string ++ + Action that popups the menu. + +*menu-file*: ++ + typeof: string ++ + Location of the menu descriptor file. There need to be an element of type GtkMenu with id *menu* + +*menu-actions*: ++ + typeof: array ++ + The actions corresponding to the buttons of the menu. + +# FORMAT REPLACEMENTS + +*{short}*: Short name of layout (e.g. "us"). Equals to {}. + +*{shortDescription}*: Short description of layout (e.g. "en"). + +*{long}*: Long name of layout (e.g. "English (Dvorak)"). + +*{variant}*: Variant of layout (e.g. "dvorak"). + +# EXAMPLES + +``` +"niri/language": { + "format": "Lang: {long}" + "format-en": "AMERICA, HELL YEAH!" + "format-tr": "As bayrakları" +} +``` + +# STYLE + +- *#language* diff --git a/man/waybar-niri-window.5.scd b/man/waybar-niri-window.5.scd new file mode 100644 index 00000000..9e2e9f63 --- /dev/null +++ b/man/waybar-niri-window.5.scd @@ -0,0 +1,81 @@ +waybar-niri-window(5) + +# NAME + +waybar - niri window module + +# DESCRIPTION + +The *window* module displays the title of the currently focused window in niri. + +# CONFIGURATION + +Addressed by *niri/window* + +*format*: ++ + typeof: string ++ + default: {title} ++ + The format, how information should be displayed. On {} the current window title is displayed. + +*rewrite*: ++ + typeof: object ++ + Rules to rewrite window title. See *rewrite rules*. + +*separate-outputs*: ++ + typeof: bool ++ + Show the active window of the monitor the bar belongs to, instead of the focused window. + +*icon*: ++ + typeof: bool ++ + default: false ++ + Option to hide the application icon. + +*icon-size*: ++ + typeof: integer ++ + default: 24 ++ + Option to change the size of the application icon. + +# FORMAT REPLACEMENTS + +See the output of "niri msg windows" for examples + +*{title}*: The current title of the focused window. + +*{app_id}*: The current app ID of the focused window. + +# REWRITE RULES + +*rewrite* is an object where keys are regular expressions and values are +rewrite rules if the expression matches. Rules may contain references to +captures of the expression. + +Regular expression and replacement follow ECMA-script rules. + +If no expression matches, the title is left unchanged. + +Invalid expressions (e.g., mismatched parentheses) are skipped. + +# EXAMPLES + +``` +"niri/window": { + "format": "{}", + "rewrite": { + "(.*) - Mozilla Firefox": "🌎 $1", + "(.*) - zsh": "> [$1]" + } +} +``` + +# STYLE + +- *#window* +- *window#waybar.empty #window* When no windows are on the workspace + +The following classes are applied to the entire Waybar rather than just the +window widget: + +- *window#waybar.empty* When no windows are in the workspace +- *window#waybar.solo* When only one window is on the workspace +- *window#waybar.* Where *app-id* is the app ID of the only window on + the workspace diff --git a/man/waybar-niri-workspaces.5.scd b/man/waybar-niri-workspaces.5.scd new file mode 100644 index 00000000..50e497cd --- /dev/null +++ b/man/waybar-niri-workspaces.5.scd @@ -0,0 +1,97 @@ +waybar-niri-workspaces(5) + +# NAME + +waybar - niri workspaces module + +# DESCRIPTION + +The *workspaces* module displays the currently used workspaces in niri. + +# CONFIGURATION + +Addressed by *niri/workspaces* + +*all-outputs*: ++ + typeof: bool ++ + default: false ++ + If set to false, workspaces will only be shown on the output they are on. If set to true all workspaces will be shown on every output. + +*format*: ++ + typeof: string ++ + default: {value} ++ + The format, how information should be displayed. + +*format-icons*: ++ + typeof: array ++ + Based on the workspace name, index and state, the corresponding icon gets selected. See *icons*. + +*disable-click*: ++ + typeof: bool ++ + default: false ++ + If set to false, you can click to change workspace. If set to true this behaviour is disabled. + +*disable-markup*: ++ + typeof: bool ++ + default: false ++ + If set to true, button label will escape pango markup. + +*current-only*: ++ + typeof: bool ++ + default: false ++ + If set to true, only the active or focused workspace will be shown. + +*on-update*: ++ + typeof: string ++ + Command to execute when the module is updated. + +# FORMAT REPLACEMENTS + +*{value}*: Name of the workspace, or index for unnamed workspaces, +as defined by niri. + +*{name}*: Name of the workspace for named workspaces. + +*{icon}*: Icon, as defined in *format-icons*. + +*{index}*: Index of the workspace on its output. + +*{output}*: Output where the workspace is located. + +# ICONS + +Additional to workspace name matching, the following *format-icons* can be set. + +- *default*: Will be shown, when no string matches are found. +- *focused*: Will be shown, when workspace is focused. +- *active*: Will be shown, when workspace is active on its output. + +# EXAMPLES + +``` +"niri/workspaces": { + "format": "{icon}", + "format-icons": { + // Named workspaces + // (you need to configure them in niri) + "browser": "", + "discord": "", + "chat": "", + + // Icons by state + "active": "", + "default": "" + } +} +``` + +# Style + +- *#workspaces button* +- *#workspaces button.focused*: The single focused workspace. +- *#workspaces button.active*: The workspace is active (visible) on its output. +- *#workspaces button.empty*: The workspace is empty. +- *#workspaces button.current_output*: The workspace is from the same output as + the bar that it is displayed on. +- *#workspaces button#niri-workspace-*: Workspaces named this, or index + for unnamed workspaces. diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index db546e17..f3a89656 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -323,6 +323,9 @@ A group may hide all but one element, showing them only on mouse hover. In order - *waybar-hyprland-submap(5)* - *waybar-hyprland-window(5)* - *waybar-hyprland-workspaces(5)* +- *waybar-niri-language(5)* +- *waybar-niri-window(5)* +- *waybar-niri-workspaces(5)* - *waybar-idle-inhibitor(5)* - *waybar-image(5)* - *waybar-inhibitor(5)* diff --git a/meson.build b/meson.build index ed5ec7b8..7097a9fb 100644 --- a/meson.build +++ b/meson.build @@ -318,6 +318,21 @@ if true ) endif +if true + add_project_arguments('-DHAVE_NIRI', language: 'cpp') + src_files += files( + 'src/modules/niri/backend.cpp', + 'src/modules/niri/language.cpp', + 'src/modules/niri/window.cpp', + 'src/modules/niri/workspaces.cpp', + ) + man_files += files( + 'man/waybar-niri-language.5.scd', + 'man/waybar-niri-window.5.scd', + 'man/waybar-niri-workspaces.5.scd', + ) +endif + if libnl.found() and libnlgen.found() add_project_arguments('-DHAVE_LIBNL', language: 'cpp') src_files += files('src/modules/network.cpp') diff --git a/src/factory.cpp b/src/factory.cpp index ca10ef95..6c2313e3 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -36,6 +36,11 @@ #include "modules/hyprland/window.hpp" #include "modules/hyprland/workspaces.hpp" #endif +#ifdef HAVE_NIRI +#include "modules/niri/language.hpp" +#include "modules/niri/window.hpp" +#include "modules/niri/workspaces.hpp" +#endif #if defined(__FreeBSD__) || defined(__linux__) #include "modules/battery.hpp" #endif @@ -205,6 +210,17 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, if (ref == "hyprland/workspaces") { return new waybar::modules::hyprland::Workspaces(id, bar_, config_[name]); } +#endif +#ifdef HAVE_NIRI + if (ref == "niri/language") { + return new waybar::modules::niri::Language(id, bar_, config_[name]); + } + if (ref == "niri/window") { + return new waybar::modules::niri::Window(id, bar_, config_[name]); + } + if (ref == "niri/workspaces") { + return new waybar::modules::niri::Workspaces(id, bar_, config_[name]); + } #endif if (ref == "idle_inhibitor") { return new waybar::modules::IdleInhibitor(id, bar_, config_[name]); diff --git a/src/modules/niri/backend.cpp b/src/modules/niri/backend.cpp new file mode 100644 index 00000000..ef23c881 --- /dev/null +++ b/src/modules/niri/backend.cpp @@ -0,0 +1,249 @@ +#include "modules/niri/backend.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace waybar::modules::niri { + +int IPC::connectToSocket() { + const char* socket_path = getenv("NIRI_SOCKET"); + + if (socket_path == nullptr) { + spdlog::warn("Niri is not running, niri IPC will not be available."); + return -1; + } + + struct sockaddr_un addr; + int socketfd = socket(AF_UNIX, SOCK_STREAM, 0); + + if (socketfd == -1) { + throw std::runtime_error("socketfd failed"); + } + + addr.sun_family = AF_UNIX; + + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + + addr.sun_path[sizeof(addr.sun_path) - 1] = 0; + + int l = sizeof(struct sockaddr_un); + + if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) { + close(socketfd); + throw std::runtime_error("unable to connect"); + } + + return socketfd; +} + +void IPC::startIPC() { + // will start IPC and relay events to parseIPC + + std::thread([&]() { + int socketfd; + try { + socketfd = connectToSocket(); + } catch (std::exception &e) { + spdlog::error("Niri IPC: failed to start, reason: {}", e.what()); + return; + } + if (socketfd == -1) + return; + + spdlog::info("Niri IPC starting"); + + __gnu_cxx::stdio_filebuf filebuf(socketfd, std::ios::in | std::ios::out); + std::iostream fs(&filebuf); + fs << R"("EventStream")" << std::endl; + + std::string line; + std::getline(fs, line); + if (line != R"({"Ok":"Handled"})") { + spdlog::error("Niri IPC: failed to start event stream"); + return; + } + + while (std::getline(fs, line)) { + spdlog::debug("Niri IPC: received {}", line); + + try { + parseIPC(line); + } catch (std::exception& e) { + spdlog::warn("Failed to parse IPC message: {}, reason: {}", line, e.what()); + } catch (...) { + throw; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + }).detach(); +} + +void IPC::parseIPC(const std::string& line) { + const auto ev = parser_.parse(line); + const auto members = ev.getMemberNames(); + if (members.size() != 1) + throw std::runtime_error("Event must have a single member"); + + { + auto lock = lockData(); + + if (const auto &payload = ev["WorkspacesChanged"]) { + workspaces_.clear(); + const auto &values = payload["workspaces"]; + std::copy(values.begin(), values.end(), std::back_inserter(workspaces_)); + + std::sort(workspaces_.begin(), workspaces_.end(), + [](const auto &a, const auto &b) { + const auto &aOutput = a["output"].asString(); + const auto &bOutput = b["output"].asString(); + const auto aIdx = a["idx"].asUInt(); + const auto bIdx = b["idx"].asUInt(); + if (aOutput == bOutput) + return aIdx < bIdx; + return aOutput < bOutput; + }); + } else if (const auto& payload = ev["WorkspaceActivated"]) { + const auto id = payload["id"].asUInt64(); + const auto focused = payload["focused"].asBool(); + auto it = std::find_if(workspaces_.begin(), workspaces_.end(), + [id](const auto &ws) { return ws["id"].asUInt64() == id; }); + if (it != workspaces_.end()) { + const auto &ws = *it; + const auto &output = ws["output"].asString(); + for (auto &ws : workspaces_) { + const auto got_activated = (ws["id"].asUInt64() == id); + if (ws["output"] == output) + ws["is_active"] = got_activated; + + if (focused) + ws["is_focused"] = got_activated; + } + } else { + spdlog::error("Activated unknown workspace"); + } + } else if (const auto& payload = ev["WorkspaceActiveWindowChanged"]) { + const auto workspaceId = payload["workspace_id"].asUInt64(); + auto it = std::find_if(workspaces_.begin(), workspaces_.end(), + [workspaceId](const auto &ws) { return ws["id"].asUInt64() == workspaceId; }); + if (it != workspaces_.end()) { + auto &ws = *it; + ws["active_window_id"] = payload["active_window_id"]; + } else { + spdlog::error("Active window changed on unknown workspace"); + } + } else if (const auto &payload = ev["KeyboardLayoutsChanged"]) { + const auto &layouts = payload["keyboard_layouts"]; + const auto &names = layouts["names"]; + keyboardLayoutCurrent_ = layouts["current_idx"].asUInt(); + + keyboardLayoutNames_.clear(); + for (const auto &fullName : names) + keyboardLayoutNames_.push_back(fullName.asString()); + } else if (const auto& payload = ev["KeyboardLayoutSwitched"]) { + keyboardLayoutCurrent_ = payload["idx"].asUInt(); + } else if (const auto &payload = ev["WindowsChanged"]) { + windows_.clear(); + const auto &values = payload["windows"]; + std::copy(values.begin(), values.end(), std::back_inserter(windows_)); + } else if (const auto &payload = ev["WindowOpenedOrChanged"]) { + const auto &window = payload["window"]; + const auto id = window["id"].asUInt64(); + auto it = std::find_if(windows_.begin(), windows_.end(), + [id](const auto &win) { return win["id"].asUInt64() == id; }); + if (it == windows_.end()) { + windows_.push_back(window); + + if (window["is_focused"].asBool()) { + for (auto &win : windows_) { + win["is_focused"] = win["id"].asUInt64() == id; + } + } + } else { + *it = window; + } + } else if (const auto &payload = ev["WindowClosed"]) { + const auto id = payload["id"].asUInt64(); + auto it = std::find_if(windows_.begin(), windows_.end(), + [id](const auto &win) { return win["id"].asUInt64() == id; }); + if (it != windows_.end()) { + windows_.erase(it); + } else { + spdlog::error("Unknown window closed"); + } + } else if (const auto &payload = ev["WindowFocusChanged"]) { + const auto focused = !payload["id"].isNull(); + const auto id = payload["id"].asUInt64(); + for (auto &win : windows_) { + win["is_focused"] = focused && win["id"].asUInt64() == id; + } + } + } + + std::unique_lock lock(callbackMutex_); + + for (auto& [eventname, handler] : callbacks_) { + if (eventname == members[0]) { + handler->onEvent(ev); + } + } +} + +void IPC::registerForIPC(const std::string& ev, EventHandler* ev_handler) { + if (ev_handler == nullptr) { + return; + } + + std::unique_lock lock(callbackMutex_); + callbacks_.emplace_back(ev, ev_handler); +} + +void IPC::unregisterForIPC(EventHandler* ev_handler) { + if (ev_handler == nullptr) { + return; + } + + std::unique_lock lock(callbackMutex_); + + for (auto it = callbacks_.begin(); it != callbacks_.end();) { + auto& [eventname, handler] = *it; + if (handler == ev_handler) { + it = callbacks_.erase(it); + } else { + ++it; + } + } +} + +Json::Value IPC::send(const Json::Value& request) { + int socketfd = connectToSocket(); + if (socketfd == -1) + throw std::runtime_error("Niri is not running"); + + __gnu_cxx::stdio_filebuf filebuf(socketfd, std::ios::in | std::ios::out); + std::iostream fs(&filebuf); + + // Niri needs the request on a single line. + Json::StreamWriterBuilder builder; + builder["indentation"] = ""; + std::unique_ptr writer(builder.newStreamWriter()); + writer->write(request, &fs); + fs << std::endl; + + Json::Value response; + fs >> response; + return response; +} + +} // namespace waybar::modules::hyprland diff --git a/src/modules/niri/language.cpp b/src/modules/niri/language.cpp new file mode 100644 index 00000000..f124d4dd --- /dev/null +++ b/src/modules/niri/language.cpp @@ -0,0 +1,138 @@ +#include "modules/niri/language.hpp" + +#include +#include +#include + +#include "util/string.hpp" + +namespace waybar::modules::niri { + +Language::Language(const std::string &id, const Bar &bar, const Json::Value &config) + : ALabel(config, "language", id, "{}", 0, true), bar_(bar) { + label_.hide(); + + if (!gIPC) + gIPC = std::make_unique(); + + gIPC->registerForIPC("KeyboardLayoutsChanged", this); + gIPC->registerForIPC("KeyboardLayoutSwitched", this); + + updateFromIPC(); + dp.emit(); +} + +Language::~Language() { + gIPC->unregisterForIPC(this); + // wait for possible event handler to finish + std::lock_guard lock(mutex_); +} + +void Language::updateFromIPC() { + std::lock_guard lock(mutex_); + auto ipcLock = gIPC->lockData(); + + layouts_.clear(); + for (const auto &fullName : gIPC->keyboardLayoutNames()) + layouts_.push_back(getLayout(fullName)); + + current_idx_ = gIPC->keyboardLayoutCurrent(); +} + +/** + * Language::doUpdate - update workspaces in UI thread. + * + * Note: some member fields are modified by both UI thread and event listener thread, use mutex_ to + * protect these member fields, and lock should released before calling ALabel::update(). + */ +void Language::doUpdate() { + std::lock_guard lock(mutex_); + + if (layouts_.size() <= current_idx_) { + spdlog::error("niri language layout index out of bounds"); + label_.hide(); + return; + } + const auto &layout = layouts_[current_idx_]; + + spdlog::debug("niri language update with full name {}", layout.full_name); + spdlog::debug("niri language update with short name {}", layout.short_name); + spdlog::debug("niri language update with short description {}", layout.short_description); + spdlog::debug("niri language update with variant {}", layout.variant); + + std::string layoutName = std::string{}; + if (config_.isMember("format-" + layout.short_description + "-" + layout.variant)) { + const auto propName = "format-" + layout.short_description + "-" + layout.variant; + layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString()); + } else if (config_.isMember("format-" + layout.short_description)) { + const auto propName = "format-" + layout.short_description; + layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString()); + } else { + layoutName = trim(fmt::format(fmt::runtime(format_), fmt::arg("long", layout.full_name), + fmt::arg("short", layout.short_name), + fmt::arg("shortDescription", layout.short_description), + fmt::arg("variant", layout.variant))); + } + + spdlog::debug("niri language formatted layout name {}", layoutName); + + if (!format_.empty()) { + label_.show(); + label_.set_markup(layoutName); + } else { + label_.hide(); + } +} + +void Language::update() { + doUpdate(); + ALabel::update(); +} + +void Language::onEvent(const Json::Value& ev) { + if (ev["KeyboardLayoutsChanged"]) { + updateFromIPC(); + } else if (ev["KeyboardLayoutSwitched"]) { + std::lock_guard lock(mutex_); + auto ipcLock = gIPC->lockData(); + current_idx_ = gIPC->keyboardLayoutCurrent(); + } + + dp.emit(); +} + +Language::Layout Language::getLayout(const std::string &fullName) { + auto* const context = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES); + rxkb_context_parse_default_ruleset(context); + + rxkb_layout* layout = rxkb_layout_first(context); + while (layout != nullptr) { + std::string nameOfLayout = rxkb_layout_get_description(layout); + + if (nameOfLayout != fullName) { + layout = rxkb_layout_next(layout); + continue; + } + + auto name = std::string(rxkb_layout_get_name(layout)); + const auto* variantPtr = rxkb_layout_get_variant(layout); + std::string variant = variantPtr == nullptr ? "" : std::string(variantPtr); + + const auto* descriptionPtr = rxkb_layout_get_brief(layout); + std::string description = descriptionPtr == nullptr ? "" : std::string(descriptionPtr); + + Layout info = Layout{nameOfLayout, name, variant, description}; + + rxkb_context_unref(context); + + return info; + } + + rxkb_context_unref(context); + + spdlog::debug("niri language didn't find matching layout for {}", fullName); + + return Layout{"", "", "", ""}; +} + +} // namespace waybar::modules::niri diff --git a/src/modules/niri/window.cpp b/src/modules/niri/window.cpp new file mode 100644 index 00000000..b2405435 --- /dev/null +++ b/src/modules/niri/window.cpp @@ -0,0 +1,121 @@ +#include "modules/niri/window.hpp" + +#include +#include +#include + +#include "util/rewrite_string.hpp" +#include "util/sanitize_str.hpp" + +namespace waybar::modules::niri { + +Window::Window(const std::string &id, const Bar &bar, const Json::Value &config) + : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) { + if (!gIPC) + gIPC = std::make_unique(); + + gIPC->registerForIPC("WindowsChanged", this); + gIPC->registerForIPC("WindowOpenedOrChanged", this); + gIPC->registerForIPC("WindowClosed", this); + gIPC->registerForIPC("WindowFocusChanged", this); + + dp.emit(); +} + +Window::~Window() { + gIPC->unregisterForIPC(this); +} + +void Window::onEvent(const Json::Value &ev) { + dp.emit(); +} + +void Window::doUpdate() { + auto ipcLock = gIPC->lockData(); + + const auto &windows = gIPC->windows(); + const auto &workspaces = gIPC->workspaces(); + + const auto separateOutputs = config_["separate-outputs"].asBool(); + const auto ws_it = std::find_if(workspaces.cbegin(), workspaces.cend(), + [&](const auto &ws) { + if (separateOutputs) { + return ws["is_active"].asBool() && ws["output"].asString() == bar_.output->name; + } + + return ws["is_focused"].asBool(); + }); + + std::vector::const_iterator it; + if (ws_it == workspaces.cend() || (*ws_it)["active_window_id"].isNull()) { + it = windows.cend(); + } else { + const auto id = (*ws_it)["active_window_id"].asUInt64(); + it = std::find_if(windows.cbegin(), windows.cend(), + [id](const auto &win) { return win["id"].asUInt64() == id; }); + } + + setClass("empty", ws_it == workspaces.cend() || (*ws_it)["active_window_id"].isNull()); + + if (it != windows.cend()) { + const auto &window = *it; + + const auto title = window["title"].asString(); + const auto appId = window["app_id"].asString(); + const auto sanitizedTitle = waybar::util::sanitize_string(title); + const auto sanitizedAppId = waybar::util::sanitize_string(appId); + + label_.show(); + label_.set_markup(waybar::util::rewriteString( + fmt::format(fmt::runtime(format_), + fmt::arg("title", sanitizedTitle), + fmt::arg("app_id", sanitizedAppId)), + config_["rewrite"])); + + updateAppIconName(appId, ""); + + if (tooltipEnabled()) + label_.set_tooltip_text(title); + + const auto id = window["id"].asUInt64(); + const auto workspaceId = window["workspace_id"].asUInt64(); + const auto isSolo = std::none_of(windows.cbegin(), windows.cend(), + [&](const auto &win) { + return win["id"].asUInt64() != id && win["workspace_id"].asUInt64() == workspaceId; + }); + setClass("solo", isSolo); + if (!appId.empty()) + setClass(appId, isSolo); + + if (oldAppId_ != appId) { + if (!oldAppId_.empty()) + setClass(oldAppId_, false); + oldAppId_ = appId; + } + } else { + label_.hide(); + updateAppIconName("", ""); + setClass("solo", false); + if (!oldAppId_.empty()) + setClass(oldAppId_, false); + oldAppId_.clear(); + } +} + +void Window::update() { + doUpdate(); + AAppIconLabel::update(); +} + +void Window::setClass(const std::string &className, bool enable) { + auto styleContext = bar_.window.get_style_context(); + if (enable) { + if (!styleContext->has_class(className)) { + styleContext->add_class(className); + } + } else { + styleContext->remove_class(className); + } +} + +} // namespace waybar::modules::niri diff --git a/src/modules/niri/workspaces.cpp b/src/modules/niri/workspaces.cpp new file mode 100644 index 00000000..2ecfa1ba --- /dev/null +++ b/src/modules/niri/workspaces.cpp @@ -0,0 +1,204 @@ +#include "modules/niri/workspaces.hpp" + +#include +#include +#include + +namespace waybar::modules::niri { + +Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) + : AModule(config, "workspaces", id, false, false), + bar_(bar), + box_(bar.orientation, 0) { + box_.set_name("workspaces"); + if (!id.empty()) { + box_.get_style_context()->add_class(id); + } + box_.get_style_context()->add_class(MODULE_CLASS); + event_box_.add(box_); + + if (!gIPC) + gIPC = std::make_unique(); + + gIPC->registerForIPC("WorkspacesChanged", this); + gIPC->registerForIPC("WorkspaceActivated", this); + gIPC->registerForIPC("WorkspaceActiveWindowChanged", this); + + dp.emit(); +} + +Workspaces::~Workspaces() { + gIPC->unregisterForIPC(this); +} + +void Workspaces::onEvent(const Json::Value &ev) { + dp.emit(); +} + +void Workspaces::doUpdate() { + auto ipcLock = gIPC->lockData(); + + const auto alloutputs = config_["all-outputs"].asBool(); + std::vector my_workspaces; + const auto &workspaces = gIPC->workspaces(); + std::copy_if(workspaces.cbegin(), workspaces.cend(), std::back_inserter(my_workspaces), + [&](const auto &ws) { + if (alloutputs) + return true; + return ws["output"].asString() == bar_.output->name; + }); + + // Remove buttons for removed workspaces. + for (auto it = buttons_.begin(); it != buttons_.end(); ) { + auto ws = std::find_if(my_workspaces.begin(), my_workspaces.end(), + [it](const auto &ws) { return ws["id"].asUInt64() == it->first; }); + if (ws == my_workspaces.end()) { + it = buttons_.erase(it); + } else { + ++it; + } + } + + // Add buttons for new workspaces, update existing ones. + for (const auto &ws : my_workspaces) { + auto bit = buttons_.find(ws["id"].asUInt64()); + auto &button = bit == buttons_.end() ? addButton(ws) : bit->second; + auto style_context = button.get_style_context(); + + if (ws["is_focused"].asBool()) + style_context->add_class("focused"); + else + style_context->remove_class("focused"); + + if (ws["is_active"].asBool()) + style_context->add_class("active"); + else + style_context->remove_class("active"); + + if (ws["output"]) { + if (ws["output"].asString() == bar_.output->name) + style_context->add_class("current_output"); + else + style_context->remove_class("current_output"); + } else { + style_context->remove_class("current_output"); + } + + if (ws["active_window_id"].isNull()) + style_context->add_class("empty"); + else + style_context->remove_class("empty"); + + std::string name; + if (ws["name"]) { + name = ws["name"].asString(); + } else { + name = std::to_string(ws["idx"].asUInt()); + } + button.set_name("niri-workspace-" + name); + + if (config_["format"].isString()) { + auto format = config_["format"].asString(); + name = fmt::format( + fmt::runtime(format), + fmt::arg("icon", getIcon(name, ws)), + fmt::arg("value", name), + fmt::arg("name", ws["name"].asString()), + fmt::arg("index", ws["idx"].asUInt()), + fmt::arg("output", ws["output"].asString())); + } + if (!config_["disable-markup"].asBool()) { + static_cast(button.get_children()[0])->set_markup(name); + } else { + button.set_label(name); + } + + if (config_["current-only"].asBool()) { + const auto *property = alloutputs ? "is_focused" : "is_active"; + if (ws[property].asBool()) + button.show(); + else + button.hide(); + } else { + button.show(); + } + } + + // Refresh the button order. + for (auto it = my_workspaces.cbegin(); it != my_workspaces.cend(); ++it) { + const auto &ws = *it; + + auto pos = ws["idx"].asUInt() - 1; + if (alloutputs) + pos = it - my_workspaces.cbegin(); + + auto &button = buttons_[ws["id"].asUInt64()]; + box_.reorder_child(button, pos); + } +} + +void Workspaces::update() { + doUpdate(); + AModule::update(); +} + +Gtk::Button &Workspaces::addButton(const Json::Value &ws) { + std::string name; + if (ws["name"]) { + name = ws["name"].asString(); + } else { + name = std::to_string(ws["idx"].asUInt()); + } + + auto pair = buttons_.emplace(ws["id"].asUInt64(), name); + auto &&button = pair.first->second; + box_.pack_start(button, false, false, 0); + button.set_relief(Gtk::RELIEF_NONE); + if (!config_["disable-click"].asBool()) { + const auto id = ws["id"].asUInt64(); + button.signal_pressed().connect([=] { + try { + // {"Action":{"FocusWorkspace":{"reference":{"Id":1}}}} + Json::Value request(Json::objectValue); + auto &action = (request["Action"] = Json::Value(Json::objectValue)); + auto &focusWorkspace = (action["FocusWorkspace"] = Json::Value(Json::objectValue)); + auto &reference = (focusWorkspace["reference"] = Json::Value(Json::objectValue)); + reference["Id"] = id; + + IPC::send(request); + } catch (const std::exception &e) { + spdlog::error("Error switching workspace: {}", e.what()); + } + }); + } + return button; +} + +std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws) { + const auto &icons = config_["format-icons"]; + if (!icons) + return value; + + if (ws["is_focused"].asBool() && icons["focused"]) + return icons["focused"].asString(); + + if (ws["is_active"].asBool() && icons["active"]) + return icons["active"].asString(); + + if (ws["name"]) { + const auto &name = ws["name"].asString(); + if (icons[name]) + return icons[name].asString(); + } + + const auto idx = ws["idx"].asString(); + if (icons[idx]) + return icons[idx].asString(); + + if (icons["default"]) + return icons["default"].asString(); + + return value; +} + +} // namespace waybar::modules::niri From fef0bb995c0300f796856460173ce731c075a0ab Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Fri, 13 Sep 2024 10:33:08 +0300 Subject: [PATCH 046/780] niri: Replace gnu extension with GDataInputStream --- src/modules/niri/backend.cpp | 45 +++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/modules/niri/backend.cpp b/src/modules/niri/backend.cpp index ef23c881..6e251d31 100644 --- a/src/modules/niri/backend.cpp +++ b/src/modules/niri/backend.cpp @@ -8,8 +8,11 @@ #include #include #include +#include "giomm/datainputstream.h" +#include "giomm/dataoutputstream.h" +#include "giomm/unixinputstream.h" +#include "giomm/unixoutputstream.h" -#include #include #include #include @@ -63,18 +66,23 @@ void IPC::startIPC() { spdlog::info("Niri IPC starting"); - __gnu_cxx::stdio_filebuf filebuf(socketfd, std::ios::in | std::ios::out); - std::iostream fs(&filebuf); - fs << R"("EventStream")" << std::endl; + auto unix_istream = Gio::UnixInputStream::create(socketfd, true); + auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false); + auto istream = Gio::DataInputStream::create(unix_istream); + auto ostream = Gio::DataOutputStream::create(unix_ostream); - std::string line; - std::getline(fs, line); - if (line != R"({"Ok":"Handled"})") { + if (!ostream->put_string("\"EventStream\"\n") || !ostream->flush()) { spdlog::error("Niri IPC: failed to start event stream"); return; } - while (std::getline(fs, line)) { + std::string line; + if (!istream->read_line(line) || line != R"({"Ok":"Handled"})") { + spdlog::error("Niri IPC: failed to start event stream"); + return; + } + + while (istream->read_line(line)) { spdlog::debug("Niri IPC: received {}", line); try { @@ -231,18 +239,29 @@ Json::Value IPC::send(const Json::Value& request) { if (socketfd == -1) throw std::runtime_error("Niri is not running"); - __gnu_cxx::stdio_filebuf filebuf(socketfd, std::ios::in | std::ios::out); - std::iostream fs(&filebuf); + auto unix_istream = Gio::UnixInputStream::create(socketfd, true); + auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false); + auto istream = Gio::DataInputStream::create(unix_istream); + auto ostream = Gio::DataOutputStream::create(unix_ostream); // Niri needs the request on a single line. Json::StreamWriterBuilder builder; builder["indentation"] = ""; std::unique_ptr writer(builder.newStreamWriter()); - writer->write(request, &fs); - fs << std::endl; + std::ostringstream oss; + writer->write(request, &oss); + oss << '\n'; + if (!ostream->put_string(oss.str()) || !ostream->flush()) + throw std::runtime_error("error writing to niri socket"); + + std::string line; + if (!istream->read_line(line)) + throw std::runtime_error("error reading from niri socket"); + + std::istringstream iss(std::move(line)); Json::Value response; - fs >> response; + iss >> response; return response; } From 34bfefcd2e6f6916a1b6b6b29c5836b5aeaf70b4 Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Fri, 13 Sep 2024 10:33:50 +0300 Subject: [PATCH 047/780] niri: Gate behind a meson option --- meson.build | 2 +- meson_options.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 7097a9fb..666c0dd0 100644 --- a/meson.build +++ b/meson.build @@ -318,7 +318,7 @@ if true ) endif -if true +if get_option('niri') add_project_arguments('-DHAVE_NIRI', language: 'cpp') src_files += files( 'src/modules/niri/backend.cpp', diff --git a/meson_options.txt b/meson_options.txt index fef50839..303ef038 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -19,3 +19,4 @@ option('experimental', type : 'boolean', value : false, description: 'Enable exp option('jack', type: 'feature', value: 'auto', description: 'Enable support for JACK') option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber') option('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava') +option('niri', type: 'boolean', description: 'Enable support for niri') From a4d31ab10d1630cb9104c695d7b777ca12468904 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 13 Sep 2024 09:40:35 +0200 Subject: [PATCH 048/780] fix: sni item fmt --- src/modules/image.cpp | 4 ++-- src/modules/sni/item.cpp | 23 ++++++++++++++--------- subprojects/gtk-layer-shell.wrap | 8 ++++---- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/modules/image.cpp b/src/modules/image.cpp index 5e6c1493..71e93b94 100644 --- a/src/modules/image.cpp +++ b/src/modules/image.cpp @@ -57,8 +57,8 @@ auto waybar::modules::Image::update() -> void { int scaled_icon_size = size_ * image_.get_scale_factor(); pixbuf = Gdk::Pixbuf::create_from_file(path_, scaled_icon_size, scaled_icon_size); - auto surface = - Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), image_.get_window()); + auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), + image_.get_window()); image_.set(surface); image_.show(); diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 6c4ec8c0..8afb39fb 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -104,9 +104,11 @@ void Item::proxyReady(Glib::RefPtr& result) { this->updateImage(); } catch (const Glib::Error& err) { - spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); + spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, + std::string(err.what())); } catch (const std::exception& err) { - spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); + spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, + std::string(err.what())); } } @@ -124,14 +126,15 @@ ToolTip get_variant(const Glib::VariantBase& value) { result.text = get_variant(container.get_child(2)); auto description = get_variant(container.get_child(3)); if (!description.empty()) { - result.text = fmt::format("{}\n{}", result.text, description); + result.text = fmt::format("{}\n{}", std::string(result.text), std::string(description)); } return result; } void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { try { - spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, name, value); + spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, + std::string(name), get_variant(value)); if (name == "Category") { category = get_variant(value); @@ -176,10 +179,12 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { } } catch (const Glib::Error& err) { spdlog::warn("Failed to set tray item property: {}.{}, value = {}, err = {}", - id.empty() ? bus_name : id, name, value, err.what()); + id.empty() ? bus_name : id, std::string(name), get_variant(value), + std::string(err.what())); } catch (const std::exception& err) { spdlog::warn("Failed to set tray item property: {}.{}, value = {}, err = {}", - id.empty() ? bus_name : id, name, value, err.what()); + id.empty() ? bus_name : id, std::string(name), get_variant(value), + std::string(err.what())); } } @@ -221,9 +226,9 @@ void Item::processUpdatedProperties(Glib::RefPtr& _result) { this->updateImage(); } catch (const Glib::Error& err) { - spdlog::warn("Failed to update properties: {}", err.what()); + spdlog::warn("Failed to update properties: {}", std::string(err.what())); } catch (const std::exception& err) { - spdlog::warn("Failed to update properties: {}", err.what()); + spdlog::warn("Failed to update properties: {}", std::string(err.what())); } update_pending_.clear(); } @@ -245,7 +250,7 @@ static const std::map> signal2props void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, const Glib::VariantContainerBase& arguments) { - spdlog::trace("Tray item '{}' got signal {}", id, signal_name); + spdlog::trace("Tray item '{}' got signal {}", id, std::string(signal_name)); auto changed = signal2props.find(signal_name.raw()); if (changed != signal2props.end()) { if (update_pending_.empty()) { diff --git a/subprojects/gtk-layer-shell.wrap b/subprojects/gtk-layer-shell.wrap index cb730345..fc0ddf74 100644 --- a/subprojects/gtk-layer-shell.wrap +++ b/subprojects/gtk-layer-shell.wrap @@ -1,5 +1,5 @@ [wrap-file] -directory = gtk-layer-shell-0.8.2 -source_filename = gtk-layer-shell-0.8.2.tar.gz -source_hash = 254dd246303127c5d5236ea640f01a82e35d2d652a48d139dd669c832a0f0dce -source_url = https://github.com/wmww/gtk-layer-shell/archive/v0.8.2/gtk-layer-shell-0.8.2.tar.gz +directory = gtk-layer-shell-0.9.0 +source_filename = gtk-layer-shell-0.9.0.tar.gz +source_hash = 3809e5565d9ed02e44bb73787ff218523e8760fef65830afe60ea7322e22da1c +source_url = https://github.com/wmww/gtk-layer-shell/archive/v0.9.0/gtk-layer-shell-0.9.0.tar.gz From 1142979581de1b6487e0ca5c65d887f4fb71eba0 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 13 Sep 2024 09:43:19 +0200 Subject: [PATCH 049/780] chore: lint --- include/modules/niri/backend.hpp | 6 +- include/modules/niri/language.hpp | 2 +- src/modules/niri/backend.cpp | 113 ++++++++++++++---------------- src/modules/niri/language.cpp | 16 ++--- src/modules/niri/window.cpp | 46 +++++------- src/modules/niri/workspaces.cpp | 52 +++++--------- 6 files changed, 98 insertions(+), 137 deletions(-) diff --git a/include/modules/niri/backend.hpp b/include/modules/niri/backend.hpp index 01af5017..42b9ff7f 100644 --- a/include/modules/niri/backend.hpp +++ b/include/modules/niri/backend.hpp @@ -26,9 +26,9 @@ class IPC { // The data members are only safe to access while dataMutex_ is locked. std::lock_guard lockData() { return std::lock_guard(dataMutex_); } - const std::vector &workspaces() const { return workspaces_; } - const std::vector &windows() const { return windows_; } - const std::vector &keyboardLayoutNames() const { return keyboardLayoutNames_; } + const std::vector& workspaces() const { return workspaces_; } + const std::vector& windows() const { return windows_; } + const std::vector& keyboardLayoutNames() const { return keyboardLayoutNames_; } unsigned keyboardLayoutCurrent() const { return keyboardLayoutCurrent_; } private: diff --git a/include/modules/niri/language.hpp b/include/modules/niri/language.hpp index 1cecd206..42b90ac4 100644 --- a/include/modules/niri/language.hpp +++ b/include/modules/niri/language.hpp @@ -10,7 +10,7 @@ namespace waybar::modules::niri { class Language : public ALabel, public EventHandler { public: - Language(const std::string&, const Bar&, const Json::Value&); + Language(const std::string &, const Bar &, const Json::Value &); ~Language() override; void update() override; diff --git a/src/modules/niri/backend.cpp b/src/modules/niri/backend.cpp index 6e251d31..383bf113 100644 --- a/src/modules/niri/backend.cpp +++ b/src/modules/niri/backend.cpp @@ -8,46 +8,47 @@ #include #include #include -#include "giomm/datainputstream.h" -#include "giomm/dataoutputstream.h" -#include "giomm/unixinputstream.h" -#include "giomm/unixoutputstream.h" #include #include #include +#include "giomm/datainputstream.h" +#include "giomm/dataoutputstream.h" +#include "giomm/unixinputstream.h" +#include "giomm/unixoutputstream.h" + namespace waybar::modules::niri { int IPC::connectToSocket() { - const char* socket_path = getenv("NIRI_SOCKET"); + const char *socket_path = getenv("NIRI_SOCKET"); - if (socket_path == nullptr) { - spdlog::warn("Niri is not running, niri IPC will not be available."); - return -1; - } + if (socket_path == nullptr) { + spdlog::warn("Niri is not running, niri IPC will not be available."); + return -1; + } - struct sockaddr_un addr; - int socketfd = socket(AF_UNIX, SOCK_STREAM, 0); + struct sockaddr_un addr; + int socketfd = socket(AF_UNIX, SOCK_STREAM, 0); - if (socketfd == -1) { - throw std::runtime_error("socketfd failed"); - } + if (socketfd == -1) { + throw std::runtime_error("socketfd failed"); + } - addr.sun_family = AF_UNIX; + addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); - addr.sun_path[sizeof(addr.sun_path) - 1] = 0; + addr.sun_path[sizeof(addr.sun_path) - 1] = 0; - int l = sizeof(struct sockaddr_un); + int l = sizeof(struct sockaddr_un); - if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) { - close(socketfd); - throw std::runtime_error("unable to connect"); - } + if (connect(socketfd, (struct sockaddr *)&addr, l) == -1) { + close(socketfd); + throw std::runtime_error("unable to connect"); + } - return socketfd; + return socketfd; } void IPC::startIPC() { @@ -61,8 +62,7 @@ void IPC::startIPC() { spdlog::error("Niri IPC: failed to start, reason: {}", e.what()); return; } - if (socketfd == -1) - return; + if (socketfd == -1) return; spdlog::info("Niri IPC starting"); @@ -87,7 +87,7 @@ void IPC::startIPC() { try { parseIPC(line); - } catch (std::exception& e) { + } catch (std::exception &e) { spdlog::warn("Failed to parse IPC message: {}, reason: {}", line, e.what()); } catch (...) { throw; @@ -98,11 +98,10 @@ void IPC::startIPC() { }).detach(); } -void IPC::parseIPC(const std::string& line) { +void IPC::parseIPC(const std::string &line) { const auto ev = parser_.parse(line); const auto members = ev.getMemberNames(); - if (members.size() != 1) - throw std::runtime_error("Event must have a single member"); + if (members.size() != 1) throw std::runtime_error("Event must have a single member"); { auto lock = lockData(); @@ -112,17 +111,15 @@ void IPC::parseIPC(const std::string& line) { const auto &values = payload["workspaces"]; std::copy(values.begin(), values.end(), std::back_inserter(workspaces_)); - std::sort(workspaces_.begin(), workspaces_.end(), - [](const auto &a, const auto &b) { - const auto &aOutput = a["output"].asString(); - const auto &bOutput = b["output"].asString(); - const auto aIdx = a["idx"].asUInt(); - const auto bIdx = b["idx"].asUInt(); - if (aOutput == bOutput) - return aIdx < bIdx; - return aOutput < bOutput; - }); - } else if (const auto& payload = ev["WorkspaceActivated"]) { + std::sort(workspaces_.begin(), workspaces_.end(), [](const auto &a, const auto &b) { + const auto &aOutput = a["output"].asString(); + const auto &bOutput = b["output"].asString(); + const auto aIdx = a["idx"].asUInt(); + const auto bIdx = b["idx"].asUInt(); + if (aOutput == bOutput) return aIdx < bIdx; + return aOutput < bOutput; + }); + } else if (const auto &payload = ev["WorkspaceActivated"]) { const auto id = payload["id"].asUInt64(); const auto focused = payload["focused"].asBool(); auto it = std::find_if(workspaces_.begin(), workspaces_.end(), @@ -132,19 +129,18 @@ void IPC::parseIPC(const std::string& line) { const auto &output = ws["output"].asString(); for (auto &ws : workspaces_) { const auto got_activated = (ws["id"].asUInt64() == id); - if (ws["output"] == output) - ws["is_active"] = got_activated; + if (ws["output"] == output) ws["is_active"] = got_activated; - if (focused) - ws["is_focused"] = got_activated; + if (focused) ws["is_focused"] = got_activated; } } else { spdlog::error("Activated unknown workspace"); } - } else if (const auto& payload = ev["WorkspaceActiveWindowChanged"]) { + } else if (const auto &payload = ev["WorkspaceActiveWindowChanged"]) { const auto workspaceId = payload["workspace_id"].asUInt64(); - auto it = std::find_if(workspaces_.begin(), workspaces_.end(), - [workspaceId](const auto &ws) { return ws["id"].asUInt64() == workspaceId; }); + auto it = std::find_if(workspaces_.begin(), workspaces_.end(), [workspaceId](const auto &ws) { + return ws["id"].asUInt64() == workspaceId; + }); if (it != workspaces_.end()) { auto &ws = *it; ws["active_window_id"] = payload["active_window_id"]; @@ -157,9 +153,8 @@ void IPC::parseIPC(const std::string& line) { keyboardLayoutCurrent_ = layouts["current_idx"].asUInt(); keyboardLayoutNames_.clear(); - for (const auto &fullName : names) - keyboardLayoutNames_.push_back(fullName.asString()); - } else if (const auto& payload = ev["KeyboardLayoutSwitched"]) { + for (const auto &fullName : names) keyboardLayoutNames_.push_back(fullName.asString()); + } else if (const auto &payload = ev["KeyboardLayoutSwitched"]) { keyboardLayoutCurrent_ = payload["idx"].asUInt(); } else if (const auto &payload = ev["WindowsChanged"]) { windows_.clear(); @@ -201,14 +196,14 @@ void IPC::parseIPC(const std::string& line) { std::unique_lock lock(callbackMutex_); - for (auto& [eventname, handler] : callbacks_) { + for (auto &[eventname, handler] : callbacks_) { if (eventname == members[0]) { handler->onEvent(ev); } } } -void IPC::registerForIPC(const std::string& ev, EventHandler* ev_handler) { +void IPC::registerForIPC(const std::string &ev, EventHandler *ev_handler) { if (ev_handler == nullptr) { return; } @@ -217,7 +212,7 @@ void IPC::registerForIPC(const std::string& ev, EventHandler* ev_handler) { callbacks_.emplace_back(ev, ev_handler); } -void IPC::unregisterForIPC(EventHandler* ev_handler) { +void IPC::unregisterForIPC(EventHandler *ev_handler) { if (ev_handler == nullptr) { return; } @@ -225,7 +220,7 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) { std::unique_lock lock(callbackMutex_); for (auto it = callbacks_.begin(); it != callbacks_.end();) { - auto& [eventname, handler] = *it; + auto &[eventname, handler] = *it; if (handler == ev_handler) { it = callbacks_.erase(it); } else { @@ -234,10 +229,9 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) { } } -Json::Value IPC::send(const Json::Value& request) { +Json::Value IPC::send(const Json::Value &request) { int socketfd = connectToSocket(); - if (socketfd == -1) - throw std::runtime_error("Niri is not running"); + if (socketfd == -1) throw std::runtime_error("Niri is not running"); auto unix_istream = Gio::UnixInputStream::create(socketfd, true); auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false); @@ -256,8 +250,7 @@ Json::Value IPC::send(const Json::Value& request) { throw std::runtime_error("error writing to niri socket"); std::string line; - if (!istream->read_line(line)) - throw std::runtime_error("error reading from niri socket"); + if (!istream->read_line(line)) throw std::runtime_error("error reading from niri socket"); std::istringstream iss(std::move(line)); Json::Value response; @@ -265,4 +258,4 @@ Json::Value IPC::send(const Json::Value& request) { return response; } -} // namespace waybar::modules::hyprland +} // namespace waybar::modules::niri diff --git a/src/modules/niri/language.cpp b/src/modules/niri/language.cpp index f124d4dd..1e4d6d10 100644 --- a/src/modules/niri/language.cpp +++ b/src/modules/niri/language.cpp @@ -12,8 +12,7 @@ Language::Language(const std::string &id, const Bar &bar, const Json::Value &con : ALabel(config, "language", id, "{}", 0, true), bar_(bar) { label_.hide(); - if (!gIPC) - gIPC = std::make_unique(); + if (!gIPC) gIPC = std::make_unique(); gIPC->registerForIPC("KeyboardLayoutsChanged", this); gIPC->registerForIPC("KeyboardLayoutSwitched", this); @@ -33,8 +32,7 @@ void Language::updateFromIPC() { auto ipcLock = gIPC->lockData(); layouts_.clear(); - for (const auto &fullName : gIPC->keyboardLayoutNames()) - layouts_.push_back(getLayout(fullName)); + for (const auto &fullName : gIPC->keyboardLayoutNames()) layouts_.push_back(getLayout(fullName)); current_idx_ = gIPC->keyboardLayoutCurrent(); } @@ -89,7 +87,7 @@ void Language::update() { ALabel::update(); } -void Language::onEvent(const Json::Value& ev) { +void Language::onEvent(const Json::Value &ev) { if (ev["KeyboardLayoutsChanged"]) { updateFromIPC(); } else if (ev["KeyboardLayoutSwitched"]) { @@ -102,10 +100,10 @@ void Language::onEvent(const Json::Value& ev) { } Language::Layout Language::getLayout(const std::string &fullName) { - auto* const context = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES); + auto *const context = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES); rxkb_context_parse_default_ruleset(context); - rxkb_layout* layout = rxkb_layout_first(context); + rxkb_layout *layout = rxkb_layout_first(context); while (layout != nullptr) { std::string nameOfLayout = rxkb_layout_get_description(layout); @@ -115,10 +113,10 @@ Language::Layout Language::getLayout(const std::string &fullName) { } auto name = std::string(rxkb_layout_get_name(layout)); - const auto* variantPtr = rxkb_layout_get_variant(layout); + const auto *variantPtr = rxkb_layout_get_variant(layout); std::string variant = variantPtr == nullptr ? "" : std::string(variantPtr); - const auto* descriptionPtr = rxkb_layout_get_brief(layout); + const auto *descriptionPtr = rxkb_layout_get_brief(layout); std::string description = descriptionPtr == nullptr ? "" : std::string(descriptionPtr); Layout info = Layout{nameOfLayout, name, variant, description}; diff --git a/src/modules/niri/window.cpp b/src/modules/niri/window.cpp index b2405435..6e6fd36f 100644 --- a/src/modules/niri/window.cpp +++ b/src/modules/niri/window.cpp @@ -11,8 +11,7 @@ namespace waybar::modules::niri { Window::Window(const std::string &id, const Bar &bar, const Json::Value &config) : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) { - if (!gIPC) - gIPC = std::make_unique(); + if (!gIPC) gIPC = std::make_unique(); gIPC->registerForIPC("WindowsChanged", this); gIPC->registerForIPC("WindowOpenedOrChanged", this); @@ -22,13 +21,9 @@ Window::Window(const std::string &id, const Bar &bar, const Json::Value &config) dp.emit(); } -Window::~Window() { - gIPC->unregisterForIPC(this); -} +Window::~Window() { gIPC->unregisterForIPC(this); } -void Window::onEvent(const Json::Value &ev) { - dp.emit(); -} +void Window::onEvent(const Json::Value &ev) { dp.emit(); } void Window::doUpdate() { auto ipcLock = gIPC->lockData(); @@ -37,14 +32,13 @@ void Window::doUpdate() { const auto &workspaces = gIPC->workspaces(); const auto separateOutputs = config_["separate-outputs"].asBool(); - const auto ws_it = std::find_if(workspaces.cbegin(), workspaces.cend(), - [&](const auto &ws) { - if (separateOutputs) { - return ws["is_active"].asBool() && ws["output"].asString() == bar_.output->name; - } + const auto ws_it = std::find_if(workspaces.cbegin(), workspaces.cend(), [&](const auto &ws) { + if (separateOutputs) { + return ws["is_active"].asBool() && ws["output"].asString() == bar_.output->name; + } - return ws["is_focused"].asBool(); - }); + return ws["is_focused"].asBool(); + }); std::vector::const_iterator it; if (ws_it == workspaces.cend() || (*ws_it)["active_window_id"].isNull()) { @@ -67,37 +61,31 @@ void Window::doUpdate() { label_.show(); label_.set_markup(waybar::util::rewriteString( - fmt::format(fmt::runtime(format_), - fmt::arg("title", sanitizedTitle), + fmt::format(fmt::runtime(format_), fmt::arg("title", sanitizedTitle), fmt::arg("app_id", sanitizedAppId)), config_["rewrite"])); updateAppIconName(appId, ""); - if (tooltipEnabled()) - label_.set_tooltip_text(title); + if (tooltipEnabled()) label_.set_tooltip_text(title); const auto id = window["id"].asUInt64(); const auto workspaceId = window["workspace_id"].asUInt64(); - const auto isSolo = std::none_of(windows.cbegin(), windows.cend(), - [&](const auto &win) { - return win["id"].asUInt64() != id && win["workspace_id"].asUInt64() == workspaceId; - }); + const auto isSolo = std::none_of(windows.cbegin(), windows.cend(), [&](const auto &win) { + return win["id"].asUInt64() != id && win["workspace_id"].asUInt64() == workspaceId; + }); setClass("solo", isSolo); - if (!appId.empty()) - setClass(appId, isSolo); + if (!appId.empty()) setClass(appId, isSolo); if (oldAppId_ != appId) { - if (!oldAppId_.empty()) - setClass(oldAppId_, false); + if (!oldAppId_.empty()) setClass(oldAppId_, false); oldAppId_ = appId; } } else { label_.hide(); updateAppIconName("", ""); setClass("solo", false); - if (!oldAppId_.empty()) - setClass(oldAppId_, false); + if (!oldAppId_.empty()) setClass(oldAppId_, false); oldAppId_.clear(); } } diff --git a/src/modules/niri/workspaces.cpp b/src/modules/niri/workspaces.cpp index 2ecfa1ba..d2fcad5d 100644 --- a/src/modules/niri/workspaces.cpp +++ b/src/modules/niri/workspaces.cpp @@ -7,9 +7,7 @@ namespace waybar::modules::niri { Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) - : AModule(config, "workspaces", id, false, false), - bar_(bar), - box_(bar.orientation, 0) { + : AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) { box_.set_name("workspaces"); if (!id.empty()) { box_.get_style_context()->add_class(id); @@ -17,8 +15,7 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value box_.get_style_context()->add_class(MODULE_CLASS); event_box_.add(box_); - if (!gIPC) - gIPC = std::make_unique(); + if (!gIPC) gIPC = std::make_unique(); gIPC->registerForIPC("WorkspacesChanged", this); gIPC->registerForIPC("WorkspaceActivated", this); @@ -27,13 +24,9 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value dp.emit(); } -Workspaces::~Workspaces() { - gIPC->unregisterForIPC(this); -} +Workspaces::~Workspaces() { gIPC->unregisterForIPC(this); } -void Workspaces::onEvent(const Json::Value &ev) { - dp.emit(); -} +void Workspaces::onEvent(const Json::Value &ev) { dp.emit(); } void Workspaces::doUpdate() { auto ipcLock = gIPC->lockData(); @@ -43,13 +36,12 @@ void Workspaces::doUpdate() { const auto &workspaces = gIPC->workspaces(); std::copy_if(workspaces.cbegin(), workspaces.cend(), std::back_inserter(my_workspaces), [&](const auto &ws) { - if (alloutputs) - return true; + if (alloutputs) return true; return ws["output"].asString() == bar_.output->name; }); // Remove buttons for removed workspaces. - for (auto it = buttons_.begin(); it != buttons_.end(); ) { + for (auto it = buttons_.begin(); it != buttons_.end();) { auto ws = std::find_if(my_workspaces.begin(), my_workspaces.end(), [it](const auto &ws) { return ws["id"].asUInt64() == it->first; }); if (ws == my_workspaces.end()) { @@ -99,13 +91,10 @@ void Workspaces::doUpdate() { if (config_["format"].isString()) { auto format = config_["format"].asString(); - name = fmt::format( - fmt::runtime(format), - fmt::arg("icon", getIcon(name, ws)), - fmt::arg("value", name), - fmt::arg("name", ws["name"].asString()), - fmt::arg("index", ws["idx"].asUInt()), - fmt::arg("output", ws["output"].asString())); + name = fmt::format(fmt::runtime(format), fmt::arg("icon", getIcon(name, ws)), + fmt::arg("value", name), fmt::arg("name", ws["name"].asString()), + fmt::arg("index", ws["idx"].asUInt()), + fmt::arg("output", ws["output"].asString())); } if (!config_["disable-markup"].asBool()) { static_cast(button.get_children()[0])->set_markup(name); @@ -129,8 +118,7 @@ void Workspaces::doUpdate() { const auto &ws = *it; auto pos = ws["idx"].asUInt() - 1; - if (alloutputs) - pos = it - my_workspaces.cbegin(); + if (alloutputs) pos = it - my_workspaces.cbegin(); auto &button = buttons_[ws["id"].asUInt64()]; box_.reorder_child(button, pos); @@ -176,27 +164,21 @@ Gtk::Button &Workspaces::addButton(const Json::Value &ws) { std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws) { const auto &icons = config_["format-icons"]; - if (!icons) - return value; + if (!icons) return value; - if (ws["is_focused"].asBool() && icons["focused"]) - return icons["focused"].asString(); + if (ws["is_focused"].asBool() && icons["focused"]) return icons["focused"].asString(); - if (ws["is_active"].asBool() && icons["active"]) - return icons["active"].asString(); + if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString(); if (ws["name"]) { const auto &name = ws["name"].asString(); - if (icons[name]) - return icons[name].asString(); + if (icons[name]) return icons[name].asString(); } const auto idx = ws["idx"].asString(); - if (icons[idx]) - return icons[idx].asString(); + if (icons[idx]) return icons[idx].asString(); - if (icons["default"]) - return icons["default"].asString(); + if (icons["default"]) return icons["default"].asString(); return value; } From d56dd6ee7fdf8c5ba4e90790af62b7f7829d3a47 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 13 Sep 2024 09:51:11 +0200 Subject: [PATCH 050/780] chore: v0.11.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 666c0dd0..42f9da92 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'waybar', 'cpp', 'c', - version: '0.10.4', + version: '0.11.0', license: 'MIT', meson_version: '>= 0.59.0', default_options : [ From d177969f51b3435308a520c9c0385ae80579b255 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 13 Sep 2024 09:53:15 +0200 Subject: [PATCH 051/780] chore: lint --- src/ALabel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ALabel.cpp b/src/ALabel.cpp index 3cb2c590..467572f1 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -45,7 +45,7 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st if (config_["rotate"].isUInt()) { rotate = config["rotate"].asUInt(); - if (not (rotate == 0 || rotate == 90 || rotate == 180 || rotate == 270)) + if (not(rotate == 0 || rotate == 90 || rotate == 180 || rotate == 270)) spdlog::warn("'rotate' is only supported in 90 degree increments {} is not valid.", rotate); label_.set_angle(rotate); } From 4a6af0da99306fdf155c5739cea9e7b9e54b6a34 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sat, 17 Aug 2024 15:30:24 -0700 Subject: [PATCH 052/780] fix(bar): use overlay layer for `hide` and `overlay` modes This fixes a major inconsistency with the swaybar implementation of these modes[^1]. `overlay` layer no longer has security implications due to a wide adoption of `ext-session-lock`, so it's safe to use. Following config will restore the previous behavior: ```json "modes": { "hide": { "layer": "top" }, "overlay": { "layer": "top" } }, ``` [^1]: https://github.com/swaywm/sway/commit/2f7247e08a16610228067c9ec34d2b6d897e15fa --- src/bar.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bar.cpp b/src/bar.cpp index 8a245ad1..8bb214c0 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -37,7 +37,7 @@ const Bar::bar_mode_map Bar::PRESET_MODES = { // .visible = true}}, {"hide", {// - .layer = bar_layer::TOP, + .layer = bar_layer::OVERLAY, .exclusive = false, .passthrough = false, .visible = true}}, @@ -49,7 +49,7 @@ const Bar::bar_mode_map Bar::PRESET_MODES = { // .visible = false}}, {"overlay", {// - .layer = bar_layer::TOP, + .layer = bar_layer::OVERLAY, .exclusive = false, .passthrough = true, .visible = true}}}; From f60c291b82181b6e871b4807625fbcc7818d3c36 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sat, 14 Sep 2024 07:36:23 -0700 Subject: [PATCH 053/780] chore: update fmt wrap to 11.0.2 --- subprojects/fmt.wrap | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/subprojects/fmt.wrap b/subprojects/fmt.wrap index 42b61596..fd508477 100644 --- a/subprojects/fmt.wrap +++ b/subprojects/fmt.wrap @@ -1,13 +1,13 @@ [wrap-file] -directory = fmt-11.0.1 -source_url = https://github.com/fmtlib/fmt/archive/11.0.1.tar.gz -source_filename = fmt-11.0.1.tar.gz -source_hash = 7d009f7f89ac84c0a83f79ed602463d092fbf66763766a907c97fd02b100f5e9 -patch_filename = fmt_11.0.1-1_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/fmt_11.0.1-1/get_patch -patch_hash = 0a8b93d1ee6d84a82d3872a9bfb4c3977d8a53f7f484d42d1f7ed63ed496d549 -source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_11.0.1-1/fmt-11.0.1.tar.gz -wrapdb_version = 11.0.1-1 +directory = fmt-11.0.2 +source_url = https://github.com/fmtlib/fmt/archive/11.0.2.tar.gz +source_filename = fmt-11.0.2.tar.gz +source_hash = 6cb1e6d37bdcb756dbbe59be438790db409cdb4868c66e888d5df9f13f7c027f +patch_filename = fmt_11.0.2-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/fmt_11.0.2-1/get_patch +patch_hash = 90c9e3b8e8f29713d40ca949f6f93ad115d78d7fb921064112bc6179e6427c5e +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_11.0.2-1/fmt-11.0.2.tar.gz +wrapdb_version = 11.0.2-1 [provide] fmt = fmt_dep From 0006e4713ae19776528038b3242ded05db884ba5 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sat, 14 Sep 2024 07:37:37 -0700 Subject: [PATCH 054/780] fix(tray): revert ustring formatting changes This reverts commit a4d31ab10d1630cb9104c695d7b777ca12468904. --- src/modules/sni/item.cpp | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 8afb39fb..6c4ec8c0 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -104,11 +104,9 @@ void Item::proxyReady(Glib::RefPtr& result) { this->updateImage(); } catch (const Glib::Error& err) { - spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, - std::string(err.what())); + spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); } catch (const std::exception& err) { - spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, - std::string(err.what())); + spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); } } @@ -126,15 +124,14 @@ ToolTip get_variant(const Glib::VariantBase& value) { result.text = get_variant(container.get_child(2)); auto description = get_variant(container.get_child(3)); if (!description.empty()) { - result.text = fmt::format("{}\n{}", std::string(result.text), std::string(description)); + result.text = fmt::format("{}\n{}", result.text, description); } return result; } void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { try { - spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, - std::string(name), get_variant(value)); + spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, name, value); if (name == "Category") { category = get_variant(value); @@ -179,12 +176,10 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { } } catch (const Glib::Error& err) { spdlog::warn("Failed to set tray item property: {}.{}, value = {}, err = {}", - id.empty() ? bus_name : id, std::string(name), get_variant(value), - std::string(err.what())); + id.empty() ? bus_name : id, name, value, err.what()); } catch (const std::exception& err) { spdlog::warn("Failed to set tray item property: {}.{}, value = {}, err = {}", - id.empty() ? bus_name : id, std::string(name), get_variant(value), - std::string(err.what())); + id.empty() ? bus_name : id, name, value, err.what()); } } @@ -226,9 +221,9 @@ void Item::processUpdatedProperties(Glib::RefPtr& _result) { this->updateImage(); } catch (const Glib::Error& err) { - spdlog::warn("Failed to update properties: {}", std::string(err.what())); + spdlog::warn("Failed to update properties: {}", err.what()); } catch (const std::exception& err) { - spdlog::warn("Failed to update properties: {}", std::string(err.what())); + spdlog::warn("Failed to update properties: {}", err.what()); } update_pending_.clear(); } @@ -250,7 +245,7 @@ static const std::map> signal2props void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, const Glib::VariantContainerBase& arguments) { - spdlog::trace("Tray item '{}' got signal {}", id, std::string(signal_name)); + spdlog::trace("Tray item '{}' got signal {}", id, signal_name); auto changed = signal2props.find(signal_name.raw()); if (changed != signal2props.end()) { if (update_pending_.empty()) { From 7b23d586846e9cdb10f203f25a472bf945f4ed68 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sat, 17 Aug 2024 21:55:28 -0700 Subject: [PATCH 055/780] fix(bar): force commit for occluded surfaces All the mode or visibility changes require `wl_surface_commit` to be applied. gtk-layer-shell will attempt to force GTK to commit, but may fail if the surface has stopped receiving frame callbacks[^1]. Thus, we could get stuck in a state where the bar is hidden and unable to regain visibility. To address this, a new API has been added to gtk-layer-shell, `gtk_layer_try_force_commit`, which does `wl_surface_commit` with the necessary safety checks to avoid corrupting GTK internal state. Note: this change bumps gtk-layer-shell requirement to 0.9.0. [^1]: https://github.com/wmww/gtk-layer-shell/issues/185 --- meson.build | 2 +- src/bar.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 42f9da92..f4941c8f 100644 --- a/meson.build +++ b/meson.build @@ -106,7 +106,7 @@ if libsndio.found() endif endif -gtk_layer_shell = dependency('gtk-layer-shell-0', version: ['>=0.6.0'], +gtk_layer_shell = dependency('gtk-layer-shell-0', version: ['>=0.9.0'], default_options: ['introspection=false', 'vapi=false'], fallback: ['gtk-layer-shell', 'gtk_layer_shell']) systemd = dependency('systemd', required: get_option('systemd')) diff --git a/src/bar.cpp b/src/bar.cpp index 8bb214c0..ebe23367 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -132,6 +132,7 @@ void from_json(const Json::Value& j, std::map& m) { waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) : output(w_output), config(w_config), + surface(nullptr), window{Gtk::WindowType::WINDOW_TOPLEVEL}, x_global(0), y_global(0), @@ -339,6 +340,13 @@ void waybar::Bar::setMode(const struct bar_mode& mode) { window.get_style_context()->add_class("hidden"); window.set_opacity(0); } + /* + * All the changes above require `wl_surface_commit`. + * gtk-layer-shell schedules a commit on the next frame event in GTK, but this could fail in + * certain scenarios, such as fully occluded bar. + */ + gtk_layer_try_force_commit(gtk_window); + wl_display_flush(Client::inst()->wl_display); } void waybar::Bar::setPassThrough(bool passthrough) { From 45fec7bcbbb475e6ab9afa32bc57b8409dc4ed86 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Sat, 17 Aug 2024 22:40:56 -0700 Subject: [PATCH 056/780] Revert "change layer for mode invisible to nullopt" Previous commit should have a better workaround for #3211. This reverts commit b61ea62732a51e564ded6e7d4d37cd4796b014f2. --- include/bar.hpp | 2 +- src/bar.cpp | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/bar.hpp b/include/bar.hpp index 936bc749..9b407abf 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -42,7 +42,7 @@ struct bar_margins { }; struct bar_mode { - std::optional layer; + bar_layer layer; bool exclusive; bool passthrough; bool visible; diff --git a/src/bar.cpp b/src/bar.cpp index ebe23367..5068e90d 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -43,7 +43,7 @@ const Bar::bar_mode_map Bar::PRESET_MODES = { // .visible = true}}, {"invisible", {// - .layer = std::nullopt, + .layer = bar_layer::BOTTOM, .exclusive = false, .passthrough = true, .visible = false}}, @@ -59,7 +59,7 @@ const std::string Bar::MODE_INVISIBLE = "invisible"; const std::string_view DEFAULT_BAR_ID = "bar-0"; /* Deserializer for enum bar_layer */ -void from_json(const Json::Value& j, std::optional& l) { +void from_json(const Json::Value& j, bar_layer& l) { if (j == "bottom") { l = bar_layer::BOTTOM; } else if (j == "top") { @@ -317,13 +317,13 @@ void waybar::Bar::setMode(const std::string& mode) { void waybar::Bar::setMode(const struct bar_mode& mode) { auto* gtk_window = window.gobj(); - if (mode.layer == bar_layer::BOTTOM) { - gtk_layer_set_layer(gtk_window, GTK_LAYER_SHELL_LAYER_BOTTOM); - } else if (mode.layer == bar_layer::TOP) { - gtk_layer_set_layer(gtk_window, GTK_LAYER_SHELL_LAYER_TOP); + auto layer = GTK_LAYER_SHELL_LAYER_BOTTOM; + if (mode.layer == bar_layer::TOP) { + layer = GTK_LAYER_SHELL_LAYER_TOP; } else if (mode.layer == bar_layer::OVERLAY) { - gtk_layer_set_layer(gtk_window, GTK_LAYER_SHELL_LAYER_OVERLAY); + layer = GTK_LAYER_SHELL_LAYER_OVERLAY; } + gtk_layer_set_layer(gtk_window, layer); if (mode.exclusive) { gtk_layer_auto_exclusive_zone_enable(gtk_window); From e0be3ac178300982f1507218026fe450f898abf6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Sep 2024 05:59:33 +0000 Subject: [PATCH 057/780] 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/71e91c409d1e654808b2621f28a327acfdad8dc2?narHash=sha256-GnR7/ibgIH1vhoy8cYdmXE6iyZqKqFxQSVkFgosBh6w%3D' (2024-08-28) → 'github:NixOS/nixpkgs/4f807e8940284ad7925ebd0a0993d2a1791acb2f?narHash=sha256-IiA3jfbR7K/B5%2B9byVi9BZGWTD4VSbWe8VLpp9B/iYk%3D' (2024-09-11) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 9bd73acc..15bf9f09 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1724819573, - "narHash": "sha256-GnR7/ibgIH1vhoy8cYdmXE6iyZqKqFxQSVkFgosBh6w=", + "lastModified": 1726062873, + "narHash": "sha256-IiA3jfbR7K/B5+9byVi9BZGWTD4VSbWe8VLpp9B/iYk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "71e91c409d1e654808b2621f28a327acfdad8dc2", + "rev": "4f807e8940284ad7925ebd0a0993d2a1791acb2f", "type": "github" }, "original": { From 085a1ede97bef27f08adb17788be901a8e29a60f Mon Sep 17 00:00:00 2001 From: Kainoa Kanter Date: Sun, 15 Sep 2024 21:28:15 -0300 Subject: [PATCH 058/780] fix: use app_identifier itself in AAppIconLabel if it's an absolute path --- src/AAppIconLabel.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/AAppIconLabel.cpp b/src/AAppIconLabel.cpp index fda5f9fd..dc22f20e 100644 --- a/src/AAppIconLabel.cpp +++ b/src/AAppIconLabel.cpp @@ -154,6 +154,16 @@ void AAppIconLabel::updateAppIcon() { update_app_icon_ = false; if (app_icon_name_.empty()) { image_.set_visible(false); + } + else if (app_icon_name_.front() == '/') { + auto pixbuf = Gdk::Pixbuf::create_from_file(app_icon_name_); + int scaled_icon_size = app_icon_size_ * image_.get_scale_factor(); + pixbuf = Gdk::Pixbuf::create_from_file(app_icon_name_, scaled_icon_size, scaled_icon_size); + + auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), + image_.get_window()); + image_.set(surface); + image_.set_visible(true); } else { image_.set_from_icon_name(app_icon_name_, Gtk::ICON_SIZE_INVALID); image_.set_visible(true); From ff66b5dd57408158f2eea84eba64269d90b92c5b Mon Sep 17 00:00:00 2001 From: Sonter <108224581+S0nter@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:47:10 +0000 Subject: [PATCH 059/780] Update waybar-cava man page --- man/waybar-cava.5.scd | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/man/waybar-cava.5.scd b/man/waybar-cava.5.scd index 2a7e8f67..7825c38a 100644 --- a/man/waybar-cava.5.scd +++ b/man/waybar-cava.5.scd @@ -64,6 +64,10 @@ libcava lives in: :[ bool :[ false :[ Hides the widget if no input (after sleep_timer elapsed) +|[ *format_silent* +:[ string +:[ +:[ Widget's text after sleep_timer elapsed (hide_on_silence has to be false) |[ *method* :[ string :[ pulse @@ -196,3 +200,8 @@ In case when cava releases new version and you're wanna get it, it should be rai } }, ``` +# STYLE + +- *#cava* +- *#cava.silent* Applied after no sound has been detected for sleep_timer seconds +- *#cava.updated* Applied when a new frame is shown From 3bb3c2d23fdecebdaa2da80cbb17fcc3beec32da Mon Sep 17 00:00:00 2001 From: Lukas Fink Date: Tue, 17 Sep 2024 00:13:23 +0200 Subject: [PATCH 060/780] fix(custom): stop mixing manual and automatic arg indexing The current documentation for the custom module suggests mixing manual (`{icon}`) and automatic (`{}`) indexing of format args. Newer versions of the fmt library seem to not support this anymore (see issue #3605). This commit introduces a name for the `text` output of the script, so that `{text}` can now be used instead of `{}` in the configuration. --- src/modules/custom.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 20d8d934..bc5df76f 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -159,7 +159,9 @@ auto waybar::modules::Custom::update() -> void { parseOutputRaw(); } - auto str = fmt::format(fmt::runtime(format_), text_, fmt::arg("alt", alt_), + auto str = fmt::format(fmt::runtime(format_), + fmt::arg("text", text_), + fmt::arg("alt", alt_), fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("percentage", percentage_)); if ((config_["hide-empty-text"].asBool() && text_.empty()) || str.empty()) { @@ -169,7 +171,9 @@ auto waybar::modules::Custom::update() -> void { if (tooltipEnabled()) { if (tooltip_format_enabled_) { auto tooltip = config_["tooltip-format"].asString(); - tooltip = fmt::format(fmt::runtime(tooltip), text_, fmt::arg("alt", alt_), + tooltip = fmt::format(fmt::runtime(tooltip), + fmt::arg("text", text_), + fmt::arg("alt", alt_), fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("percentage", percentage_)); label_.set_tooltip_markup(tooltip); From 83992d29a063361ed89e41f100d8ddbbd5456e27 Mon Sep 17 00:00:00 2001 From: Lukas Fink Date: Tue, 17 Sep 2024 00:39:33 +0200 Subject: [PATCH 061/780] Fix formatting --- src/modules/custom.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index bc5df76f..90c54128 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -159,9 +159,7 @@ auto waybar::modules::Custom::update() -> void { parseOutputRaw(); } - auto str = fmt::format(fmt::runtime(format_), - fmt::arg("text", text_), - fmt::arg("alt", alt_), + auto str = fmt::format(fmt::runtime(format_), fmt::arg("text", text_), fmt::arg("alt", alt_), fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("percentage", percentage_)); if ((config_["hide-empty-text"].asBool() && text_.empty()) || str.empty()) { @@ -171,10 +169,8 @@ auto waybar::modules::Custom::update() -> void { if (tooltipEnabled()) { if (tooltip_format_enabled_) { auto tooltip = config_["tooltip-format"].asString(); - tooltip = fmt::format(fmt::runtime(tooltip), - fmt::arg("text", text_), - fmt::arg("alt", alt_), - fmt::arg("icon", getIcon(percentage_, alt_)), + tooltip = fmt::format(fmt::runtime(tooltip), fmt::arg("text", text_), + fmt::arg("alt", alt_), fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("percentage", percentage_)); label_.set_tooltip_markup(tooltip); } else if (text_ == tooltip_) { From de170fa57989a11867b179cce222a3a01b9bbaa8 Mon Sep 17 00:00:00 2001 From: Lukas Fink Date: Tue, 17 Sep 2024 02:56:38 +0200 Subject: [PATCH 062/780] Update documentation --- man/waybar-custom.5.scd | 16 ++++++++-------- resources/config.jsonc | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/man/waybar-custom.5.scd b/man/waybar-custom.5.scd index aba1c18f..6b96d2a4 100644 --- a/man/waybar-custom.5.scd +++ b/man/waybar-custom.5.scd @@ -55,8 +55,8 @@ Addressed by *custom/* *format*: ++ typeof: string ++ - default: {} ++ - The format, how information should be displayed. On {} data gets inserted. + default: {text} ++ + The format, how information should be displayed. On {text} data gets inserted. *format-icons*: ++ typeof: array ++ @@ -160,7 +160,7 @@ $text\\n$tooltip\\n$class* # FORMAT REPLACEMENTS -*{}*: Output of the script. +*{text}*: Output of the script. *{percentage}* Percentage which can be set via a json return type. @@ -172,7 +172,7 @@ $text\\n$tooltip\\n$class* ``` "custom/spotify": { - "format": " {}", + "format": " {text}", "max-length": 40, "interval": 30, // Remove this if your script is endless and write in loop "exec": "$HOME/.config/waybar/mediaplayer.sh 2> /dev/null", // Script in resources folder @@ -185,7 +185,7 @@ $text\\n$tooltip\\n$class* ``` "custom/mpd": { - "format": "♪ {}", + "format": "♪ {text}", //"max-length": 15, "interval": 10, "exec": "mpc current", @@ -199,7 +199,7 @@ $text\\n$tooltip\\n$class* ``` "custom/cmus": { - "format": "♪ {}", + "format": "♪ {text}", //"max-length": 15, "interval": 10, "exec": "cmus-remote -C \"format_print '%a - %t'\"", // artist - title @@ -214,7 +214,7 @@ $text\\n$tooltip\\n$class* ``` "custom/pacman": { - "format": "{} ", + "format": "{text} ", "interval": "once", "exec": "pacman_packages", "on-click": "update-system", @@ -226,7 +226,7 @@ $text\\n$tooltip\\n$class* ``` "custom/pacman": { - "format": "{} ", + "format": "{text} ", "interval": 3600, // every hour "exec": "checkupdates | wc -l", // # of updates "exec-if": "exit 0", // always run; consider advanced run conditions diff --git a/resources/config.jsonc b/resources/config.jsonc index 7e0771f5..6ac1aa50 100644 --- a/resources/config.jsonc +++ b/resources/config.jsonc @@ -189,7 +189,7 @@ "on-click": "pavucontrol" }, "custom/media": { - "format": "{icon} {}", + "format": "{icon} {text}", "return-type": "json", "max-length": 40, "format-icons": { From 254111ff91c68816b35d426ab6977630e26a5a65 Mon Sep 17 00:00:00 2001 From: Lukas Fink Date: Wed, 18 Sep 2024 17:28:58 +0200 Subject: [PATCH 063/780] Improve error message for mixed arg indexing in format string --- src/modules/custom.cpp | 76 +++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 90c54128..a4255521 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -159,43 +159,51 @@ auto waybar::modules::Custom::update() -> void { parseOutputRaw(); } - auto str = fmt::format(fmt::runtime(format_), fmt::arg("text", text_), fmt::arg("alt", alt_), - fmt::arg("icon", getIcon(percentage_, alt_)), - fmt::arg("percentage", percentage_)); - if ((config_["hide-empty-text"].asBool() && text_.empty()) || str.empty()) { - event_box_.hide(); - } else { - label_.set_markup(str); - if (tooltipEnabled()) { - if (tooltip_format_enabled_) { - auto tooltip = config_["tooltip-format"].asString(); - tooltip = fmt::format(fmt::runtime(tooltip), fmt::arg("text", text_), - fmt::arg("alt", alt_), fmt::arg("icon", getIcon(percentage_, alt_)), - fmt::arg("percentage", percentage_)); - label_.set_tooltip_markup(tooltip); - } else if (text_ == tooltip_) { - if (label_.get_tooltip_markup() != str) { - label_.set_tooltip_markup(str); - } - } else { - if (label_.get_tooltip_markup() != tooltip_) { - label_.set_tooltip_markup(tooltip_); + try { + auto str = fmt::format(fmt::runtime(format_), fmt::arg("text", text_), fmt::arg("alt", alt_), + fmt::arg("icon", getIcon(percentage_, alt_)), + fmt::arg("percentage", percentage_)); + if ((config_["hide-empty-text"].asBool() && text_.empty()) || str.empty()) { + event_box_.hide(); + } else { + label_.set_markup(str); + if (tooltipEnabled()) { + if (tooltip_format_enabled_) { + auto tooltip = config_["tooltip-format"].asString(); + tooltip = fmt::format(fmt::runtime(tooltip), fmt::arg("text", text_), + fmt::arg("alt", alt_), fmt::arg("icon", getIcon(percentage_, alt_)), + fmt::arg("percentage", percentage_)); + label_.set_tooltip_markup(tooltip); + } else if (text_ == tooltip_) { + if (label_.get_tooltip_markup() != str) { + label_.set_tooltip_markup(str); + } + } else { + if (label_.get_tooltip_markup() != tooltip_) { + label_.set_tooltip_markup(tooltip_); + } } } + auto style = label_.get_style_context(); + auto classes = style->list_classes(); + for (auto const& c : classes) { + if (c == id_) continue; + style->remove_class(c); + } + for (auto const& c : class_) { + style->add_class(c); + } + style->add_class("flat"); + style->add_class("text-button"); + style->add_class(MODULE_CLASS); + event_box_.show(); } - auto style = label_.get_style_context(); - auto classes = style->list_classes(); - for (auto const& c : classes) { - if (c == id_) continue; - style->remove_class(c); - } - for (auto const& c : class_) { - style->add_class(c); - } - style->add_class("flat"); - style->add_class("text-button"); - style->add_class(MODULE_CLASS); - event_box_.show(); + } catch (const fmt::format_error& e) { + if (std::strcmp(e.what(), "cannot switch from manual to automatic argument indexing") != 0) + throw; + + throw fmt::format_error("mixing manual and automatic argument indexing is no longer supported; " + "try replacing \"{}\" with \"{text}\" in your format specifier"); } } // Call parent update From a3e7031fe25421c1f64d4b88ce2298bbd6dd2324 Mon Sep 17 00:00:00 2001 From: Lukas Fink Date: Wed, 18 Sep 2024 17:30:55 +0200 Subject: [PATCH 064/780] Fix formatting --- src/modules/custom.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index a4255521..e023aaf6 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -170,9 +170,9 @@ auto waybar::modules::Custom::update() -> void { if (tooltipEnabled()) { if (tooltip_format_enabled_) { auto tooltip = config_["tooltip-format"].asString(); - tooltip = fmt::format(fmt::runtime(tooltip), fmt::arg("text", text_), - fmt::arg("alt", alt_), fmt::arg("icon", getIcon(percentage_, alt_)), - fmt::arg("percentage", percentage_)); + tooltip = fmt::format( + fmt::runtime(tooltip), fmt::arg("text", text_), fmt::arg("alt", alt_), + fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("percentage", percentage_)); label_.set_tooltip_markup(tooltip); } else if (text_ == tooltip_) { if (label_.get_tooltip_markup() != str) { @@ -202,8 +202,9 @@ auto waybar::modules::Custom::update() -> void { if (std::strcmp(e.what(), "cannot switch from manual to automatic argument indexing") != 0) throw; - throw fmt::format_error("mixing manual and automatic argument indexing is no longer supported; " - "try replacing \"{}\" with \"{text}\" in your format specifier"); + throw fmt::format_error( + "mixing manual and automatic argument indexing is no longer supported; " + "try replacing \"{}\" with \"{text}\" in your format specifier"); } } // Call parent update From 21af48fdc95b21ad067e627abb96655757c48b36 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 19 Sep 2024 17:31:07 +0200 Subject: [PATCH 065/780] chore: lint --- src/AAppIconLabel.cpp | 3 +-- src/modules/cava.cpp | 8 +++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/AAppIconLabel.cpp b/src/AAppIconLabel.cpp index dc22f20e..3f47eff1 100644 --- a/src/AAppIconLabel.cpp +++ b/src/AAppIconLabel.cpp @@ -154,8 +154,7 @@ void AAppIconLabel::updateAppIcon() { update_app_icon_ = false; if (app_icon_name_.empty()) { image_.set_visible(false); - } - else if (app_icon_name_.front() == '/') { + } else if (app_icon_name_.front() == '/') { auto pixbuf = Gdk::Pixbuf::create_from_file(app_icon_name_); int scaled_icon_size = app_icon_size_ * image_.get_scale_factor(); pixbuf = Gdk::Pixbuf::create_from_file(app_icon_name_, scaled_icon_size, scaled_icon_size); diff --git a/src/modules/cava.cpp b/src/modules/cava.cpp index f81bf799..f16d3f63 100644 --- a/src/modules/cava.cpp +++ b/src/modules/cava.cpp @@ -175,12 +175,14 @@ auto waybar::modules::Cava::update() -> void { 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_); + 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"); From dedee8cd1456616e145d6ceb31788b28e4863918 Mon Sep 17 00:00:00 2001 From: Philipp Hentschel Date: Mon, 22 Jul 2024 12:17:21 +0200 Subject: [PATCH 066/780] pulseaudio: show correct sink volume on default output changes on sinkInfo callbacks, the default sink now has highest priority. That fixes an issue that the volume indicator is not updated when the changes the default output to another devices. added PA_SINK_IDLE as valid state. PA_SINK_RUNNING is only true if any sound output is happening on sink switch. Indicator should also update when no sound is being played. --- include/util/audio_backend.hpp | 2 ++ src/modules/pulseaudio.cpp | 7 +++++++ src/util/audio_backend.cpp | 28 +++++++++++++++++++++++++--- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/include/util/audio_backend.hpp b/include/util/audio_backend.hpp index 2f53103e..3737ae26 100644 --- a/include/util/audio_backend.hpp +++ b/include/util/audio_backend.hpp @@ -38,6 +38,8 @@ class AudioBackend { std::string desc_; std::string monitor_; std::string current_sink_name_; + std::string default_sink_name; + bool default_sink_running_; bool current_sink_running_; // SOURCE uint32_t source_idx_{0}; diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index 255ca571..9d3f1862 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -1,4 +1,6 @@ #include "modules/pulseaudio.hpp" +#include +#include waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value &config) : ALabel(config, "pulseaudio", id, "{volume}%") { @@ -52,6 +54,7 @@ const std::vector waybar::modules::Pulseaudio::getPulseIcon() const std::string nameLC = backend->getSinkPortName() + backend->getFormFactor(); std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower); for (auto const &port : ports) { + spdlog::trace("Port: {}", nameLC); if (nameLC.find(port) != std::string::npos) { if (sink_muted) { res.emplace_back(port + "-muted"); @@ -63,6 +66,10 @@ const std::vector waybar::modules::Pulseaudio::getPulseIcon() const if (sink_muted) { res.emplace_back("default-muted"); } + spdlog::trace("Ports:"); + for (auto const &item : res) { + spdlog::trace(" {}", item); + } return res; } diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index 3d90b6d5..cfff1818 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -1,14 +1,18 @@ #include "util/audio_backend.hpp" #include +#include #include +#include #include #include #include #include +#include #include #include +#include namespace waybar::util { @@ -132,6 +136,7 @@ void AudioBackend::volumeModifyCb(pa_context *c, int success, void *data) { } } + /* * Called when the requested sink information is ready. */ @@ -139,6 +144,11 @@ void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, i void *data) { if (i == nullptr) return; + spdlog::trace("Callback start"); + auto running = i->state == PA_SINK_RUNNING; + auto idle = i->state == PA_SINK_IDLE; + spdlog::trace("Sink name {} Running:[{}] Idle:[{}]", i->name, running,idle ); + auto *backend = static_cast(data); if (!backend->ignored_sinks_.empty()) { @@ -155,11 +165,22 @@ void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, i } } - if (backend->current_sink_name_ == i->name) { - backend->current_sink_running_ = i->state == PA_SINK_RUNNING; + backend->default_sink_running_ = + backend->default_sink_name == i->name; + + if ( i->name != backend->default_sink_name) { + return; } - if (!backend->current_sink_running_ && i->state == PA_SINK_RUNNING) { + if (backend->current_sink_name_ == i->name) { + backend->current_sink_running_ = + (i->state == PA_SINK_RUNNING || + i->state == PA_SINK_IDLE); + } + + if (!backend->current_sink_running_ && ( + i->state == PA_SINK_RUNNING || + i->state == PA_SINK_IDLE)) { backend->current_sink_name_ = i->name; backend->current_sink_running_ = true; } @@ -207,6 +228,7 @@ void AudioBackend::sourceInfoCb(pa_context * /*context*/, const pa_source_info * void AudioBackend::serverInfoCb(pa_context *context, const pa_server_info *i, void *data) { auto *backend = static_cast(data); backend->current_sink_name_ = i->default_sink_name; + backend->default_sink_name = i->default_sink_name; backend->default_source_name_ = i->default_source_name; pa_context_get_sink_info_list(context, sinkInfoCb, data); From 9c47b2e9dddcb5fe58825cce69ff3fe785ad857f Mon Sep 17 00:00:00 2001 From: Philipp Hentschel Date: Mon, 22 Jul 2024 12:37:17 +0200 Subject: [PATCH 067/780] removed debug logging --- src/modules/pulseaudio.cpp | 7 ------- src/util/audio_backend.cpp | 3 --- 2 files changed, 10 deletions(-) diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index 9d3f1862..255ca571 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -1,6 +1,4 @@ #include "modules/pulseaudio.hpp" -#include -#include waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value &config) : ALabel(config, "pulseaudio", id, "{volume}%") { @@ -54,7 +52,6 @@ const std::vector waybar::modules::Pulseaudio::getPulseIcon() const std::string nameLC = backend->getSinkPortName() + backend->getFormFactor(); std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower); for (auto const &port : ports) { - spdlog::trace("Port: {}", nameLC); if (nameLC.find(port) != std::string::npos) { if (sink_muted) { res.emplace_back(port + "-muted"); @@ -66,10 +63,6 @@ const std::vector waybar::modules::Pulseaudio::getPulseIcon() const if (sink_muted) { res.emplace_back("default-muted"); } - spdlog::trace("Ports:"); - for (auto const &item : res) { - spdlog::trace(" {}", item); - } return res; } diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index cfff1818..d084f94a 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -136,7 +135,6 @@ void AudioBackend::volumeModifyCb(pa_context *c, int success, void *data) { } } - /* * Called when the requested sink information is ready. */ @@ -144,7 +142,6 @@ void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, i void *data) { if (i == nullptr) return; - spdlog::trace("Callback start"); auto running = i->state == PA_SINK_RUNNING; auto idle = i->state == PA_SINK_IDLE; spdlog::trace("Sink name {} Running:[{}] Idle:[{}]", i->name, running,idle ); From 8b1d73690d025379937bb63c063ecf088848f800 Mon Sep 17 00:00:00 2001 From: Philipp Hentschel Date: Mon, 22 Jul 2024 12:40:30 +0200 Subject: [PATCH 068/780] added running check to default sink return condition --- src/util/audio_backend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index d084f94a..5f1cf2f2 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -165,7 +165,7 @@ void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, i backend->default_sink_running_ = backend->default_sink_name == i->name; - if ( i->name != backend->default_sink_name) { + if ( i->name != backend->default_sink_name && !backend->default_sink_running_) { return; } From d6bfeb5a44996fe21fafbc93cf450e5a119a9644 Mon Sep 17 00:00:00 2001 From: Philipp Hentschel Date: Mon, 22 Jul 2024 13:05:25 +0200 Subject: [PATCH 069/780] added is running condition to default_sink_is_running check --- src/util/audio_backend.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index 5f1cf2f2..d2c2928d 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -162,8 +162,9 @@ void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, i } } - backend->default_sink_running_ = - backend->default_sink_name == i->name; + backend->default_sink_running_ = backend->default_sink_name == i->name + && (i->state == PA_SINK_RUNNING || i->state == PA_SINK_IDLE); + if ( i->name != backend->default_sink_name && !backend->default_sink_running_) { return; From 951b89ffcb1cdb0d8ccc34fbe13b7bd42971e7ad Mon Sep 17 00:00:00 2001 From: Findus Date: Tue, 23 Jul 2024 15:57:32 +0200 Subject: [PATCH 070/780] Update clang-format.yml workflow dispatch to debug failing workflow manually --- .github/workflows/clang-format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 4a774dbd..89793f5e 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -1,6 +1,6 @@ name: clang-format -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] concurrency: group: ${{ github.workflow }}-format-${{ github.event.pull_request.number || github.ref }} From e3095c6d1d1647d46b730007ca34c8f8b2ebd452 Mon Sep 17 00:00:00 2001 From: Philipp Hentschel Date: Tue, 23 Jul 2024 16:01:24 +0200 Subject: [PATCH 071/780] clang-format --- src/util/audio_backend.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index d2c2928d..73aac148 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -6,12 +6,12 @@ #include #include #include +#include #include #include #include #include -#include namespace waybar::util { @@ -144,7 +144,7 @@ void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, i auto running = i->state == PA_SINK_RUNNING; auto idle = i->state == PA_SINK_IDLE; - spdlog::trace("Sink name {} Running:[{}] Idle:[{}]", i->name, running,idle ); + spdlog::trace("Sink name {} Running:[{}] Idle:[{}]", i->name, running, idle); auto *backend = static_cast(data); @@ -162,23 +162,19 @@ void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, i } } - backend->default_sink_running_ = backend->default_sink_name == i->name - && (i->state == PA_SINK_RUNNING || i->state == PA_SINK_IDLE); + backend->default_sink_running_ = backend->default_sink_name == i->name && + (i->state == PA_SINK_RUNNING || i->state == PA_SINK_IDLE); - - if ( i->name != backend->default_sink_name && !backend->default_sink_running_) { + if (i->name != backend->default_sink_name && !backend->default_sink_running_) { return; } if (backend->current_sink_name_ == i->name) { - backend->current_sink_running_ = - (i->state == PA_SINK_RUNNING || - i->state == PA_SINK_IDLE); + backend->current_sink_running_ = (i->state == PA_SINK_RUNNING || i->state == PA_SINK_IDLE); } - if (!backend->current_sink_running_ && ( - i->state == PA_SINK_RUNNING || - i->state == PA_SINK_IDLE)) { + if (!backend->current_sink_running_ && + (i->state == PA_SINK_RUNNING || i->state == PA_SINK_IDLE)) { backend->current_sink_name_ = i->name; backend->current_sink_running_ = true; } From 57156bce7e1239349661cbcf6c96a73a670d36a3 Mon Sep 17 00:00:00 2001 From: Philipp Hentschel Date: Tue, 23 Jul 2024 16:06:01 +0200 Subject: [PATCH 072/780] removed manual flag from clang format again --- .github/workflows/clang-format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 89793f5e..4a774dbd 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -1,6 +1,6 @@ name: clang-format -on: [push, pull_request, workflow_dispatch] +on: [push, pull_request] concurrency: group: ${{ github.workflow }}-format-${{ github.event.pull_request.number || github.ref }} From 773b1d4806b01e426516e70f673b949175159d95 Mon Sep 17 00:00:00 2001 From: Viktar Lukashonak Date: Mon, 23 Sep 2024 15:51:01 +0300 Subject: [PATCH 073/780] Default value for cldYearShift_ = 1900/01/01 Signed-off-by: Viktar Lukashonak --- src/modules/clock.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index db2979eb..0accdd0c 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -24,6 +24,7 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) m_tlpFmt_{(config_["tooltip-format"].isString()) ? config_["tooltip-format"].asString() : ""}, m_tooltip_{new Gtk::Label()}, cldInTooltip_{m_tlpFmt_.find("{" + kCldPlaceholder + "}") != std::string::npos}, + cldYearShift_{1900y / 1 / 1}, tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos}, tzCurrIdx_{0}, ordInTooltip_{m_tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} { From b4e97eb2f49ef2ca111896bf43846b5b24be8361 Mon Sep 17 00:00:00 2001 From: Viktar Lukashonak Date: Mon, 23 Sep 2024 16:06:50 +0300 Subject: [PATCH 074/780] FreeBSD format fix Signed-off-by: Viktar Lukashonak --- src/modules/clock.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 0accdd0c..7f5a4d55 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -24,7 +24,7 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) m_tlpFmt_{(config_["tooltip-format"].isString()) ? config_["tooltip-format"].asString() : ""}, m_tooltip_{new Gtk::Label()}, cldInTooltip_{m_tlpFmt_.find("{" + kCldPlaceholder + "}") != std::string::npos}, - cldYearShift_{1900y / 1 / 1}, + cldYearShift_{January / 1 / 1900}, tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos}, tzCurrIdx_{0}, ordInTooltip_{m_tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} { From 71a53eb79d78a98d6388678c29bf46baf32ae25c Mon Sep 17 00:00:00 2001 From: Rowan Leeder Date: Wed, 25 Sep 2024 03:16:14 +1000 Subject: [PATCH 075/780] Issue-3092 Add source support to wireplumber module - Adds microphone support etc to the wireplumber module. The existing module hardcodes the selected node type to "Audio/Sink". This feature allows the user to override this via `"node-type": "Audio/Source"`. - Unlike the pulseaudio module, this change does not try to see the module manage both input and output. The same effect can be achieved by running two instances of the wireplumber module. This approach: - Works around some of the complexity overhead that seem to have caused similar PRs to stall. - Using separate module instances also allows both the microphone and speaker levels to be controlled with a scroll wheel. This is something a unified module like pulseaudio struggles with. - Similarly, separate instances allows the source volume level to be exposed as the state. Ie- the linear-gradient css patterns can be applied to both input and output. --- include/modules/wireplumber.hpp | 3 ++- man/waybar-wireplumber.5.scd | 27 +++++++++++++++++++++++++++ src/modules/wireplumber.cpp | 17 +++++++++++------ 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/include/modules/wireplumber.hpp b/include/modules/wireplumber.hpp index 6255b95f..eb39653a 100644 --- a/include/modules/wireplumber.hpp +++ b/include/modules/wireplumber.hpp @@ -18,7 +18,7 @@ class Wireplumber : public ALabel { private: void asyncLoadRequiredApiModules(); - void prepare(); + void prepare(waybar::modules::Wireplumber* self); void activatePlugins(); static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id); static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id); @@ -44,6 +44,7 @@ class Wireplumber : public ALabel { double min_step_; uint32_t node_id_{0}; std::string node_name_; + gchar* type_; }; } // namespace waybar::modules diff --git a/man/waybar-wireplumber.5.scd b/man/waybar-wireplumber.5.scd index 9c26ebaf..ae78f184 100644 --- a/man/waybar-wireplumber.5.scd +++ b/man/waybar-wireplumber.5.scd @@ -19,6 +19,11 @@ The *wireplumber* module displays the current volume reported by WirePlumber. typeof: string ++ This format is used when the sound is muted. +*node-type*: ++ + typeof: string ++ + default: *Audio/Sink* ++ + The WirePlumber node type to attach to. Use *Audio/Source* to manage microphones etc. + *tooltip*: ++ typeof: bool ++ default: *true* ++ @@ -108,6 +113,8 @@ The *wireplumber* module displays the current volume reported by WirePlumber. # EXAMPLES +## Basic: + ``` "wireplumber": { "format": "{volume}%", @@ -116,6 +123,26 @@ The *wireplumber* module displays the current volume reported by WirePlumber. } ``` +## Separate Sink and Source Widgets + +``` +"wireplumber#sink": { + "format": "{volume}% {icon}", + "format-muted": "", + "format-icons": ["", "", ""], + "on-click": "helvum", + "on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle", + "scroll-step": 5 +}, +"wireplumber#source": { + "node-type": "Audio/Source", + "format": "{volume}% ", + "format-muted": "", + "on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle", + "scroll-step": 5 +} +``` + # STYLE - *#wireplumber* diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index eddc3e6b..d6d1e594 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -16,13 +16,17 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val muted_(false), volume_(0.0), min_step_(0.0), - node_id_(0) { + node_id_(0), + type_(nullptr) { wp_init(WP_INIT_PIPEWIRE); wp_core_ = wp_core_new(nullptr, nullptr, nullptr); apis_ = g_ptr_array_new_with_free_func(g_object_unref); om_ = wp_object_manager_new(); - prepare(); + type_ = g_strdup(config_["node-type"].isString() ? config_["node-type"].asString().c_str() + : "Audio/Sink"); + + prepare(this); spdlog::debug("[{}]: connecting to pipewire...", name_); @@ -46,6 +50,7 @@ waybar::modules::Wireplumber::~Wireplumber() { g_clear_object(&mixer_api_); g_clear_object(&def_nodes_api_); g_free(default_node_name_); + g_free(type_); } void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self, uint32_t id) { @@ -138,7 +143,7 @@ void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wir spdlog::debug("[{}]: (onDefaultNodesApiChanged)", self->name_); uint32_t defaultNodeId; - g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &defaultNodeId); + g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &defaultNodeId); if (!isValidNodeId(defaultNodeId)) { spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node change.", self->name_, @@ -200,9 +205,9 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir throw std::runtime_error("Mixer api is not loaded\n"); } - g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", "Audio/Sink", + g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", self->type_, &self->default_node_name_); - g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &self->node_id_); + g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &self->node_id_); if (self->default_node_name_ != nullptr) { spdlog::debug("[{}]: (onObjectManagerInstalled) - default configured node name: {} and id: {}", @@ -246,7 +251,7 @@ void waybar::modules::Wireplumber::activatePlugins() { void waybar::modules::Wireplumber::prepare() { spdlog::debug("[{}]: preparing object manager", name_); wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class", - "=s", "Audio/Sink", nullptr); + "=s", self->type_, nullptr); } void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res, From 2dfef1c213fe008e3520b168ba96f59db6d55d58 Mon Sep 17 00:00:00 2001 From: Rowan Leeder Date: Wed, 25 Sep 2024 04:03:31 +1000 Subject: [PATCH 076/780] Issue-3092 Add node type to wireplumber logs - The module only fetches nodes for "node-type". This causes the 'onMixerChanged' log to spam whenever two or more wireplumber modules were registered on different nodes. To reduce this the unknown node warning will now only print if the node is not the focus of any current module. --- include/modules/wireplumber.hpp | 2 + src/modules/wireplumber.cpp | 81 ++++++++++++++++++++------------- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/include/modules/wireplumber.hpp b/include/modules/wireplumber.hpp index eb39653a..e0033e8a 100644 --- a/include/modules/wireplumber.hpp +++ b/include/modules/wireplumber.hpp @@ -32,6 +32,8 @@ class Wireplumber : public ALabel { bool handleScroll(GdkEventScroll* e) override; + static std::list modules; + WpCore* wp_core_; GPtrArray* apis_; WpObjectManager* om_; diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index d6d1e594..c25b3b51 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -4,6 +4,8 @@ bool isValidNodeId(uint32_t id) { return id > 0 && id < G_MAXUINT32; } +std::list waybar::modules::Wireplumber::modules; + waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Value& config) : ALabel(config, "wireplumber", id, "{volume}%"), wp_core_(nullptr), @@ -18,6 +20,8 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val min_step_(0.0), node_id_(0), type_(nullptr) { + waybar::modules::Wireplumber::modules.push_back(this); + wp_init(WP_INIT_PIPEWIRE); wp_core_ = wp_core_new(nullptr, nullptr, nullptr); apis_ = g_ptr_array_new_with_free_func(g_object_unref); @@ -28,14 +32,14 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val prepare(this); - spdlog::debug("[{}]: connecting to pipewire...", name_); + spdlog::debug("[{}]: connecting to pipewire: '{}'...", name_, type_); if (wp_core_connect(wp_core_) == 0) { - spdlog::error("[{}]: Could not connect to PipeWire", name_); + spdlog::error("[{}]: Could not connect to PipeWire: '{}'", name_, type_); throw std::runtime_error("Could not connect to PipeWire\n"); } - spdlog::debug("[{}]: connected!", name_); + spdlog::debug("[{}]: {} connected!", name_, type_); g_signal_connect_swapped(om_, "installed", (GCallback)onObjectManagerInstalled, this); @@ -43,6 +47,7 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val } waybar::modules::Wireplumber::~Wireplumber() { + waybar::modules::Wireplumber::modules.remove(this); wp_core_disconnect(wp_core_); g_clear_pointer(&apis_, g_ptr_array_unref); g_clear_object(&om_); @@ -54,10 +59,11 @@ waybar::modules::Wireplumber::~Wireplumber() { } void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self, uint32_t id) { - spdlog::debug("[{}]: updating node name with node.id {}", self->name_, id); + spdlog::debug("[{}]: updating '{}' node name with node.id {}", self->name_, self->type_, id); if (!isValidNodeId(id)) { - spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node name update.", self->name_, id); + spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring '{}' node name update.", self->name_, + id, self->type_); return; } @@ -85,7 +91,7 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self->node_name_ = nick != nullptr ? nick : description != nullptr ? description : "Unknown node name"; - spdlog::debug("[{}]: Updating node name to: {}", self->name_, self->node_name_); + spdlog::debug("[{}]: Updating '{}' node name to: {}", self->name_, self->type_, self->node_name_); } void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self, uint32_t id) { @@ -93,7 +99,8 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se GVariant* variant = nullptr; if (!isValidNodeId(id)) { - spdlog::error("[{}]: '{}' is not a valid node ID. Ignoring volume update.", self->name_, id); + spdlog::error("[{}]: '{}' is not a valid '{}' node ID. Ignoring volume update.", self->name_, + id, self->type_); return; } @@ -114,13 +121,22 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se } void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id) { - spdlog::debug("[{}]: (onMixerChanged) - id: {}", self->name_, id); - g_autoptr(WpNode) node = static_cast(wp_object_manager_lookup( self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, nullptr)); if (node == nullptr) { - spdlog::warn("[{}]: (onMixerChanged) - Object with id {} not found", self->name_, id); + // log a warning only if no other widget is targeting the id. + // this reduces log spam when multiple instances of the module are used on different node types. + if (id != self->node_id_) { + for (auto const& module : waybar::modules::Wireplumber::modules) { + if (module->node_id_ == id) { + return; + } + } + } + + spdlog::warn("[{}]: (onMixerChanged: {}) - Object with id {} not found", self->name_, + self->type_, id); return; } @@ -128,26 +144,27 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* if (self->node_id_ != id) { spdlog::debug( - "[{}]: (onMixerChanged) - ignoring mixer update for node: id: {}, name: {} as it is not " - "the default node: {} with id: {}", - self->name_, id, name, self->default_node_name_, self->node_id_); + "[{}]: (onMixerChanged: {}) - ignoring mixer update for node: id: {}, name: {} as it is " + "not the default node: {} with id: {}", + self->name_, self->type_, id, name, self->default_node_name_, self->node_id_); return; } - spdlog::debug("[{}]: (onMixerChanged) - Need to update volume for node with id {} and name {}", - self->name_, id, name); + spdlog::debug( + "[{}]: (onMixerChanged: {}) - Need to update volume for node with id {} and name {}", + self->name_, self->type_, id, name); updateVolume(self, id); } void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wireplumber* self) { - spdlog::debug("[{}]: (onDefaultNodesApiChanged)", self->name_); + spdlog::debug("[{}]: (onDefaultNodesApiChanged: {})", self->name_, self->type_); uint32_t defaultNodeId; g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &defaultNodeId); if (!isValidNodeId(defaultNodeId)) { - spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node change.", self->name_, - defaultNodeId); + spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring '{}' node change.", self->name_, + defaultNodeId, self->type_); return; } @@ -156,8 +173,8 @@ void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wir "=u", defaultNodeId, nullptr)); if (node == nullptr) { - spdlog::warn("[{}]: (onDefaultNodesApiChanged) - Object with id {} not found", self->name_, - defaultNodeId); + spdlog::warn("[{}]: (onDefaultNodesApiChanged: {}) - Object with id {} not found", self->name_, + self->type_, defaultNodeId); return; } @@ -165,21 +182,22 @@ void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wir wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name"); spdlog::debug( - "[{}]: (onDefaultNodesApiChanged) - got the following default node: Node(name: {}, id: {})", - self->name_, defaultNodeName, defaultNodeId); + "[{}]: (onDefaultNodesApiChanged: {}) - got the following default node: Node(name: {}, id: " + "{})", + self->name_, self->type_, defaultNodeName, defaultNodeId); if (g_strcmp0(self->default_node_name_, defaultNodeName) == 0 && self->node_id_ == defaultNodeId) { spdlog::debug( - "[{}]: (onDefaultNodesApiChanged) - Default node has not changed. Node(name: {}, id: {}). " - "Ignoring.", - self->name_, self->default_node_name_, defaultNodeId); + "[{}]: (onDefaultNodesApiChanged: {}) - Default node has not changed. Node(name: {}, id: " + "{}). Ignoring.", + self->name_, self->type_, self->default_node_name_, defaultNodeId); return; } spdlog::debug( - "[{}]: (onDefaultNodesApiChanged) - Default node changed to -> Node(name: {}, id: {})", - self->name_, defaultNodeName, defaultNodeId); + "[{}]: (onDefaultNodesApiChanged: {}) - Default node changed to -> Node(name: {}, id: {})", + self->name_, self->type_, defaultNodeName, defaultNodeId); g_free(self->default_node_name_); self->default_node_name_ = g_strdup(defaultNodeName); @@ -210,8 +228,9 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &self->node_id_); if (self->default_node_name_ != nullptr) { - spdlog::debug("[{}]: (onObjectManagerInstalled) - default configured node name: {} and id: {}", - self->name_, self->default_node_name_, self->node_id_); + spdlog::debug( + "[{}]: (onObjectManagerInstalled: {}) - default configured node name: {} and id: {}", + self->name_, self->type_, self->default_node_name_, self->node_id_); } updateVolume(self, self->node_id_); @@ -248,8 +267,8 @@ void waybar::modules::Wireplumber::activatePlugins() { } } -void waybar::modules::Wireplumber::prepare() { - spdlog::debug("[{}]: preparing object manager", name_); +void waybar::modules::Wireplumber::prepare(waybar::modules::Wireplumber* self) { + spdlog::debug("[{}]: preparing object manager: '{}'", name_, self->type_); wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class", "=s", self->type_, nullptr); } From c88a86f510820005426d998bdc0d8d5ac20ba304 Mon Sep 17 00:00:00 2001 From: mslxl Date: Wed, 25 Sep 2024 09:56:42 +0800 Subject: [PATCH 077/780] nix: remove patches from downstream The patches is the modification of downstream, it should not affect upstream. Any changes of upstream would caused patch fail. --- nix/default.nix | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nix/default.nix b/nix/default.nix index 9ce39a9b..e7f07929 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -25,6 +25,9 @@ in mesonFlags = lib.remove "-Dgtk-layer-shell=enabled" oldAttrs.mesonFlags; + # downstream patch should not affect upstream + patches = []; + buildInputs = (builtins.filter (p: p.pname != "wireplumber") oldAttrs.buildInputs) ++ [ pkgs.wireplumber ]; From 04bda9f443a2ab8ac21ce3efe762bd1a907dc248 Mon Sep 17 00:00:00 2001 From: Lars Niesen Date: Tue, 24 Sep 2024 21:41:54 +0200 Subject: [PATCH 078/780] Backlight: Add minimum brightness As currently it is possible to turn the brightness to zero which may not be desirable, this patch add a configurable brightness check. --- src/modules/backlight.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/modules/backlight.cpp b/src/modules/backlight.cpp index 4ae511eb..6bfe8cee 100644 --- a/src/modules/backlight.cpp +++ b/src/modules/backlight.cpp @@ -112,6 +112,14 @@ bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) { step = config_["scroll-step"].asDouble(); } + double min_brightness = 10; + if (config_["min-brightness"].isDouble()) { + min_brightness = config_["min-brightness"].asDouble(); + } + if (backend.get_scaled_brightness(preferred_device_) <= min_brightness && + ct == util::ChangeType::Decrease) { + return true; + } backend.set_brightness(preferred_device_, ct, step); return true; From 47f767b0ee32de13a2ff893a9146f59b47681601 Mon Sep 17 00:00:00 2001 From: Lars Niesen Date: Wed, 25 Sep 2024 06:24:24 +0200 Subject: [PATCH 079/780] Backlight: Add documentation for min-brightness --- man/waybar-backlight.5.scd | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/man/waybar-backlight.5.scd b/man/waybar-backlight.5.scd index 5286c2ed..a57b8278 100644 --- a/man/waybar-backlight.5.scd +++ b/man/waybar-backlight.5.scd @@ -81,6 +81,11 @@ The *backlight* module displays the current backlight level. default: 1.0 ++ The speed at which to change the brightness when scrolling. +*min-brightness*: ++ + typeof: double ++ + default: 10.0 ++ + The minimum brightness of the backlight. + *menu*: ++ typeof: string ++ Action that popups the menu. From d684a6de21ee78d58f819eb759b905a9091b3dd4 Mon Sep 17 00:00:00 2001 From: Lars Niesen Date: Wed, 25 Sep 2024 16:37:21 +0200 Subject: [PATCH 080/780] Backlight: Set default to 0 to prevent breaking existing setups --- man/waybar-backlight.5.scd | 2 +- src/modules/backlight.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/man/waybar-backlight.5.scd b/man/waybar-backlight.5.scd index a57b8278..e1a688db 100644 --- a/man/waybar-backlight.5.scd +++ b/man/waybar-backlight.5.scd @@ -83,7 +83,7 @@ The *backlight* module displays the current backlight level. *min-brightness*: ++ typeof: double ++ - default: 10.0 ++ + default: 0.0 ++ The minimum brightness of the backlight. *menu*: ++ diff --git a/src/modules/backlight.cpp b/src/modules/backlight.cpp index 6bfe8cee..ff58951c 100644 --- a/src/modules/backlight.cpp +++ b/src/modules/backlight.cpp @@ -112,7 +112,7 @@ bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) { step = config_["scroll-step"].asDouble(); } - double min_brightness = 10; + double min_brightness = 0; if (config_["min-brightness"].isDouble()) { min_brightness = config_["min-brightness"].asDouble(); } From e46a1c6bfc1de05cb267eb80f6ccd2f67f90edd9 Mon Sep 17 00:00:00 2001 From: Viktar Lukashonak Date: Sat, 28 Sep 2024 00:57:02 +0300 Subject: [PATCH 081/780] cava bump Signed-off-by: Viktar Lukashonak --- meson.build | 2 +- subprojects/cava.wrap | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/meson.build b/meson.build index f4941c8f..726d492b 100644 --- a/meson.build +++ b/meson.build @@ -482,7 +482,7 @@ if get_option('experimental') endif cava = dependency('cava', - version : '>=0.10.2', + version : '>=0.10.3', required: get_option('cava'), fallback : ['cava', 'cava_dep'], not_found_message: 'cava is not found. Building waybar without cava') diff --git a/subprojects/cava.wrap b/subprojects/cava.wrap index 275ba114..f0309bf5 100644 --- a/subprojects/cava.wrap +++ b/subprojects/cava.wrap @@ -1,7 +1,7 @@ [wrap-file] -directory = cava-0.10.2 -source_url = https://github.com/LukashonakV/cava/archive/0.10.2.tar.gz -source_filename = cava-0.10.2.tar.gz -source_hash = dff78c4787c9843583086408a0a6e5bde7a5dee1fa17ae526847366846cb19c3 +directory = cava-0.10.3 +source_url = https://github.com/LukashonakV/cava/archive/0.10.3.tar.gz +source_filename = cava-0.10.3.tar.gz +source_hash = aab0a4ed3f999e8461ad9de63ef8a77f28b6b2011f7dd0c69ba81819d442f6f9 [provide] cava = cava_dep From edab49f2912d29a89325a359261b8b1de878aebb Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Sat, 28 Sep 2024 12:41:10 -0500 Subject: [PATCH 082/780] nix/default: cava bump --- nix/default.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/default.nix b/nix/default.nix index e7f07929..a9ff180b 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -5,12 +5,12 @@ }: let libcava = rec { - version = "0.10.2"; + version = "0.10.3"; src = pkgs.fetchFromGitHub { owner = "LukashonakV"; repo = "cava"; rev = version; - hash = "sha256-jU7RQV2txruu/nUUl0TzjK4nai7G38J1rcTjO7UXumY="; + hash = "sha256-ZDFbI69ECsUTjbhlw2kHRufZbQMu+FQSMmncCJ5pagg="; }; }; in From e394485857d64d63b76a7555c26456051c65d3bf Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Sat, 28 Sep 2024 12:51:08 -0500 Subject: [PATCH 083/780] .github/workflows: don't run on forks --- .github/workflows/docker.yml | 1 + .github/workflows/nix-update-flake-lock.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index d9fc5d3e..66c465ba 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -8,6 +8,7 @@ on: jobs: build-and-push: runs-on: ubuntu-latest + if: github.repository == 'Alexays/Waybar' strategy: fail-fast: false # don't fail the other jobs if one of the images fails to build matrix: diff --git a/.github/workflows/nix-update-flake-lock.yml b/.github/workflows/nix-update-flake-lock.yml index 2b65c329..6ba18904 100644 --- a/.github/workflows/nix-update-flake-lock.yml +++ b/.github/workflows/nix-update-flake-lock.yml @@ -9,6 +9,7 @@ on: jobs: lockfile: runs-on: ubuntu-latest + if: github.repository == 'Alexays/Waybar' steps: - name: Checkout repository uses: actions/checkout@v4 From e53497bab6f826f7eace501f21f5e6f178253059 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Sat, 28 Sep 2024 13:21:55 -0500 Subject: [PATCH 084/780] .github/workflows: allow forks to manually run flake lock update --- .github/workflows/nix-update-flake-lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nix-update-flake-lock.yml b/.github/workflows/nix-update-flake-lock.yml index 6ba18904..a1679ead 100644 --- a/.github/workflows/nix-update-flake-lock.yml +++ b/.github/workflows/nix-update-flake-lock.yml @@ -9,7 +9,7 @@ on: jobs: lockfile: runs-on: ubuntu-latest - if: github.repository == 'Alexays/Waybar' + if: github.event_name != 'schedule' || github.repository == 'Alexays/Waybar' steps: - name: Checkout repository uses: actions/checkout@v4 From 95eaffcfb1af0e826a7e93ecd3da7c3be56c02e2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 1 Oct 2024 00:11:21 +0000 Subject: [PATCH 085/780] 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/4f807e8940284ad7925ebd0a0993d2a1791acb2f?narHash=sha256-IiA3jfbR7K/B5%2B9byVi9BZGWTD4VSbWe8VLpp9B/iYk%3D' (2024-09-11) → 'github:NixOS/nixpkgs/06cf0e1da4208d3766d898b7fdab6513366d45b9?narHash=sha256-S5kVU7U82LfpEukbn/ihcyNt2%2BEvG7Z5unsKW9H/yFA%3D' (2024-09-29) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 15bf9f09..4b3e7212 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1726062873, - "narHash": "sha256-IiA3jfbR7K/B5+9byVi9BZGWTD4VSbWe8VLpp9B/iYk=", + "lastModified": 1727634051, + "narHash": "sha256-S5kVU7U82LfpEukbn/ihcyNt2+EvG7Z5unsKW9H/yFA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "4f807e8940284ad7925ebd0a0993d2a1791acb2f", + "rev": "06cf0e1da4208d3766d898b7fdab6513366d45b9", "type": "github" }, "original": { From 6df26ccba761af1c41bf0494320f98d6b93b6e09 Mon Sep 17 00:00:00 2001 From: PassiHD Date: Wed, 9 Oct 2024 20:22:58 +0200 Subject: [PATCH 086/780] feat: add warning threshold to temperature module Signed-off-by: PassiHD --- include/modules/temperature.hpp | 1 + src/modules/temperature.cpp | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/modules/temperature.hpp b/include/modules/temperature.hpp index 5440df77..918281be 100644 --- a/include/modules/temperature.hpp +++ b/include/modules/temperature.hpp @@ -18,6 +18,7 @@ class Temperature : public ALabel { private: float getTemperature(); bool isCritical(uint16_t); + bool isWarning(uint16_t); std::string file_path_; util::SleeperThread thread_; diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index 30287763..356536f9 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -69,12 +69,17 @@ auto waybar::modules::Temperature::update() -> void { uint16_t temperature_f = std::round(temperature * 1.8 + 32); uint16_t temperature_k = std::round(temperature + 273.15); auto critical = isCritical(temperature_c); + auto warning = isWarning(temperature_c); auto format = format_; if (critical) { format = config_["format-critical"].isString() ? config_["format-critical"].asString() : format; label_.get_style_context()->add_class("critical"); - } else { + } 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 (format.empty()) { @@ -135,7 +140,12 @@ float waybar::modules::Temperature::getTemperature() { #endif } +bool waybar::modules::Temperature::isWarning(uint16_t temperature_c) { + return config_["warning-threshold"].isInt() && + temperature_c >= config_["warning-threshold"].asInt(); +} + bool waybar::modules::Temperature::isCritical(uint16_t temperature_c) { return config_["critical-threshold"].isInt() && temperature_c >= config_["critical-threshold"].asInt(); -} +} \ No newline at end of file From 0e03c7a811ef477eae4465ffb3ff443e1b6e1e87 Mon Sep 17 00:00:00 2001 From: Christian Fillion Date: Wed, 16 Oct 2024 02:12:06 -0400 Subject: [PATCH 087/780] fix a segfault on signals received after main returns The waybar process does not exit instantaneously. Signals may be recevied after main has started freeing resources. When a worker thread is in `fgets` this time window can last forever. An easy way to duplicate the crash is pressing ^C twice with a Hyprland module. Thread 1 "waybar" received signal SIGSEGV, Segmentation fault. spdlog::sinks::sink::should_log (this=0x5f620b542ca5, msg_level=spdlog::level::info) at /usr/src/debug/spdlog/spdlog-1.14.1/include/spdlog/sinks/sink-inl.h:13 13 return msg_level >= level_.load(std::memory_order_relaxed); (gdb) p $_siginfo._sifields._sigfault.si_addr $1 = (void *) 0x5f620b542cad --- src/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 679c66d6..442c530c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -107,6 +107,10 @@ int main(int argc, char* argv[]) { ret = client->main(argc, argv); } while (reload); + std::signal(SIGUSR1, SIG_IGN); + std::signal(SIGUSR2, SIG_IGN); + std::signal(SIGINT, SIG_IGN); + delete client; return ret; } catch (const std::exception& e) { From 92242f0b9d7f37f4ccf87f03b0c0ce30255d2d54 Mon Sep 17 00:00:00 2001 From: Christian Fillion Date: Wed, 16 Oct 2024 09:56:05 -0400 Subject: [PATCH 088/780] hyprland: fix a data race at startup between sockets 1 and 2 `Workspaces::*` and `IPC::startIPC` may both call `getSocketFolder` at the same time. This randomly causes crashes and/or corruption of the socket path. Typical crash A: [2024-10-16 07:42:09.987] [info] Hyprland IPC starting malloc(): unaligned tcache chunk detected [2024-10-16 07:42:09.987] [error] Hyprland IPC: Unable to connect? Thread 1 "waybar" received signal SIGABRT, Aborted. (gdb) bt #0 __pthread_kill_implementation (threadid=, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44 (omitted for brievety) #9 0x00007ffff64ae745 in operator new (sz=sz@entry=296) at /usr/src/debug/gcc/gcc/libstdc++-v3/libsupc++/new_op.cc:50 #10 0x00007ffff65ab1f1 in std::filesystem::__cxx11::path::_List::_Impl::copy (this=0x555555a23350) at /usr/src/debug/gcc/gcc/libstdc++-v3/src/c++17/fs_path.cc:249 #11 0x00007ffff65ab3bd in std::filesystem::__cxx11::path::_List::_List (this=0x7fffffff9d30, other=) at /usr/src/debug/gcc/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/unique_ptr.h:454 #12 0x00005555556f4ab1 in waybar::modules::hyprland::IPC::getSocket1Reply(std::__cxx11::basic_string, std::allocator > const&) () #13 0x00005555556f5e3d in waybar::modules::hyprland::IPC::getSocket1JsonReply(std::__cxx11::basic_string, std::allocator > const&) () #14 0x000055555571289c in waybar::modules::hyprland::Workspaces::setCurrentMonitorId() () Typical crash B: [2024-10-16 10:01:15.859] [info] Hyprland IPC starting [2024-10-16 10:01:15.859] [info] Loading persistent workspaces from Hyprland workspace rules Thread 8 "waybar" received signal SIGSEGV, Segmentation fault. (gdb) bt #0 std::__cxx11::basic_string, std::allocator >::_S_copy (__d=0x5555558fbca8 "/", __s=0x2973961a26d35726 , __n=1) at /usr/src/debug/gcc/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/basic_string.h:433 (omitted for brievety) #15 waybar::modules::hyprland::IPC::getSocketFolder[abi:cxx11](char const*) (instanceSig=0x7fffffffe604 "4520b30d498daca8079365bdb909a8dea38e8d55_1729051218_1982280648") at ../src/modules/hyprland/backend.cpp:41 #16 0x000055555564230f in waybar::modules::hyprland::IPC::startIPC()::{lambda()#1}::operator()() const () at ../src/modules/hyprland/backend.cpp:70 #17 0x00007ffff64e1c34 in std::execute_native_thread_routine (__p=0x5555558119c0) at /usr/src/debug/gcc/gcc/libstdc++-v3/src/c++11/thread.cc:104 #18 0x00007ffff62a339d in start_thread (arg=) at pthread_create.c:447 --- src/modules/hyprland/backend.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp index 77f534e0..39341a14 100644 --- a/src/modules/hyprland/backend.cpp +++ b/src/modules/hyprland/backend.cpp @@ -18,6 +18,9 @@ namespace waybar::modules::hyprland { std::filesystem::path IPC::socketFolder_; std::filesystem::path IPC::getSocketFolder(const char* instanceSig) { + static std::mutex folderMutex; + std::unique_lock lock(folderMutex); + // socket path, specified by EventManager of Hyprland if (!socketFolder_.empty()) { return socketFolder_; From 696f7e1542ee2786d27e6a6e4e86bf2359132673 Mon Sep 17 00:00:00 2001 From: Victor Perez Cano Date: Fri, 18 Oct 2024 20:41:13 +0200 Subject: [PATCH 089/780] tooltip functionality on hyprland/language module --- include/modules/hyprland/language.hpp | 1 + src/modules/hyprland/language.cpp | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/include/modules/hyprland/language.hpp b/include/modules/hyprland/language.hpp index 47a4d69c..ce5b9262 100644 --- a/include/modules/hyprland/language.hpp +++ b/include/modules/hyprland/language.hpp @@ -35,6 +35,7 @@ class Language : public waybar::ALabel, public EventHandler { std::mutex mutex_; const Bar& bar_; util::JsonParser parser_; + const bool tooltip_format_enabled_; Layout layout_; }; diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index d86393af..901749a0 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -10,7 +10,9 @@ namespace waybar::modules::hyprland { Language::Language(const std::string& id, const Bar& bar, const Json::Value& config) - : ALabel(config, "language", id, "{}", 0, true), bar_(bar) { + : ALabel(config, "language", id, "{}", 0, true), + bar_(bar), + tooltip_format_enabled_{config_["tooltip-format"].isString()} { modulesReady = true; if (!gIPC) { @@ -57,9 +59,27 @@ auto Language::update() -> void { spdlog::debug("hyprland language formatted layout name {}", layoutName); + std::string tooltipContent = std::string{}; + if (tooltipEnabled()) { + if (tooltip_format_enabled_) { + auto tooltip_format = config_["tooltip-format"].asString(); + tooltipContent = fmt::format(fmt::runtime(tooltip_format), + fmt::arg("long", layout_.full_name), + fmt::arg("short", layout_.short_name), + fmt::arg("shortDescription", layout_.short_description), + fmt::arg("variant", layout_.variant)); + + } else { + tooltipContent = layoutName; + } + } + + spdlog::debug("hyprland language formatted tooltip content {}", tooltipContent); + if (!format_.empty()) { label_.show(); label_.set_markup(layoutName); + label_.set_tooltip_markup(tooltipContent); } else { label_.hide(); } From 550d108a03a4d86dced4eef8e2db095a294b23f1 Mon Sep 17 00:00:00 2001 From: Victor Perez Cano Date: Sat, 19 Oct 2024 17:30:23 +0200 Subject: [PATCH 090/780] added tooltip-format- on hyprland/language tooltip --- include/modules/hyprland/language.hpp | 1 - src/modules/hyprland/language.cpp | 37 +++++++++++++++++---------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/include/modules/hyprland/language.hpp b/include/modules/hyprland/language.hpp index ce5b9262..47a4d69c 100644 --- a/include/modules/hyprland/language.hpp +++ b/include/modules/hyprland/language.hpp @@ -35,7 +35,6 @@ class Language : public waybar::ALabel, public EventHandler { std::mutex mutex_; const Bar& bar_; util::JsonParser parser_; - const bool tooltip_format_enabled_; Layout layout_; }; diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index 901749a0..ecb3ea2c 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -11,8 +11,7 @@ namespace waybar::modules::hyprland { Language::Language(const std::string& id, const Bar& bar, const Json::Value& config) : ALabel(config, "language", id, "{}", 0, true), - bar_(bar), - tooltip_format_enabled_{config_["tooltip-format"].isString()} { + bar_(bar) { modulesReady = true; if (!gIPC) { @@ -56,30 +55,40 @@ auto Language::update() -> void { fmt::arg("shortDescription", layout_.short_description), fmt::arg("variant", layout_.variant))); } - spdlog::debug("hyprland language formatted layout name {}", layoutName); std::string tooltipContent = std::string{}; - if (tooltipEnabled()) { - if (tooltip_format_enabled_) { + bool tooltip_enabled = tooltipEnabled(); + if (tooltip_enabled) { + if (config_.isMember("tooltip-format")) { auto tooltip_format = config_["tooltip-format"].asString(); - tooltipContent = fmt::format(fmt::runtime(tooltip_format), - fmt::arg("long", layout_.full_name), - fmt::arg("short", layout_.short_name), - fmt::arg("shortDescription", layout_.short_description), - fmt::arg("variant", layout_.variant)); - - } else { + if (config_.isMember("tooltip-format-" + layout_.short_description + "-" + layout_.variant)) { + const auto propName = "tooltip-format-" + layout_.short_description + "-" + layout_.variant; + tooltipContent = fmt::format(fmt::runtime(tooltip_format), config_[propName].asString()); + } else if (config_.isMember("tooltip-format-" + layout_.short_description)) { + const auto propName = "tooltip-format-" + layout_.short_description; + tooltipContent = fmt::format(fmt::runtime(tooltip_format), config_[propName].asString()); + } else { + tooltipContent = trim(fmt::format(fmt::runtime(tooltip_format), fmt::arg("long", layout_.full_name), + fmt::arg("short", layout_.short_name), + fmt::arg("shortDescription", layout_.short_description), + fmt::arg("variant", layout_.variant))); + } + } + else { + // if no tooltip format is provided, use the same text as the module tooltipContent = layoutName; } + spdlog::debug("hyprland language formatted tooltip content {}", tooltipContent); } - spdlog::debug("hyprland language formatted tooltip content {}", tooltipContent); if (!format_.empty()) { label_.show(); label_.set_markup(layoutName); - label_.set_tooltip_markup(tooltipContent); + if (tooltip_enabled) { + label_.set_tooltip_markup(tooltipContent); + } } else { label_.hide(); } From cb0ecfbddc6d4e2d305608b01fd03945e167706b Mon Sep 17 00:00:00 2001 From: Victor Perez Cano Date: Sat, 19 Oct 2024 18:04:07 +0200 Subject: [PATCH 091/780] updated man for hyprland/language --- man/waybar-hyprland-language.5.scd | 41 +++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/man/waybar-hyprland-language.5.scd b/man/waybar-hyprland-language.5.scd index 33b28ae4..9ebc8801 100644 --- a/man/waybar-hyprland-language.5.scd +++ b/man/waybar-hyprland-language.5.scd @@ -38,6 +38,19 @@ Addressed by *hyprland/language* typeof: array ++ The actions corresponding to the buttons of the menu. +*tooltip*: ++ + typeof: boolean ++ + default: true ++ + Enables or disables the tooltip for the language module. By default, the tooltip is enabled. Set to *false* to disable. + +*tooltip-format*: ++ + typeof: string ++ + default: {long} ++ + Specifies the format of the tooltip when it is enabled. It follows the same format replacement rules as the *format* key. + +*tooltip-format-*: ++ + typeof: string ++ + Allows specifying a different tooltip format for each language. The ** should be replaced with the language code. This can be used to provide a custom tooltip for each language. # FORMAT REPLACEMENTS @@ -54,13 +67,35 @@ Addressed by *hyprland/language* ``` "hyprland/language": { - "format": "Lang: {long}" - "format-en": "AMERICA, HELL YEAH!" - "format-tr": "As bayrakları" + "format": "Lang: {long}", + "format-en": "AMERICA, HELL YEAH!", + "format-tr": "As bayrakları", "keyboard-name": "at-translated-set-2-keyboard" } ``` +``` +"hyprland/language": { + "format": "{}", + "format-en": "US", + "format-es": "ES", + "tooltip": true, + "tooltip-format": "{long}" +} +``` + +``` +"hyprland/language": { + "format": "{}", + "format-en": "US", + "format-es": "ES", + "tooltip": true, + "tooltip-format": "{}", + "tooltip-format-es": "{Español}", + "tooltip-format-en": "{English (american)}" +} +``` + # STYLE - *#language* From 6e4160dc532069e3fe5136f8424d036ad0abbb86 Mon Sep 17 00:00:00 2001 From: ico277 Date: Tue, 22 Oct 2024 03:49:52 +0200 Subject: [PATCH 092/780] add an option to seach by hwmon name for the temperature module --- src/modules/temperature.cpp | 43 +++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index 30287763..9ae5c1fb 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -1,6 +1,7 @@ #include "modules/temperature.hpp" #include +#include #include #if defined(__FreeBSD__) @@ -20,12 +21,42 @@ waybar::modules::Temperature::Temperature(const std::string& id, const Json::Val if (check_set_path(item.asString())) break; }; - // if hwmon_path is an array, loop to find first valid item - traverseAsArray(config_["hwmon-path"], [this](const std::string& path) { - if (!std::filesystem::exists(path)) return false; - file_path_ = path; - return true; - }); + if (config_["hwmon-by-name"].isString() && config_["input-filename"].isString()) { + auto name = config_["hwmon-by-name"].asString(); + auto input_filename = config_["input-filename"].asString(); + for (const auto& entry : std::filesystem::directory_iterator("/sys/class/hwmon/")) { + if (std::filesystem::is_directory(entry) && file_path_.empty()) { + std::string name_filepath = entry.path().string() + "/name"; + std::string input_filepath = entry.path().string() + "/" + input_filename; + + if (std::filesystem::exists(name_filepath) && std::filesystem::exists(input_filepath)) { + std::ifstream name_file(name_filepath); + if (!name_file.is_open()) { + throw std::runtime_error("Error: Could not open file " + name_filepath); + } + + std::string line; + while (std::getline(name_file, line)) { + if (line.find(name) != std::string::npos) { + file_path_ = input_filepath; + break; + } + } + } + } + } + if (file_path_.empty()) + throw std::runtime_error("Could not find hwmon by name " + name); + } + + if (file_path_.empty()) { + // if hwmon_path is an array, loop to find first valid item + traverseAsArray(config_["hwmon-path"], [this](const std::string& path) { + if (!std::filesystem::exists(path)) return false; + file_path_ = path; + return true; + }); + } if (file_path_.empty() && config_["input-filename"].isString()) { // fallback to hwmon_paths-abs From bb40e169fd3cb77a46a623ff44db496c9b161749 Mon Sep 17 00:00:00 2001 From: Blexyel Date: Tue, 22 Oct 2024 10:56:26 +0200 Subject: [PATCH 093/780] feat: update man page --- man/waybar-temperature.5.scd | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/man/waybar-temperature.5.scd b/man/waybar-temperature.5.scd index 541bf3af..bf41ecc8 100644 --- a/man/waybar-temperature.5.scd +++ b/man/waybar-temperature.5.scd @@ -31,6 +31,10 @@ Addressed by *temperature* typeof: string ++ The temperature filename of your *hwmon-path-abs*, e.g. *temp1_input* +*warning-threshold*: ++ + typeof: integer ++ + The threshold before it is considered warning (Celsius). + *critical-threshold*: ++ typeof: integer ++ The threshold before it is considered critical (Celsius). @@ -40,6 +44,10 @@ Addressed by *temperature* default: 10 ++ The interval in which the information gets polled. +*format-warning*: ++ + typeof: string ++ + The format to use when temperature is considered warning + *format-critical*: ++ typeof: string ++ The format to use when temperature is considered critical From 8cae35d49f2da063af422d5b3538b962768da078 Mon Sep 17 00:00:00 2001 From: ico277 Date: Tue, 22 Oct 2024 18:23:24 +0200 Subject: [PATCH 094/780] try to adhere more to the rest of the coding style --- src/modules/temperature.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index 9ae5c1fb..fae1c1f6 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -26,8 +26,8 @@ waybar::modules::Temperature::Temperature(const std::string& id, const Json::Val auto input_filename = config_["input-filename"].asString(); for (const auto& entry : std::filesystem::directory_iterator("/sys/class/hwmon/")) { if (std::filesystem::is_directory(entry) && file_path_.empty()) { - std::string name_filepath = entry.path().string() + "/name"; - std::string input_filepath = entry.path().string() + "/" + input_filename; + auto name_filepath = entry.path().string() + "/name"; + auto input_filepath = entry.path().string() + "/" + input_filename; if (std::filesystem::exists(name_filepath) && std::filesystem::exists(input_filepath)) { std::ifstream name_file(name_filepath); From b92394cd4dd51183d0184937cedc8f1802009802 Mon Sep 17 00:00:00 2001 From: ico277 Date: Tue, 22 Oct 2024 18:53:13 +0200 Subject: [PATCH 095/780] man page edit for the configuration addition --- man/waybar-temperature.5.scd | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/man/waybar-temperature.5.scd b/man/waybar-temperature.5.scd index 541bf3af..4ca17410 100644 --- a/man/waybar-temperature.5.scd +++ b/man/waybar-temperature.5.scd @@ -29,7 +29,12 @@ Addressed by *temperature* *input-filename*: ++ typeof: string ++ - The temperature filename of your *hwmon-path-abs*, e.g. *temp1_input* + The temperature filename of your *hwmon-path-abs* (also used by *hwmon-by-name*), e.g. *temp1_input* + +*hwmon-by-name*: ++ + typeof: string ++ + The substring to search for in */sys/class/hwmon/hwmonX/name* (where hwmonX is any folder in */sys/class/hwmon/*). + Waybar will search for every directory in */sys/class/hwmon/* and uses the directory in which the *name* matches *hwmon-by-name*. *critical-threshold*: ++ typeof: integer ++ From 00e5cb7568107d6416536dc484f4a7ccb293ec8f Mon Sep 17 00:00:00 2001 From: ico277 Date: Tue, 22 Oct 2024 18:56:10 +0200 Subject: [PATCH 096/780] clang-format run --- src/modules/temperature.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index fae1c1f6..ec0f585e 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -43,10 +43,9 @@ waybar::modules::Temperature::Temperature(const std::string& id, const Json::Val } } } - } + } } - if (file_path_.empty()) - throw std::runtime_error("Could not find hwmon by name " + name); + if (file_path_.empty()) throw std::runtime_error("Could not find hwmon by name " + name); } if (file_path_.empty()) { From 38cf981f78896888a51d4785af05b75c83015ec2 Mon Sep 17 00:00:00 2001 From: Victor Perez Cano Date: Fri, 25 Oct 2024 18:58:55 +0200 Subject: [PATCH 097/780] fixed clang format for language.cpp --- src/modules/hyprland/language.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index ecb3ea2c..26985fcb 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -10,8 +10,7 @@ namespace waybar::modules::hyprland { Language::Language(const std::string& id, const Bar& bar, const Json::Value& config) - : ALabel(config, "language", id, "{}", 0, true), - bar_(bar) { + : ALabel(config, "language", id, "{}", 0, true), bar_(bar) { modulesReady = true; if (!gIPC) { @@ -69,20 +68,19 @@ auto Language::update() -> void { const auto propName = "tooltip-format-" + layout_.short_description; tooltipContent = fmt::format(fmt::runtime(tooltip_format), config_[propName].asString()); } else { - tooltipContent = trim(fmt::format(fmt::runtime(tooltip_format), fmt::arg("long", layout_.full_name), - fmt::arg("short", layout_.short_name), - fmt::arg("shortDescription", layout_.short_description), - fmt::arg("variant", layout_.variant))); + tooltipContent = + trim(fmt::format(fmt::runtime(tooltip_format), fmt::arg("long", layout_.full_name), + fmt::arg("short", layout_.short_name), + fmt::arg("shortDescription", layout_.short_description), + fmt::arg("variant", layout_.variant))); } - } - else { + } else { // if no tooltip format is provided, use the same text as the module tooltipContent = layoutName; } spdlog::debug("hyprland language formatted tooltip content {}", tooltipContent); } - if (!format_.empty()) { label_.show(); label_.set_markup(layoutName); From 3f80e507fd53f759cc7d7e97635c9d69b375f6a0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 1 Nov 2024 00:11:22 +0000 Subject: [PATCH 098/780] 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/06cf0e1da4208d3766d898b7fdab6513366d45b9?narHash=sha256-S5kVU7U82LfpEukbn/ihcyNt2%2BEvG7Z5unsKW9H/yFA%3D' (2024-09-29) → 'github:NixOS/nixpkgs/807e9154dcb16384b1b765ebe9cd2bba2ac287fd?narHash=sha256-l253w0XMT8nWHGXuXqyiIC/bMvh1VRszGXgdpQlfhvU%3D' (2024-10-29) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 4b3e7212..d0ca27b8 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1727634051, - "narHash": "sha256-S5kVU7U82LfpEukbn/ihcyNt2+EvG7Z5unsKW9H/yFA=", + "lastModified": 1730200266, + "narHash": "sha256-l253w0XMT8nWHGXuXqyiIC/bMvh1VRszGXgdpQlfhvU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "06cf0e1da4208d3766d898b7fdab6513366d45b9", + "rev": "807e9154dcb16384b1b765ebe9cd2bba2ac287fd", "type": "github" }, "original": { From 77b50b4c7a58408a80328f4f3177bdbeac31c5ce Mon Sep 17 00:00:00 2001 From: Aqa-Ib Date: Wed, 6 Nov 2024 13:07:09 +0000 Subject: [PATCH 099/780] fix hyprland's grouped window flags Both flags are wrong, because: - the active group member can be fullscreened. - technically, a grouped window can be solo as well, because only the active group member is shown, the other members are hidden. Also you can have a group consisting of only one window. --- src/modules/hyprland/window.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index b5ed8f02..152eea03 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -191,12 +191,6 @@ void Window::queryActiveWorkspace() { solo_ = true; } - // Grouped windows have a tab bar and therefore don't look fullscreen or solo - if (windowData_.grouped) { - fullscreen_ = false; - solo_ = false; - } - if (solo_) { soloClass_ = windowData_.class_name; } else { From 724a4a5ed35d74f5c7a7f55aaeeec5c87b4a9e49 Mon Sep 17 00:00:00 2001 From: ArijanJ Date: Sun, 10 Nov 2024 12:50:26 +0100 Subject: [PATCH 100/780] Add signals section to manpage --- man/waybar.5.scd.in | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index f3a89656..45248552 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -181,6 +181,19 @@ A minimal *config* file could look like this: } ``` +# SIGNALS + +Waybar accepts the following signals: + +*SIGUSR1* + Toggles the bar visibility (hides if shown, shows if hidden) +*SIGUSR2* + Reloads (resets) the bar +*SIGINT* + Quits the bar + +For example, to toggle the bar programmatically, you can invoke `killall -SIGUSR1 waybar`. + # MULTI OUTPUT CONFIGURATION ## Limit a configuration to some outputs From ddb30164405b169cca3f726c1a0c2cb195a8c0f2 Mon Sep 17 00:00:00 2001 From: Ricardo Nogueira Date: Tue, 12 Nov 2024 17:10:05 -0300 Subject: [PATCH 101/780] add rewrite to wlr tooltip --- src/modules/wlr/taskbar.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 30e4ee48..e5be88d8 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -682,6 +682,9 @@ void Task::update() { fmt::format(fmt::runtime(format_tooltip_), fmt::arg("title", title), fmt::arg("name", name), fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::arg("short_state", state_string(true))); + + txt = waybar::util::rewriteString(txt, config_["rewrite"]); + if (markup) button.set_tooltip_markup(txt); else From 86ce9f7278edd5a27467caf3749e8a07e4588260 Mon Sep 17 00:00:00 2001 From: Kate Adkins Date: Fri, 15 Nov 2024 12:04:56 -0700 Subject: [PATCH 102/780] fix null-deref due to unconstructed PrivacyNodeInfo This would cause Waybar to crash if the privacy module ever got e.g. a empty (but properly null-terminated) string for the application_name. --- src/util/pipewire/pipewire_backend.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util/pipewire/pipewire_backend.cpp b/src/util/pipewire/pipewire_backend.cpp index 5bb7c19a..3b897178 100644 --- a/src/util/pipewire/pipewire_backend.cpp +++ b/src/util/pipewire/pipewire_backend.cpp @@ -126,6 +126,7 @@ void PipewireBackend::handleRegistryEventGlobal(uint32_t id, uint32_t permission if (proxy == nullptr) return; auto *pNodeInfo = (PrivacyNodeInfo *)pw_proxy_get_user_data(proxy); + new(pNodeInfo) PrivacyNodeInfo{}; pNodeInfo->id = id; pNodeInfo->data = this; pNodeInfo->type = mediaType; @@ -142,6 +143,7 @@ void PipewireBackend::handleRegistryEventGlobalRemove(uint32_t id) { mutex_.lock(); auto iter = privacy_nodes.find(id); if (iter != privacy_nodes.end()) { + privacy_nodes[id]->~PrivacyNodeInfo(); privacy_nodes.erase(id); } mutex_.unlock(); From f9acc1fed9265a349cf8fb3d75ee34f4f0e93aab Mon Sep 17 00:00:00 2001 From: Lars Niesen Date: Wed, 30 Oct 2024 17:04:13 +0100 Subject: [PATCH 103/780] Add stretching of modules and modules-center toggling This Patch allows the stretching of modules-{left,center,right} as well add a "expand" flag to AModule. This allows one module to consume the leftover space. To allow the left or right modules to fully consume the center, the changes also include a way to remove the center box (center_) altogether. --- include/AModule.hpp | 3 +++ man/waybar-backlight-slider.5.scd | 5 ++++ man/waybar-backlight.5.scd | 5 ++++ man/waybar-battery.5.scd | 5 ++++ man/waybar-bluetooth.5.scd | 5 ++++ man/waybar-cffi.5.scd | 5 ++++ man/waybar-clock.5.scd | 4 +++ man/waybar-cpu.5.scd | 5 ++++ man/waybar-custom.5.scd | 5 ++++ man/waybar-disk.5.scd | 5 ++++ man/waybar-dwl-tags.5.scd | 5 ++++ man/waybar-dwl-window.5.scd | 5 ++++ man/waybar-gamemode.5.scd | 5 ++++ man/waybar-hyprland-language.5.scd | 5 ++++ man/waybar-hyprland-submap.5.scd | 5 ++++ man/waybar-hyprland-window.5.scd | 5 ++++ man/waybar-hyprland-workspaces.5.scd | 5 ++++ man/waybar-idle-inhibitor.5.scd | 5 ++++ man/waybar-image.5.scd | 5 ++++ man/waybar-inhibitor.5.scd | 5 ++++ man/waybar-jack.5.scd | 5 ++++ man/waybar-keyboard-state.5.scd | 5 ++++ man/waybar-memory.5.scd | 5 ++++ man/waybar-mpd.5.scd | 5 ++++ man/waybar-mpris.5.scd | 5 ++++ man/waybar-network.5.scd | 5 ++++ man/waybar-niri-language.5.scd | 5 ++++ man/waybar-niri-window.5.scd | 5 ++++ man/waybar-niri-workspaces.5.scd | 5 ++++ man/waybar-power-profiles-daemon.5.scd | 7 +++++ man/waybar-privacy.5.scd | 5 ++++ man/waybar-pulseaudio-slider.5.scd | 5 ++++ man/waybar-pulseaudio.5.scd | 5 ++++ man/waybar-river-layout.5.scd | 5 ++++ man/waybar-river-mode.5.scd | 5 ++++ man/waybar-river-tags.5.scd | 5 ++++ man/waybar-river-window.5.scd | 5 ++++ man/waybar-sndio.5.scd | 5 ++++ man/waybar-sway-language.5.scd | 5 ++++ man/waybar-sway-mode.5.scd | 5 ++++ man/waybar-sway-scratchpad.5.scd | 5 ++++ man/waybar-sway-window.5.scd | 5 ++++ man/waybar-sway-workspaces.5.scd | 5 ++++ man/waybar-systemd-failed-units.5.scd | 5 ++++ man/waybar-temperature.5.scd | 5 ++++ man/waybar-tray.5.scd | 5 ++++ man/waybar.5.scd.in | 20 ++++++++++++++ src/AModule.cpp | 2 ++ src/bar.cpp | 36 +++++++++++++++++++------- 49 files changed, 277 insertions(+), 10 deletions(-) diff --git a/include/AModule.hpp b/include/AModule.hpp index 94a88371..2ffc429c 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -23,6 +23,8 @@ class AModule : public IModule { /// Emitting on this dispatcher triggers a update() call Glib::Dispatcher dp; + bool expandEnabled() const; + protected: // Don't need to make an object directly // Derived classes are able to use it @@ -50,6 +52,7 @@ class AModule : public IModule { private: bool handleUserEvent(GdkEventButton *const &ev); const bool isTooltip; + const bool isExpand; bool hasUserEvents_; std::vector pid_; gdouble distance_scrolled_y_; diff --git a/man/waybar-backlight-slider.5.scd b/man/waybar-backlight-slider.5.scd index cd5b5184..8d8353c3 100644 --- a/man/waybar-backlight-slider.5.scd +++ b/man/waybar-backlight-slider.5.scd @@ -31,6 +31,11 @@ The brightness can be controlled by dragging the slider across the bar or clicki typeof: string ++ The name of the preferred device to control. If left empty, a device will be chosen automatically. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # EXAMPLES ``` diff --git a/man/waybar-backlight.5.scd b/man/waybar-backlight.5.scd index e1a688db..20810051 100644 --- a/man/waybar-backlight.5.scd +++ b/man/waybar-backlight.5.scd @@ -99,6 +99,11 @@ The *backlight* module displays the current backlight level. typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # EXAMPLE: ``` diff --git a/man/waybar-battery.5.scd b/man/waybar-battery.5.scd index 29cb7d6d..a3c21a20 100644 --- a/man/waybar-battery.5.scd +++ b/man/waybar-battery.5.scd @@ -122,6 +122,11 @@ The *battery* module displays the current capacity and state (eg. charging) of y typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{capacity}*: Capacity in percentage diff --git a/man/waybar-bluetooth.5.scd b/man/waybar-bluetooth.5.scd index bd64f457..fd7d5fb5 100644 --- a/man/waybar-bluetooth.5.scd +++ b/man/waybar-bluetooth.5.scd @@ -142,6 +142,11 @@ Addressed by *bluetooth* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{status}*: Status of the bluetooth device. diff --git a/man/waybar-cffi.5.scd b/man/waybar-cffi.5.scd index 926511d8..0203ef77 100644 --- a/man/waybar-cffi.5.scd +++ b/man/waybar-cffi.5.scd @@ -15,6 +15,11 @@ Addressed by *cffi/* typeof: string ++ The path to the dynamic library to load to control the widget. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + Some additional configuration may be required depending on the cffi dynamic library being used. diff --git a/man/waybar-clock.5.scd b/man/waybar-clock.5.scd index 3947266d..f0a88d24 100644 --- a/man/waybar-clock.5.scd +++ b/man/waybar-clock.5.scd @@ -96,6 +96,10 @@ $XDG_CONFIG_HOME/waybar/config ++ :[ array :[ :[ The actions corresponding to the buttons of the menu. +|[ *expand*: +:[ bool +:[ false +:[ Enables this module to consume all left over space dynamically. View all valid format options in *strftime(3)* or have a look https://en.cppreference.com/w/cpp/chrono/duration/formatter diff --git a/man/waybar-cpu.5.scd b/man/waybar-cpu.5.scd index 6b13a563..287bf123 100644 --- a/man/waybar-cpu.5.scd +++ b/man/waybar-cpu.5.scd @@ -82,6 +82,11 @@ The *cpu* module displays the current CPU utilization. default: true ++ Option to disable tooltip on hover. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{load}*: Current CPU load. diff --git a/man/waybar-custom.5.scd b/man/waybar-custom.5.scd index 6b96d2a4..309fc184 100644 --- a/man/waybar-custom.5.scd +++ b/man/waybar-custom.5.scd @@ -138,6 +138,11 @@ Addressed by *custom/* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # RETURN-TYPE When *return-type* is set to *json*, Waybar expects the *exec*-script to output its data in JSON format. diff --git a/man/waybar-disk.5.scd b/man/waybar-disk.5.scd index 1699a511..00af2b90 100644 --- a/man/waybar-disk.5.scd +++ b/man/waybar-disk.5.scd @@ -106,6 +106,11 @@ Addressed by *disk* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{percentage_used}*: Percentage of disk in use. diff --git a/man/waybar-dwl-tags.5.scd b/man/waybar-dwl-tags.5.scd index 07c94be9..a2146dfd 100644 --- a/man/waybar-dwl-tags.5.scd +++ b/man/waybar-dwl-tags.5.scd @@ -26,6 +26,11 @@ Addressed by *dwl/tags* default: false ++ If set to false, you can left-click to set focused tag. Right-click to toggle tag focus. If set to true this behaviour is disabled. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # EXAMPLE ``` diff --git a/man/waybar-dwl-window.5.scd b/man/waybar-dwl-window.5.scd index f185c82c..9ac33d94 100644 --- a/man/waybar-dwl-window.5.scd +++ b/man/waybar-dwl-window.5.scd @@ -84,6 +84,11 @@ Addressed by *dwl/window* default: 24 ++ Option to change the size of the application icon. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{title}*: The title of the focused window. diff --git a/man/waybar-gamemode.5.scd b/man/waybar-gamemode.5.scd index 492e9850..a6ca9af0 100644 --- a/man/waybar-gamemode.5.scd +++ b/man/waybar-gamemode.5.scd @@ -61,6 +61,11 @@ Feral Gamemode optimizations. default: 4 ++ Defines the spacing between the icon and the text. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{glyph}*: The string icon glyph to use instead. diff --git a/man/waybar-hyprland-language.5.scd b/man/waybar-hyprland-language.5.scd index 33b28ae4..a9a18008 100644 --- a/man/waybar-hyprland-language.5.scd +++ b/man/waybar-hyprland-language.5.scd @@ -38,6 +38,11 @@ Addressed by *hyprland/language* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS diff --git a/man/waybar-hyprland-submap.5.scd b/man/waybar-hyprland-submap.5.scd index e27138e7..f6cdff94 100644 --- a/man/waybar-hyprland-submap.5.scd +++ b/man/waybar-hyprland-submap.5.scd @@ -93,6 +93,11 @@ Addressed by *hyprland/submap* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # EXAMPLES diff --git a/man/waybar-hyprland-window.5.scd b/man/waybar-hyprland-window.5.scd index 4e9c5d18..34ebf89b 100644 --- a/man/waybar-hyprland-window.5.scd +++ b/man/waybar-hyprland-window.5.scd @@ -35,6 +35,11 @@ Addressed by *hyprland/window* default: 24 ++ Option to change the size of the application icon. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS See the output of "hyprctl clients" for examples diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index c71168d4..18c39898 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -78,6 +78,11 @@ Addressed by *hyprland/workspaces* If set to id, workspaces will sort by id. If none of those, workspaces will sort with default behavior. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{id}*: id of workspace assigned by compositor diff --git a/man/waybar-idle-inhibitor.5.scd b/man/waybar-idle-inhibitor.5.scd index 81a097a7..6d5a2170 100644 --- a/man/waybar-idle-inhibitor.5.scd +++ b/man/waybar-idle-inhibitor.5.scd @@ -102,6 +102,11 @@ screensaver, also known as "presentation mode". typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{status}*: status (*activated* or *deactivated*) diff --git a/man/waybar-image.5.scd b/man/waybar-image.5.scd index e3a69e38..a2dcc938 100644 --- a/man/waybar-image.5.scd +++ b/man/waybar-image.5.scd @@ -64,6 +64,11 @@ The *image* module displays an image from a path. default: true ++ Option to enable tooltip on hover. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # SCRIPT OUTPUT Similar to the *custom* module, output values of the script are *newline* separated. diff --git a/man/waybar-inhibitor.5.scd b/man/waybar-inhibitor.5.scd index 5513cc49..fce6f4f8 100644 --- a/man/waybar-inhibitor.5.scd +++ b/man/waybar-inhibitor.5.scd @@ -89,6 +89,11 @@ See *systemd-inhibit*(1) for more information. typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{status}*: status (*activated* or *deactivated*) diff --git a/man/waybar-jack.5.scd b/man/waybar-jack.5.scd index 180143b7..85ce7180 100644 --- a/man/waybar-jack.5.scd +++ b/man/waybar-jack.5.scd @@ -98,6 +98,11 @@ Addressed by *jack* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{load}*: The current CPU load estimated by JACK. diff --git a/man/waybar-keyboard-state.5.scd b/man/waybar-keyboard-state.5.scd index 9ecc5515..79498414 100644 --- a/man/waybar-keyboard-state.5.scd +++ b/man/waybar-keyboard-state.5.scd @@ -53,6 +53,11 @@ You must be a member of the input group to use this module. default: [58, 69, 70] ++ Customize the key to trigger this module, the key number can be found in /usr/include/linux/input-event-codes.h or running sudo libinput debug-events --show-keycodes. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{name}*: Caps, Num, or Scroll. diff --git a/man/waybar-memory.5.scd b/man/waybar-memory.5.scd index 5c368ae8..cc42d5a3 100644 --- a/man/waybar-memory.5.scd +++ b/man/waybar-memory.5.scd @@ -97,6 +97,11 @@ Addressed by *memory* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{percentage}*: Percentage of memory in use. diff --git a/man/waybar-mpd.5.scd b/man/waybar-mpd.5.scd index c576a5c0..84abc2e8 100644 --- a/man/waybar-mpd.5.scd +++ b/man/waybar-mpd.5.scd @@ -175,6 +175,11 @@ Addressed by *mpd* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS ## WHEN PLAYING/PAUSED diff --git a/man/waybar-mpris.5.scd b/man/waybar-mpris.5.scd index 380a1a19..9c192bd1 100644 --- a/man/waybar-mpris.5.scd +++ b/man/waybar-mpris.5.scd @@ -148,6 +148,11 @@ The *mpris* module displays currently playing media via libplayerctl. typeof: map[string]string ++ Allows setting _{status_icon}_ based on player status (playing, paused, stopped). +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS diff --git a/man/waybar-network.5.scd b/man/waybar-network.5.scd index e1cf810d..ee409d0a 100644 --- a/man/waybar-network.5.scd +++ b/man/waybar-network.5.scd @@ -142,6 +142,11 @@ Addressed by *network* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{ifname}*: Name of the network interface. diff --git a/man/waybar-niri-language.5.scd b/man/waybar-niri-language.5.scd index 6895d25c..44876fd9 100644 --- a/man/waybar-niri-language.5.scd +++ b/man/waybar-niri-language.5.scd @@ -33,6 +33,11 @@ Addressed by *niri/language* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{short}*: Short name of layout (e.g. "us"). Equals to {}. diff --git a/man/waybar-niri-window.5.scd b/man/waybar-niri-window.5.scd index 9e2e9f63..8e886c29 100644 --- a/man/waybar-niri-window.5.scd +++ b/man/waybar-niri-window.5.scd @@ -35,6 +35,11 @@ Addressed by *niri/window* default: 24 ++ Option to change the size of the application icon. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS See the output of "niri msg windows" for examples diff --git a/man/waybar-niri-workspaces.5.scd b/man/waybar-niri-workspaces.5.scd index 50e497cd..0c0249ca 100644 --- a/man/waybar-niri-workspaces.5.scd +++ b/man/waybar-niri-workspaces.5.scd @@ -45,6 +45,11 @@ Addressed by *niri/workspaces* typeof: string ++ Command to execute when the module is updated. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{value}*: Name of the workspace, or index for unnamed workspaces, diff --git a/man/waybar-power-profiles-daemon.5.scd b/man/waybar-power-profiles-daemon.5.scd index 82fad13b..6488767b 100644 --- a/man/waybar-power-profiles-daemon.5.scd +++ b/man/waybar-power-profiles-daemon.5.scd @@ -35,6 +35,13 @@ $XDG_CONFIG_HOME/waybar/config :[ object :[ See default value in the example below. :[ Icons used to represent the various power-profile. *Note*: the default configuration uses the font-awesome icons. You may want to override it if you don't have this font installed on your system. +|[ *expand*: +:[ bool +:[ false +:[ Enables this module to consume all left over space dynamically. + + + # CONFIGURATION EXAMPLES diff --git a/man/waybar-privacy.5.scd b/man/waybar-privacy.5.scd index d13d8ed3..946fe136 100644 --- a/man/waybar-privacy.5.scd +++ b/man/waybar-privacy.5.scd @@ -32,6 +32,11 @@ the screen or playing audio. Which privacy modules to monitor. See *MODULES CONFIGURATION* for++ more information. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # MODULES CONFIGURATION *type*: ++ diff --git a/man/waybar-pulseaudio-slider.5.scd b/man/waybar-pulseaudio-slider.5.scd index cf07fed1..cb274826 100644 --- a/man/waybar-pulseaudio-slider.5.scd +++ b/man/waybar-pulseaudio-slider.5.scd @@ -27,6 +27,11 @@ The volume can be controlled by dragging the slider across the bar or clicking o default: horizontal ++ The orientation of the slider. Can be either `horizontal` or `vertical`. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # EXAMPLES ``` diff --git a/man/waybar-pulseaudio.5.scd b/man/waybar-pulseaudio.5.scd index 5b38e8b7..d47fc744 100644 --- a/man/waybar-pulseaudio.5.scd +++ b/man/waybar-pulseaudio.5.scd @@ -126,6 +126,11 @@ Additionally, you can control the volume by scrolling *up* or *down* while the c typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{desc}*: Pulseaudio port's description, for bluetooth it'll be the device name. diff --git a/man/waybar-river-layout.5.scd b/man/waybar-river-layout.5.scd index 1368bda9..78e03634 100644 --- a/man/waybar-river-layout.5.scd +++ b/man/waybar-river-layout.5.scd @@ -64,6 +64,11 @@ Addressed by *river/layout* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # EXAMPLE ``` diff --git a/man/waybar-river-mode.5.scd b/man/waybar-river-mode.5.scd index b992fdaf..5837411d 100644 --- a/man/waybar-river-mode.5.scd +++ b/man/waybar-river-mode.5.scd @@ -78,6 +78,11 @@ Addressed by *river/mode* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # EXAMPLES ``` diff --git a/man/waybar-river-tags.5.scd b/man/waybar-river-tags.5.scd index f0b2b84e..5669456a 100644 --- a/man/waybar-river-tags.5.scd +++ b/man/waybar-river-tags.5.scd @@ -26,6 +26,11 @@ Addressed by *river/tags* default: false ++ If set to false, you can left-click to set focused tag. Right-click to toggle tag focus. If set to true this behaviour is disabled. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # EXAMPLE ``` diff --git a/man/waybar-river-window.5.scd b/man/waybar-river-window.5.scd index 6db9a2fa..82eee0a5 100644 --- a/man/waybar-river-window.5.scd +++ b/man/waybar-river-window.5.scd @@ -62,6 +62,11 @@ Addressed by *river/window* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # EXAMPLES ``` diff --git a/man/waybar-sndio.5.scd b/man/waybar-sndio.5.scd index 03dfe0af..f14d35e9 100644 --- a/man/waybar-sndio.5.scd +++ b/man/waybar-sndio.5.scd @@ -87,6 +87,11 @@ cursor is over the module, and clicking on the module toggles mute. typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{volume}*: Volume in percentage. diff --git a/man/waybar-sway-language.5.scd b/man/waybar-sway-language.5.scd index a1fc5d08..5710e69d 100644 --- a/man/waybar-sway-language.5.scd +++ b/man/waybar-sway-language.5.scd @@ -45,6 +45,11 @@ Addressed by *sway/language* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{short}*: Short name of layout (e.g. "us"). Equals to {}. diff --git a/man/waybar-sway-mode.5.scd b/man/waybar-sway-mode.5.scd index 8d5d7c2c..52827376 100644 --- a/man/waybar-sway-mode.5.scd +++ b/man/waybar-sway-mode.5.scd @@ -83,6 +83,11 @@ Addressed by *sway/mode* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # EXAMPLES ``` diff --git a/man/waybar-sway-scratchpad.5.scd b/man/waybar-sway-scratchpad.5.scd index 5ae104bc..e51ad12a 100644 --- a/man/waybar-sway-scratchpad.5.scd +++ b/man/waybar-sway-scratchpad.5.scd @@ -49,6 +49,11 @@ Addressed by *sway/scratchpad* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{icon}*: Icon, as defined in *format-icons*. diff --git a/man/waybar-sway-window.5.scd b/man/waybar-sway-window.5.scd index 6d1e3196..a7eb4f05 100644 --- a/man/waybar-sway-window.5.scd +++ b/man/waybar-sway-window.5.scd @@ -103,6 +103,11 @@ Addressed by *sway/window* default: 24 ++ Option to change the size of the application icon. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{title}*: The title of the focused window. diff --git a/man/waybar-sway-workspaces.5.scd b/man/waybar-sway-workspaces.5.scd index fc73a85a..a710ab50 100644 --- a/man/waybar-sway-workspaces.5.scd +++ b/man/waybar-sway-workspaces.5.scd @@ -99,6 +99,11 @@ warp-on-scroll: ++ default: " " ++ The separator to be used between windows in a workspace. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS diff --git a/man/waybar-systemd-failed-units.5.scd b/man/waybar-systemd-failed-units.5.scd index ada3ab8b..92e74e9d 100644 --- a/man/waybar-systemd-failed-units.5.scd +++ b/man/waybar-systemd-failed-units.5.scd @@ -49,6 +49,11 @@ Addressed by *systemd-failed-units* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{nr_failed_system}*: Number of failed units from systemwide (PID=1) systemd. diff --git a/man/waybar-temperature.5.scd b/man/waybar-temperature.5.scd index bf41ecc8..923d643d 100644 --- a/man/waybar-temperature.5.scd +++ b/man/waybar-temperature.5.scd @@ -132,6 +132,11 @@ Addressed by *temperature* typeof: array ++ The actions corresponding to the buttons of the menu. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # FORMAT REPLACEMENTS *{temperatureC}*: Temperature in Celsius. diff --git a/man/waybar-tray.5.scd b/man/waybar-tray.5.scd index 294a261d..381d593d 100644 --- a/man/waybar-tray.5.scd +++ b/man/waybar-tray.5.scd @@ -37,6 +37,11 @@ Addressed by *tray* typeof: string ++ Command to execute when the module is updated. +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + # EXAMPLES ``` diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 45248552..5bb62724 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -23,6 +23,21 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles( # BAR CONFIGURATION +*expand-center* ++ + typeof: bool ++ + default: false ++ + Enables the modules-center to consume all left over space dynamically. + +*expand-left* ++ + typeof: bool ++ + default: false ++ + Enables the modules-left to consume all left over space dynamically. + +*expand-right* ++ + typeof: bool ++ + default: false ++ + Enables the modules-left to consume all left over space dynamically. + *layer* ++ typeof: string ++ default: bottom ++ @@ -68,6 +83,11 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles( typeof: integer ++ Margins value without units. +*no-center* ++ + typeof: bool ++ + default: false ++ + Option to disable the center modules fully usefull together with expand-\*. + *spacing* ++ typeof: integer ++ Size of gaps in between the different modules. diff --git a/src/AModule.cpp b/src/AModule.cpp index c180b480..5abb779a 100644 --- a/src/AModule.cpp +++ b/src/AModule.cpp @@ -15,6 +15,7 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std:: : name_(name), config_(config), isTooltip{config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true}, + isExpand{config_["expand"].isBool() ? config_["expand"].asBool() : false}, distance_scrolled_y_(0.0), distance_scrolled_x_(0.0) { // Configure module action Map @@ -273,6 +274,7 @@ bool AModule::handleScroll(GdkEventScroll* e) { } bool AModule::tooltipEnabled() const { return isTooltip; } +bool AModule::expandEnabled() const { return isExpand; } AModule::operator Gtk::Widget&() { return event_box_; } diff --git a/src/bar.cpp b/src/bar.cpp index 5068e90d..b7737d36 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -534,13 +534,22 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, auto waybar::Bar::setupWidgets() -> void { window.add(box_); - box_.pack_start(left_, false, false); - if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) { - box_.set_center_widget(center_); - } else { - box_.pack_start(center_, true, false); + + bool expand_left = config["expand-left"].isBool() ? config["expand-left"].asBool() : false; + bool expand_center = config["expand-center"].isBool() ? config["expand-center"].asBool() : false; + bool expand_right = config["expand-right"].isBool() ? config["expand-right"].asBool() : false; + bool no_center = config["no-center"].isBool() ? config["no-center"].asBool() : false; + + box_.pack_start(left_, expand_left, expand_left); + if (!no_center) { + if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) { + box_.set_center_widget(center_); + } else { + spdlog::error("No fixed center_"); + box_.pack_start(center_, true, expand_center); + } } - box_.pack_end(right_, false, false); + box_.pack_end(right_, expand_right, expand_right); // Convert to button code for every module that is used. setupAltFormatKeyForModuleList("modules-left"); @@ -549,14 +558,21 @@ auto waybar::Bar::setupWidgets() -> void { Factory factory(*this, config); getModules(factory, "modules-left"); - getModules(factory, "modules-center"); + if (!no_center) { + getModules(factory, "modules-center"); + } getModules(factory, "modules-right"); + for (auto const& module : modules_left_) { - left_.pack_start(*module, false, false); + left_.pack_start(*module, module->expandEnabled(), module->expandEnabled()); } - for (auto const& module : modules_center_) { - center_.pack_start(*module, false, false); + + if (!no_center) { + for (auto const& module : modules_center_) { + center_.pack_start(*module, false, false); + } } + std::reverse(modules_right_.begin(), modules_right_.end()); for (auto const& module : modules_right_) { right_.pack_end(*module, false, false); From 1e481b7ac0e7eefa610f5b4b5a6c4126cb189eeb Mon Sep 17 00:00:00 2001 From: Lars Niesen Date: Wed, 30 Oct 2024 17:48:35 +0100 Subject: [PATCH 104/780] fix broken clang-format to run pipeline --- src/main.cpp | 2 +- src/modules/temperature.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 442c530c..045b2cd4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -109,7 +109,7 @@ int main(int argc, char* argv[]) { std::signal(SIGUSR1, SIG_IGN); std::signal(SIGUSR2, SIG_IGN); - std::signal(SIGINT, SIG_IGN); + std::signal(SIGINT, SIG_IGN); delete client; return ret; diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index 356536f9..89d3db2d 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -77,7 +77,7 @@ auto waybar::modules::Temperature::update() -> void { } else if (warning) { format = config_["format-warning"].isString() ? config_["format-warning"].asString() : format; label_.get_style_context()->add_class("warning"); - } else { + } else { label_.get_style_context()->remove_class("critical"); label_.get_style_context()->remove_class("warning"); } From 4e05659109f95652fcdb9b582ffc691dff503a46 Mon Sep 17 00:00:00 2001 From: Jan Larres Date: Sun, 1 Dec 2024 12:48:07 +1300 Subject: [PATCH 105/780] sway/workspaces: use X11 class for XWayland windows When using `window-rewrite`, the `class<>` rule would previously only match against the `app_id` of a window. However, XWayland windows don't have an app ID. This change falls back to checking the `class` window property if there is no app ID to support matching against XWayland windows. --- man/waybar-sway-workspaces.5.scd | 1 + src/modules/sway/workspaces.cpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/man/waybar-sway-workspaces.5.scd b/man/waybar-sway-workspaces.5.scd index a710ab50..1c4360e9 100644 --- a/man/waybar-sway-workspaces.5.scd +++ b/man/waybar-sway-workspaces.5.scd @@ -88,6 +88,7 @@ warp-on-scroll: ++ Keys are the rules, while the values are the methods of representation. Rules may specify `class<...>`, `title<...>`, or both in order to fine-tune the matching. You may assign an empty value to a rule to have it ignored from generating any representation in workspaces. + For Wayland windows `class` is matched against the `app_id`, and for X11 windows against the `class` property. *window-rewrite-default*: typeof: string ++ diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 8f273300..33d4bb29 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -260,7 +260,9 @@ void Workspaces::updateWindows(const Json::Value &node, std::string &windows) { if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") && node["name"].isString()) { std::string title = g_markup_escape_text(node["name"].asString().c_str(), -1); - std::string windowClass = node["app_id"].asString(); + std::string windowClass = node["app_id"].isString() + ? node["app_id"].asString() + : node["window_properties"]["class"].asString(); // Only add window rewrites that can be looked up if (!windowClass.empty()) { From 0e5728b53388de6104bc6a644009fa87e3008ee9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 1 Dec 2024 00:12:55 +0000 Subject: [PATCH 106/780] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'flake-compat': 'github:edolstra/flake-compat/0f9255e01c2351cc7d116c072cb317785dd33b33?narHash=sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U%3D' (2023-10-04) → 'github:edolstra/flake-compat/9ed2ac151eada2306ca8c418ebd97807bb08f6ac?narHash=sha256-HRJ/18p%2BWoXpWJkcdsk9St5ZiukCqSDgbOGFa8Okehg%3D' (2024-11-27) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/807e9154dcb16384b1b765ebe9cd2bba2ac287fd?narHash=sha256-l253w0XMT8nWHGXuXqyiIC/bMvh1VRszGXgdpQlfhvU%3D' (2024-10-29) → 'github:NixOS/nixpkgs/970e93b9f82e2a0f3675757eb0bfc73297cc6370?narHash=sha256-jNRNr49UiuIwaarqijgdTR2qLPifxsVhlJrKzQ8XUIE%3D' (2024-11-28) --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index d0ca27b8..24b83454 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "lastModified": 1732722421, + "narHash": "sha256-HRJ/18p+WoXpWJkcdsk9St5ZiukCqSDgbOGFa8Okehg=", "owner": "edolstra", "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "rev": "9ed2ac151eada2306ca8c418ebd97807bb08f6ac", "type": "github" }, "original": { @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1730200266, - "narHash": "sha256-l253w0XMT8nWHGXuXqyiIC/bMvh1VRszGXgdpQlfhvU=", + "lastModified": 1732837521, + "narHash": "sha256-jNRNr49UiuIwaarqijgdTR2qLPifxsVhlJrKzQ8XUIE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "807e9154dcb16384b1b765ebe9cd2bba2ac287fd", + "rev": "970e93b9f82e2a0f3675757eb0bfc73297cc6370", "type": "github" }, "original": { From 6d28740896b33005cfe6344719bcebb8de0cabfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20K=C4=85dzio=C5=82ka?= Date: Tue, 3 Dec 2024 19:37:36 +0100 Subject: [PATCH 107/780] Add locking for interacting with the pulseaudio thread Before this commit, Waybar would sometimes get into a state where it would consume 100% of a CPU core, and the pulseaudio widget would stop responding to volume adjustments. In this state, the pulseaudio mainloop thread would spin, with the counter of enabled defer events at 1, but no actual enabled defer event in the list to get the counter back to zero after an iteration in the mainloop. This could happen if the unsynchronized interactions with the mainloop thread happened to modify the list of deferred events at the same time as the mainloop. This commit introduces locking in accordance with the PulseAudio documentation on the threaded mainloop: > The lock needs to be held whenever you call any PulseAudio function that > uses an object associated with this main loop. Those objects include > pa_mainloop, pa_context, pa_stream and pa_operation, and the various event > objects (pa_io_event, pa_time_event, pa_defer_event). --- src/util/audio_backend.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index 73aac148..807b5dc7 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -236,7 +236,9 @@ void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t m volume = std::clamp(volume, min_volume, max_volume); pa_cvolume_set(&pa_volume, pa_volume_.channels, volume * volume_tick); + pa_threaded_mainloop_lock(mainloop_); pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); + pa_threaded_mainloop_unlock(mainloop_); } void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t max_volume) { @@ -265,31 +267,41 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma pa_cvolume_dec(&pa_volume, change); } } + pa_threaded_mainloop_lock(mainloop_); pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); + pa_threaded_mainloop_unlock(mainloop_); } void AudioBackend::toggleSinkMute() { muted_ = !muted_; + pa_threaded_mainloop_lock(mainloop_); pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast(muted_), nullptr, nullptr); + pa_threaded_mainloop_unlock(mainloop_); } void AudioBackend::toggleSinkMute(bool mute) { muted_ = mute; + pa_threaded_mainloop_lock(mainloop_); pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast(muted_), nullptr, nullptr); + pa_threaded_mainloop_unlock(mainloop_); } void AudioBackend::toggleSourceMute() { source_muted_ = !muted_; + pa_threaded_mainloop_lock(mainloop_); pa_context_set_source_mute_by_index(context_, source_idx_, static_cast(source_muted_), nullptr, nullptr); + pa_threaded_mainloop_unlock(mainloop_); } void AudioBackend::toggleSourceMute(bool mute) { source_muted_ = mute; + pa_threaded_mainloop_lock(mainloop_); pa_context_set_source_mute_by_index(context_, source_idx_, static_cast(source_muted_), nullptr, nullptr); + pa_threaded_mainloop_unlock(mainloop_); } bool AudioBackend::isBluetooth() { From 6bac784b51bc319bba7ab91bc1cc5ee8815a2cae Mon Sep 17 00:00:00 2001 From: eritque0arcus Date: Wed, 4 Dec 2024 12:46:09 -0600 Subject: [PATCH 108/780] fix: use auto and add self as fallback --- src/util/backlight_backend.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/util/backlight_backend.cpp b/src/util/backlight_backend.cpp index bb102cd9..41236462 100644 --- a/src/util/backlight_backend.cpp +++ b/src/util/backlight_backend.cpp @@ -153,7 +153,13 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval, // Connect to the login interface login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync( Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1", - "/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session"); + "/org/freedesktop/login1/session/auto", "org.freedesktop.login1.Session"); + + if (!login_proxy_) { + login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync( + Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1", + "/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session"); + } udev_thread_ = [this] { std::unique_ptr udev{udev_new()}; From db943dae9817d989031cf06309d759f8a7416284 Mon Sep 17 00:00:00 2001 From: Gustaf Ullberg Date: Thu, 5 Dec 2024 15:15:33 +0100 Subject: [PATCH 109/780] Let network module handle ipv4 and ipv6 simultaneously --- include/modules/network.hpp | 1 - src/modules/network.cpp | 34 +++++++--------------------------- 2 files changed, 7 insertions(+), 28 deletions(-) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index 4a84b02f..df0ba9c3 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -50,7 +50,6 @@ class Network : public ALabel { std::optional> readBandwidthUsage(); int ifid_; - sa_family_t family_; struct sockaddr_nl nladdr_ = {0}; struct nl_sock* sock_ = nullptr; struct nl_sock* ev_sock_ = nullptr; diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 0bbea631..393b4296 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -80,7 +80,6 @@ waybar::modules::Network::readBandwidthUsage() { waybar::modules::Network::Network(const std::string &id, const Json::Value &config) : ALabel(config, "network", id, DEFAULT_FORMAT, 60), ifid_(-1), - family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET), efd_(-1), ev_fd_(-1), want_route_dump_(false), @@ -141,12 +140,7 @@ waybar::modules::Network::~Network() { close(efd_); } if (ev_sock_ != nullptr) { - nl_socket_drop_membership(ev_sock_, RTNLGRP_LINK); - if (family_ == AF_INET) { - nl_socket_drop_membership(ev_sock_, RTNLGRP_IPV4_IFADDR); - } else { - nl_socket_drop_membership(ev_sock_, RTNLGRP_IPV6_IFADDR); - } + nl_socket_drop_memberships(ev_sock_, RTNLGRP_LINK, RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV6_IFADDR); nl_close(ev_sock_); nl_socket_free(ev_sock_); } @@ -161,7 +155,7 @@ void waybar::modules::Network::createEventSocket() { nl_socket_disable_seq_check(ev_sock_); nl_socket_modify_cb(ev_sock_, NL_CB_VALID, NL_CB_CUSTOM, handleEvents, this); nl_socket_modify_cb(ev_sock_, NL_CB_FINISH, NL_CB_CUSTOM, handleEventsDone, this); - auto groups = RTMGRP_LINK | (family_ == AF_INET ? RTMGRP_IPV4_IFADDR : RTMGRP_IPV6_IFADDR); + auto groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; nl_join_groups(ev_sock_, groups); // Deprecated if (nl_connect(ev_sock_, NETLINK_ROUTE) != 0) { throw std::runtime_error("Can't connect network socket"); @@ -169,18 +163,9 @@ void waybar::modules::Network::createEventSocket() { if (nl_socket_set_nonblocking(ev_sock_)) { throw std::runtime_error("Can't set non-blocking on network socket"); } - nl_socket_add_membership(ev_sock_, RTNLGRP_LINK); - if (family_ == AF_INET) { - nl_socket_add_membership(ev_sock_, RTNLGRP_IPV4_IFADDR); - } else { - nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_IFADDR); - } + nl_socket_add_memberships(ev_sock_, RTNLGRP_LINK, RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV6_IFADDR, 0); if (!config_["interface"].isString()) { - if (family_ == AF_INET) { - nl_socket_add_membership(ev_sock_, RTNLGRP_IPV4_ROUTE); - } else { - nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_ROUTE); - } + nl_socket_add_memberships(ev_sock_, RTNLGRP_IPV4_ROUTE, RTNLGRP_IPV6_ROUTE, 0); } efd_ = epoll_create1(EPOLL_CLOEXEC); @@ -531,10 +516,6 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { return NL_OK; } - if (ifa->ifa_family != net->family_) { - return NL_OK; - } - // We ignore address mark as scope for the link or host, // which should leave scope global addresses. if (ifa->ifa_scope >= RT_SCOPE_LINK) { @@ -591,6 +572,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { // to find the interface used to reach the outside world struct rtmsg *rtm = static_cast(NLMSG_DATA(nh)); + int family = rtm->rtm_family; ssize_t attrlen = RTM_PAYLOAD(nh); struct rtattr *attr = RTM_RTA(rtm); bool has_gateway = false; @@ -618,14 +600,14 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { * If someone ever needs to figure out the gateway address as well, * it's here as the attribute payload. */ - inet_ntop(net->family_, RTA_DATA(attr), temp_gw_addr, sizeof(temp_gw_addr)); + inet_ntop(family, RTA_DATA(attr), temp_gw_addr, sizeof(temp_gw_addr)); has_gateway = true; break; case RTA_DST: { /* The destination address. * Should be either missing, or maybe all 0s. Accept both. */ - const uint32_t nr_zeroes = (net->family_ == AF_INET) ? 4 : 16; + const uint32_t nr_zeroes = (family == AF_INET) ? 4 : 16; unsigned char c = 0; size_t dstlen = RTA_PAYLOAD(attr); if (dstlen != nr_zeroes) { @@ -717,7 +699,6 @@ void waybar::modules::Network::askForStateDump(void) { }; if (want_route_dump_) { - rt_hdr.rtgen_family = family_; nl_send_simple(ev_sock_, RTM_GETROUTE, NLM_F_DUMP, &rt_hdr, sizeof(rt_hdr)); want_route_dump_ = false; dump_in_progress_ = true; @@ -728,7 +709,6 @@ void waybar::modules::Network::askForStateDump(void) { dump_in_progress_ = true; } else if (want_addr_dump_) { - rt_hdr.rtgen_family = family_; nl_send_simple(ev_sock_, RTM_GETADDR, NLM_F_DUMP, &rt_hdr, sizeof(rt_hdr)); want_addr_dump_ = false; dump_in_progress_ = true; From 43af1b9ea00eceb32fec081445959c0e9b5dc94c Mon Sep 17 00:00:00 2001 From: twistedlogic Date: Wed, 11 Dec 2024 22:18:00 -0400 Subject: [PATCH 110/780] feat: implement hide vacant for river --- man/waybar-river-tags.5.scd | 5 +++++ src/modules/river/tags.cpp | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/man/waybar-river-tags.5.scd b/man/waybar-river-tags.5.scd index 5669456a..64621229 100644 --- a/man/waybar-river-tags.5.scd +++ b/man/waybar-river-tags.5.scd @@ -31,6 +31,11 @@ Addressed by *river/tags* default: false ++ Enables this module to consume all left over space dynamically. +*hide-vacant*: ++ + typeof: bool ++ + default: false ++ + Only show relevant tags: tags that are either focused or have a window on them. + # EXAMPLE ``` diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index 9e7cd5aa..26c8e3ad 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -189,11 +189,18 @@ bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) { } void Tags::handle_focused_tags(uint32_t tags) { + auto hide_vacant = config_["hide-vacant"].asBool(); for (size_t i = 0; i < buttons_.size(); ++i) { if ((1 << i) & tags) { + if (hide_vacant) { + buttons_[i].set_visible(true); + } buttons_[i].get_style_context()->add_class("focused"); } else { buttons_[i].get_style_context()->remove_class("focused"); + if (hide_vacant && !buttons_[i].get_style_context()->has_class("occupied")) { + buttons_[i].set_visible(false); + } } } } @@ -205,10 +212,17 @@ void Tags::handle_view_tags(struct wl_array *view_tags) { for (; view_tag < end; ++view_tag) { tags |= *view_tag; } + auto hide_vacant = config_["hide-vacant"].asBool(); for (size_t i = 0; i < buttons_.size(); ++i) { if ((1 << i) & tags) { + if (hide_vacant) { + buttons_[i].set_visible(true); + } buttons_[i].get_style_context()->add_class("occupied"); } else { + if (hide_vacant) { + buttons_[i].set_visible(false); + } buttons_[i].get_style_context()->remove_class("occupied"); } } From 8024df0430b2e031aa70fefb9b9655623fa9d412 Mon Sep 17 00:00:00 2001 From: twistedlogic Date: Wed, 11 Dec 2024 22:50:01 -0400 Subject: [PATCH 111/780] fix: edge case where tags get hidden after all views are killed This fixes an edge case where focused tags would get hidden if all clients on a tag get killed --- src/modules/river/tags.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index 26c8e3ad..5ba67f2f 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -220,7 +220,7 @@ void Tags::handle_view_tags(struct wl_array *view_tags) { } buttons_[i].get_style_context()->add_class("occupied"); } else { - if (hide_vacant) { + if (hide_vacant && !buttons_[i].get_style_context()->has_class("focused")) { buttons_[i].set_visible(false); } buttons_[i].get_style_context()->remove_class("occupied"); From 8e0964ad15688c23c6e0738b76e19686390162e9 Mon Sep 17 00:00:00 2001 From: twistedlogic Date: Thu, 12 Dec 2024 10:11:11 -0400 Subject: [PATCH 112/780] feat: is visible and urgent checks as well --- src/modules/river/tags.cpp | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index 5ba67f2f..359e5a23 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -191,16 +191,19 @@ bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) { void Tags::handle_focused_tags(uint32_t tags) { auto hide_vacant = config_["hide-vacant"].asBool(); for (size_t i = 0; i < buttons_.size(); ++i) { + bool visible = buttons_[i].is_visible(); + bool occupied = buttons_[i].get_style_context()->has_class("occupied"); + bool urgent = buttons_[i].get_style_context()->has_class("urgent"); if ((1 << i) & tags) { - if (hide_vacant) { + if (hide_vacant && !visible) { buttons_[i].set_visible(true); } buttons_[i].get_style_context()->add_class("focused"); } else { - buttons_[i].get_style_context()->remove_class("focused"); - if (hide_vacant && !buttons_[i].get_style_context()->has_class("occupied")) { + if (hide_vacant && !(occupied || urgent)) { buttons_[i].set_visible(false); } + buttons_[i].get_style_context()->remove_class("focused"); } } } @@ -214,13 +217,16 @@ void Tags::handle_view_tags(struct wl_array *view_tags) { } auto hide_vacant = config_["hide-vacant"].asBool(); for (size_t i = 0; i < buttons_.size(); ++i) { + bool visible = buttons_[i].is_visible(); + bool focused = buttons_[i].get_style_context()->has_class("focused"); + bool urgent = buttons_[i].get_style_context()->has_class("urgent"); if ((1 << i) & tags) { - if (hide_vacant) { + if (hide_vacant && !visible) { buttons_[i].set_visible(true); } buttons_[i].get_style_context()->add_class("occupied"); } else { - if (hide_vacant && !buttons_[i].get_style_context()->has_class("focused")) { + if (hide_vacant && !(focused || urgent)) { buttons_[i].set_visible(false); } buttons_[i].get_style_context()->remove_class("occupied"); @@ -229,10 +235,20 @@ void Tags::handle_view_tags(struct wl_array *view_tags) { } void Tags::handle_urgent_tags(uint32_t tags) { + auto hide_vacant = config_["hide-vacant"].asBool(); for (size_t i = 0; i < buttons_.size(); ++i) { + bool visible = buttons_[i].is_visible(); + bool occupied = buttons_[i].get_style_context()->has_class("occupied"); + bool focused = buttons_[i].get_style_context()->has_class("focused"); if ((1 << i) & tags) { + if (hide_vacant && !visible) { + buttons_[i].set_visible(true); + } buttons_[i].get_style_context()->add_class("urgent"); } else { + if (hide_vacant && !(occupied || focused)) { + buttons_[i].set_visible(false); + } buttons_[i].get_style_context()->remove_class("urgent"); } } From 01ae117cfe322fcbd8734e272733c9fca785b564 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Sat, 14 Dec 2024 21:02:10 +0800 Subject: [PATCH 113/780] fix: hyprland/window get empty ipc json data --- src/modules/hyprland/window.cpp | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index 152eea03..decb2565 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -12,10 +12,18 @@ #include "util/rewrite_string.hpp" #include "util/sanitize_str.hpp" +#include +#include + namespace waybar::modules::hyprland { +std::shared_mutex windowIpcSmtx; + Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) { + + std::unique_lock windowIpcUniqueLock(windowIpcSmtx); + modulesReady = true; separateOutputs_ = config["separate-outputs"].asBool(); @@ -23,27 +31,28 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) gIPC = std::make_unique(); } - queryActiveWorkspace(); - update(); - dp.emit(); - // register for hyprland ipc gIPC->registerForIPC("activewindow", this); gIPC->registerForIPC("closewindow", this); gIPC->registerForIPC("movewindow", this); gIPC->registerForIPC("changefloatingmode", this); gIPC->registerForIPC("fullscreen", this); + + windowIpcUniqueLock.unlock(); + + queryActiveWorkspace(); + update(); + dp.emit(); } Window::~Window() { + std::unique_lock windowIpcUniqueLock(windowIpcSmtx); gIPC->unregisterForIPC(this); - // wait for possible event handler to finish - std::lock_guard lg(mutex_); } auto Window::update() -> void { - // fix ampersands - std::lock_guard lg(mutex_); + + std::shared_lock windowIpcShareLock(windowIpcSmtx); std::string windowName = waybar::util::sanitize_string(workspace_.last_window_title); std::string windowAddress = workspace_.last_window; @@ -144,7 +153,8 @@ auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData { } void Window::queryActiveWorkspace() { - std::lock_guard lg(mutex_); + + std::shared_lock windowIpcShareLock(windowIpcSmtx); if (separateOutputs_) { workspace_ = getActiveWorkspace(this->bar_.output->name); From 157ea445102b0bc2db02733095a4350c7b2089d1 Mon Sep 17 00:00:00 2001 From: "K. Adam Christensen" Date: Sat, 14 Dec 2024 09:06:13 -0800 Subject: [PATCH 114/780] Escape markup characters in dwl/window Without this, markup characters like [&><] will be injected directly into the Label. Escaping them makes sure that the values will be printed exactly as they appear in the window title or layout symbol. Signed-off-by: K. Adam Christensen --- src/modules/dwl/window.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/modules/dwl/window.cpp b/src/modules/dwl/window.cpp index 870d87e4..8fa826b1 100644 --- a/src/modules/dwl/window.cpp +++ b/src/modules/dwl/window.cpp @@ -9,6 +9,7 @@ #include "client.hpp" #include "dwl-ipc-unstable-v2-client-protocol.h" +#include "glibmm/markup.h" #include "util/rewrite_string.hpp" namespace waybar::modules::dwl { @@ -97,11 +98,17 @@ Window::~Window() { } } -void Window::handle_title(const char *title) { title_ = title; } +void Window::handle_title(const char *title) { + title_ = Glib::Markup::escape_text(title); +} -void Window::handle_appid(const char *appid) { appid_ = appid; } +void Window::handle_appid(const char *appid) { + appid_ = Glib::Markup::escape_text(appid); +} -void Window::handle_layout_symbol(const char *layout_symbol) { layout_symbol_ = layout_symbol; } +void Window::handle_layout_symbol(const char *layout_symbol) { + layout_symbol_ = Glib::Markup::escape_text(layout_symbol); +} void Window::handle_layout(const uint32_t layout) { layout_ = layout; } From 8e276bb3f66be36740fef5209e96fdd9adcfbe13 Mon Sep 17 00:00:00 2001 From: Carlo Teubner Date: Tue, 17 Dec 2024 22:05:16 +0000 Subject: [PATCH 115/780] sway: fix "Mapping is not an object" warning Fixes #3763. Also a little code simplications while we're at it. --- include/modules/sway/workspaces.hpp | 1 - src/modules/sway/workspaces.cpp | 15 +++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/include/modules/sway/workspaces.hpp b/include/modules/sway/workspaces.hpp index 97f4e950..58c173ec 100644 --- a/include/modules/sway/workspaces.hpp +++ b/include/modules/sway/workspaces.hpp @@ -49,7 +49,6 @@ class Workspaces : public AModule, public sigc::trackable { std::vector workspaces_order_; Gtk::Box box_; std::string m_formatWindowSeperator; - std::string m_windowRewriteDefault; util::RegexCollection m_windowRewriteRules; util::JsonParser parser_; std::unordered_map buttons_; diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 33d4bb29..dec5cddf 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -62,14 +62,13 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value m_formatWindowSeperator = " "; } const Json::Value &windowRewrite = config["window-rewrite"]; - - const Json::Value &windowRewriteDefaultConfig = config["window-rewrite-default"]; - m_windowRewriteDefault = - windowRewriteDefaultConfig.isString() ? windowRewriteDefaultConfig.asString() : "?"; - - m_windowRewriteRules = waybar::util::RegexCollection( - windowRewrite, m_windowRewriteDefault, - [](std::string &window_rule) { return windowRewritePriorityFunction(window_rule); }); + if (windowRewrite.isObject()) { + const Json::Value &windowRewriteDefaultConfig = config["window-rewrite-default"]; + std::string windowRewriteDefault = + windowRewriteDefaultConfig.isString() ? windowRewriteDefaultConfig.asString() : "?"; + m_windowRewriteRules = waybar::util::RegexCollection( + windowRewrite, std::move(windowRewriteDefault), windowRewritePriorityFunction); + } ipc_.subscribe(R"(["workspace"])"); ipc_.subscribe(R"(["window"])"); ipc_.signal_event.connect(sigc::mem_fun(*this, &Workspaces::onEvent)); From 622f22d6b59da9bfbfabb9e9f70fa2c520ffad86 Mon Sep 17 00:00:00 2001 From: Bahnschrift Date: Thu, 19 Dec 2024 12:09:31 +1100 Subject: [PATCH 116/780] fix: hide tray when there are no non-passive icons --- src/modules/sni/tray.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modules/sni/tray.cpp b/src/modules/sni/tray.cpp index a2c56808..f194c1d9 100644 --- a/src/modules/sni/tray.cpp +++ b/src/modules/sni/tray.cpp @@ -2,6 +2,8 @@ #include +#include + namespace waybar::modules::SNI { Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) @@ -39,7 +41,9 @@ void Tray::onRemove(std::unique_ptr& item) { auto Tray::update() -> void { // Show tray only when items are available - event_box_.set_visible(!box_.get_children().empty()); + std::vector children = box_.get_children(); + event_box_.set_visible(std::any_of(children.begin(), children.end(), + [](Gtk::Widget* child) { return child->get_visible(); })); // Call parent update AModule::update(); } From cf64b2c88c2c2184cb3690668bd678a9f0853c15 Mon Sep 17 00:00:00 2001 From: Bahnschrift Date: Thu, 19 Dec 2024 12:29:58 +1100 Subject: [PATCH 117/780] fix: un-hide tray when new icons are added --- include/modules/sni/tray.hpp | 1 + src/modules/sni/tray.cpp | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/include/modules/sni/tray.hpp b/include/modules/sni/tray.hpp index 6cda35d7..5f12d7f2 100644 --- a/include/modules/sni/tray.hpp +++ b/include/modules/sni/tray.hpp @@ -21,6 +21,7 @@ class Tray : public AModule { void onRemove(std::unique_ptr& item); static inline std::size_t nb_hosts_ = 0; + bool show_passive_ = false; Gtk::Box box_; SNI::Watcher::singleton watcher_; SNI::Host host_; diff --git a/src/modules/sni/tray.cpp b/src/modules/sni/tray.cpp index f194c1d9..33827145 100644 --- a/src/modules/sni/tray.cpp +++ b/src/modules/sni/tray.cpp @@ -21,6 +21,9 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) if (config_["spacing"].isUInt()) { box_.set_spacing(config_["spacing"].asUInt()); } + if (config["show-passive-items"].isBool()) { + show_passive_ = config["show-passive-items"].asBool(); + } nb_hosts_ += 1; dp.emit(); } @@ -42,8 +45,14 @@ void Tray::onRemove(std::unique_ptr& item) { auto Tray::update() -> void { // Show tray only when items are available std::vector children = box_.get_children(); - event_box_.set_visible(std::any_of(children.begin(), children.end(), - [](Gtk::Widget* child) { return child->get_visible(); })); + if (show_passive_) { + event_box_.set_visible(!children.empty()); + } else { + event_box_.set_visible(!std::all_of(children.begin(), children.end(), [](Gtk::Widget* child) { + return child->get_style_context()->has_class("passive"); + })); + } + // Call parent update AModule::update(); } From d100e788722019926b4e2a16cd9daa49953e4573 Mon Sep 17 00:00:00 2001 From: Stefan Haan Date: Sat, 28 Dec 2024 13:58:19 +0100 Subject: [PATCH 118/780] mpd: add uri and filename format arguments --- man/waybar-mpd.5.scd | 4 ++++ src/modules/mpd/mpd.cpp | 9 ++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/man/waybar-mpd.5.scd b/man/waybar-mpd.5.scd index 84abc2e8..d310efd4 100644 --- a/man/waybar-mpd.5.scd +++ b/man/waybar-mpd.5.scd @@ -204,6 +204,10 @@ Addressed by *mpd* *{queueLength}*: The length of the current queue. +*{uri}*: The URI of the song relative to the MPD music directory. + +*{filename}* The last part of the URI. + *{stateIcon}*: The icon corresponding to the playing or paused status of the player (see *state-icons* option) *{consumeIcon}*: The icon corresponding the "consume" option (see *consume-icons* option) diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 192e6c1a..396ce3e1 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -118,7 +118,7 @@ void waybar::modules::MPD::setLabel() { auto format = format_; Glib::ustring artist, album_artist, album, title; - std::string date, filename; + std::string date, filename, uri; int song_pos = 0, queue_length = 0, volume = 0; std::chrono::seconds elapsedTime, totalTime; @@ -151,6 +151,7 @@ void waybar::modules::MPD::setLabel() { title = sanitize_string(getTag(MPD_TAG_TITLE)); date = sanitize_string(getTag(MPD_TAG_DATE)); filename = sanitize_string(getFilename()); + uri = mpd_song_get_uri(song_.get()); song_pos = mpd_status_get_song_pos(status_.get()) + 1; volume = mpd_status_get_volume(status_.get()); if (volume < 0) { @@ -184,7 +185,8 @@ void waybar::modules::MPD::setLabel() { fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon), - fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename)); + fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename), + fmt::arg("uri", uri)); if (text.empty()) { label_.hide(); } else { @@ -208,7 +210,8 @@ void waybar::modules::MPD::setLabel() { fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), - fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon)); + fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon), + fmt::arg("filename", filename), fmt::arg("uri", uri)); label_.set_tooltip_text(tooltip_text); } catch (fmt::format_error const& e) { spdlog::warn("mpd: format error (tooltip): {}", e.what()); From 884550964ec8f14283592740bfd4162673b53fd0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 1 Jan 2025 00:11:33 +0000 Subject: [PATCH 119/780] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'flake-compat': 'github:edolstra/flake-compat/9ed2ac151eada2306ca8c418ebd97807bb08f6ac?narHash=sha256-HRJ/18p%2BWoXpWJkcdsk9St5ZiukCqSDgbOGFa8Okehg%3D' (2024-11-27) → 'github:edolstra/flake-compat/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec?narHash=sha256-NeCCThCEP3eCl2l/%2B27kNNK7QrwZB1IJCrXfrbv5oqU%3D' (2024-12-04) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/970e93b9f82e2a0f3675757eb0bfc73297cc6370?narHash=sha256-jNRNr49UiuIwaarqijgdTR2qLPifxsVhlJrKzQ8XUIE%3D' (2024-11-28) → 'github:NixOS/nixpkgs/88195a94f390381c6afcdaa933c2f6ff93959cb4?narHash=sha256-0q9NGQySwDQc7RhAV2ukfnu7Gxa5/ybJ2ANT8DQrQrs%3D' (2024-12-29) --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 24b83454..2b8235c4 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1732722421, - "narHash": "sha256-HRJ/18p+WoXpWJkcdsk9St5ZiukCqSDgbOGFa8Okehg=", + "lastModified": 1733328505, + "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", "owner": "edolstra", "repo": "flake-compat", - "rev": "9ed2ac151eada2306ca8c418ebd97807bb08f6ac", + "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", "type": "github" }, "original": { @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1732837521, - "narHash": "sha256-jNRNr49UiuIwaarqijgdTR2qLPifxsVhlJrKzQ8XUIE=", + "lastModified": 1735471104, + "narHash": "sha256-0q9NGQySwDQc7RhAV2ukfnu7Gxa5/ybJ2ANT8DQrQrs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "970e93b9f82e2a0f3675757eb0bfc73297cc6370", + "rev": "88195a94f390381c6afcdaa933c2f6ff93959cb4", "type": "github" }, "original": { From 3555417a4f62d0122d303ae20d756a1f35daec52 Mon Sep 17 00:00:00 2001 From: JasonnnW3000 Date: Wed, 1 Jan 2025 06:34:11 -0500 Subject: [PATCH 120/780] Update LICENSE, fix license year Signed-off-by: JasonnnW3000 --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 41eb81d8..d1bad1b4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Alex +Copyright (c) 2025 Alex Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From d1dac2854a2b58c9baab569370415edff3bba299 Mon Sep 17 00:00:00 2001 From: Ethan Martin Date: Thu, 2 Jan 2025 20:50:39 -0500 Subject: [PATCH 121/780] Allow using wildcards in config include paths Updates `Config::tryExpandPath()` to return a vector of expanded path matches instead of a single path wrapped in an optional, with an empty vector indicating no matches. `Config::resolveConfigIncludes()` iterates over all of these matches, while other instances of path expansion (such as finding the base config path) retain their existing behavior and only use the first match. --- include/config.hpp | 4 ++-- src/ALabel.cpp | 4 ++-- src/config.cpp | 34 ++++++++++++++++++++-------------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/include/config.hpp b/include/config.hpp index 18a1daed..5256bb46 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -20,8 +20,8 @@ class Config { static std::optional findConfigPath( const std::vector &names, const std::vector &dirs = CONFIG_DIRS); - static std::optional tryExpandPath(const std::string &base, - const std::string &filename); + static std::vector tryExpandPath(const std::string &base, + const std::string &filename); Config() = default; diff --git a/src/ALabel.cpp b/src/ALabel.cpp index 467572f1..c218e402 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -68,11 +68,11 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st // there might be "~" or "$HOME" in original path, try to expand it. auto result = Config::tryExpandPath(menuFile, ""); - if (!result.has_value()) { + if (result.empty()) { throw std::runtime_error("Failed to expand file: " + menuFile); } - menuFile = result.value(); + menuFile = result.front(); // Read the menu descriptor file std::ifstream file(menuFile); if (!file.is_open()) { diff --git a/src/config.cpp b/src/config.cpp index 375dc4cb..7096ba89 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -21,8 +21,8 @@ const std::vector Config::CONFIG_DIRS = { const char *Config::CONFIG_PATH_ENV = "WAYBAR_CONFIG_DIR"; -std::optional Config::tryExpandPath(const std::string &base, - const std::string &filename) { +std::vector Config::tryExpandPath(const std::string &base, + const std::string &filename) { fs::path path; if (!filename.empty()) { @@ -33,33 +33,35 @@ std::optional Config::tryExpandPath(const std::string &base, spdlog::debug("Try expanding: {}", path.string()); + std::vector results; wordexp_t p; if (wordexp(path.c_str(), &p, 0) == 0) { - if (access(*p.we_wordv, F_OK) == 0) { - std::string result = *p.we_wordv; - wordfree(&p); - spdlog::debug("Found config file: {}", path.string()); - return result; + for (size_t i = 0; i < p.we_wordc; i++) { + if (access(p.we_wordv[i], F_OK) == 0) { + results.emplace_back(p.we_wordv[i]); + spdlog::debug("Found config file: {}", p.we_wordv[i]); + } } wordfree(&p); } - return std::nullopt; + + return results; } std::optional Config::findConfigPath(const std::vector &names, const std::vector &dirs) { if (const char *dir = std::getenv(Config::CONFIG_PATH_ENV)) { for (const auto &name : names) { - if (auto res = tryExpandPath(dir, name); res) { - return res; + if (auto res = tryExpandPath(dir, name); !res.empty()) { + return res.front(); } } } for (const auto &dir : dirs) { for (const auto &name : names) { - if (auto res = tryExpandPath(dir, name); res) { - return res; + if (auto res = tryExpandPath(dir, name); !res.empty()) { + return res.front(); } } } @@ -92,11 +94,15 @@ void Config::resolveConfigIncludes(Json::Value &config, int depth) { if (includes.isArray()) { for (const auto &include : includes) { spdlog::info("Including resource file: {}", include.asString()); - setupConfig(config, tryExpandPath(include.asString(), "").value_or(""), ++depth); + for (const auto &match : tryExpandPath(include.asString(), "")) { + setupConfig(config, match, depth + 1); + } } } else if (includes.isString()) { spdlog::info("Including resource file: {}", includes.asString()); - setupConfig(config, tryExpandPath(includes.asString(), "").value_or(""), ++depth); + for (const auto &match : tryExpandPath(includes.asString(), "")) { + setupConfig(config, match, depth + 1); + } } } From d7e4a7d91ff8d8005d1fb0f6159f84c3ec783a8d Mon Sep 17 00:00:00 2001 From: YamaD Date: Wed, 30 Oct 2024 16:54:24 +0900 Subject: [PATCH 122/780] add module wayfire/window, wayfire/workspaces --- include/modules/wayfire/backend.hpp | 122 +++++++ include/modules/wayfire/window.hpp | 24 ++ include/modules/wayfire/workspaces.hpp | 32 ++ man/waybar-wayfire-window.5.scd | 82 +++++ man/waybar-wayfire-workspaces.5.scd | 86 +++++ meson.build | 9 + src/factory.cpp | 12 + src/modules/wayfire/backend.cpp | 445 +++++++++++++++++++++++++ src/modules/wayfire/window.cpp | 77 +++++ src/modules/wayfire/workspaces.cpp | 183 ++++++++++ 10 files changed, 1072 insertions(+) create mode 100644 include/modules/wayfire/backend.hpp create mode 100644 include/modules/wayfire/window.hpp create mode 100644 include/modules/wayfire/workspaces.hpp create mode 100644 man/waybar-wayfire-window.5.scd create mode 100644 man/waybar-wayfire-workspaces.5.scd create mode 100644 src/modules/wayfire/backend.cpp create mode 100644 src/modules/wayfire/window.cpp create mode 100644 src/modules/wayfire/workspaces.cpp diff --git a/include/modules/wayfire/backend.hpp b/include/modules/wayfire/backend.hpp new file mode 100644 index 00000000..9d55c820 --- /dev/null +++ b/include/modules/wayfire/backend.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace waybar::modules::wayfire { + +using EventHandler = std::function; + +struct State { + /* + ┌───────────┐ ┌───────────┐ + │ output #1 │ │ output #2 │ + └─────┬─────┘ └─────┬─────┘ + └─┐ └─────┐─ ─ ─ ─ ─ ─ ─ ─ ┐ + ┌───────┴───────┐ ┌───────┴──────┐ ┌───────┴───────┐ + │ wset #1 │ │ wset #2 │ │ wset #3 │ + │┌────────────┐ │ │┌────────────┐│ │┌────────────┐ │ + ││ workspaces │ │ ││ workspaces ││ ││ workspaces │ │ + │└─┬──────────┘ │ │└────────────┘│ │└─┬──────────┘ │ + │ │ ┌─────────┐│ └──────────────┘ │ │ ┌─────────┐│ + │ ├─┤ view #1 ││ │ └─┤ view #3 ││ + │ │ └─────────┘│ │ └─────────┘│ + │ │ ┌─────────┐│ └───────────────┘ + │ └─┤ view #2 ││ + │ └─────────┘│ + └───────────────┘ + */ + + struct Output { + size_t id; + size_t w, h; + size_t wset_idx; + }; + + struct Workspace { + size_t num_views; + size_t num_sticky_views; + }; + + struct Wset { + std::optional> output; + std::vector wss; + size_t ws_w, ws_h, ws_x, ws_y; + size_t focused_view_id; + + auto ws_idx() const { return ws_w * ws_y + ws_x; } + auto count_ws(const Json::Value& pos) -> Workspace&; + auto locate_ws(const Json::Value& geo) -> Workspace&; + auto locate_ws(const Json::Value& geo) const -> const Workspace&; + }; + + std::unordered_map outputs; + std::unordered_map wsets; + std::unordered_map views; + std::string focused_output_name; + size_t maybe_empty_focus_wset_idx = {}; + size_t vswitch_sticky_view_id = {}; + bool new_output_detected = {}; + bool vswitching = {}; + + auto update_view(const Json::Value& view) -> void; +}; + +struct Sock { + int fd; + + Sock(int fd) : fd{fd} {} + ~Sock() { close(fd); } + Sock(const Sock&) = delete; + auto operator=(const Sock&) = delete; + Sock(Sock&& rhs) noexcept { + fd = rhs.fd; + rhs.fd = -1; + } + auto& operator=(Sock&& rhs) noexcept { + fd = rhs.fd; + rhs.fd = -1; + return *this; + } +}; + +class IPC { + static std::weak_ptr instance; + Json::CharReaderBuilder reader_builder; + Json::StreamWriterBuilder writer_builder; + std::list>> handlers; + std::mutex handlers_mutex; + State state; + std::mutex state_mutex; + + IPC() { start(); } + + static auto connect() -> Sock; + auto receive(Sock& sock) -> Json::Value; + auto start() -> void; + auto root_event_handler(const std::string& event, const Json::Value& data) -> void; + auto update_state_handler(const std::string& event, const Json::Value& data) -> void; + + public: + static auto get_instance() -> std::shared_ptr; + auto send(const std::string& method, Json::Value&& data) -> Json::Value; + auto register_handler(const std::string& event, const EventHandler& handler) -> void; + auto unregister_handler(EventHandler& handler) -> void; + + auto lock_state() -> std::lock_guard { return std::lock_guard{state_mutex}; } + auto& get_outputs() const { return state.outputs; } + auto& get_wsets() const { return state.wsets; } + auto& get_views() const { return state.views; } + auto& get_focused_output_name() const { return state.focused_output_name; } +}; + +} // namespace waybar::modules::wayfire diff --git a/include/modules/wayfire/window.hpp b/include/modules/wayfire/window.hpp new file mode 100644 index 00000000..3e8cb291 --- /dev/null +++ b/include/modules/wayfire/window.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "AAppIconLabel.hpp" +#include "bar.hpp" +#include "modules/wayfire/backend.hpp" + +namespace waybar::modules::wayfire { + +class Window : public AAppIconLabel { + std::shared_ptr ipc; + EventHandler handler; + + const Bar& bar_; + std::string old_app_id_; + + public: + Window(const std::string& id, const Bar& bar, const Json::Value& config); + ~Window() override; + + auto update() -> void override; + auto update_icon_label() -> void; +}; + +} // namespace waybar::modules::wayfire diff --git a/include/modules/wayfire/workspaces.hpp b/include/modules/wayfire/workspaces.hpp new file mode 100644 index 00000000..ab7cac44 --- /dev/null +++ b/include/modules/wayfire/workspaces.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#include +#include + +#include "AModule.hpp" +#include "bar.hpp" +#include "modules/wayfire/backend.hpp" + +namespace waybar::modules::wayfire { + +class Workspaces : public AModule { + std::shared_ptr ipc; + EventHandler handler; + + const Bar& bar_; + Gtk::Box box_; + std::vector buttons_; + + auto handleScroll(GdkEventScroll* e) -> bool override; + auto update() -> void override; + auto update_box() -> void; + + public: + Workspaces(const std::string& id, const Bar& bar, const Json::Value& config); + ~Workspaces() override; +}; + +} // namespace waybar::modules::wayfire diff --git a/man/waybar-wayfire-window.5.scd b/man/waybar-wayfire-window.5.scd new file mode 100644 index 00000000..290b0c65 --- /dev/null +++ b/man/waybar-wayfire-window.5.scd @@ -0,0 +1,82 @@ +waybar-wayfire-window(5) + +# NAME + +waybar - wayfire window module + +# DESCRIPTION + +The *window* module displays the title of the currently focused window in wayfire. + +# CONFIGURATION + +Addressed by *wayfire/window* + +*format*: ++ + typeof: string ++ + default: {title} ++ + The format, how information should be displayed. On {} the current window title is displayed. + +*rewrite*: ++ + typeof: object ++ + Rules to rewrite window title. See *rewrite rules*. + +*icon*: ++ + typeof: bool ++ + default: false ++ + Option to hide the application icon. + +*icon-size*: ++ + typeof: integer ++ + default: 24 ++ + Option to change the size of the application icon. + +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + +# FORMAT REPLACEMENTS + +See the output of "wayfire msg windows" for examples + +*{title}*: The current title of the focused window. + +*{app_id}*: The current app ID of the focused window. + +# REWRITE RULES + +*rewrite* is an object where keys are regular expressions and values are +rewrite rules if the expression matches. Rules may contain references to +captures of the expression. + +Regular expression and replacement follow ECMA-script rules. + +If no expression matches, the title is left unchanged. + +Invalid expressions (e.g., mismatched parentheses) are skipped. + +# EXAMPLES + +``` +"wayfire/window": { + "format": "{}", + "rewrite": { + "(.*) - Mozilla Firefox": "🌎 $1", + "(.*) - zsh": "> [$1]" + } +} +``` + +# STYLE + +- *#window* +- *window#waybar.empty #window* When no windows are on the workspace + +The following classes are applied to the entire Waybar rather than just the +window widget: + +- *window#waybar.empty* When no windows are in the workspace +- *window#waybar.solo* When only one window is on the workspace +- *window#waybar.* Where *app-id* is the app ID of the only window on + the workspace diff --git a/man/waybar-wayfire-workspaces.5.scd b/man/waybar-wayfire-workspaces.5.scd new file mode 100644 index 00000000..53a179e8 --- /dev/null +++ b/man/waybar-wayfire-workspaces.5.scd @@ -0,0 +1,86 @@ +waybar-wayfire-workspaces(5) + +# NAME + +waybar - wayfire workspaces module + +# DESCRIPTION + +The *workspaces* module displays the currently used workspaces in wayfire. + +# CONFIGURATION + +Addressed by *wayfire/workspaces* + +*format*: ++ + typeof: string ++ + default: {value} ++ + The format, how information should be displayed. + +*format-icons*: ++ + typeof: array ++ + Based on the workspace name, index and state, the corresponding icon gets selected. See *icons*. + +*disable-click*: ++ + typeof: bool ++ + default: false ++ + If set to false, you can click to change workspace. If set to true this behaviour is disabled. + +*disable-markup*: ++ + typeof: bool ++ + default: false ++ + If set to true, button label will escape pango markup. + +*current-only*: ++ + typeof: bool ++ + default: false ++ + If set to true, only the active or focused workspace will be shown. + +*on-update*: ++ + typeof: string ++ + Command to execute when the module is updated. + +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + +# FORMAT REPLACEMENTS + +*{icon}*: Icon, as defined in *format-icons*. + +*{index}*: Index of the workspace on its output. + +*{output}*: Output where the workspace is located. + +# ICONS + +Additional to workspace name matching, the following *format-icons* can be set. + +- *default*: Will be shown, when no string matches are found. +- *focused*: Will be shown, when workspace is focused. + +# EXAMPLES + +``` +"wayfire/workspaces": { + "format": "{icon}", + "format-icons": { + "1": "", + "2": "", + "3": "", + "4": "", + "5": "", + "focused": "", + "default": "" + } +} +``` + +# Style + +- *#workspaces button* +- *#workspaces button.focused*: The single focused workspace. +- *#workspaces button.empty*: The workspace is empty. +- *#workspaces button.current_output*: The workspace is from the same output as + the bar that it is displayed on. diff --git a/meson.build b/meson.build index 726d492b..2610a67c 100644 --- a/meson.build +++ b/meson.build @@ -333,6 +333,15 @@ if get_option('niri') ) endif +if true + add_project_arguments('-DHAVE_WAYFIRE', language: 'cpp') + src_files += files( + 'src/modules/wayfire/backend.cpp', + 'src/modules/wayfire/window.cpp', + 'src/modules/wayfire/workspaces.cpp', + ) +endif + if libnl.found() and libnlgen.found() add_project_arguments('-DHAVE_LIBNL', language: 'cpp') src_files += files('src/modules/network.cpp') diff --git a/src/factory.cpp b/src/factory.cpp index 6c2313e3..cdeba3e3 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -41,6 +41,10 @@ #include "modules/niri/window.hpp" #include "modules/niri/workspaces.hpp" #endif +#ifdef HAVE_WAYFIRE +#include "modules/wayfire/window.hpp" +#include "modules/wayfire/workspaces.hpp" +#endif #if defined(__FreeBSD__) || defined(__linux__) #include "modules/battery.hpp" #endif @@ -221,6 +225,14 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, if (ref == "niri/workspaces") { return new waybar::modules::niri::Workspaces(id, bar_, config_[name]); } +#endif +#ifdef HAVE_WAYFIRE + if (ref == "wayfire/window") { + return new waybar::modules::wayfire::Window(id, bar_, config_[name]); + } + if (ref == "wayfire/workspaces") { + return new waybar::modules::wayfire::Workspaces(id, bar_, config_[name]); + } #endif if (ref == "idle_inhibitor") { return new waybar::modules::IdleInhibitor(id, bar_, config_[name]); diff --git a/src/modules/wayfire/backend.cpp b/src/modules/wayfire/backend.cpp new file mode 100644 index 00000000..5a9c0c1a --- /dev/null +++ b/src/modules/wayfire/backend.cpp @@ -0,0 +1,445 @@ +#include "modules/wayfire/backend.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace waybar::modules::wayfire { + +std::weak_ptr IPC::instance; + +// C++23: std::byteswap +inline auto byteswap(uint32_t x) -> uint32_t { + return (x & 0xff000000) >> 24 | (x & 0x00ff0000) >> 8 | (x & 0x0000ff00) << 8 | + (x & 0x000000ff) << 24; +} + +auto pack_and_write(Sock& sock, std::string&& buf) -> void { + uint32_t len = buf.size(); + if constexpr (std::endian::native != std::endian::little) len = byteswap(len); + (void)write(sock.fd, &len, 4); + (void)write(sock.fd, buf.data(), buf.size()); +} + +auto read_exact(Sock& sock, size_t n) -> std::string { + auto buf = std::string(n, 0); + for (size_t i = 0; i < n;) i += read(sock.fd, &buf[i], n - i); + return buf; +} + +// https://github.com/WayfireWM/pywayfire/blob/69b7c21/wayfire/ipc.py#L438 +inline auto is_mapped_toplevel_view(const Json::Value& view) -> bool { + return view["mapped"].asBool() && view["role"] != "desktop-environment" && + view["pid"].asInt() != -1; +} + +auto State::Wset::count_ws(const Json::Value& pos) -> Workspace& { + auto x = pos["x"].asInt(); + auto y = pos["y"].asInt(); + return wss.at(ws_w * y + x); +} + +auto State::Wset::locate_ws(const Json::Value& geo) -> Workspace& { + return const_cast(std::as_const(*this).locate_ws(geo)); +} + +auto State::Wset::locate_ws(const Json::Value& geo) const -> const Workspace& { + const auto& out = output.value().get(); + auto [qx, rx] = std::div(geo["x"].asInt(), out.w); + auto [qy, ry] = std::div(geo["y"].asInt(), out.h); + auto x = std::max(0, (int)ws_x + qx - int{rx < 0}); + auto y = std::max(0, (int)ws_y + qy - int{ry < 0}); + return wss.at(ws_w * y + x); +} + +auto State::update_view(const Json::Value& view) -> void { + auto id = view["id"].asUInt(); + + // erase old view information + if (views.contains(id)) { + auto& old_view = views.at(id); + auto& ws = wsets.at(old_view["wset-index"].asUInt()).locate_ws(old_view["geometry"]); + ws.num_views--; + if (old_view["sticky"].asBool()) ws.num_sticky_views--; + views.erase(id); + } + + // insert or assign new view information + if (is_mapped_toplevel_view(view)) { + try { + // view["wset-index"] could be messed up + auto& ws = wsets.at(view["wset-index"].asUInt()).locate_ws(view["geometry"]); + ws.num_views++; + if (view["sticky"].asBool()) ws.num_sticky_views++; + views.emplace(id, view); + } catch (const std::exception&) { + } + } +} + +auto IPC::get_instance() -> std::shared_ptr { + auto p = instance.lock(); + if (!p) instance = p = std::shared_ptr(new IPC); + return p; +} + +auto IPC::connect() -> Sock { + auto* path = std::getenv("WAYFIRE_SOCKET"); + if (path == nullptr) { + throw std::runtime_error{"Wayfire IPC: ipc not available"}; + } + + auto sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock == -1) { + throw std::runtime_error{"Wayfire IPC: socket() failed"}; + } + + auto addr = sockaddr_un{.sun_family = AF_UNIX}; + std::strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + addr.sun_path[sizeof(addr.sun_path) - 1] = 0; + + if (::connect(sock, (const sockaddr*)&addr, sizeof(addr)) == -1) { + close(sock); + throw std::runtime_error{"Wayfire IPC: connect() failed"}; + } + + return {sock}; +} + +auto IPC::receive(Sock& sock) -> Json::Value { + auto len = *reinterpret_cast(read_exact(sock, 4).data()); + if constexpr (std::endian::native != std::endian::little) len = byteswap(len); + auto buf = read_exact(sock, len); + + Json::Value json; + std::string err; + auto* reader = reader_builder.newCharReader(); + if (!reader->parse(&*buf.begin(), &*buf.end(), &json, &err)) { + throw std::runtime_error{"Wayfire IPC: parse json failed: " + err}; + } + return json; +} + +auto IPC::send(const std::string& method, Json::Value&& data) -> Json::Value { + spdlog::debug("Wayfire IPC: send method \"{}\"", method); + auto sock = connect(); + + Json::Value json; + json["method"] = method; + json["data"] = std::move(data); + + pack_and_write(sock, Json::writeString(writer_builder, json)); + auto res = receive(sock); + root_event_handler(method, res); + return res; +} + +auto IPC::start() -> void { + spdlog::info("Wayfire IPC: starting"); + + // init state + send("window-rules/list-outputs", {}); + send("window-rules/list-wsets", {}); + send("window-rules/list-views", {}); + send("window-rules/get-focused-view", {}); + send("window-rules/get-focused-output", {}); + + std::thread([&] { + auto sock = connect(); + + { + Json::Value json; + json["method"] = "window-rules/events/watch"; + + pack_and_write(sock, Json::writeString(writer_builder, json)); + if (receive(sock)["result"] != "ok") { + spdlog::error( + "Wayfire IPC: method \"window-rules/events/watch\"" + " have failed"); + return; + } + } + + while (auto json = receive(sock)) { + auto ev = json["event"].asString(); + spdlog::debug("Wayfire IPC: received event \"{}\"", ev); + root_event_handler(ev, json); + } + }).detach(); +} + +auto IPC::register_handler(const std::string& event, const EventHandler& handler) -> void { + auto _ = std::lock_guard{handlers_mutex}; + handlers.emplace_back(event, handler); +} + +auto IPC::unregister_handler(EventHandler& handler) -> void { + auto _ = std::lock_guard{handlers_mutex}; + handlers.remove_if([&](auto& e) { return &e.second.get() == &handler; }); +} + +auto IPC::root_event_handler(const std::string& event, const Json::Value& data) -> void { + bool new_output_detected; + { + auto _ = lock_state(); + update_state_handler(event, data); + new_output_detected = state.new_output_detected; + state.new_output_detected = false; + } + if (new_output_detected) { + send("window-rules/list-outputs", {}); + send("window-rules/list-wsets", {}); + } + { + auto _ = std::lock_guard{handlers_mutex}; + for (const auto& [_event, handler] : handlers) + if (_event == event) handler(event); + } +} + +auto IPC::update_state_handler(const std::string& event, const Json::Value& data) -> void { + // IPC events + // https://github.com/WayfireWM/wayfire/blob/053b222/plugins/ipc-rules/ipc-events.hpp#L108-L125 + /* + [x] view-mapped + [x] view-unmapped + [-] view-set-output // for detect new output + [ ] view-geometry-changed // -> view-workspace-changed + [x] view-wset-changed + [x] view-focused + [x] view-title-changed + [x] view-app-id-changed + [x] plugin-activation-state-changed + [x] output-gain-focus + + [ ] view-tiled + [ ] view-minimized + [ ] view-fullscreened + [x] view-sticky + [x] view-workspace-changed + [x] output-wset-changed + [x] wset-workspace-changed + */ + + if (event == "view-mapped") { + // data: { event, view } + state.update_view(data["view"]); + return; + } + + if (event == "view-unmapped") { + // data: { event, view } + try { + // data["view"]["wset-index"] could be messed up + state.update_view(data["view"]); + state.maybe_empty_focus_wset_idx = data["view"]["wset-index"].asUInt(); + } catch (const std::exception&) { + } + return; + } + + if (event == "view-set-output") { + // data: { event, output?, view } + // new output event + if (!state.outputs.contains(data["view"]["output-name"].asString())) { + state.new_output_detected = true; + } + return; + } + + if (event == "view-wset-changed") { + // data: { event, old-wset: wset, new-wset: wset, view } + state.maybe_empty_focus_wset_idx = data["old-wset"]["index"].asUInt(); + state.update_view(data["view"]); + return; + } + + if (event == "view-focused") { + // data: { event, view? } + if (const auto& view = data["view"]) { + try { + // view["wset-index"] could be messed up + auto& wset = state.wsets.at(view["wset-index"].asUInt()); + wset.focused_view_id = view["id"].asUInt(); + } catch (const std::exception&) { + } + } else { + // focused to null + if (state.wsets.contains(state.maybe_empty_focus_wset_idx)) + state.wsets.at(state.maybe_empty_focus_wset_idx).focused_view_id = {}; + } + return; + } + + if (event == "view-title-changed" || event == "view-app-id-changed" || event == "view-sticky") { + // data: { event, view } + state.update_view(data["view"]); + return; + } + + if (event == "plugin-activation-state-changed") { + // data: { event, plugin: name, state: bool, output: id, output-data: output } + auto plugin = data["plugin"].asString(); + auto plugin_state = data["state"].asBool(); + + if (plugin == "vswitch") { + state.vswitching = plugin_state; + if (plugin_state) { + state.maybe_empty_focus_wset_idx = data["output-data"]["wset-index"].asUInt(); + } + } + + return; + } + + if (event == "output-gain-focus") { + // data: { event, output } + state.focused_output_name = data["output"]["name"].asString(); + return; + } + + if (event == "view-workspace-changed") { + // data: { event, from: point, to: point, view } + if (state.vswitching) { + if (state.vswitch_sticky_view_id == 0) { + auto& wset = state.wsets.at(data["view"]["wset-index"].asUInt()); + auto& old_ws = wset.locate_ws(state.views.at(data["view"]["id"].asUInt())["geometry"]); + auto& new_ws = wset.count_ws(data["to"]); + old_ws.num_views--; + new_ws.num_views++; + if (data["view"]["sticky"].asBool()) { + old_ws.num_sticky_views--; + new_ws.num_sticky_views++; + } + state.update_view(data["view"]); + state.vswitch_sticky_view_id = data["view"]["id"].asUInt(); + } else { + state.vswitch_sticky_view_id = {}; + } + return; + } + state.update_view(data["view"]); + return; + } + + if (event == "output-wset-changed") { + // data: { event, new-wset: wset.name, output: id, new-wset-data: wset, output-data: output } + auto& output = state.outputs.at(data["output-data"]["name"].asString()); + auto wset_idx = data["new-wset-data"]["index"].asUInt(); + state.wsets.at(wset_idx).output = output; + output.wset_idx = wset_idx; + return; + } + + if (event == "wset-workspace-changed") { + // data: { event, previous-workspace: point, new-workspace: point, + // output: id, wset: wset.name, output-data: output, wset-data: wset } + auto wset_idx = data["wset-data"]["index"].asUInt(); + auto& wset = state.wsets.at(wset_idx); + wset.ws_x = data["new-workspace"]["x"].asUInt(); + wset.ws_y = data["new-workspace"]["y"].asUInt(); + + // correct existing views geometry + auto& out = wset.output.value().get(); + auto dx = (int)out.w * ((int)wset.ws_x - data["previous-workspace"]["x"].asInt()); + auto dy = (int)out.h * ((int)wset.ws_y - data["previous-workspace"]["y"].asInt()); + for (auto& [_, view] : state.views) { + if (view["wset-index"].asUInt() == wset_idx && + view["id"].asUInt() != state.vswitch_sticky_view_id) { + view["geometry"]["x"] = view["geometry"]["x"].asInt() - dx; + view["geometry"]["y"] = view["geometry"]["y"].asInt() - dy; + } + } + return; + } + + // IPC responses + // https://github.com/WayfireWM/wayfire/blob/053b222/plugins/ipc-rules/ipc-rules.cpp#L27-L37 + + if (event == "window-rules/list-views") { + // data: [ view ] + state.views.clear(); + for (auto& [_, wset] : state.wsets) std::ranges::fill(wset.wss, State::Workspace{}); + for (const auto& view : data | std::views::filter(is_mapped_toplevel_view)) { + state.update_view(view); + } + return; + } + + if (event == "window-rules/list-outputs") { + // data: [ output ] + state.outputs.clear(); + for (const auto& output_data : data) { + state.outputs.emplace(output_data["name"].asString(), + State::Output{ + .id = output_data["id"].asUInt(), + .w = output_data["geometry"]["width"].asUInt(), + .h = output_data["geometry"]["height"].asUInt(), + .wset_idx = output_data["wset-index"].asUInt(), + }); + } + return; + } + + if (event == "window-rules/list-wsets") { + // data: [ wset ] + std::unordered_map wsets; + for (const auto& wset_data : data) { + auto wset_idx = wset_data["index"].asUInt(); + + auto output_name = wset_data["output-name"].asString(); + auto output = state.outputs.contains(output_name) + ? std::optional{std::ref(state.outputs.at(output_name))} + : std::nullopt; + + const auto& ws_data = wset_data["workspace"]; + auto ws_w = ws_data["grid_width"].asUInt(); + auto ws_h = ws_data["grid_height"].asUInt(); + + wsets.emplace(wset_idx, State::Wset{ + .output = output, + .wss = std::vector(ws_w * ws_h), + .ws_w = ws_w, + .ws_h = ws_h, + .ws_x = ws_data["x"].asUInt(), + .ws_y = ws_data["y"].asUInt(), + }); + + if (state.wsets.contains(wset_idx)) { + auto& old_wset = state.wsets.at(wset_idx); + auto& new_wset = wsets.at(wset_idx); + new_wset.wss = std::move(old_wset.wss); + new_wset.focused_view_id = old_wset.focused_view_id; + } + } + state.wsets = std::move(wsets); + return; + } + + if (event == "window-rules/get-focused-view") { + // data: { ok, info: view? } + if (const auto& view = data["info"]) { + auto& wset = state.wsets.at(view["wset-index"].asUInt()); + wset.focused_view_id = view["id"].asUInt(); + state.update_view(view); + } + return; + } + + if (event == "window-rules/get-focused-output") { + // data: { ok, info: output } + state.focused_output_name = data["info"]["name"].asString(); + return; + } +} + +} // namespace waybar::modules::wayfire diff --git a/src/modules/wayfire/window.cpp b/src/modules/wayfire/window.cpp new file mode 100644 index 00000000..fbcde6ec --- /dev/null +++ b/src/modules/wayfire/window.cpp @@ -0,0 +1,77 @@ +#include "modules/wayfire/window.hpp" + +#include +#include +#include + +#include "util/rewrite_string.hpp" +#include "util/sanitize_str.hpp" + +namespace waybar::modules::wayfire { + +Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) + : AAppIconLabel(config, "window", id, "{title}", 0, true), + ipc{IPC::get_instance()}, + handler{[this](const auto&) { dp.emit(); }}, + bar_{bar} { + ipc->register_handler("view-unmapped", handler); + ipc->register_handler("view-focused", handler); + ipc->register_handler("view-title-changed", handler); + ipc->register_handler("view-app-id-changed", handler); + + ipc->register_handler("window-rules/get-focused-view", handler); + + dp.emit(); +} + +Window::~Window() { ipc->unregister_handler(handler); } + +auto Window::update() -> void { + update_icon_label(); + AAppIconLabel::update(); +} + +auto Window::update_icon_label() -> void { + auto _ = ipc->lock_state(); + + const auto& output = ipc->get_outputs().at(bar_.output->name); + const auto& wset = ipc->get_wsets().at(output.wset_idx); + const auto& views = ipc->get_views(); + auto ctx = bar_.window.get_style_context(); + + if (views.contains(wset.focused_view_id)) { + const auto& view = views.at(wset.focused_view_id); + auto title = view["title"].asString(); + auto app_id = view["app-id"].asString(); + + // update label + label_.set_markup(waybar::util::rewriteString( + fmt::format(fmt::runtime(format_), fmt::arg("title", waybar::util::sanitize_string(title)), + fmt::arg("app_id", waybar::util::sanitize_string(app_id))), + config_["rewrite"])); + + // update window#waybar.solo + if (wset.locate_ws(view["geometry"]).num_views > 1) + ctx->remove_class("solo"); + else + ctx->add_class("solo"); + + // update window#waybar. + ctx->remove_class(old_app_id_); + ctx->add_class(old_app_id_ = app_id); + + // update window#waybar.empty + ctx->remove_class("empty"); + + // + updateAppIconName(app_id, ""); + label_.show(); + } else { + ctx->add_class("empty"); + + updateAppIconName("", ""); + label_.hide(); + } +} + +} // namespace waybar::modules::wayfire diff --git a/src/modules/wayfire/workspaces.cpp b/src/modules/wayfire/workspaces.cpp new file mode 100644 index 00000000..6814004e --- /dev/null +++ b/src/modules/wayfire/workspaces.cpp @@ -0,0 +1,183 @@ +#include "modules/wayfire/workspaces.hpp" + +#include +#include +#include + +#include +#include + +#include "modules/wayfire/backend.hpp" + +namespace waybar::modules::wayfire { + +Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config) + : AModule{config, "workspaces", id, false, !config["disable-scroll"].asBool()}, + ipc{IPC::get_instance()}, + handler{[this](const auto&) { dp.emit(); }}, + bar_{bar} { + // init box_ + box_.set_name("workspaces"); + if (!id.empty()) box_.get_style_context()->add_class(id); + box_.get_style_context()->add_class(MODULE_CLASS); + event_box_.add(box_); + + // scroll events + if (!config_["disable-scroll"].asBool()) { + auto& target = config_["enable-bar-scroll"].asBool() ? const_cast(bar_).window + : dynamic_cast(box_); + target.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); + target.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll)); + } + + // listen events + ipc->register_handler("view-mapped", handler); + ipc->register_handler("view-unmapped", handler); + ipc->register_handler("view-wset-changed", handler); + ipc->register_handler("output-gain-focus", handler); + ipc->register_handler("view-sticky", handler); + ipc->register_handler("view-workspace-changed", handler); + ipc->register_handler("output-wset-changed", handler); + ipc->register_handler("wset-workspace-changed", handler); + + ipc->register_handler("window-rules/list-views", handler); + ipc->register_handler("window-rules/list-outputs", handler); + ipc->register_handler("window-rules/list-wsets", handler); + ipc->register_handler("window-rules/get-focused-output", handler); + + // initial render + dp.emit(); +} + +Workspaces::~Workspaces() { ipc->unregister_handler(handler); } + +auto Workspaces::handleScroll(GdkEventScroll* e) -> bool { + // Ignore emulated scroll events on window + if (gdk_event_get_pointer_emulated((GdkEvent*)e) != 0) return false; + + auto dir = AModule::getScrollDir(e); + if (dir == SCROLL_DIR::NONE) return true; + + int delta; + if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) + delta = 1; + else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) + delta = -1; + else + return true; + + // cycle workspace + Json::Value data; + { + auto _ = ipc->lock_state(); + const auto& output = ipc->get_outputs().at(bar_.output->name); + const auto& wset = ipc->get_wsets().at(output.wset_idx); + auto n = wset.ws_w * wset.ws_h; + auto i = (wset.ws_idx() + delta + n) % n; + data["x"] = i % wset.ws_w; + data["y"] = i / wset.ws_h; + data["output-id"] = output.id; + } + ipc->send("vswitch/set-workspace", std::move(data)); + + return true; +} + +auto Workspaces::update() -> void { + update_box(); + AModule::update(); +} + +auto Workspaces::update_box() -> void { + auto _ = ipc->lock_state(); + + const auto& output_name = bar_.output->name; + const auto& output = ipc->get_outputs().at(output_name); + const auto& wset = ipc->get_wsets().at(output.wset_idx); + + auto output_focused = ipc->get_focused_output_name() == output_name; + auto ws_w = wset.ws_w; + auto ws_h = wset.ws_h; + auto num_wss = ws_w * ws_h; + + // add buttons for new workspaces + for (auto i = buttons_.size(); i < num_wss; i++) { + auto& btn = buttons_.emplace_back(""); + box_.pack_start(btn, false, false, 0); + btn.set_relief(Gtk::RELIEF_NONE); + if (!config_["disable-click"].asBool()) { + btn.signal_pressed().connect([=, this] { + Json::Value data; + data["x"] = i % ws_w; + data["y"] = i / ws_h; + data["output-id"] = output.id; + ipc->send("vswitch/set-workspace", std::move(data)); + }); + } + } + + // remove buttons for removed workspaces + buttons_.resize(num_wss); + + // update buttons + for (size_t i = 0; i < num_wss; i++) { + const auto& ws = wset.wss[i]; + auto& btn = buttons_[i]; + auto ctx = btn.get_style_context(); + auto ws_focused = i == wset.ws_idx(); + auto ws_empty = ws.num_views == 0; + + // update #workspaces button.focused + if (ws_focused) + ctx->add_class("focused"); + else + ctx->remove_class("focused"); + + // update #workspaces button.empty + if (ws_empty) + ctx->add_class("empty"); + else + ctx->remove_class("empty"); + + // update #workspaces button.current_output + if (output_focused) + ctx->add_class("current_output"); + else + ctx->remove_class("current_output"); + + // update label + auto label = std::to_string(i + 1); + if (config_["format"].isString()) { + auto format = config_["format"].asString(); + auto ws_idx = std::to_string(i + 1); + + const auto& icons = config_["format-icons"]; + std::string icon; + if (!icons) + icon = ws_idx; + else if (ws_focused && icons["focused"]) + icon = icons["focused"].asString(); + else if (icons[ws_idx]) + icon = icons[ws_idx].asString(); + else if (icons["default"]) + icon = icons["default"].asString(); + else + icon = ws_idx; + + label = fmt::format(fmt::runtime(format), fmt::arg("icon", icon), fmt::arg("index", ws_idx), + fmt::arg("output", output_name)); + } + if (!config_["disable-markup"].asBool()) + static_cast(btn.get_children()[0])->set_markup(label); + else + btn.set_label(label); + + // + if (config_["current-only"].asBool() && i != wset.ws_idx()) + btn.hide(); + else + btn.show(); + } +} + +} // namespace waybar::modules::wayfire From 865121b21dcdf3bf6733562e90a94efeeeb006ff Mon Sep 17 00:00:00 2001 From: "Sv. Lockal" Date: Sat, 4 Jan 2025 10:28:14 +0000 Subject: [PATCH 123/780] Fix compilation with libc++ This file uses std::sort and does not import correct header. Compilation with libstdc++ worked due to some indirect import, but compilation with LLVM libc++ fails. --- src/util/regex_collection.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util/regex_collection.cpp b/src/util/regex_collection.cpp index 929e67cd..51dd6ff7 100644 --- a/src/util/regex_collection.cpp +++ b/src/util/regex_collection.cpp @@ -3,6 +3,7 @@ #include #include +#include #include namespace waybar::util { From 6a29abb49e9aa1acd591d98dadb9cd1d6b6d3fff Mon Sep 17 00:00:00 2001 From: Alex Murkoff <413x1nkp@gmail.com> Date: Fri, 10 Jan 2025 15:27:41 +0700 Subject: [PATCH 124/780] fix: never sleep cava when sleep_timer is 0 --- src/modules/cava.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/cava.cpp b/src/modules/cava.cpp index f16d3f63..405a351a 100644 --- a/src/modules/cava.cpp +++ b/src/modules/cava.cpp @@ -139,7 +139,7 @@ auto waybar::modules::Cava::update() -> void { } } - if (silence_ && prm_.sleep_timer) { + if (silence_ && prm_.sleep_timer != 0) { if (sleep_counter_ <= (int)(std::chrono::milliseconds(prm_.sleep_timer * 1s) / frame_time_milsec_)) { ++sleep_counter_; @@ -147,7 +147,7 @@ auto waybar::modules::Cava::update() -> void { } } - if (!silence_) { + if (!silence_ || prm_.sleep_timer == 0) { downThreadDelay(frame_time_milsec_, suspend_silence_delay_); // Process: execute cava pthread_mutex_lock(&audio_data_.lock); From 0992bf1b879a4780aa47d5847ac5e15e3fd2f150 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Thu, 9 Jan 2025 07:33:52 +0100 Subject: [PATCH 125/780] Escape tray tooltip text Fix errors when the tooltip set by the tray apps contains markup characters --- src/modules/sni/item.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 6c4ec8c0..b3e84885 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -124,7 +124,8 @@ ToolTip get_variant(const Glib::VariantBase& value) { result.text = get_variant(container.get_child(2)); auto description = get_variant(container.get_child(3)); if (!description.empty()) { - result.text = fmt::format("{}\n{}", result.text, description); + auto escapedDescription = Glib::Markup::escape_text(description); + result.text = fmt::format("{}\n{}", result.text, escapedDescription); } return result; } From 6004316f1a13217313a2bce9403c261e7fe941e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torstein=20Huseb=C3=B8?= Date: Thu, 16 Jan 2025 13:24:13 +0100 Subject: [PATCH 126/780] Fix typos in function, variable names and in documentation --- include/modules/hyprland/windowcreationpayload.hpp | 2 +- include/modules/hyprland/workspace.hpp | 4 ++-- include/modules/hyprland/workspaces.hpp | 2 +- include/modules/sway/language.hpp | 4 ++-- include/modules/sway/workspaces.hpp | 2 +- include/util/prepare_for_sleep.h | 2 +- man/waybar-menu.5.scd | 2 +- man/waybar.5.scd.in | 2 +- src/AModule.cpp | 4 ++-- src/modules/hyprland/submap.cpp | 2 +- src/modules/hyprland/windowcreationpayload.cpp | 2 +- src/modules/hyprland/workspace.cpp | 14 +++++++------- src/modules/hyprland/workspaces.cpp | 2 +- src/modules/power_profiles_daemon.cpp | 4 ++-- src/modules/sway/language.cpp | 4 ++-- src/modules/sway/workspaces.cpp | 8 ++++---- src/modules/temperature.cpp | 4 ++-- 17 files changed, 32 insertions(+), 32 deletions(-) diff --git a/include/modules/hyprland/windowcreationpayload.hpp b/include/modules/hyprland/windowcreationpayload.hpp index 45229ed4..e4180ed9 100644 --- a/include/modules/hyprland/windowcreationpayload.hpp +++ b/include/modules/hyprland/windowcreationpayload.hpp @@ -42,7 +42,7 @@ class WindowCreationPayload { std::string getWorkspaceName() const { return m_workspaceName; } WindowAddress getAddress() const { return m_windowAddress; } - void moveToWorksace(std::string& new_workspace_name); + void moveToWorkspace(std::string& new_workspace_name); private: void clearAddr(); diff --git a/include/modules/hyprland/workspace.hpp b/include/modules/hyprland/workspace.hpp index f1fea4e8..b67d8e14 100644 --- a/include/modules/hyprland/workspace.hpp +++ b/include/modules/hyprland/workspace.hpp @@ -55,11 +55,11 @@ class Workspace { void setName(std::string const& value) { m_name = value; }; void setOutput(std::string const& value) { m_output = value; }; bool containsWindow(WindowAddress const& addr) const { return m_windowMap.contains(addr); } - void insertWindow(WindowCreationPayload create_window_paylod); + void insertWindow(WindowCreationPayload create_window_payload); std::string removeWindow(WindowAddress const& addr); void initializeWindowMap(const Json::Value& clients_data); - bool onWindowOpened(WindowCreationPayload const& create_window_paylod); + bool onWindowOpened(WindowCreationPayload const& create_window_payload); std::optional closeWindow(WindowAddress const& addr); void update(const std::string& format, const std::string& icon); diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index a9d56b79..a2a8558c 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -118,7 +118,7 @@ class Workspaces : public AModule, public EventHandler { // Map for windows stored in workspaces not present in the current bar. // This happens when the user has multiple monitors (hence, multiple bars) - // and doesn't share windows accross bars (a.k.a `all-outputs` = false) + // and doesn't share windows across bars (a.k.a `all-outputs` = false) std::map m_orphanWindowMap; enum class SortMethod { ID, NAME, NUMBER, DEFAULT }; diff --git a/include/modules/sway/language.hpp b/include/modules/sway/language.hpp index ea58c4f0..91aa181d 100644 --- a/include/modules/sway/language.hpp +++ b/include/modules/sway/language.hpp @@ -21,7 +21,7 @@ class Language : public ALabel, public sigc::trackable { auto update() -> void override; private: - enum class DispayedShortFlag { None = 0, ShortName = 1, ShortDescription = 1 << 1 }; + enum class DisplayedShortFlag { None = 0, ShortName = 1, ShortDescription = 1 << 1 }; struct Layout { std::string full_name; @@ -58,7 +58,7 @@ class Language : public ALabel, public sigc::trackable { std::map layouts_map_; bool hide_single_; bool is_variant_displayed; - std::byte displayed_short_flag = static_cast(DispayedShortFlag::None); + std::byte displayed_short_flag = static_cast(DisplayedShortFlag::None); util::JsonParser parser_; std::mutex mutex_; diff --git a/include/modules/sway/workspaces.hpp b/include/modules/sway/workspaces.hpp index 58c173ec..d8a9e18a 100644 --- a/include/modules/sway/workspaces.hpp +++ b/include/modules/sway/workspaces.hpp @@ -48,7 +48,7 @@ class Workspaces : public AModule, public sigc::trackable { std::vector high_priority_named_; std::vector workspaces_order_; Gtk::Box box_; - std::string m_formatWindowSeperator; + std::string m_formatWindowSeparator; util::RegexCollection m_windowRewriteRules; util::JsonParser parser_; std::unordered_map buttons_; diff --git a/include/util/prepare_for_sleep.h b/include/util/prepare_for_sleep.h index 68db8d8e..82f3b627 100644 --- a/include/util/prepare_for_sleep.h +++ b/include/util/prepare_for_sleep.h @@ -4,6 +4,6 @@ namespace waybar::util { -// Get a signal emited with value true when entering sleep, and false when exiting +// Get a signal emitted with value true when entering sleep, and false when exiting SafeSignal& prepare_for_sleep(); } // namespace waybar::util diff --git a/man/waybar-menu.5.scd b/man/waybar-menu.5.scd index 19790ed4..47e10432 100644 --- a/man/waybar-menu.5.scd +++ b/man/waybar-menu.5.scd @@ -7,7 +7,7 @@ waybar - menu property # OVERVIEW -Some modules support a 'menu', which allows to have a popup menu whan a defined +Some modules support a 'menu', which allows to have a popup menu when a defined click is done over the module. # PROPERTIES diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 5bb62724..e2e0627d 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -86,7 +86,7 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles( *no-center* ++ typeof: bool ++ default: false ++ - Option to disable the center modules fully usefull together with expand-\*. + Option to disable the center modules fully useful together with expand-\*. *spacing* ++ typeof: integer ++ diff --git a/src/AModule.cpp b/src/AModule.cpp index 5abb779a..7de1ad9a 100644 --- a/src/AModule.cpp +++ b/src/AModule.cpp @@ -97,11 +97,11 @@ auto AModule::update() -> void { } } // Get mapping between event name and module action name -// Then call overrided doAction in order to call appropriate module action +// Then call overridden doAction in order to call appropriate module action auto AModule::doAction(const std::string& name) -> void { if (!name.empty()) { const std::map::const_iterator& recA{eventActionMap_.find(name)}; - // Call overrided action if derrived class has implemented it + // Call overridden action if derived class has implemented it if (recA != eventActionMap_.cend() && name != recA->second) this->doAction(recA->second); } } diff --git a/src/modules/hyprland/submap.cpp b/src/modules/hyprland/submap.cpp index 96677d12..67aaeac0 100644 --- a/src/modules/hyprland/submap.cpp +++ b/src/modules/hyprland/submap.cpp @@ -20,7 +20,7 @@ Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config) ALabel::update(); // Displays widget immediately if always_on_ assuming default submap - // Needs an actual way to retrive current submap on startup + // Needs an actual way to retrieve current submap on startup if (always_on_) { submap_ = default_submap_; label_.get_style_context()->add_class(submap_); diff --git a/src/modules/hyprland/windowcreationpayload.cpp b/src/modules/hyprland/windowcreationpayload.cpp index df7fe784..2a62ac12 100644 --- a/src/modules/hyprland/windowcreationpayload.cpp +++ b/src/modules/hyprland/windowcreationpayload.cpp @@ -88,7 +88,7 @@ bool WindowCreationPayload::isEmpty(Workspaces &workspace_manager) { int WindowCreationPayload::incrementTimeSpentUncreated() { return m_timeSpentUncreated++; } -void WindowCreationPayload::moveToWorksace(std::string &new_workspace_name) { +void WindowCreationPayload::moveToWorkspace(std::string &new_workspace_name) { m_workspaceName = new_workspace_name; } diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index e575d1c4..048d50c1 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -90,19 +90,19 @@ void Workspace::initializeWindowMap(const Json::Value &clients_data) { } } -void Workspace::insertWindow(WindowCreationPayload create_window_paylod) { - if (!create_window_paylod.isEmpty(m_workspaceManager)) { - auto repr = create_window_paylod.repr(m_workspaceManager); +void Workspace::insertWindow(WindowCreationPayload create_window_payload) { + if (!create_window_payload.isEmpty(m_workspaceManager)) { + auto repr = create_window_payload.repr(m_workspaceManager); if (!repr.empty()) { - m_windowMap[create_window_paylod.getAddress()] = repr; + m_windowMap[create_window_payload.getAddress()] = repr; } } }; -bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_paylod) { - if (create_window_paylod.getWorkspaceName() == name()) { - insertWindow(create_window_paylod); +bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_payload) { + if (create_window_payload.getWorkspaceName() == name()) { + insertWindow(create_window_payload); return true; } return false; diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 13364f3f..1cf8a184 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -487,7 +487,7 @@ void Workspaces::onWindowMoved(std::string const &payload) { // and exit for (auto &window : m_windowsToCreate) { if (window.getAddress() == windowAddress) { - window.moveToWorksace(workspaceName); + window.moveToWorkspace(workspaceName); return; } } diff --git a/src/modules/power_profiles_daemon.cpp b/src/modules/power_profiles_daemon.cpp index 3ae3ae83..abad763d 100644 --- a/src/modules/power_profiles_daemon.cpp +++ b/src/modules/power_profiles_daemon.cpp @@ -29,14 +29,14 @@ PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Valu // method on the proxy to see whether or not something's responding // on the other side. - // NOTE: the DBus adresses are under migration. They should be + // NOTE: the DBus addresses are under migration. They should be // changed to org.freedesktop.UPower.PowerProfiles at some point. // // See // https://gitlab.freedesktop.org/upower/power-profiles-daemon/-/releases/0.20 // // The old name is still announced for now. Let's rather use the old - // adresses for compatibility sake. + // addresses for compatibility sake. // // Revisit this in 2026, systems should be updated by then. diff --git a/src/modules/sway/language.cpp b/src/modules/sway/language.cpp index a005df17..f4cfa6c3 100644 --- a/src/modules/sway/language.cpp +++ b/src/modules/sway/language.cpp @@ -22,10 +22,10 @@ Language::Language(const std::string& id, const Json::Value& config) hide_single_ = config["hide-single-layout"].isBool() && config["hide-single-layout"].asBool(); is_variant_displayed = format_.find("{variant}") != std::string::npos; if (format_.find("{}") != std::string::npos || format_.find("{short}") != std::string::npos) { - displayed_short_flag |= static_cast(DispayedShortFlag::ShortName); + displayed_short_flag |= static_cast(DisplayedShortFlag::ShortName); } if (format_.find("{shortDescription}") != std::string::npos) { - displayed_short_flag |= static_cast(DispayedShortFlag::ShortDescription); + displayed_short_flag |= static_cast(DisplayedShortFlag::ShortDescription); } if (config.isMember("tooltip-format")) { tooltip_format_ = config["tooltip-format"].asString(); diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index dec5cddf..6349d6cc 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -57,9 +57,9 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value box_.get_style_context()->add_class(MODULE_CLASS); event_box_.add(box_); if (config_["format-window-separator"].isString()) { - m_formatWindowSeperator = config_["format-window-separator"].asString(); + m_formatWindowSeparator = config_["format-window-separator"].asString(); } else { - m_formatWindowSeperator = " "; + m_formatWindowSeparator = " "; } const Json::Value &windowRewrite = config["window-rewrite"]; if (windowRewrite.isObject()) { @@ -271,7 +271,7 @@ void Workspaces::updateWindows(const Json::Value &node, std::string &windows) { window = fmt::format(fmt::runtime(window), fmt::arg("name", title), fmt::arg("class", windowClass)); windows.append(window); - windows.append(m_formatWindowSeperator); + windows.append(m_formatWindowSeparator); } } for (const Json::Value &child : node["nodes"]) { @@ -340,7 +340,7 @@ auto Workspaces::update() -> void { fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)), fmt::arg("value", output), fmt::arg("name", trimWorkspaceName(output)), fmt::arg("index", (*it)["num"].asString()), fmt::arg("windows", - windows.substr(0, windows.length() - m_formatWindowSeperator.length())), + windows.substr(0, windows.length() - m_formatWindowSeparator.length())), fmt::arg("output", (*it)["output"].asString())); } if (!config_["disable-markup"].asBool()) { diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index 89d3db2d..2904417b 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -45,7 +45,7 @@ waybar::modules::Temperature::Temperature(const std::string& id, const Json::Val file_path_ = fmt::format("/sys/class/thermal/thermal_zone{}/temp", zone); } - // check if file_path_ can be used to retrive the temperature + // check if file_path_ can be used to retrieve the temperature std::ifstream temp(file_path_); if (!temp.is_open()) { throw std::runtime_error("Can't open " + file_path_); @@ -148,4 +148,4 @@ bool waybar::modules::Temperature::isWarning(uint16_t temperature_c) { bool waybar::modules::Temperature::isCritical(uint16_t temperature_c) { return config_["critical-threshold"].isInt() && temperature_c >= config_["critical-threshold"].asInt(); -} \ No newline at end of file +} From ba12ca71c080704f631a960e321d5fe89b9da407 Mon Sep 17 00:00:00 2001 From: Duckulus Date: Wed, 22 Jan 2025 18:39:50 +0100 Subject: [PATCH 127/780] enable tooltip for hyprland window module --- src/modules/hyprland/window.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index decb2565..ba0a5321 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -59,18 +59,35 @@ auto Window::update() -> void { windowData_.title = windowName; + std::string label_text; if (!format_.empty()) { label_.show(); - label_.set_markup(waybar::util::rewriteString( + label_text = waybar::util::rewriteString( fmt::format(fmt::runtime(format_), fmt::arg("title", windowName), fmt::arg("initialTitle", windowData_.initial_title), fmt::arg("class", windowData_.class_name), fmt::arg("initialClass", windowData_.initial_class_name)), - config_["rewrite"])); + config_["rewrite"]); + label_.set_markup(label_text); } else { label_.hide(); } + if (tooltipEnabled()) { + std::string tooltip_format; + if (config_["tooltip-format"].isString()) { + tooltip_format = config_["tooltip-format"].asString(); + } + if (!tooltip_format.empty()) { + label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format), fmt::arg("title", windowName), + fmt::arg("initialTitle", windowData_.initial_title), + fmt::arg("class", windowData_.class_name), + fmt::arg("initialClass", windowData_.initial_class_name))); + } else if (!label_text.empty()){ + label_.set_tooltip_text(label_text); + } + } + if (focused_) { image_.show(); } else { @@ -153,7 +170,6 @@ auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData { } void Window::queryActiveWorkspace() { - std::shared_lock windowIpcShareLock(windowIpcSmtx); if (separateOutputs_) { From bb2c67ebadb0c7c48a4e12a8bf16c1a225e57ed4 Mon Sep 17 00:00:00 2001 From: Alexis Rouillard Date: Sat, 25 Jan 2025 09:24:48 +0100 Subject: [PATCH 128/780] Revert "Make battery module update on plugging/unplugging again (refs #2519)" --- include/modules/battery.hpp | 8 ++----- include/util/udev_deleter.hpp | 21 ------------------ src/modules/battery.cpp | 40 +++++++++++++++------------------- src/util/backlight_backend.cpp | 17 ++++++++++++++- 4 files changed, 35 insertions(+), 51 deletions(-) delete mode 100644 include/util/udev_deleter.hpp diff --git a/include/modules/battery.hpp b/include/modules/battery.hpp index fc403be0..8e1a2ad2 100644 --- a/include/modules/battery.hpp +++ b/include/modules/battery.hpp @@ -5,11 +5,8 @@ #include #if defined(__linux__) #include -#include "util/udev_deleter.hpp" #endif -#include - #include #include #include @@ -39,12 +36,11 @@ class Battery : public ALabel { const std::string formatTimeRemaining(float hoursRemaining); void setBarClass(std::string&); + int global_watch; std::map batteries_; - std::unique_ptr udev_; - std::array poll_fds_; - std::unique_ptr mon_; fs::path adapter_; int battery_watch_fd_; + int global_watch_fd_; std::mutex battery_list_mutex_; std::string old_status_; bool warnFirstTime_{true}; diff --git a/include/util/udev_deleter.hpp b/include/util/udev_deleter.hpp deleted file mode 100644 index b2f1e538..00000000 --- a/include/util/udev_deleter.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include - -namespace waybar::util { -struct UdevDeleter { - void operator()(udev *ptr) const { udev_unref(ptr); } -}; - -struct UdevDeviceDeleter { - void operator()(udev_device *ptr) const { udev_device_unref(ptr); } -}; - -struct UdevEnumerateDeleter { - void operator()(udev_enumerate *ptr) const { udev_enumerate_unref(ptr); } -}; - -struct UdevMonitorDeleter { - void operator()(udev_monitor *ptr) const { udev_monitor_unref(ptr); } -}; -} // namespace waybar::util \ No newline at end of file diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index bad72e6b..d87cc612 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -5,9 +5,6 @@ #include #endif #include -#include -#include -#include #include waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const Json::Value& config) @@ -17,18 +14,17 @@ waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const J if (battery_watch_fd_ == -1) { throw std::runtime_error("Unable to listen batteries."); } - udev_ = std::unique_ptr(udev_new()); - if (udev_ == nullptr) { - throw std::runtime_error("udev_new failed"); + + global_watch_fd_ = inotify_init1(IN_CLOEXEC); + if (global_watch_fd_ == -1) { + throw std::runtime_error("Unable to listen batteries."); } - mon_ = std::unique_ptr(udev_monitor_new_from_netlink(udev_.get(), "kernel")); - if (mon_ == nullptr) { - throw std::runtime_error("udev monitor new failed"); + + // Watch the directory for any added or removed batteries + global_watch = inotify_add_watch(global_watch_fd_, data_dir_.c_str(), IN_CREATE | IN_DELETE); + if (global_watch < 0) { + throw std::runtime_error("Could not watch for battery plug/unplug"); } - if (udev_monitor_filter_add_match_subsystem_devtype(mon_.get(), "power_supply", nullptr) < 0) { - throw std::runtime_error("udev failed to add monitor filter"); - } - udev_monitor_enable_receiving(mon_.get()); #endif worker(); } @@ -37,6 +33,11 @@ waybar::modules::Battery::~Battery() { #if defined(__linux__) std::lock_guard guard(battery_list_mutex_); + if (global_watch >= 0) { + inotify_rm_watch(global_watch_fd_, global_watch); + } + close(global_watch_fd_); + for (auto it = batteries_.cbegin(), next_it = it; it != batteries_.cend(); it = next_it) { ++next_it; auto watch_id = (*it).second; @@ -73,18 +74,12 @@ void waybar::modules::Battery::worker() { dp.emit(); }; thread_battery_update_ = [this] { - poll_fds_[0].revents = 0; - poll_fds_[0].events = POLLIN; - poll_fds_[0].fd = udev_monitor_get_fd(mon_.get()); - int ret = poll(poll_fds_.data(), poll_fds_.size(), -1); - if (ret < 0) { + struct inotify_event event = {0}; + int nbytes = read(global_watch_fd_, &event, sizeof(event)); + if (nbytes != sizeof(event) || event.mask & IN_IGNORED) { thread_.stop(); return; } - if ((poll_fds_[0].revents & POLLIN) != 0) { - signalfd_siginfo signal_info; - read(poll_fds_[0].fd, &signal_info, sizeof(signal_info)); - } refreshBatteries(); dp.emit(); }; @@ -673,7 +668,6 @@ auto waybar::modules::Battery::update() -> void { status = getAdapterStatus(capacity); } auto status_pretty = status; - puts(status.c_str()); // Transform to lowercase and replace space with dash std::transform(status.begin(), status.end(), status.begin(), [](char ch) { return ch == ' ' ? '-' : std::tolower(ch); }); diff --git a/src/util/backlight_backend.cpp b/src/util/backlight_backend.cpp index 86b0f894..41236462 100644 --- a/src/util/backlight_backend.cpp +++ b/src/util/backlight_backend.cpp @@ -1,5 +1,4 @@ #include "util/backlight_backend.hpp" -#include "util/udev_deleter.hpp" #include #include @@ -30,6 +29,22 @@ class FileDescriptor { int fd_; }; +struct UdevDeleter { + void operator()(udev *ptr) { udev_unref(ptr); } +}; + +struct UdevDeviceDeleter { + void operator()(udev_device *ptr) { udev_device_unref(ptr); } +}; + +struct UdevEnumerateDeleter { + void operator()(udev_enumerate *ptr) { udev_enumerate_unref(ptr); } +}; + +struct UdevMonitorDeleter { + void operator()(udev_monitor *ptr) { udev_monitor_unref(ptr); } +}; + void check_eq(int rc, int expected, const char *message = "eq, rc was: ") { if (rc != expected) { throw std::runtime_error(fmt::format(fmt::runtime(message), rc)); From 481b01d9af1e34b7501665bbfc0b19fce5082eb5 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 25 Jan 2025 09:31:32 +0100 Subject: [PATCH 129/780] fix: lint --- include/modules/cava.hpp | 12 ++++++------ include/modules/clock.hpp | 6 +++--- include/modules/hyprland/workspaces.hpp | 4 ++-- include/util/clara.hpp | 12 ++++++------ src/modules/bluetooth.cpp | 17 +++++++++-------- src/modules/clock.cpp | 4 ++-- src/modules/dwl/window.cpp | 8 ++------ src/modules/hyprland/window.cpp | 8 ++------ src/modules/hyprland/workspaces.cpp | 4 ++-- src/modules/wlr/workspace_manager.cpp | 4 ++-- src/util/pipewire/pipewire_backend.cpp | 2 +- 11 files changed, 37 insertions(+), 44 deletions(-) diff --git a/include/modules/cava.hpp b/include/modules/cava.hpp index 219d9302..b4d2325b 100644 --- a/include/modules/cava.hpp +++ b/include/modules/cava.hpp @@ -23,11 +23,11 @@ class Cava final : public ALabel { 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{}}; + 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 @@ -44,7 +44,7 @@ class Cava final : public ALabel { // Cava method void pause_resume(); // ModuleActionMap - static inline std::map actionMap_{ + static inline std::map actionMap_{ {"mode", &waybar::modules::Cava::pause_resume}}; }; } // namespace waybar::modules diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index 0c62b676..40b4f80e 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -51,8 +51,8 @@ class Clock final : public ALabel { day cldBaseDay_{0}; // calendar Cached day. Is used when today is changing(midnight) std::string cldText_{""}; // calendar text to print CldMode cldMode_{CldMode::MONTH}; - auto get_calendar(const year_month_day& today, const year_month_day& ymd, - const time_zone* tz) -> const std::string; + auto get_calendar(const year_month_day& today, const year_month_day& ymd, const time_zone* tz) + -> const std::string; // get local time zone auto local_zone() -> const time_zone*; @@ -79,7 +79,7 @@ class Clock final : public ALabel { void tz_up(); void tz_down(); // Module Action Map - static inline std::map actionMap_{ + static inline std::map actionMap_{ {"mode", &waybar::modules::Clock::cldModeSwitch}, {"shift_up", &waybar::modules::Clock::cldShift_up}, {"shift_down", &waybar::modules::Clock::cldShift_down}, diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index a9d56b79..f5c20f69 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -61,8 +61,8 @@ class Workspaces : public AModule, public EventHandler { // Config void parseConfig(const Json::Value& config); auto populateIconsMap(const Json::Value& formatIcons) -> void; - static auto populateBoolConfig(const Json::Value& config, const std::string& key, - bool& member) -> void; + static auto populateBoolConfig(const Json::Value& config, const std::string& key, bool& member) + -> void; auto populateSortByConfig(const Json::Value& config) -> void; auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void; auto populateFormatWindowSeparatorConfig(const Json::Value& config) -> void; diff --git a/include/util/clara.hpp b/include/util/clara.hpp index da7151fe..73fa5415 100644 --- a/include/util/clara.hpp +++ b/include/util/clara.hpp @@ -622,8 +622,8 @@ inline auto convertInto(std::string const &source, bool &target) -> ParserResult } #ifdef CLARA_CONFIG_OPTIONAL_TYPE template -inline auto convertInto(std::string const &source, - CLARA_CONFIG_OPTIONAL_TYPE &target) -> ParserResult { +inline auto convertInto(std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE &target) + -> ParserResult { T temp; auto result = convertInto(source, temp); if (result) target = std::move(temp); @@ -751,8 +751,8 @@ class ParserBase { public: virtual ~ParserBase() = default; virtual auto validate() const -> Result { return Result::ok(); } - virtual auto parse(std::string const &exeName, - TokenStream const &tokens) const -> InternalParseResult = 0; + virtual auto parse(std::string const &exeName, TokenStream const &tokens) const + -> InternalParseResult = 0; virtual auto cardinality() const -> size_t { return 1; } auto parse(Args const &args) const -> InternalParseResult { @@ -1098,8 +1098,8 @@ struct Parser : ParserBase { using ParserBase::parse; - auto parse(std::string const &exeName, - TokenStream const &tokens) const -> InternalParseResult override { + auto parse(std::string const &exeName, TokenStream const &tokens) const + -> InternalParseResult override { struct ParserInfo { ParserBase const *parser = nullptr; size_t count = 0; diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp index c8f1f996..06475a2e 100644 --- a/src/modules/bluetooth.cpp +++ b/src/modules/bluetooth.cpp @@ -49,8 +49,8 @@ auto getBoolProperty(GDBusProxy* proxy, const char* property_name) -> bool { return false; } -auto getOptionalStringProperty(GDBusProxy* proxy, - const char* property_name) -> std::optional { +auto getOptionalStringProperty(GDBusProxy* proxy, const char* property_name) + -> std::optional { auto gvar = g_dbus_proxy_get_cached_property(proxy, property_name); if (gvar) { std::string property_value = g_variant_get_string(gvar, NULL); @@ -345,8 +345,8 @@ auto waybar::modules::Bluetooth::onInterfaceAddedOrRemoved(GDBusObjectManager* m auto waybar::modules::Bluetooth::onInterfaceProxyPropertiesChanged( GDBusObjectManagerClient* manager, GDBusObjectProxy* object_proxy, GDBusProxy* interface_proxy, - GVariant* changed_properties, const gchar* const* invalidated_properties, - gpointer user_data) -> void { + GVariant* changed_properties, const gchar* const* invalidated_properties, gpointer user_data) + -> void { std::string interface_name = g_dbus_proxy_get_interface_name(interface_proxy); std::string object_path = g_dbus_object_get_object_path(G_DBUS_OBJECT(object_proxy)); @@ -395,8 +395,8 @@ auto waybar::modules::Bluetooth::getDeviceBatteryPercentage(GDBusObject* object) return std::nullopt; } -auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, - DeviceInfo& device_info) -> bool { +auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, DeviceInfo& device_info) + -> bool { GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Device1")); if (proxy_device != NULL) { @@ -462,8 +462,9 @@ auto waybar::modules::Bluetooth::findCurController() -> std::optional& connected_devices) -> void { +auto waybar::modules::Bluetooth::findConnectedDevices(const std::string& cur_controller_path, + std::vector& connected_devices) + -> void { GList* objects = g_dbus_object_manager_get_objects(manager_.get()); for (GList* l = objects; l != NULL; l = l->next) { GDBusObject* object = G_DBUS_OBJECT(l->data); diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 7f5a4d55..269fa765 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -199,8 +199,8 @@ const unsigned cldRowsInMonth(const year_month& ym, const weekday& firstdow) { return 2u + ceil((weekday{ym / 1} - firstdow) + ((ym / last).day() - day{0})).count(); } -auto cldGetWeekForLine(const year_month& ym, const weekday& firstdow, - const unsigned line) -> const year_month_weekday { +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]; diff --git a/src/modules/dwl/window.cpp b/src/modules/dwl/window.cpp index 8fa826b1..a960a1f0 100644 --- a/src/modules/dwl/window.cpp +++ b/src/modules/dwl/window.cpp @@ -98,13 +98,9 @@ Window::~Window() { } } -void Window::handle_title(const char *title) { - title_ = Glib::Markup::escape_text(title); -} +void Window::handle_title(const char *title) { title_ = Glib::Markup::escape_text(title); } -void Window::handle_appid(const char *appid) { - appid_ = Glib::Markup::escape_text(appid); -} +void Window::handle_appid(const char *appid) { appid_ = Glib::Markup::escape_text(appid); } void Window::handle_layout_symbol(const char *layout_symbol) { layout_symbol_ = Glib::Markup::escape_text(layout_symbol); diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index decb2565..3a879205 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -6,22 +6,20 @@ #include #include +#include +#include #include #include "modules/hyprland/backend.hpp" #include "util/rewrite_string.hpp" #include "util/sanitize_str.hpp" -#include -#include - namespace waybar::modules::hyprland { std::shared_mutex windowIpcSmtx; Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) { - std::unique_lock windowIpcUniqueLock(windowIpcSmtx); modulesReady = true; @@ -51,7 +49,6 @@ Window::~Window() { } auto Window::update() -> void { - std::shared_lock windowIpcShareLock(windowIpcSmtx); std::string windowName = waybar::util::sanitize_string(workspace_.last_window_title); @@ -153,7 +150,6 @@ auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData { } void Window::queryActiveWorkspace() { - std::shared_lock windowIpcShareLock(windowIpcSmtx); if (separateOutputs_) { diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 13364f3f..047703cc 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -590,8 +590,8 @@ auto Workspaces::populateIconsMap(const Json::Value &formatIcons) -> void { m_iconsMap.emplace("", ""); } -auto Workspaces::populateBoolConfig(const Json::Value &config, const std::string &key, - bool &member) -> void { +auto Workspaces::populateBoolConfig(const Json::Value &config, const std::string &key, bool &member) + -> void { const auto &configValue = config[key]; if (configValue.isBool()) { member = configValue.asBool(); diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index 3c630d81..f556a161 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -118,8 +118,8 @@ auto WorkspaceManager::sort_workspaces() -> void { } } -auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, - uint32_t version) -> void { +auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version) + -> void { if (workspace_manager_) { spdlog::warn("Register workspace manager again although already registered!"); return; diff --git a/src/util/pipewire/pipewire_backend.cpp b/src/util/pipewire/pipewire_backend.cpp index 3b897178..6d859a91 100644 --- a/src/util/pipewire/pipewire_backend.cpp +++ b/src/util/pipewire/pipewire_backend.cpp @@ -126,7 +126,7 @@ void PipewireBackend::handleRegistryEventGlobal(uint32_t id, uint32_t permission if (proxy == nullptr) return; auto *pNodeInfo = (PrivacyNodeInfo *)pw_proxy_get_user_data(proxy); - new(pNodeInfo) PrivacyNodeInfo{}; + new (pNodeInfo) PrivacyNodeInfo{}; pNodeInfo->id = id; pNodeInfo->data = this; pNodeInfo->type = mediaType; From bc2e143ac50ec186d966249f191f85e3c17b1c3f Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Tue, 31 Dec 2024 17:56:41 +0100 Subject: [PATCH 130/780] Extract icon loading logic to separate class --- include/modules/wlr/taskbar.hpp | 8 +- include/util/icon_loader.hpp | 34 +++++ include/util/string.hpp | 16 +++ meson.build | 1 + src/modules/wlr/taskbar.cpp | 235 ++------------------------------ src/util/icon_loader.cpp | 207 ++++++++++++++++++++++++++++ 6 files changed, 270 insertions(+), 231 deletions(-) create mode 100644 include/util/icon_loader.hpp create mode 100644 src/util/icon_loader.cpp diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp index 07110dde..8dc4dadd 100644 --- a/include/modules/wlr/taskbar.hpp +++ b/include/modules/wlr/taskbar.hpp @@ -19,6 +19,7 @@ #include "bar.hpp" #include "client.hpp" #include "giomm/desktopappinfo.h" +#include "util/icon_loader.hpp" #include "util/json.hpp" #include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" @@ -89,9 +90,6 @@ class Task { std::string state_string(bool = false) const; void set_minimize_hint(); void on_button_size_allocated(Gtk::Allocation &alloc); - void set_app_info_from_app_id_list(const std::string &app_id_list); - bool image_load_icon(Gtk::Image &image, const Glib::RefPtr &icon_theme, - Glib::RefPtr app_info, int size); void hide_if_ignored(); public: @@ -153,7 +151,7 @@ class Taskbar : public waybar::AModule { Gtk::Box box_; std::vector tasks_; - std::vector> icon_themes_; + IconLoader icon_loader_; std::unordered_set ignore_list_; std::map app_ids_replace_map_; @@ -178,7 +176,7 @@ class Taskbar : public waybar::AModule { bool show_output(struct wl_output *) const; bool all_outputs() const; - const std::vector> &icon_themes() const; + const IconLoader &icon_loader() const; const std::unordered_set &ignore_list() const; const std::map &app_ids_replace_map() const; }; diff --git a/include/util/icon_loader.hpp b/include/util/icon_loader.hpp new file mode 100644 index 00000000..664e510c --- /dev/null +++ b/include/util/icon_loader.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "util/gtk_icon.hpp" + +class IconLoader { + private: + std::vector> custom_icon_themes_; + Glib::RefPtr default_icon_theme_ = Gtk::IconTheme::get_default(); + static std::vector search_prefix(); + static Glib::RefPtr get_app_info_by_name(const std::string &app_id); + static Glib::RefPtr get_desktop_app_info(const std::string &app_id); + static Glib::RefPtr load_icon_from_file(std::string const &icon_path, int size); + static std::string get_icon_name_from_icon_theme(const Glib::RefPtr &icon_theme, + const std::string &app_id); + static bool image_load_icon(Gtk::Image &image, const Glib::RefPtr &icon_theme, + Glib::RefPtr app_info, int size); + + public: + void add_custom_icon_theme(const std::string &theme_name); + bool image_load_icon(Gtk::Image &image, Glib::RefPtr app_info, + int size) const; + static Glib::RefPtr get_app_info_from_app_id_list( + const std::string &app_id_list); +}; diff --git a/include/util/string.hpp b/include/util/string.hpp index d06557c1..2da2bcc5 100644 --- a/include/util/string.hpp +++ b/include/util/string.hpp @@ -23,3 +23,19 @@ inline std::string capitalize(const std::string& str) { [](unsigned char c) { return std::toupper(c); }); return result; } + +inline std::vector split(std::string_view s, std::string_view delimiter, + int max_splits = -1) { + std::vector result; + size_t pos = 0; + size_t next_pos = 0; + while ((next_pos = s.find(delimiter, pos)) != std::string::npos) { + result.push_back(std::string(s.substr(pos, next_pos - pos))); + pos = next_pos + delimiter.size(); + if (max_splits > 0 && result.size() == static_cast(max_splits)) { + break; + } + } + result.push_back(std::string(s.substr(pos))); + return result; +} diff --git a/meson.build b/meson.build index 726d492b..8bc355a6 100644 --- a/meson.build +++ b/meson.build @@ -182,6 +182,7 @@ src_files = files( 'src/util/sanitize_str.cpp', 'src/util/rewrite_string.cpp', 'src/util/gtk_icon.cpp', + 'src/util/icon_loader.cpp', 'src/util/regex_collection.cpp', 'src/util/css_reload_helper.cpp' ) diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 30e4ee48..a2ddafdb 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -26,194 +26,6 @@ namespace waybar::modules::wlr { -/* Icon loading functions */ -static std::vector search_prefix() { - std::vector prefixes = {""}; - - std::string home_dir = std::getenv("HOME"); - prefixes.push_back(home_dir + "/.local/share/"); - - auto xdg_data_dirs = std::getenv("XDG_DATA_DIRS"); - if (!xdg_data_dirs) { - prefixes.emplace_back("/usr/share/"); - prefixes.emplace_back("/usr/local/share/"); - } else { - std::string xdg_data_dirs_str(xdg_data_dirs); - size_t start = 0, end = 0; - - do { - end = xdg_data_dirs_str.find(':', start); - auto p = xdg_data_dirs_str.substr(start, end - start); - prefixes.push_back(trim(p) + "/"); - - start = end == std::string::npos ? end : end + 1; - } while (end != std::string::npos); - } - - for (auto &p : prefixes) spdlog::debug("Using 'desktop' search path prefix: {}", p); - - return prefixes; -} - -static Glib::RefPtr load_icon_from_file(std::string icon_path, int size) { - try { - auto pb = Gdk::Pixbuf::create_from_file(icon_path, size, size); - return pb; - } catch (...) { - return {}; - } -} - -static Glib::RefPtr get_app_info_by_name(const std::string &app_id) { - static std::vector prefixes = search_prefix(); - - std::vector app_folders = {"", "applications/", "applications/kde/", - "applications/org.kde."}; - - std::vector suffixes = {"", ".desktop"}; - - for (auto &prefix : prefixes) { - for (auto &folder : app_folders) { - for (auto &suffix : suffixes) { - auto app_info_ = - Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix); - if (!app_info_) { - continue; - } - - return app_info_; - } - } - } - - return {}; -} - -Glib::RefPtr get_desktop_app_info(const std::string &app_id) { - auto app_info = get_app_info_by_name(app_id); - if (app_info) { - return app_info; - } - - std::string desktop_file = ""; - - gchar ***desktop_list = g_desktop_app_info_search(app_id.c_str()); - if (desktop_list != nullptr && desktop_list[0] != nullptr) { - for (size_t i = 0; desktop_list[0][i]; i++) { - if (desktop_file == "") { - desktop_file = desktop_list[0][i]; - } else { - auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]); - if (!tmp_info) - // see https://github.com/Alexays/Waybar/issues/1446 - continue; - - auto startup_class = tmp_info->get_startup_wm_class(); - if (startup_class == app_id) { - desktop_file = desktop_list[0][i]; - break; - } - } - } - g_strfreev(desktop_list[0]); - } - g_free(desktop_list); - - return get_app_info_by_name(desktop_file); -} - -void Task::set_app_info_from_app_id_list(const std::string &app_id_list) { - std::string app_id; - std::istringstream stream(app_id_list); - - /* Wayfire sends a list of app-id's in space separated format, other compositors - * send a single app-id, but in any case this works fine */ - while (stream >> app_id) { - app_info_ = get_desktop_app_info(app_id); - if (app_info_) { - return; - } - - auto lower_app_id = app_id; - std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(), - [](char c) { return std::tolower(c); }); - app_info_ = get_desktop_app_info(lower_app_id); - if (app_info_) { - return; - } - - size_t start = 0, end = app_id.size(); - start = app_id.rfind(".", end); - std::string app_name = app_id.substr(start + 1, app_id.size()); - app_info_ = get_desktop_app_info(app_name); - if (app_info_) { - return; - } - - start = app_id.find("-"); - app_name = app_id.substr(0, start); - app_info_ = get_desktop_app_info(app_name); - } -} - -static std::string get_icon_name_from_icon_theme(const Glib::RefPtr &icon_theme, - const std::string &app_id) { - if (icon_theme->lookup_icon(app_id, 24)) return app_id; - - return ""; -} - -bool Task::image_load_icon(Gtk::Image &image, const Glib::RefPtr &icon_theme, - Glib::RefPtr app_info, int size) { - std::string ret_icon_name = "unknown"; - if (app_info) { - std::string icon_name = - get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class()); - if (!icon_name.empty()) { - ret_icon_name = icon_name; - } else { - if (app_info->get_icon()) { - ret_icon_name = app_info->get_icon()->to_string(); - } - } - } - - Glib::RefPtr pixbuf; - auto scaled_icon_size = size * image.get_scale_factor(); - - try { - pixbuf = icon_theme->load_icon(ret_icon_name, scaled_icon_size, Gtk::ICON_LOOKUP_FORCE_SIZE); - spdlog::debug("{} Loaded icon '{}'", repr(), ret_icon_name); - } catch (...) { - if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS)) { - pixbuf = load_icon_from_file(ret_icon_name, scaled_icon_size); - spdlog::debug("{} Loaded icon from file '{}'", repr(), ret_icon_name); - } else { - try { - pixbuf = DefaultGtkIconThemeWrapper::load_icon( - "image-missing", scaled_icon_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE); - spdlog::debug("{} Loaded icon from resource", repr()); - } catch (...) { - pixbuf = {}; - spdlog::debug("{} Unable to load icon.", repr()); - } - } - } - - if (pixbuf) { - if (pixbuf->get_width() != scaled_icon_size) { - int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height(); - pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR); - } - auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image.get_scale_factor(), - image.get_window()); - image.set(surface); - return true; - } - - return false; -} - /* Task class implementation */ uint32_t Task::global_id = 0; @@ -299,16 +111,11 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, with_name_ = true; } - auto icon_pos = format.find("{icon}"); - if (icon_pos == 0) { + auto parts = split(format, "{icon}", 1); + format_before_ = parts[0]; + if (parts.size() > 1) { with_icon_ = true; - format_after_ = format.substr(6); - } else if (icon_pos == std::string::npos) { - format_before_ = format; - } else { - with_icon_ = true; - format_before_ = format.substr(0, icon_pos); - format_after_ = format.substr(icon_pos + 6); + format_after_ = parts[1]; } } else { /* The default is to only show the icon */ @@ -430,7 +237,7 @@ void Task::handle_app_id(const char *app_id) { return; } - set_app_info_from_app_id_list(app_id_); + app_info_ = IconLoader::get_app_info_from_app_id_list(app_id_); name_ = app_info_ ? app_info_->get_display_name() : app_id; if (!with_icon_) { @@ -438,15 +245,7 @@ void Task::handle_app_id(const char *app_id) { } int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16; - bool found = false; - for (auto &icon_theme : tbar_->icon_themes()) { - if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) { - found = true; - break; - } - } - - if (found) + if (tbar_->icon_loader().image_load_icon(icon_, app_info_, icon_size)) icon_.show(); else spdlog::debug("Couldn't find icon for {}", app_id_); @@ -769,22 +568,10 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu /* Get the configured icon theme if specified */ if (config_["icon-theme"].isArray()) { for (auto &c : config_["icon-theme"]) { - auto it_name = c.asString(); - - auto it = Gtk::IconTheme::create(); - it->set_custom_theme(it_name); - spdlog::debug("Use custom icon theme: {}", it_name); - - icon_themes_.push_back(it); + icon_loader_.add_custom_icon_theme(c.asString()); } } else if (config_["icon-theme"].isString()) { - auto it_name = config_["icon-theme"].asString(); - - auto it = Gtk::IconTheme::create(); - it->set_custom_theme(it_name); - spdlog::debug("Use custom icon theme: {}", it_name); - - icon_themes_.push_back(it); + icon_loader_.add_custom_icon_theme(config_["icon-theme"].asString()); } // Load ignore-list @@ -803,8 +590,6 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu } } - icon_themes_.push_back(Gtk::IconTheme::get_default()); - for (auto &t : tasks_) { t->handle_app_id(t->app_id().c_str()); } @@ -939,9 +724,7 @@ bool Taskbar::all_outputs() const { return config_["all-outputs"].isBool() && config_["all-outputs"].asBool(); } -const std::vector> &Taskbar::icon_themes() const { - return icon_themes_; -} +const IconLoader &Taskbar::icon_loader() const { return icon_loader_; } const std::unordered_set &Taskbar::ignore_list() const { return ignore_list_; } diff --git a/src/util/icon_loader.cpp b/src/util/icon_loader.cpp new file mode 100644 index 00000000..69e25dbc --- /dev/null +++ b/src/util/icon_loader.cpp @@ -0,0 +1,207 @@ +#include "util/icon_loader.hpp" + +#include "util/string.hpp" + +std::vector IconLoader::search_prefix() { + std::vector prefixes = {""}; + + std::string home_dir = std::getenv("HOME"); + prefixes.push_back(home_dir + "/.local/share/"); + + auto xdg_data_dirs = std::getenv("XDG_DATA_DIRS"); + if (!xdg_data_dirs) { + prefixes.emplace_back("/usr/share/"); + prefixes.emplace_back("/usr/local/share/"); + } else { + std::string xdg_data_dirs_str(xdg_data_dirs); + size_t start = 0; + size_t end = 0; + + do { + end = xdg_data_dirs_str.find(':', start); + auto p = xdg_data_dirs_str.substr(start, end - start); + prefixes.push_back(trim(p) + "/"); + + start = end == std::string::npos ? end : end + 1; + } while (end != std::string::npos); + } + + for (auto &p : prefixes) spdlog::debug("Using 'desktop' search path prefix: {}", p); + + return prefixes; +} + +Glib::RefPtr IconLoader::get_app_info_by_name(const std::string &app_id) { + static std::vector prefixes = search_prefix(); + + std::vector app_folders = {"", "applications/", "applications/kde/", + "applications/org.kde."}; + + std::vector suffixes = {"", ".desktop"}; + + for (auto const &prefix : prefixes) { + for (auto const &folder : app_folders) { + for (auto const &suffix : suffixes) { + auto app_info_ = + Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix); + if (!app_info_) { + continue; + } + + return app_info_; + } + } + } + + return {}; +} + +Glib::RefPtr IconLoader::get_desktop_app_info(const std::string &app_id) { + auto app_info = get_app_info_by_name(app_id); + if (app_info) { + return app_info; + } + + std::string desktop_file = ""; + + gchar ***desktop_list = g_desktop_app_info_search(app_id.c_str()); + if (desktop_list != nullptr && desktop_list[0] != nullptr) { + for (size_t i = 0; desktop_list[0][i]; i++) { + if (desktop_file == "") { + desktop_file = desktop_list[0][i]; + } else { + auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]); + if (!tmp_info) + // see https://github.com/Alexays/Waybar/issues/1446 + continue; + + auto startup_class = tmp_info->get_startup_wm_class(); + if (startup_class == app_id) { + desktop_file = desktop_list[0][i]; + break; + } + } + } + g_strfreev(desktop_list[0]); + } + g_free(desktop_list); + + return get_app_info_by_name(desktop_file); +} + +Glib::RefPtr IconLoader::load_icon_from_file(std::string const &icon_path, int size) { + try { + auto pb = Gdk::Pixbuf::create_from_file(icon_path, size, size); + return pb; + } catch (...) { + return {}; + } +} + +std::string IconLoader::get_icon_name_from_icon_theme( + const Glib::RefPtr &icon_theme, const std::string &app_id) { + if (icon_theme->lookup_icon(app_id, 24)) return app_id; + + return ""; +} + +bool IconLoader::image_load_icon(Gtk::Image &image, const Glib::RefPtr &icon_theme, + Glib::RefPtr app_info, int size) { + std::string ret_icon_name = "unknown"; + if (app_info) { + std::string icon_name = + get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class()); + if (!icon_name.empty()) { + ret_icon_name = icon_name; + } else { + if (app_info->get_icon()) { + ret_icon_name = app_info->get_icon()->to_string(); + } + } + } + + Glib::RefPtr pixbuf; + auto scaled_icon_size = size * image.get_scale_factor(); + + try { + pixbuf = icon_theme->load_icon(ret_icon_name, scaled_icon_size, Gtk::ICON_LOOKUP_FORCE_SIZE); + } catch (...) { + if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS)) { + pixbuf = load_icon_from_file(ret_icon_name, scaled_icon_size); + } else { + try { + pixbuf = DefaultGtkIconThemeWrapper::load_icon( + "image-missing", scaled_icon_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE); + } catch (...) { + pixbuf = {}; + } + } + } + + if (pixbuf) { + if (pixbuf->get_width() != scaled_icon_size) { + int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height(); + pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR); + } + auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image.get_scale_factor(), + image.get_window()); + image.set(surface); + return true; + } + + return false; +} + +void IconLoader::add_custom_icon_theme(const std::string &theme_name) { + auto icon_theme = Gtk::IconTheme::create(); + icon_theme->set_custom_theme(theme_name); + custom_icon_themes_.push_back(icon_theme); + spdlog::debug("Use custom icon theme: {}", theme_name); +} + +bool IconLoader::image_load_icon(Gtk::Image &image, Glib::RefPtr app_info, + int size) const { + for (auto &icon_theme : custom_icon_themes_) { + if (image_load_icon(image, icon_theme, app_info, size)) { + return true; + } + } + return image_load_icon(image, default_icon_theme_, app_info, size); +} + +Glib::RefPtr IconLoader::get_app_info_from_app_id_list( + const std::string &app_id_list) { + std::string app_id; + std::istringstream stream(app_id_list); + Glib::RefPtr app_info_; + + /* Wayfire sends a list of app-id's in space separated format, other compositors + * send a single app-id, but in any case this works fine */ + while (stream >> app_id) { + app_info_ = get_desktop_app_info(app_id); + if (app_info_) { + return app_info_; + } + + auto lower_app_id = app_id; + std::ranges::transform(lower_app_id, lower_app_id.begin(), + [](char c) { return std::tolower(c); }); + app_info_ = get_desktop_app_info(lower_app_id); + if (app_info_) { + return app_info_; + } + + size_t start = 0, end = app_id.size(); + start = app_id.rfind(".", end); + std::string app_name = app_id.substr(start + 1, app_id.size()); + app_info_ = get_desktop_app_info(app_name); + if (app_info_) { + return app_info_; + } + + start = app_id.find("-"); + app_name = app_id.substr(0, start); + app_info_ = get_desktop_app_info(app_name); + } + return app_info_; +} From 69e2e249a61f4671edd3326c6b682a0e43a1983b Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Tue, 31 Dec 2024 18:33:08 +0100 Subject: [PATCH 131/780] Initial implementation of workspace taskbars Add a list of window titles and icons to each workspace (like wlr/taskbar but grouped by workspace). Only implemented on hyprland for now. --- .../hyprland/windowcreationpayload.hpp | 15 ++++- include/modules/hyprland/workspace.hpp | 6 +- include/modules/hyprland/workspaces.hpp | 5 +- .../hyprland/windowcreationpayload.cpp | 6 +- src/modules/hyprland/workspace.cpp | 61 +++++++++++++++---- src/modules/hyprland/workspaces.cpp | 2 +- 6 files changed, 73 insertions(+), 22 deletions(-) diff --git a/include/modules/hyprland/windowcreationpayload.hpp b/include/modules/hyprland/windowcreationpayload.hpp index 45229ed4..06af3074 100644 --- a/include/modules/hyprland/windowcreationpayload.hpp +++ b/include/modules/hyprland/windowcreationpayload.hpp @@ -26,10 +26,19 @@ namespace waybar::modules::hyprland { class Workspaces; +struct WindowRepr { + std::string window_class; + std::string window_title; + std::string repr_rewrite; + + public: + bool empty() const { return repr_rewrite.empty(); } +}; + class WindowCreationPayload { public: WindowCreationPayload(std::string workspace_name, WindowAddress window_address, - std::string window_repr); + WindowRepr window_repr); WindowCreationPayload(std::string workspace_name, WindowAddress window_address, std::string window_class, std::string window_title); WindowCreationPayload(Json::Value const& client_data); @@ -37,7 +46,7 @@ class WindowCreationPayload { int incrementTimeSpentUncreated(); bool isEmpty(Workspaces& workspace_manager); bool reprIsReady() const { return std::holds_alternative(m_window); } - std::string repr(Workspaces& workspace_manager); + WindowRepr repr(Workspaces& workspace_manager); std::string getWorkspaceName() const { return m_workspaceName; } WindowAddress getAddress() const { return m_windowAddress; } @@ -48,7 +57,7 @@ class WindowCreationPayload { void clearAddr(); void clearWorkspaceName(); - using Repr = std::string; + using Repr = WindowRepr; using ClassAndTitle = std::pair; std::variant m_window; diff --git a/include/modules/hyprland/workspace.hpp b/include/modules/hyprland/workspace.hpp index f1fea4e8..7a6531f9 100644 --- a/include/modules/hyprland/workspace.hpp +++ b/include/modules/hyprland/workspace.hpp @@ -56,11 +56,11 @@ class Workspace { void setOutput(std::string const& value) { m_output = value; }; bool containsWindow(WindowAddress const& addr) const { return m_windowMap.contains(addr); } void insertWindow(WindowCreationPayload create_window_paylod); - std::string removeWindow(WindowAddress const& addr); + WindowRepr removeWindow(WindowAddress const& addr); void initializeWindowMap(const Json::Value& clients_data); bool onWindowOpened(WindowCreationPayload const& create_window_paylod); - std::optional closeWindow(WindowAddress const& addr); + std::optional closeWindow(WindowAddress const& addr); void update(const std::string& format, const std::string& icon); @@ -78,7 +78,7 @@ class Workspace { bool m_isUrgent = false; bool m_isVisible = false; - std::map m_windowMap; + std::map> m_windowMap; Gtk::Button m_button; Gtk::Box m_content; diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index f5c20f69..fa9c0ac0 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -17,6 +17,7 @@ #include "modules/hyprland/windowcreationpayload.hpp" #include "modules/hyprland/workspace.hpp" #include "util/enum.hpp" +#include "util/icon_loader.hpp" #include "util/regex_collection.hpp" using WindowAddress = std::string; @@ -45,6 +46,7 @@ class Workspaces : public AModule, public EventHandler { bool isWorkspaceIgnored(std::string const& workspace_name); bool windowRewriteConfigUsesTitle() const { return m_anyWindowRewriteRuleUsesTitle; } + const IconLoader& iconLoader() const { return m_iconLoader; } private: void onEvent(const std::string& e) override; @@ -119,7 +121,7 @@ class Workspaces : public AModule, public EventHandler { // Map for windows stored in workspaces not present in the current bar. // This happens when the user has multiple monitors (hence, multiple bars) // and doesn't share windows accross bars (a.k.a `all-outputs` = false) - std::map m_orphanWindowMap; + std::map m_orphanWindowMap; enum class SortMethod { ID, NAME, NUMBER, DEFAULT }; util::EnumParser m_enumParser; @@ -136,6 +138,7 @@ class Workspaces : public AModule, public EventHandler { bool m_anyWindowRewriteRuleUsesTitle = false; std::string m_formatWindowSeparator; + IconLoader m_iconLoader; bool m_withIcon; uint64_t m_monitorId; std::string m_activeWorkspaceName; diff --git a/src/modules/hyprland/windowcreationpayload.cpp b/src/modules/hyprland/windowcreationpayload.cpp index df7fe784..d8e3bdd5 100644 --- a/src/modules/hyprland/windowcreationpayload.cpp +++ b/src/modules/hyprland/windowcreationpayload.cpp @@ -20,7 +20,7 @@ WindowCreationPayload::WindowCreationPayload(Json::Value const &client_data) } WindowCreationPayload::WindowCreationPayload(std::string workspace_name, - WindowAddress window_address, std::string window_repr) + WindowAddress window_address, WindowRepr window_repr) : m_window(std::move(window_repr)), m_windowAddress(std::move(window_address)), m_workspaceName(std::move(workspace_name)) { @@ -92,13 +92,13 @@ void WindowCreationPayload::moveToWorksace(std::string &new_workspace_name) { m_workspaceName = new_workspace_name; } -std::string WindowCreationPayload::repr(Workspaces &workspace_manager) { +WindowRepr WindowCreationPayload::repr(Workspaces &workspace_manager) { if (std::holds_alternative(m_window)) { return std::get(m_window); } if (std::holds_alternative(m_window)) { auto [window_class, window_title] = std::get(m_window); - return workspace_manager.getRewrite(window_class, window_title); + return {window_class, window_title, workspace_manager.getRewrite(window_class, window_title)}; } // Unreachable spdlog::error("WorkspaceWindow::repr: Unreachable"); diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index e575d1c4..229e36d2 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -6,6 +6,7 @@ #include #include "modules/hyprland/workspaces.hpp" +#include "util/icon_loader.hpp" namespace waybar::modules::hyprland { @@ -31,7 +32,13 @@ Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_ma false); m_button.set_relief(Gtk::RELIEF_NONE); - m_content.set_center_widget(m_label); + if (true) { + // TODO-WorkspaceTaskbar: Allow vertical? + m_content.set_orientation(Gtk::ORIENTATION_HORIZONTAL); + m_content.pack_start(m_label, false, false); + } else { + m_content.set_center_widget(m_label); + } m_button.add(m_content); initializeWindowMap(clients_data); @@ -46,7 +53,7 @@ void addOrRemoveClass(const Glib::RefPtr &context, bool condi } } -std::optional Workspace::closeWindow(WindowAddress const &addr) { +std::optional Workspace::closeWindow(WindowAddress const &addr) { if (m_windowMap.contains(addr)) { return removeWindow(addr); } @@ -108,8 +115,8 @@ bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_paylod return false; } -std::string Workspace::removeWindow(WindowAddress const &addr) { - std::string windowRepr = m_windowMap[addr]; +WindowRepr Workspace::removeWindow(WindowAddress const &addr) { + WindowRepr windowRepr = m_windowMap[addr]; m_windowMap.erase(addr); return windowRepr; } @@ -199,21 +206,53 @@ void Workspace::update(const std::string &format, const std::string &icon) { addOrRemoveClass(styleContext, m_workspaceManager.getBarOutput() == output(), "hosting-monitor"); std::string windows; - auto windowSeparator = m_workspaceManager.getWindowSeparator(); + // TODO-WorkspaceTaskbar + if (false) { + auto windowSeparator = m_workspaceManager.getWindowSeparator(); - bool isNotFirst = false; + bool isNotFirst = false; - for (auto &[_pid, window_repr] : m_windowMap) { - if (isNotFirst) { - windows.append(windowSeparator); + for (auto &[_pid, window_repr] : m_windowMap) { + if (isNotFirst) { + windows.append(windowSeparator); + } + isNotFirst = true; + windows.append(window_repr.repr_rewrite); } - isNotFirst = true; - windows.append(window_repr); } m_label.set_markup(fmt::format(fmt::runtime(format), fmt::arg("id", id()), fmt::arg("name", name()), fmt::arg("icon", icon), fmt::arg("windows", windows))); + + auto children = m_content.get_children(); + for (auto child : children) { + if (child != &m_label) { + m_content.remove(*child); + } + } + + for (auto &[_addr, window_repr] : m_windowMap) { + auto window_box = Gtk::make_managed(Gtk::ORIENTATION_HORIZONTAL); + auto window_icon = Gtk::make_managed(); + auto window_label = Gtk::make_managed(window_repr.window_title); + + // TODO-WorkspaceTaskbar: customizable max width and ellipsize + window_label->set_max_width_chars(20); + window_label->set_ellipsize(Pango::ELLIPSIZE_END); + + // TODO-WorkspaceTaskbar: support themes + auto app_info_ = IconLoader::get_app_info_from_app_id_list(window_repr.window_class); + // TODO-WorkspaceTaskbar: icon size + m_workspaceManager.iconLoader().image_load_icon(*window_icon, app_info_, 24); + + window_box->pack_start(*window_icon, false, false); + window_box->pack_start(*window_label, true, true); + window_box->set_tooltip_text(window_repr.window_title); + + m_content.pack_start(*window_box, true, false); + window_box->show_all(); + } } } // namespace waybar::modules::hyprland diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 047703cc..543e3e61 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -481,7 +481,7 @@ void Workspaces::onWindowMoved(std::string const &payload) { std::string workspaceName = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx); - std::string windowRepr; + WindowRepr windowRepr; // If the window was still queued to be created, just change its destination // and exit From 1c07ca0099951ae307ffc3418c15722e6e4f59ed Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Tue, 31 Dec 2024 19:51:53 +0100 Subject: [PATCH 132/780] workspace taskbars: Add config parsing Use format from config instead of hardcoding --- include/modules/hyprland/workspace.hpp | 6 +- include/modules/hyprland/workspaces.hpp | 19 +++++- src/modules/hyprland/workspace.cpp | 80 ++++++++++++++++--------- src/modules/hyprland/workspaces.cpp | 40 ++++++++++++- 4 files changed, 110 insertions(+), 35 deletions(-) diff --git a/include/modules/hyprland/workspace.hpp b/include/modules/hyprland/workspace.hpp index 7a6531f9..48a027c3 100644 --- a/include/modules/hyprland/workspace.hpp +++ b/include/modules/hyprland/workspace.hpp @@ -62,7 +62,8 @@ class Workspace { bool onWindowOpened(WindowCreationPayload const& create_window_paylod); std::optional closeWindow(WindowAddress const& addr); - void update(const std::string& format, const std::string& icon); + void update(const std::string& workspace_icon); + void updateTaskbar(const std::string& workspace_icon); private: Workspaces& m_workspaceManager; @@ -82,7 +83,8 @@ class Workspace { Gtk::Button m_button; Gtk::Box m_content; - Gtk::Label m_label; + Gtk::Label m_labelBefore; + Gtk::Label m_labelAfter; }; } // namespace waybar::modules::hyprland diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index fa9c0ac0..d2842b73 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -38,8 +38,14 @@ class Workspaces : public AModule, public EventHandler { auto activeOnly() const -> bool { return m_activeOnly; } auto specialVisibleOnly() const -> bool { return m_specialVisibleOnly; } auto moveToMonitor() const -> bool { return m_moveToMonitor; } + auto enableWorkspaceTaskbar() const -> bool { return m_enableWorkspaceTaskbar; } + auto taskbarWithIcon() const -> bool { return m_taskbarWithIcon; } auto getBarOutput() const -> std::string { return m_bar.output->name; } + auto formatBefore() const -> std::string { return m_formatBefore; } + auto formatAfter() const -> std::string { return m_formatAfter; } + auto taskbarFormatBefore() const -> std::string { return m_taskbarFormatBefore; } + auto taskbarFormatAfter() const -> std::string { return m_taskbarFormatAfter; } std::string getRewrite(std::string window_class, std::string window_title); std::string& getWindowSeparator() { return m_formatWindowSeparator; } @@ -69,6 +75,7 @@ class Workspaces : public AModule, public EventHandler { auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void; auto populateFormatWindowSeparatorConfig(const Json::Value& config) -> void; auto populateWindowRewriteConfig(const Json::Value& config) -> void; + auto populateWorkspaceTaskbarConfig(const Json::Value& config) -> void; void registerIpc(); @@ -121,7 +128,7 @@ class Workspaces : public AModule, public EventHandler { // Map for windows stored in workspaces not present in the current bar. // This happens when the user has multiple monitors (hence, multiple bars) // and doesn't share windows accross bars (a.k.a `all-outputs` = false) - std::map m_orphanWindowMap; + std::map> m_orphanWindowMap; enum class SortMethod { ID, NAME, NUMBER, DEFAULT }; util::EnumParser m_enumParser; @@ -131,14 +138,14 @@ class Workspaces : public AModule, public EventHandler { {"NUMBER", SortMethod::NUMBER}, {"DEFAULT", SortMethod::DEFAULT}}; - std::string m_format; + std::string m_formatBefore; + std::string m_formatAfter; std::map m_iconsMap; util::RegexCollection m_windowRewriteRules; bool m_anyWindowRewriteRuleUsesTitle = false; std::string m_formatWindowSeparator; - IconLoader m_iconLoader; bool m_withIcon; uint64_t m_monitorId; std::string m_activeWorkspaceName; @@ -148,6 +155,12 @@ class Workspaces : public AModule, public EventHandler { std::vector m_workspacesToRemove; std::vector m_windowsToCreate; + IconLoader m_iconLoader; + bool m_enableWorkspaceTaskbar = false; + bool m_taskbarWithIcon = false; + std::string m_taskbarFormatBefore; + std::string m_taskbarFormatAfter; + std::vector m_ignoreWorkspaces; std::mutex m_mutex; diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 229e36d2..140f4f77 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -35,9 +35,9 @@ Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_ma if (true) { // TODO-WorkspaceTaskbar: Allow vertical? m_content.set_orientation(Gtk::ORIENTATION_HORIZONTAL); - m_content.pack_start(m_label, false, false); + m_content.pack_start(m_labelBefore, false, false); } else { - m_content.set_center_widget(m_label); + m_content.set_center_widget(m_labelBefore); } m_button.add(m_content); @@ -178,7 +178,7 @@ std::string &Workspace::selectIcon(std::map &icons_map return m_name; } -void Workspace::update(const std::string &format, const std::string &icon) { +void Workspace::update(const std::string &workspace_icon) { // clang-format off if (this->m_workspaceManager.activeOnly() && \ !this->isActive() && \ @@ -206,13 +206,14 @@ void Workspace::update(const std::string &format, const std::string &icon) { addOrRemoveClass(styleContext, m_workspaceManager.getBarOutput() == output(), "hosting-monitor"); std::string windows; - // TODO-WorkspaceTaskbar - if (false) { + // Optimization: The {windows} substitution string is only possible if the taskbar is disabled, no + // need to compute this if enableWorkspaceTaskbar() is true + if (!m_workspaceManager.enableWorkspaceTaskbar()) { auto windowSeparator = m_workspaceManager.getWindowSeparator(); bool isNotFirst = false; - for (auto &[_pid, window_repr] : m_windowMap) { + for (const auto &[_pid, window_repr] : m_windowMap) { if (isNotFirst) { windows.append(windowSeparator); } @@ -221,38 +222,63 @@ void Workspace::update(const std::string &format, const std::string &icon) { } } - m_label.set_markup(fmt::format(fmt::runtime(format), fmt::arg("id", id()), - fmt::arg("name", name()), fmt::arg("icon", icon), - fmt::arg("windows", windows))); + auto formatBefore = m_workspaceManager.formatBefore(); + m_labelBefore.set_markup(fmt::format(fmt::runtime(formatBefore), fmt::arg("id", id()), + fmt::arg("name", name()), fmt::arg("icon", workspace_icon), + fmt::arg("windows", windows))); - auto children = m_content.get_children(); - for (auto child : children) { - if (child != &m_label) { + if (m_workspaceManager.enableWorkspaceTaskbar()) { + updateTaskbar(workspace_icon); + } +} + +void Workspace::updateTaskbar(const std::string &workspace_icon) { + for (auto child : m_content.get_children()) { + if (child != &m_labelBefore) { m_content.remove(*child); } } - for (auto &[_addr, window_repr] : m_windowMap) { + for (const auto &[_addr, window_repr] : m_windowMap) { auto window_box = Gtk::make_managed(Gtk::ORIENTATION_HORIZONTAL); - auto window_icon = Gtk::make_managed(); - auto window_label = Gtk::make_managed(window_repr.window_title); - - // TODO-WorkspaceTaskbar: customizable max width and ellipsize - window_label->set_max_width_chars(20); - window_label->set_ellipsize(Pango::ELLIPSIZE_END); - - // TODO-WorkspaceTaskbar: support themes - auto app_info_ = IconLoader::get_app_info_from_app_id_list(window_repr.window_class); - // TODO-WorkspaceTaskbar: icon size - m_workspaceManager.iconLoader().image_load_icon(*window_icon, app_info_, 24); - - window_box->pack_start(*window_icon, false, false); - window_box->pack_start(*window_label, true, true); window_box->set_tooltip_text(window_repr.window_title); + auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()), + fmt::arg("title", window_repr.window_title)); + if (!text_before.empty()) { + auto window_label_before = Gtk::make_managed(text_before); + window_box->pack_start(*window_label_before, true, true); + } + + if (m_workspaceManager.taskbarWithIcon()) { + // TODO-WorkspaceTaskbar: support themes + auto app_info_ = IconLoader::get_app_info_from_app_id_list(window_repr.window_class); + + // TODO-WorkspaceTaskbar: icon size + auto window_icon = Gtk::make_managed(); + m_workspaceManager.iconLoader().image_load_icon(*window_icon, app_info_, 24); + window_box->pack_start(*window_icon, false, false); + } + + auto text_after = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatAfter()), + fmt::arg("title", window_repr.window_title)); + if (!text_after.empty()) { + auto window_label_after = Gtk::make_managed(text_after); + window_box->pack_start(*window_label_after, true, true); + } + m_content.pack_start(*window_box, true, false); window_box->show_all(); } + + auto formatAfter = m_workspaceManager.formatAfter(); + if (!formatAfter.empty()) { + m_labelAfter.set_markup(fmt::format(fmt::runtime(formatAfter), fmt::arg("id", id()), + fmt::arg("name", name()), + fmt::arg("icon", workspace_icon))); + m_content.pack_end(m_labelAfter, false, false); + m_labelAfter.show(); + } } } // namespace waybar::modules::hyprland diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 543e3e61..917999b7 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -9,6 +9,7 @@ #include #include "util/regex_collection.hpp" +#include "util/string.hpp" namespace waybar::modules::hyprland { @@ -563,8 +564,9 @@ void Workspaces::onConfigReloaded() { auto Workspaces::parseConfig(const Json::Value &config) -> void { const auto &configFormat = config["format"]; - m_format = configFormat.isString() ? configFormat.asString() : "{name}"; - m_withIcon = m_format.find("{icon}") != std::string::npos; + m_formatBefore = configFormat.isString() ? configFormat.asString() : "{name}"; + m_withIcon = m_formatBefore.find("{icon}") != std::string::npos; + auto withWindows = m_formatBefore.find("{windows}") != std::string::npos; if (m_withIcon && m_iconsMap.empty()) { populateIconsMap(config["format-icons"]); @@ -581,6 +583,15 @@ auto Workspaces::parseConfig(const Json::Value &config) -> void { populateIgnoreWorkspacesConfig(config); populateFormatWindowSeparatorConfig(config); populateWindowRewriteConfig(config); + + if (withWindows) { + populateWorkspaceTaskbarConfig(config); + } + if (m_enableWorkspaceTaskbar) { + auto parts = split(m_formatBefore, "{windows}", 1); + m_formatBefore = parts[0]; + m_formatAfter = parts.size() > 1 ? parts[1] : ""; + } } auto Workspaces::populateIconsMap(const Json::Value &formatIcons) -> void { @@ -653,6 +664,29 @@ auto Workspaces::populateWindowRewriteConfig(const Json::Value &config) -> void [this](std::string &window_rule) { return windowRewritePriorityFunction(window_rule); }); } +auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> void { + const auto &workspaceTaskbar = config["workspace-taskbar"]; + if (!workspaceTaskbar.isObject()) { + spdlog::debug("workspace-taskbar is not defined or is not an object, using default rules."); + return; + } + + populateBoolConfig(workspaceTaskbar, "enable", m_enableWorkspaceTaskbar); + + if (workspaceTaskbar["format"].isString()) { + /* The user defined a format string, use it */ + auto parts = split(workspaceTaskbar["format"].asString(), "{icon}", 1); + m_taskbarFormatBefore = parts[0]; + if (parts.size() > 1) { + m_taskbarWithIcon = true; + m_taskbarFormatAfter = parts[1]; + } + } else { + /* The default is to only show the icon */ + m_taskbarWithIcon = true; + } +} + void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payload) { if (!create_window_payload.isEmpty(*this)) { m_orphanWindowMap[create_window_payload.getAddress()] = create_window_payload.repr(*this); @@ -881,7 +915,7 @@ void Workspaces::updateWorkspaceStates() { if (updatedWorkspace != updatedWorkspaces.end()) { workspace->setOutput((*updatedWorkspace)["monitor"].asString()); } - workspace->update(m_format, workspaceIcon); + workspace->update(workspaceIcon); } } From fdb900404852b2f25f2af712c3f63a43cd0eb25b Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Tue, 31 Dec 2024 20:15:21 +0100 Subject: [PATCH 133/780] workspace taskbars: More config options - orientation - icon-size - icon-theme --- include/modules/hyprland/workspace.hpp | 3 ++- include/modules/hyprland/workspaces.hpp | 5 +++++ include/util/string.hpp | 7 +++++++ src/modules/hyprland/workspace.cpp | 11 ++++------- src/modules/hyprland/workspaces.cpp | 17 +++++++++++++++++ 5 files changed, 35 insertions(+), 8 deletions(-) diff --git a/include/modules/hyprland/workspace.hpp b/include/modules/hyprland/workspace.hpp index 48a027c3..71529363 100644 --- a/include/modules/hyprland/workspace.hpp +++ b/include/modules/hyprland/workspace.hpp @@ -63,7 +63,6 @@ class Workspace { std::optional closeWindow(WindowAddress const& addr); void update(const std::string& workspace_icon); - void updateTaskbar(const std::string& workspace_icon); private: Workspaces& m_workspaceManager; @@ -85,6 +84,8 @@ class Workspace { Gtk::Box m_content; Gtk::Label m_labelBefore; Gtk::Label m_labelAfter; + + void updateTaskbar(const std::string& workspace_icon); }; } // namespace waybar::modules::hyprland diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index d2842b73..75ac95dc 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -46,6 +47,8 @@ class Workspaces : public AModule, public EventHandler { auto formatAfter() const -> std::string { return m_formatAfter; } auto taskbarFormatBefore() const -> std::string { return m_taskbarFormatBefore; } auto taskbarFormatAfter() const -> std::string { return m_taskbarFormatAfter; } + auto taskbarIconSize() const -> int { return m_taskbarIconSize; } + auto taskbarOrientation() const -> Gtk::Orientation { return m_taskbarOrientation; } std::string getRewrite(std::string window_class, std::string window_title); std::string& getWindowSeparator() { return m_formatWindowSeparator; } @@ -160,6 +163,8 @@ class Workspaces : public AModule, public EventHandler { bool m_taskbarWithIcon = false; std::string m_taskbarFormatBefore; std::string m_taskbarFormatAfter; + int m_taskbarIconSize = 16; + Gtk::Orientation m_taskbarOrientation = Gtk::ORIENTATION_HORIZONTAL; std::vector m_ignoreWorkspaces; diff --git a/include/util/string.hpp b/include/util/string.hpp index 2da2bcc5..cb8a6892 100644 --- a/include/util/string.hpp +++ b/include/util/string.hpp @@ -24,6 +24,13 @@ inline std::string capitalize(const std::string& str) { return result; } +inline std::string toLower(const std::string& str) { + std::string result = str; + std::transform(result.begin(), result.end(), result.begin(), + [](unsigned char c) { return std::tolower(c); }); + return result; +} + inline std::vector split(std::string_view s, std::string_view delimiter, int max_splits = -1) { std::vector result; diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 140f4f77..aa12f33c 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -32,9 +32,8 @@ Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_ma false); m_button.set_relief(Gtk::RELIEF_NONE); - if (true) { - // TODO-WorkspaceTaskbar: Allow vertical? - m_content.set_orientation(Gtk::ORIENTATION_HORIZONTAL); + if (m_workspaceManager.enableWorkspaceTaskbar()) { + m_content.set_orientation(m_workspaceManager.taskbarOrientation()); m_content.pack_start(m_labelBefore, false, false); } else { m_content.set_center_widget(m_labelBefore); @@ -251,12 +250,10 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { } if (m_workspaceManager.taskbarWithIcon()) { - // TODO-WorkspaceTaskbar: support themes auto app_info_ = IconLoader::get_app_info_from_app_id_list(window_repr.window_class); - - // TODO-WorkspaceTaskbar: icon size + int icon_size = m_workspaceManager.taskbarIconSize(); auto window_icon = Gtk::make_managed(); - m_workspaceManager.iconLoader().image_load_icon(*window_icon, app_info_, 24); + m_workspaceManager.iconLoader().image_load_icon(*window_icon, app_info_, icon_size); window_box->pack_start(*window_icon, false, false); } diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 917999b7..ebc4c17c 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -685,6 +685,23 @@ auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> vo /* The default is to only show the icon */ m_taskbarWithIcon = true; } + + auto iconTheme = workspaceTaskbar["icon-theme"]; + if (iconTheme.isArray()) { + for (auto &c : iconTheme) { + m_iconLoader.add_custom_icon_theme(c.asString()); + } + } else if (iconTheme.isString()) { + m_iconLoader.add_custom_icon_theme(iconTheme.asString()); + } + + if (workspaceTaskbar["icon-size"].isInt()) { + m_taskbarIconSize = workspaceTaskbar["icon-size"].asInt(); + } + if (workspaceTaskbar["orientation"].isString() && + toLower(workspaceTaskbar["orientation"].asString()) == "vertical") { + m_taskbarOrientation = Gtk::ORIENTATION_VERTICAL; + } } void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payload) { From 5e1d6d1cc597cf58194f0de30134ec8f0df6d9a9 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Wed, 1 Jan 2025 10:52:56 +0100 Subject: [PATCH 134/780] workspace taskbars: Fix title not updating This seems to be an old bug that has been made visible with the new workspace taskbars feature. Sometimes, when closing a window and re-opening a window of the same program, hyprland reuses the window address. Since m_orphanWindowMap was not being cleaned up on window close, the new window would not be updated properly. --- src/modules/hyprland/workspaces.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index ebc4c17c..ef3fd67b 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -466,6 +466,7 @@ void Workspaces::onWindowOpened(std::string const &payload) { void Workspaces::onWindowClosed(std::string const &addr) { spdlog::trace("Window closed: {}", addr); updateWindowCount(); + m_orphanWindowMap.erase(addr); for (auto &workspace : m_workspaces) { if (workspace->closeWindow(addr)) { break; From e1649b001f2af34e14ac80cc25c6e7bc1d495889 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Wed, 1 Jan 2025 17:44:53 +0100 Subject: [PATCH 135/780] workspace taskbars: Fix title not updating Fix another older bug where the title of a window will not be updated after moving it to another monitor. In onWindowMoved, when moving an orphan window to the display of the current bar, that window should no longer be an orphan. --- src/modules/hyprland/workspaces.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index ef3fd67b..5af28ced 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -509,6 +509,7 @@ void Workspaces::onWindowMoved(std::string const &payload) { // ...and then add it to the new workspace if (!windowRepr.empty()) { + m_orphanWindowMap.erase(windowAddress); m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowRepr); } } From 53ca5a48830a3cbe4b15fd1b3f5992a40384bd2f Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Thu, 2 Jan 2025 07:41:24 +0100 Subject: [PATCH 136/780] workspace taskbars: Display windows in a consistent order Use a vector instead of a map for for storing the workspace windows. This orders the windows by the time they were added to the workspace, instead of sorting by address (which is effectively a random order). The new ordering seems to match the wlr/taskbar module --- .../hyprland/windowcreationpayload.hpp | 1 + include/modules/hyprland/workspace.hpp | 8 +++-- .../hyprland/windowcreationpayload.cpp | 3 +- src/modules/hyprland/workspace.cpp | 29 ++++++++++++------- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/include/modules/hyprland/windowcreationpayload.hpp b/include/modules/hyprland/windowcreationpayload.hpp index 06af3074..dca7ba9e 100644 --- a/include/modules/hyprland/windowcreationpayload.hpp +++ b/include/modules/hyprland/windowcreationpayload.hpp @@ -27,6 +27,7 @@ namespace waybar::modules::hyprland { class Workspaces; struct WindowRepr { + std::string address; std::string window_class; std::string window_title; std::string repr_rewrite; diff --git a/include/modules/hyprland/workspace.hpp b/include/modules/hyprland/workspace.hpp index 71529363..2dc9239b 100644 --- a/include/modules/hyprland/workspace.hpp +++ b/include/modules/hyprland/workspace.hpp @@ -54,9 +54,11 @@ class Workspace { void setWindows(uint value) { m_windows = value; }; void setName(std::string const& value) { m_name = value; }; void setOutput(std::string const& value) { m_output = value; }; - bool containsWindow(WindowAddress const& addr) const { return m_windowMap.contains(addr); } + bool containsWindow(WindowAddress const& addr) const { + return std::ranges::any_of(m_windowMap, + [&addr](const auto& window) { return window.address == addr; }); + }; void insertWindow(WindowCreationPayload create_window_paylod); - WindowRepr removeWindow(WindowAddress const& addr); void initializeWindowMap(const Json::Value& clients_data); bool onWindowOpened(WindowCreationPayload const& create_window_paylod); @@ -78,7 +80,7 @@ class Workspace { bool m_isUrgent = false; bool m_isVisible = false; - std::map> m_windowMap; + std::vector m_windowMap; Gtk::Button m_button; Gtk::Box m_content; diff --git a/src/modules/hyprland/windowcreationpayload.cpp b/src/modules/hyprland/windowcreationpayload.cpp index d8e3bdd5..5e587d51 100644 --- a/src/modules/hyprland/windowcreationpayload.cpp +++ b/src/modules/hyprland/windowcreationpayload.cpp @@ -98,7 +98,8 @@ WindowRepr WindowCreationPayload::repr(Workspaces &workspace_manager) { } if (std::holds_alternative(m_window)) { auto [window_class, window_title] = std::get(m_window); - return {window_class, window_title, workspace_manager.getRewrite(window_class, window_title)}; + return {m_windowAddress, window_class, window_title, + workspace_manager.getRewrite(window_class, window_title)}; } // Unreachable spdlog::error("WorkspaceWindow::repr: Unreachable"); diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index aa12f33c..0baa67a8 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -53,8 +53,13 @@ void addOrRemoveClass(const Glib::RefPtr &context, bool condi } std::optional Workspace::closeWindow(WindowAddress const &addr) { - if (m_windowMap.contains(addr)) { - return removeWindow(addr); + auto it = std::ranges::find_if(m_windowMap, + [&addr](const auto &window) { return window.address == addr; }); + // If the vector contains the address, remove it and return the window representation + if (it != m_windowMap.end()) { + WindowRepr windowRepr = *it; + m_windowMap.erase(it); + return windowRepr; } return std::nullopt; } @@ -101,7 +106,15 @@ void Workspace::insertWindow(WindowCreationPayload create_window_paylod) { auto repr = create_window_paylod.repr(m_workspaceManager); if (!repr.empty()) { - m_windowMap[create_window_paylod.getAddress()] = repr; + auto addr = create_window_paylod.getAddress(); + auto it = std::ranges::find_if( + m_windowMap, [&addr](const auto &window) { return window.address == addr; }); + // If the vector contains the address, update the window representation, otherwise insert it + if (it != m_windowMap.end()) { + *it = repr; + } else { + m_windowMap.emplace_back(repr); + } } } }; @@ -114,12 +127,6 @@ bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_paylod return false; } -WindowRepr Workspace::removeWindow(WindowAddress const &addr) { - WindowRepr windowRepr = m_windowMap[addr]; - m_windowMap.erase(addr); - return windowRepr; -} - std::string &Workspace::selectIcon(std::map &icons_map) { spdlog::trace("Selecting icon for workspace {}", name()); if (isUrgent()) { @@ -212,7 +219,7 @@ void Workspace::update(const std::string &workspace_icon) { bool isNotFirst = false; - for (const auto &[_pid, window_repr] : m_windowMap) { + for (const auto &window_repr : m_windowMap) { if (isNotFirst) { windows.append(windowSeparator); } @@ -238,7 +245,7 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { } } - for (const auto &[_addr, window_repr] : m_windowMap) { + for (const auto &window_repr : m_windowMap) { auto window_box = Gtk::make_managed(Gtk::ORIENTATION_HORIZONTAL); window_box->set_tooltip_text(window_repr.window_title); From 3948c0d15498e27c6e29851edadfdc1d7633df5b Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Sat, 4 Jan 2025 15:56:24 +0100 Subject: [PATCH 137/780] workspace taskbars: Focus window on click --- include/modules/hyprland/workspace.hpp | 1 + src/modules/hyprland/workspace.cpp | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/include/modules/hyprland/workspace.hpp b/include/modules/hyprland/workspace.hpp index 2dc9239b..b5ff3b41 100644 --- a/include/modules/hyprland/workspace.hpp +++ b/include/modules/hyprland/workspace.hpp @@ -88,6 +88,7 @@ class Workspace { Gtk::Label m_labelAfter; void updateTaskbar(const std::string& workspace_icon); + static void focusWindow(WindowAddress const& addr); }; } // namespace waybar::modules::hyprland diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 0baa67a8..0e33f600 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -248,6 +248,18 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { for (const auto &window_repr : m_windowMap) { 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"); + auto event_box = Gtk::manage(new Gtk::EventBox()); + event_box->add(*window_box); + event_box->signal_button_press_event().connect( + [window_repr](GdkEventButton const *bt) { + if (bt->type == GDK_BUTTON_PRESS) { + focusWindow(window_repr.address); + return true; + } + return false; + }, + false); auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()), fmt::arg("title", window_repr.window_title)); @@ -271,8 +283,8 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { window_box->pack_start(*window_label_after, true, true); } - m_content.pack_start(*window_box, true, false); - window_box->show_all(); + m_content.pack_start(*event_box, true, false); + event_box->show_all(); } auto formatAfter = m_workspaceManager.formatAfter(); @@ -285,4 +297,12 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { } } +void Workspace::focusWindow(WindowAddress const &addr) { + try { + IPC::getSocket1Reply("dispatch focuswindow address:0x" + addr); + } catch (const std::exception &e) { + spdlog::error("Failed to dispatch window: {}", e.what()); + } +} + } // namespace waybar::modules::hyprland From 5ee0d1c7fedf4dae3588fd977e8d823b5698bed1 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Sat, 4 Jan 2025 22:10:23 +0100 Subject: [PATCH 138/780] workspace taskbars: Fix windows not showing Windows were not being shown or updated unless the window-rewrite config were present. --- include/modules/hyprland/windowcreationpayload.hpp | 2 +- include/modules/hyprland/workspaces.hpp | 5 +++-- src/modules/hyprland/workspace.cpp | 10 +++++----- src/modules/hyprland/workspaces.cpp | 10 ++++++---- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/include/modules/hyprland/windowcreationpayload.hpp b/include/modules/hyprland/windowcreationpayload.hpp index dca7ba9e..3bd30c8e 100644 --- a/include/modules/hyprland/windowcreationpayload.hpp +++ b/include/modules/hyprland/windowcreationpayload.hpp @@ -33,7 +33,7 @@ struct WindowRepr { std::string repr_rewrite; public: - bool empty() const { return repr_rewrite.empty(); } + bool empty() const { return address.empty(); } }; class WindowCreationPayload { diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 75ac95dc..b31909ce 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -39,7 +39,7 @@ class Workspaces : public AModule, public EventHandler { auto activeOnly() const -> bool { return m_activeOnly; } auto specialVisibleOnly() const -> bool { return m_specialVisibleOnly; } auto moveToMonitor() const -> bool { return m_moveToMonitor; } - auto enableWorkspaceTaskbar() const -> bool { return m_enableWorkspaceTaskbar; } + auto enableTaskbar() const -> bool { return m_enableTaskbar; } auto taskbarWithIcon() const -> bool { return m_taskbarWithIcon; } auto getBarOutput() const -> std::string { return m_bar.output->name; } @@ -159,8 +159,9 @@ class Workspaces : public AModule, public EventHandler { std::vector m_windowsToCreate; IconLoader m_iconLoader; - bool m_enableWorkspaceTaskbar = false; + bool m_enableTaskbar = false; bool m_taskbarWithIcon = false; + bool m_taskbarWithTitle = false; std::string m_taskbarFormatBefore; std::string m_taskbarFormatAfter; int m_taskbarIconSize = 16; diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 0e33f600..cbcfbce9 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -32,7 +32,7 @@ Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_ma false); m_button.set_relief(Gtk::RELIEF_NONE); - if (m_workspaceManager.enableWorkspaceTaskbar()) { + if (m_workspaceManager.enableTaskbar()) { m_content.set_orientation(m_workspaceManager.taskbarOrientation()); m_content.pack_start(m_labelBefore, false, false); } else { @@ -105,7 +105,7 @@ void Workspace::insertWindow(WindowCreationPayload create_window_paylod) { if (!create_window_paylod.isEmpty(m_workspaceManager)) { auto repr = create_window_paylod.repr(m_workspaceManager); - if (!repr.empty()) { + if (!repr.empty() || m_workspaceManager.enableTaskbar()) { auto addr = create_window_paylod.getAddress(); auto it = std::ranges::find_if( m_windowMap, [&addr](const auto &window) { return window.address == addr; }); @@ -213,8 +213,8 @@ void Workspace::update(const std::string &workspace_icon) { std::string windows; // Optimization: The {windows} substitution string is only possible if the taskbar is disabled, no - // need to compute this if enableWorkspaceTaskbar() is true - if (!m_workspaceManager.enableWorkspaceTaskbar()) { + // need to compute this if enableTaskbar() is true + if (!m_workspaceManager.enableTaskbar()) { auto windowSeparator = m_workspaceManager.getWindowSeparator(); bool isNotFirst = false; @@ -233,7 +233,7 @@ void Workspace::update(const std::string &workspace_icon) { fmt::arg("name", name()), fmt::arg("icon", workspace_icon), fmt::arg("windows", windows))); - if (m_workspaceManager.enableWorkspaceTaskbar()) { + if (m_workspaceManager.enableTaskbar()) { updateTaskbar(workspace_icon); } } diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 5af28ced..b2722c0e 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -589,7 +589,7 @@ auto Workspaces::parseConfig(const Json::Value &config) -> void { if (withWindows) { populateWorkspaceTaskbarConfig(config); } - if (m_enableWorkspaceTaskbar) { + if (m_enableTaskbar) { auto parts = split(m_formatBefore, "{windows}", 1); m_formatBefore = parts[0]; m_formatAfter = parts.size() > 1 ? parts[1] : ""; @@ -673,11 +673,13 @@ auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> vo return; } - populateBoolConfig(workspaceTaskbar, "enable", m_enableWorkspaceTaskbar); + populateBoolConfig(workspaceTaskbar, "enable", m_enableTaskbar); if (workspaceTaskbar["format"].isString()) { /* The user defined a format string, use it */ - auto parts = split(workspaceTaskbar["format"].asString(), "{icon}", 1); + std::string format = workspaceTaskbar["format"].asString(); + m_taskbarWithTitle = format.find("{title") != std::string::npos; /* {title} or {title.length} */ + auto parts = split(format, "{icon}", 1); m_taskbarFormatBefore = parts[0]; if (parts.size() > 1) { m_taskbarWithIcon = true; @@ -726,7 +728,7 @@ auto Workspaces::registerIpc() -> void { gIPC->registerForIPC("urgent", this); gIPC->registerForIPC("configreloaded", this); - if (windowRewriteConfigUsesTitle()) { + if (windowRewriteConfigUsesTitle() || m_taskbarWithTitle) { spdlog::info( "Registering for Hyprland's 'windowtitle' events because a user-defined window " "rewrite rule uses the 'title' field."); From 42affa4edae53996ef1618823a7e226dee4759a1 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Mon, 6 Jan 2025 12:19:21 +0100 Subject: [PATCH 139/780] workspace taskbars: Update manpage --- man/waybar-hyprland-workspaces.5.scd | 40 +++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index 18c39898..48ef2a74 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -26,17 +26,49 @@ Addressed by *hyprland/workspaces* Regex rules to map window class to an icon or preferred method of representation for a workspace's window. Keys are the rules, while the values are the methods of representation. Values may use the placeholders {class} and {title} to use the window's original class and/or title respectively. Rules may specify `class<...>`, `title<...>`, or both in order to fine-tune the matching. - You may assign an empty value to a rule to have it ignored from generating any representation in workspaces. + You may assign an empty value to a rule to have it ignored from generating any representation in workspaces. ++ +This setting is ignored if *workspace-taskbar.enable* is set to true. -*window-rewrite-default*: +*window-rewrite-default*: ++ typeof: string ++ default: "?" ++ - The default method of representation for a workspace's window. This will be used for windows whose classes do not match any of the rules in *window-rewrite*. + The default method of representation for a workspace's window. This will be used for windows whose classes do not match any of the rules in *window-rewrite*. ++ + This setting is ignored if *workspace-taskbar.enable* is set to true. *format-window-separator*: ++ typeof: string ++ default: " " ++ - The separator to be used between windows in a workspace. + The separator to be used between windows in a workspace. ++ + This setting is ignored if *workspace-taskbar.enable* is set to true. + +*workspace-taskbar*: ++ + typeof: object ++ + Contains settings for the workspace taskbar, an alternative mode for the workspaces module which displays the window icons as images instead of text. + + *enable*: ++ + typeof: bool ++ + default: false ++ + Enables the workspace taskbar mode. + + *format*: ++ + typeof: string ++ + default: {icon} ++ + Format to use for each window in the workspace taskbar. Available placeholders are {icon} and {title}. + + *icon-size*: ++ + typeof: int ++ + default: 16 ++ + Size of the icons in the workspace taskbar. + + *icon-theme*: ++ + typeof: string | array ++ + default: [] ++ + Icon theme to use for the workspace taskbar. If an array is provided, the first theme that is found for a given icon will be used. If no theme is found (or the array is empty), the default icon theme is used. + + *orientation*: ++ + typeof: "horizontal" | "vertical" ++ + default: horizontal ++ + Direction in which the workspace taskbar is displayed. *show-special*: ++ typeof: bool ++ From e0f369552305a2c5a862d5d46f4eaba54ca35523 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Wed, 8 Jan 2025 12:47:35 +0100 Subject: [PATCH 140/780] workspace taskbars: Minor fixes - Add missing CSS class to manpage - Fix rare segfault when address is not found (seems to only happen when compiled for production) --- man/waybar-hyprland-workspaces.5.scd | 1 + src/modules/hyprland/workspaces.cpp | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index 48ef2a74..01374a2c 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -210,3 +210,4 @@ 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) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index b2722c0e..0bee8446 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -548,12 +548,11 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { Json::Value clientsData = gIPC->getSocket1JsonReply("clients"); std::string jsonWindowAddress = fmt::format("0x{}", payload); - auto client = - std::find_if(clientsData.begin(), clientsData.end(), [jsonWindowAddress](auto &client) { - return client["address"].asString() == jsonWindowAddress; - }); + auto client = std::ranges::find_if(clientsData, [&jsonWindowAddress](auto &c) { + return c["address"].asString() == jsonWindowAddress; + }); - if (!client->empty()) { + if (client != clientsData.end() && !client->empty()) { (*inserter)({*client}); } } From b4519c081943b6515604f28504b49aa97efbf8b5 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Fri, 24 Jan 2025 18:26:04 +0100 Subject: [PATCH 141/780] workspace taskbars: Use sigc::mem_fun instead of lambda --- include/modules/hyprland/workspace.hpp | 2 +- src/modules/hyprland/workspace.cpp | 16 ++++------------ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/include/modules/hyprland/workspace.hpp b/include/modules/hyprland/workspace.hpp index b5ff3b41..a46d24e6 100644 --- a/include/modules/hyprland/workspace.hpp +++ b/include/modules/hyprland/workspace.hpp @@ -88,7 +88,7 @@ class Workspace { Gtk::Label m_labelAfter; void updateTaskbar(const std::string& workspace_icon); - static void focusWindow(WindowAddress const& addr); + bool handleClick(const GdkEventButton* event_button, WindowAddress const& addr) const; }; } // namespace waybar::modules::hyprland diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index cbcfbce9..515ed18f 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -252,14 +252,7 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { auto event_box = Gtk::manage(new Gtk::EventBox()); event_box->add(*window_box); event_box->signal_button_press_event().connect( - [window_repr](GdkEventButton const *bt) { - if (bt->type == GDK_BUTTON_PRESS) { - focusWindow(window_repr.address); - return true; - } - return false; - }, - false); + sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address)); auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()), fmt::arg("title", window_repr.window_title)); @@ -297,12 +290,11 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { } } -void Workspace::focusWindow(WindowAddress const &addr) { - try { +bool Workspace::handleClick(const GdkEventButton *event_button, WindowAddress const &addr) const { + if (event_button->type == GDK_BUTTON_PRESS) { IPC::getSocket1Reply("dispatch focuswindow address:0x" + addr); - } catch (const std::exception &e) { - spdlog::error("Failed to dispatch window: {}", e.what()); } + return true; } } // namespace waybar::modules::hyprland From 7b854112edf7b2b3a487251ef24c1b1106669232 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Fri, 24 Jan 2025 20:48:45 +0100 Subject: [PATCH 142/780] workspace taskbars: Allow custom command on window click --- include/modules/hyprland/workspaces.hpp | 2 ++ src/modules/hyprland/workspace.cpp | 14 +++++++++++--- src/modules/hyprland/workspaces.cpp | 4 ++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index b31909ce..9c0a60ab 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -49,6 +49,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 onClickWindow() const -> std::string { return m_onClickWindow; } std::string getRewrite(std::string window_class, std::string window_title); std::string& getWindowSeparator() { return m_formatWindowSeparator; } @@ -166,6 +167,7 @@ class Workspaces : public AModule, public EventHandler { std::string m_taskbarFormatAfter; int m_taskbarIconSize = 16; Gtk::Orientation m_taskbarOrientation = Gtk::ORIENTATION_HORIZONTAL; + std::string m_onClickWindow; std::vector m_ignoreWorkspaces; diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 515ed18f..79075f21 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -6,6 +6,7 @@ #include #include "modules/hyprland/workspaces.hpp" +#include "util/command.hpp" #include "util/icon_loader.hpp" namespace waybar::modules::hyprland { @@ -251,8 +252,10 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { window_box->get_style_context()->add_class("taskbar-window"); auto event_box = Gtk::manage(new Gtk::EventBox()); event_box->add(*window_box); - event_box->signal_button_press_event().connect( - sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address)); + if (m_workspaceManager.onClickWindow() != "") { + event_box->signal_button_press_event().connect( + sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address)); + } auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()), fmt::arg("title", window_repr.window_title)); @@ -292,7 +295,12 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { bool Workspace::handleClick(const GdkEventButton *event_button, WindowAddress const &addr) const { if (event_button->type == GDK_BUTTON_PRESS) { - IPC::getSocket1Reply("dispatch focuswindow address:0x" + addr); + std::string command = std::regex_replace(m_workspaceManager.onClickWindow(), + std::regex("\\{address\\}"), "0x" + addr); + auto res = util::command::execNoRead(command); + if (res.exit_code != 0) { + spdlog::error("Failed to execute {}: {}", command, res.out); + } } return true; } diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 0bee8446..2b4eda54 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -705,6 +705,10 @@ auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> vo toLower(workspaceTaskbar["orientation"].asString()) == "vertical") { m_taskbarOrientation = Gtk::ORIENTATION_VERTICAL; } + + if (workspaceTaskbar["on-click-window"].isString()) { + m_onClickWindow = workspaceTaskbar["on-click-window"].asString(); + } } void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payload) { From 1a9f5aced778139f272827adacd95c7a0d36489a Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Mon, 27 Jan 2025 11:47:16 +0100 Subject: [PATCH 143/780] workspace taskbars: Add button param to click command --- src/modules/hyprland/workspace.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 79075f21..0e07de4e 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -297,6 +297,8 @@ bool Workspace::handleClick(const GdkEventButton *event_button, WindowAddress co if (event_button->type == GDK_BUTTON_PRESS) { std::string command = std::regex_replace(m_workspaceManager.onClickWindow(), std::regex("\\{address\\}"), "0x" + addr); + command = std::regex_replace(command, std::regex("\\{button\\}"), + std::to_string(event_button->button)); auto res = util::command::execNoRead(command); if (res.exit_code != 0) { spdlog::error("Failed to execute {}: {}", command, res.out); From 8a741f6ec78e7d5a8dcbb0e26730e80987453ffa Mon Sep 17 00:00:00 2001 From: ladenburger Date: Sat, 1 Feb 2025 00:38:52 +0100 Subject: [PATCH 144/780] fix: JSON format for Hyprland keyboard example --- man/waybar-hyprland-language.5.scd | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/man/waybar-hyprland-language.5.scd b/man/waybar-hyprland-language.5.scd index a9a18008..5a7ba941 100644 --- a/man/waybar-hyprland-language.5.scd +++ b/man/waybar-hyprland-language.5.scd @@ -59,9 +59,9 @@ Addressed by *hyprland/language* ``` "hyprland/language": { - "format": "Lang: {long}" - "format-en": "AMERICA, HELL YEAH!" - "format-tr": "As bayrakları" + "format": "Lang: {long}", + "format-en": "AMERICA, HELL YEAH!", + "format-tr": "As bayrakları", "keyboard-name": "at-translated-set-2-keyboard" } ``` From 8bdb5c19069a4286512bfae316e90c947b7cdcce Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 1 Feb 2025 00:10:55 +0000 Subject: [PATCH 145/780] 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/88195a94f390381c6afcdaa933c2f6ff93959cb4?narHash=sha256-0q9NGQySwDQc7RhAV2ukfnu7Gxa5/ybJ2ANT8DQrQrs%3D' (2024-12-29) → 'github:NixOS/nixpkgs/9d3ae807ebd2981d593cddd0080856873139aa40?narHash=sha256-NGqpVVxNAHwIicXpgaVqJEJWeyqzoQJ9oc8lnK9%2BWC4%3D' (2025-01-29) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 2b8235c4..c4bb5dee 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1735471104, - "narHash": "sha256-0q9NGQySwDQc7RhAV2ukfnu7Gxa5/ybJ2ANT8DQrQrs=", + "lastModified": 1738142207, + "narHash": "sha256-NGqpVVxNAHwIicXpgaVqJEJWeyqzoQJ9oc8lnK9+WC4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "88195a94f390381c6afcdaa933c2f6ff93959cb4", + "rev": "9d3ae807ebd2981d593cddd0080856873139aa40", "type": "github" }, "original": { From e32a6784783c9ae9ac9b633a1dec25973b6a5234 Mon Sep 17 00:00:00 2001 From: Konstantin Vukolov Date: Wed, 5 Feb 2025 00:00:58 +0300 Subject: [PATCH 146/780] Hide upower module when specified device disconnected --- src/modules/upower.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/modules/upower.cpp b/src/modules/upower.cpp index 5ee6d64c..4b832b7e 100644 --- a/src/modules/upower.cpp +++ b/src/modules/upower.cpp @@ -358,10 +358,12 @@ void UPower::resetDevices() { void UPower::setDisplayDevice() { std::lock_guard guard{mutex_}; - if (nativePath_.empty() && model_.empty()) { - // Unref current upDevice - if (upDevice_.upDevice != NULL) g_object_unref(upDevice_.upDevice); + if (upDevice_.upDevice != NULL) { + g_object_unref(upDevice_.upDevice); + upDevice_.upDevice = NULL; + } + if (nativePath_.empty() && model_.empty()) { upDevice_.upDevice = up_client_get_display_device(upClient_); getUpDeviceInfo(upDevice_); } else { @@ -386,7 +388,6 @@ void UPower::setDisplayDevice() { } // Unref current upDevice if it exists if (displayDevice.upDevice != NULL) { - if (thisPtr->upDevice_.upDevice != NULL) g_object_unref(thisPtr->upDevice_.upDevice); thisPtr->upDevice_ = displayDevice; } }, From a26ed50d0f42657bf09c69c996238aa71d670c69 Mon Sep 17 00:00:00 2001 From: Bruno Andreotti Date: Fri, 7 Feb 2025 14:39:07 -0300 Subject: [PATCH 147/780] Add support for vertical bars in privacy module --- include/modules/privacy/privacy.hpp | 2 +- include/modules/privacy/privacy_item.hpp | 4 ++-- src/factory.cpp | 2 +- src/modules/privacy/privacy.cpp | 9 +++++---- src/modules/privacy/privacy_item.cpp | 19 ++++++++++++++----- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/include/modules/privacy/privacy.hpp b/include/modules/privacy/privacy.hpp index d7656d31..6179098c 100644 --- a/include/modules/privacy/privacy.hpp +++ b/include/modules/privacy/privacy.hpp @@ -13,7 +13,7 @@ namespace waybar::modules::privacy { class Privacy : public AModule { public: - Privacy(const std::string &, const Json::Value &, const std::string &pos); + Privacy(const std::string &, const Json::Value &, Gtk::Orientation, const std::string &pos); auto update() -> void override; void onPrivacyNodesChanged(); diff --git a/include/modules/privacy/privacy_item.hpp b/include/modules/privacy/privacy_item.hpp index 836bd994..f5f572c0 100644 --- a/include/modules/privacy/privacy_item.hpp +++ b/include/modules/privacy/privacy_item.hpp @@ -17,8 +17,8 @@ namespace waybar::modules::privacy { class PrivacyItem : public Gtk::Revealer { public: PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_, - std::list *nodes, const std::string &pos, const uint icon_size, - const uint transition_duration); + std::list *nodes, Gtk::Orientation orientation, + const std::string &pos, const uint icon_size, const uint transition_duration); enum PrivacyNodeType privacy_type; diff --git a/src/factory.cpp b/src/factory.cpp index 6c2313e3..1483397d 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -140,7 +140,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, #endif #ifdef HAVE_PIPEWIRE if (ref == "privacy") { - return new waybar::modules::privacy::Privacy(id, config_[name], pos); + return new waybar::modules::privacy::Privacy(id, config_[name], bar_.orientation, pos); } #endif #ifdef HAVE_MPRIS diff --git a/src/modules/privacy/privacy.cpp b/src/modules/privacy/privacy.cpp index 97996c33..48bba888 100644 --- a/src/modules/privacy/privacy.cpp +++ b/src/modules/privacy/privacy.cpp @@ -15,13 +15,14 @@ using util::PipewireBackend::PRIVACY_NODE_TYPE_AUDIO_OUTPUT; using util::PipewireBackend::PRIVACY_NODE_TYPE_NONE; using util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT; -Privacy::Privacy(const std::string& id, const Json::Value& config, const std::string& pos) +Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientation orientation, + const std::string& pos) : AModule(config, "privacy", id), nodes_screenshare(), nodes_audio_in(), nodes_audio_out(), visibility_conn(), - box_(Gtk::ORIENTATION_HORIZONTAL, 0) { + box_(orientation, 0) { box_.set_name(name_); event_box_.add(box_); @@ -67,8 +68,8 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, const std::st auto iter = typeMap.find(type); if (iter != typeMap.end()) { auto& [nodePtr, nodeType] = iter->second; - auto* item = Gtk::make_managed(module, nodeType, nodePtr, pos, iconSize, - transition_duration); + auto* item = Gtk::make_managed(module, nodeType, nodePtr, orientation, pos, + iconSize, transition_duration); box_.add(*item); } } diff --git a/src/modules/privacy/privacy_item.cpp b/src/modules/privacy/privacy_item.cpp index a38b95a4..54e61b43 100644 --- a/src/modules/privacy/privacy_item.cpp +++ b/src/modules/privacy/privacy_item.cpp @@ -11,8 +11,9 @@ namespace waybar::modules::privacy { PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_, - std::list *nodes_, const std::string &pos, - const uint icon_size, const uint transition_duration) + std::list *nodes_, Gtk::Orientation orientation, + const std::string &pos, const uint icon_size, + const uint transition_duration) : Gtk::Revealer(), privacy_type(privacy_type_), nodes(nodes_), @@ -40,16 +41,24 @@ PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privac // Set the reveal transition to not look weird when sliding in if (pos == "modules-left") { - set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT); + set_transition_type(orientation == Gtk::ORIENTATION_HORIZONTAL + ? Gtk::REVEALER_TRANSITION_TYPE_SLIDE_RIGHT + : Gtk::REVEALER_TRANSITION_TYPE_SLIDE_DOWN); } else if (pos == "modules-center") { set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_CROSSFADE); } else if (pos == "modules-right") { - set_transition_type(Gtk::REVEALER_TRANSITION_TYPE_SLIDE_LEFT); + set_transition_type(orientation == Gtk::ORIENTATION_HORIZONTAL + ? Gtk::REVEALER_TRANSITION_TYPE_SLIDE_LEFT + : Gtk::REVEALER_TRANSITION_TYPE_SLIDE_UP); } set_transition_duration(transition_duration); box_.set_name("privacy-item"); - box_.add(icon_); + + // We use `set_center_widget` instead of `add` to make sure the icon is + // centered even if the orientation is vertical + box_.set_center_widget(icon_); + icon_.set_pixel_size(icon_size); add(box_); From 0abb2166a40da300597aa3559daade14f6b6a694 Mon Sep 17 00:00:00 2001 From: hansi Date: Sat, 8 Feb 2025 00:29:09 +0400 Subject: [PATCH 148/780] turn off ellipsize for niri/language module --- src/modules/niri/language.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/niri/language.cpp b/src/modules/niri/language.cpp index 1e4d6d10..3b55ff24 100644 --- a/src/modules/niri/language.cpp +++ b/src/modules/niri/language.cpp @@ -9,7 +9,7 @@ namespace waybar::modules::niri { Language::Language(const std::string &id, const Bar &bar, const Json::Value &config) - : ALabel(config, "language", id, "{}", 0, true), bar_(bar) { + : ALabel(config, "language", id, "{}", 0, false), bar_(bar) { label_.hide(); if (!gIPC) gIPC = std::make_unique(); From 807208cba8c52b56c5c8430a4fc7c88d9666c098 Mon Sep 17 00:00:00 2001 From: Alex Denes Date: Sat, 8 Feb 2025 03:53:28 +0000 Subject: [PATCH 149/780] dwl/tags: add hide-vacant option --- include/modules/dwl/tags.hpp | 1 + man/waybar-dwl-tags.5.scd | 5 +++++ src/modules/dwl/tags.cpp | 10 ++++++++++ 3 files changed, 16 insertions(+) diff --git a/include/modules/dwl/tags.hpp b/include/modules/dwl/tags.hpp index 53dff989..8f2a158a 100644 --- a/include/modules/dwl/tags.hpp +++ b/include/modules/dwl/tags.hpp @@ -28,6 +28,7 @@ class Tags : public waybar::AModule { const waybar::Bar &bar_; Gtk::Box box_; std::vector buttons_; + bool hide_vacant_; struct zdwl_ipc_output_v2 *output_status_; }; diff --git a/man/waybar-dwl-tags.5.scd b/man/waybar-dwl-tags.5.scd index a2146dfd..d81cc546 100644 --- a/man/waybar-dwl-tags.5.scd +++ b/man/waybar-dwl-tags.5.scd @@ -21,6 +21,11 @@ Addressed by *dwl/tags* typeof: array ++ The label to display for each tag. +*hide-vacant*: ++ + typeof: bool ++ + default: false ++ + If set to true, tags without clients and that are not active will be hidden. + *disable-click*: ++ typeof: bool ++ default: false ++ diff --git a/src/modules/dwl/tags.cpp b/src/modules/dwl/tags.cpp index f8b250c8..ce4c2324 100644 --- a/src/modules/dwl/tags.cpp +++ b/src/modules/dwl/tags.cpp @@ -94,7 +94,11 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con seat_{nullptr}, bar_(bar), box_{bar.orientation, 0}, + hide_vacant_(false), output_status_{nullptr} { + if (config_["hide-vacant"].asBool()) { + hide_vacant_ = config_["hide-vacant"].asBool(); + } struct wl_display *display = Client::inst()->wl_display; struct wl_registry *registry = wl_display_get_registry(display); @@ -198,6 +202,12 @@ void Tags::handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint } else { button.get_style_context()->remove_class("urgent"); } + + if (hide_vacant_ && !clients && !(state & TAG_ACTIVE)) { + button.set_visible(false); + } else { + button.set_visible(true); + } } } /* namespace waybar::modules::dwl */ From 8685e88ec439ac4bbb1c745a643f8c63f64375c1 Mon Sep 17 00:00:00 2001 From: Alex Denes Date: Sat, 8 Feb 2025 01:35:32 +0000 Subject: [PATCH 150/780] dwl/window: add .active styling class for active windows --- include/modules/dwl/window.hpp | 2 ++ man/waybar-dwl-window.5.scd | 4 ++++ src/modules/dwl/window.cpp | 13 +++++++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/include/modules/dwl/window.hpp b/include/modules/dwl/window.hpp index 43586399..619360a5 100644 --- a/include/modules/dwl/window.hpp +++ b/include/modules/dwl/window.hpp @@ -19,6 +19,7 @@ class Window : public AAppIconLabel, public sigc::trackable { void handle_layout(const uint32_t layout); void handle_title(const char *title); void handle_appid(const char *ppid); + void handle_active(const uint32_t active); void handle_layout_symbol(const char *layout_symbol); void handle_frame(); @@ -30,6 +31,7 @@ class Window : public AAppIconLabel, public sigc::trackable { std::string title_; std::string appid_; std::string layout_symbol_; + bool active_; uint32_t layout_; struct zdwl_ipc_output_v2 *output_status_; diff --git a/man/waybar-dwl-window.5.scd b/man/waybar-dwl-window.5.scd index 9ac33d94..c7f70908 100644 --- a/man/waybar-dwl-window.5.scd +++ b/man/waybar-dwl-window.5.scd @@ -109,6 +109,10 @@ If no expression matches, the format output is left unchanged. Invalid expressions (e.g., mismatched parentheses) are skipped. +# STYLE + +- *#window.active* + # EXAMPLES ``` diff --git a/src/modules/dwl/window.cpp b/src/modules/dwl/window.cpp index a960a1f0..82d92003 100644 --- a/src/modules/dwl/window.cpp +++ b/src/modules/dwl/window.cpp @@ -19,7 +19,7 @@ static void toggle_visibility(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) { } static void active(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t active) { - // Intentionally empty + static_cast(data)->handle_active(active); } static void set_tag(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t tag, uint32_t state, @@ -74,7 +74,9 @@ static const wl_registry_listener registry_listener_impl = {.global = handle_glo .global_remove = handle_global_remove}; Window::Window(const std::string &id, const Bar &bar, const Json::Value &config) - : AAppIconLabel(config, "window", id, "{}", 0, true), bar_(bar) { + : AAppIconLabel(config, "window", id, "{}", 0, true), + bar_(bar), + active_(false) { struct wl_display *display = Client::inst()->wl_display; struct wl_registry *registry = wl_display_get_registry(display); @@ -102,6 +104,8 @@ void Window::handle_title(const char *title) { title_ = Glib::Markup::escape_tex void Window::handle_appid(const char *appid) { appid_ = Glib::Markup::escape_text(appid); } +void Window::handle_active(const uint32_t active) { active_ = active != 0; } + void Window::handle_layout_symbol(const char *layout_symbol) { layout_symbol_ = Glib::Markup::escape_text(layout_symbol); } @@ -118,6 +122,11 @@ void Window::handle_frame() { if (tooltipEnabled()) { label_.set_tooltip_text(title_); } + if (active_) { + box_.get_style_context()->add_class("active"); + } else { + box_.get_style_context()->remove_class("active"); + } } } // namespace waybar::modules::dwl From cc94278c4ed83fa63c72d90eee8c16bf99358b7c Mon Sep 17 00:00:00 2001 From: Denis Kazimirov Date: Mon, 10 Feb 2025 18:32:57 +0500 Subject: [PATCH 151/780] hyprland/workspaces: fixed urgent for special workspaces --- src/modules/hyprland/workspaces.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 047703cc..a489bb7f 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -863,7 +863,7 @@ void Workspaces::updateWorkspaceStates() { for (auto &workspace : m_workspaces) { workspace->setActive(workspace->name() == m_activeWorkspaceName || workspace->name() == m_activeSpecialWorkspaceName); - if (workspace->name() == m_activeWorkspaceName && workspace->isUrgent()) { + if (workspace->isActive() && workspace->isUrgent()) { workspace->setUrgent(false); } workspace->setVisible(std::find(visibleWorkspaces.begin(), visibleWorkspaces.end(), From 6c4f0af2fe4bb8a35c031efafb0f78cfc3805e4d Mon Sep 17 00:00:00 2001 From: Malix Date: Sat, 15 Feb 2025 20:33:53 +0100 Subject: [PATCH 152/780] update(docs): link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 55a6c7d9..5266e916 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ - River (Mapping mode, Tags, Focused window name) - Hyprland (Window Icons, Workspaces, Focused window name) - Niri (Workspaces, Focused window name, Language) -- DWL (Tags, Focused window name) [requires dwl ipc patch](https://github.com/djpohly/dwl/wiki/ipc) +- DWL (Tags, Focused window name) [requires dwl ipc patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/ipc) - Tray [#21](https://github.com/Alexays/Waybar/issues/21) - Local time - Battery From 937b62ea9a1cd32474855c1241d1dd95f91c0a6e Mon Sep 17 00:00:00 2001 From: Kaosu Date: Sun, 16 Feb 2025 14:21:08 +0100 Subject: [PATCH 153/780] add SNI custom icon manager --- include/modules/sni/icon_manager.hpp | 43 ++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 include/modules/sni/icon_manager.hpp diff --git a/include/modules/sni/icon_manager.hpp b/include/modules/sni/icon_manager.hpp new file mode 100644 index 00000000..614d42d9 --- /dev/null +++ b/include/modules/sni/icon_manager.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#include +#include + +class IconManager { + public: + static IconManager& instance() { + static IconManager instance; + return instance; + } + + void setIconsConfig(const Json::Value& icons_config) { + if (icons_config.isObject()) { + for (const auto& key : icons_config.getMemberNames()) { + std::string app_name = key; + const Json::Value& icon_value = icons_config[key]; + + if (icon_value.isString()) { + std::string icon_path = icon_value.asString(); + icons_map_[app_name] = icon_path; + } + } + } else { + spdlog::warn("Invalid icon config format."); + } + } + + std::string getIconForApp(const std::string& app_name) const { + auto it = icons_map_.find(app_name); + if (it != icons_map_.end()) { + return it->second; + } + return ""; + } + + private: + IconManager() = default; + std::unordered_map icons_map_; +}; From 78d5c3ef3a3bd78b92eaae2a67cb624d65ab3bf5 Mon Sep 17 00:00:00 2001 From: Kaosu Date: Sun, 16 Feb 2025 14:21:34 +0100 Subject: [PATCH 154/780] init custom icons from config per tray --- src/modules/sni/tray.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/sni/tray.cpp b/src/modules/sni/tray.cpp index a2c56808..abd23ebd 100644 --- a/src/modules/sni/tray.cpp +++ b/src/modules/sni/tray.cpp @@ -1,4 +1,5 @@ #include "modules/sni/tray.hpp" +#include "modules/sni/icon_manager.hpp" #include @@ -20,6 +21,9 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) box_.set_spacing(config_["spacing"].asUInt()); } nb_hosts_ += 1; + if (config_["icons"].isObject()) { + IconManager::instance().setIconsConfig(config_["icons"]); + } dp.emit(); } From d1998de47a0e51688d29f8d08e1b1477315ccbbd Mon Sep 17 00:00:00 2001 From: Kaosu Date: Sun, 16 Feb 2025 14:22:10 +0100 Subject: [PATCH 155/780] add setCustomIcon and try to apply such when ID is known --- include/modules/sni/item.hpp | 1 + src/modules/sni/item.cpp | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/include/modules/sni/item.hpp b/include/modules/sni/item.hpp index ebc08d45..c5e86d37 100644 --- a/include/modules/sni/item.hpp +++ b/include/modules/sni/item.hpp @@ -62,6 +62,7 @@ class Item : public sigc::trackable { void proxyReady(Glib::RefPtr& result); void setProperty(const Glib::ustring& name, Glib::VariantBase& value); void setStatus(const Glib::ustring& value); + void setCustomIcon(const std::string& id); void getUpdatedProperties(); void processUpdatedProperties(Glib::RefPtr& result); void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index b3e84885..978e8b07 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -1,4 +1,5 @@ #include "modules/sni/item.hpp" +#include "modules/sni/icon_manager.hpp" #include #include @@ -7,6 +8,7 @@ #include #include +#include #include "gdk/gdk.h" #include "util/format.hpp" @@ -138,6 +140,7 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { category = get_variant(value); } else if (name == "Id") { id = get_variant(value); + setCustomIcon(id); } else if (name == "Title") { title = get_variant(value); if (tooltip.text.empty()) { @@ -199,6 +202,19 @@ void Item::setStatus(const Glib::ustring& value) { style->add_class(lower); } +void Item::setCustomIcon(const std::string& id) { + std::string custom_icon = IconManager::instance().getIconForApp(id); + if (!custom_icon.empty()) { + if (std::filesystem::exists(custom_icon)) { + Glib::RefPtr custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon); + icon_name = ""; // icon_name has priority over pixmap + icon_pixmap = custom_pixbuf; + } else { // if file doesn't exist it's most likely an icon_name + icon_name = custom_icon; + } + } +} + void Item::getUpdatedProperties() { auto params = Glib::VariantContainerBase::create_tuple( {Glib::Variant::create(SNI_INTERFACE_NAME)}); From ddf5b3e07b4b5c9484f7bb5ca81e90973e5ade27 Mon Sep 17 00:00:00 2001 From: Kaosu Date: Sun, 16 Feb 2025 14:30:08 +0100 Subject: [PATCH 156/780] add tray icons docs --- man/waybar-tray.5.scd | 6 +++++- resources/config.jsonc | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/man/waybar-tray.5.scd b/man/waybar-tray.5.scd index 381d593d..dec5347f 100644 --- a/man/waybar-tray.5.scd +++ b/man/waybar-tray.5.scd @@ -47,7 +47,11 @@ Addressed by *tray* ``` "tray": { "icon-size": 21, - "spacing": 10 + "spacing": 10, + "icons": { + "blueman": "bluetooth", + "TelegramDesktop": "$HOME/.local/share/icons/hicolor/16x16/apps/telegram.png" + } } ``` diff --git a/resources/config.jsonc b/resources/config.jsonc index 6ac1aa50..67d5ff5b 100644 --- a/resources/config.jsonc +++ b/resources/config.jsonc @@ -104,7 +104,11 @@ }, "tray": { // "icon-size": 21, - "spacing": 10 + "spacing": 10, + // "icons": { + // "blueman": "bluetooth", + // "TelegramDesktop": "$HOME/.local/share/icons/hicolor/16x16/apps/telegram.png" + // } }, "clock": { // "timezone": "America/New_York", From 4be1f3bf42328b0053ab8d4ec14e6fe6cce1148c Mon Sep 17 00:00:00 2001 From: Anthony Ruhier Date: Sun, 16 Feb 2025 18:23:59 +0100 Subject: [PATCH 157/780] fix: battery runtime estimation with negative sysfs values Some drivers (example: qualcomm-battmgr, present on Snapdragon X1 laptops) expose the current_now and power_now values in sysfs as negative int when the device is discharging, positive when charging. This breaks the battery runtime estimation in Waybar, as it expects a uint32 for power_now. Change the battery module to use the absolute values of current_now and power_now. --- src/modules/battery.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index d87cc612..44481448 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -273,14 +273,18 @@ waybar::modules::Battery::getInfos() { // Scale these by the voltage to get μW/μWh. uint32_t current_now = 0; + int32_t _current_now_int = 0; bool current_now_exists = false; if (fs::exists(bat / "current_now")) { current_now_exists = true; - std::ifstream(bat / "current_now") >> current_now; + std::ifstream(bat / "current_now") >> _current_now_int; } else if (fs::exists(bat / "current_avg")) { current_now_exists = true; - std::ifstream(bat / "current_avg") >> current_now; + std::ifstream(bat / "current_avg") >> _current_now_int; } + // Documentation ABI allows a negative value when discharging, positive + // value when charging. + current_now = std::abs(_current_now_int); if (fs::exists(bat / "time_to_empty_now")) { time_to_empty_now_exists = true; @@ -324,11 +328,15 @@ waybar::modules::Battery::getInfos() { } uint32_t power_now = 0; + int32_t _power_now_int = 0; bool power_now_exists = false; if (fs::exists(bat / "power_now")) { power_now_exists = true; - std::ifstream(bat / "power_now") >> power_now; + std::ifstream(bat / "power_now") >> _power_now_int; } + // Some drivers (example: Qualcomm) exposes use a negative value when + // discharging, positive value when charging. + power_now = std::abs(_power_now_int); uint32_t energy_now = 0; bool energy_now_exists = false; From 5b8839ab5cffb9dd8027588d749504df363569fe Mon Sep 17 00:00:00 2001 From: zjeffer <4633209+zjeffer@users.noreply.github.com> Date: Wed, 19 Feb 2025 00:58:08 +0100 Subject: [PATCH 158/780] Hyprland IPC improvements Fixes IPC being blocked at shutdown --- include/modules/hyprland/backend.hpp | 11 ++- include/modules/hyprland/language.hpp | 2 + include/modules/hyprland/submap.hpp | 2 + include/modules/hyprland/window.hpp | 2 + include/modules/hyprland/workspace.hpp | 1 + include/modules/hyprland/workspaces.hpp | 1 + src/modules/hyprland/backend.cpp | 120 ++++++++++++++---------- src/modules/hyprland/language.cpp | 12 +-- src/modules/hyprland/submap.cpp | 10 +- src/modules/hyprland/window.cpp | 99 +++++++++---------- src/modules/hyprland/workspace.cpp | 15 +-- src/modules/hyprland/workspaces.cpp | 67 +++++++------ 12 files changed, 188 insertions(+), 154 deletions(-) diff --git a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp index 11e73d8f..52f1c3a7 100644 --- a/include/modules/hyprland/backend.hpp +++ b/include/modules/hyprland/backend.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "util/json.hpp" @@ -19,7 +20,9 @@ class EventHandler { class IPC { public: - IPC() { startIPC(); } + IPC(); + ~IPC(); + static IPC& inst(); void registerForIPC(const std::string& ev, EventHandler* ev_handler); void unregisterForIPC(EventHandler* handler); @@ -32,14 +35,16 @@ class IPC { static std::filesystem::path socketFolder_; private: - void startIPC(); + void socketListener(); void parseIPC(const std::string&); + std::jthread ipcThread_; std::mutex callbackMutex_; util::JsonParser parser_; std::list> callbacks_; + int socketfd_; // the hyprland socket file descriptor + bool running_ = true; }; -inline std::unique_ptr gIPC; inline bool modulesReady = false; }; // namespace waybar::modules::hyprland diff --git a/include/modules/hyprland/language.hpp b/include/modules/hyprland/language.hpp index 47a4d69c..ec59e5c3 100644 --- a/include/modules/hyprland/language.hpp +++ b/include/modules/hyprland/language.hpp @@ -37,6 +37,8 @@ class Language : public waybar::ALabel, public EventHandler { util::JsonParser parser_; Layout layout_; + + IPC& m_ipc; }; } // namespace waybar::modules::hyprland diff --git a/include/modules/hyprland/submap.hpp b/include/modules/hyprland/submap.hpp index ce980f36..7e3425ef 100644 --- a/include/modules/hyprland/submap.hpp +++ b/include/modules/hyprland/submap.hpp @@ -28,6 +28,8 @@ class Submap : public waybar::ALabel, public EventHandler { std::string submap_; bool always_on_ = false; std::string default_submap_ = "Default"; + + IPC& m_ipc; }; } // namespace waybar::modules::hyprland diff --git a/include/modules/hyprland/window.hpp b/include/modules/hyprland/window.hpp index f2c266bd..2be64594 100644 --- a/include/modules/hyprland/window.hpp +++ b/include/modules/hyprland/window.hpp @@ -60,6 +60,8 @@ class Window : public waybar::AAppIconLabel, public EventHandler { bool swallowing_; bool fullscreen_; bool focused_; + + IPC& m_ipc; }; } // namespace waybar::modules::hyprland diff --git a/include/modules/hyprland/workspace.hpp b/include/modules/hyprland/workspace.hpp index f1fea4e8..3dedba4c 100644 --- a/include/modules/hyprland/workspace.hpp +++ b/include/modules/hyprland/workspace.hpp @@ -83,6 +83,7 @@ class Workspace { Gtk::Button m_button; Gtk::Box m_content; Gtk::Label m_label; + IPC& m_ipc; }; } // namespace waybar::modules::hyprland diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index f5c20f69..3f0252c8 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -150,6 +150,7 @@ class Workspaces : public AModule, public EventHandler { std::mutex m_mutex; const Bar& m_bar; Gtk::Box m_box; + IPC& m_ipc; }; } // namespace waybar::modules::hyprland diff --git a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp index 39341a14..0561fb61 100644 --- a/src/modules/hyprland/backend.cpp +++ b/src/modules/hyprland/backend.cpp @@ -11,7 +11,6 @@ #include #include -#include namespace waybar::modules::hyprland { @@ -44,71 +43,96 @@ std::filesystem::path IPC::getSocketFolder(const char* instanceSig) { return socketFolder_; } -void IPC::startIPC() { +IPC::IPC() { // will start IPC and relay events to parseIPC + ipcThread_ = std::jthread([this]() { socketListener(); }); +} - std::thread([&]() { - // check for hyprland - const char* his = getenv("HYPRLAND_INSTANCE_SIGNATURE"); - - if (his == nullptr) { - spdlog::warn("Hyprland is not running, Hyprland IPC will not be available."); - return; +IPC::~IPC() { + running_ = false; + spdlog::info("Hyprland IPC stopping..."); + if (socketfd_ != -1) { + spdlog::trace("Shutting down socket"); + if (shutdown(socketfd_, SHUT_RDWR) == -1) { + spdlog::error("Hyprland IPC: Couldn't shutdown socket"); } - - if (!modulesReady) return; - - spdlog::info("Hyprland IPC starting"); - - struct sockaddr_un addr; - int socketfd = socket(AF_UNIX, SOCK_STREAM, 0); - - if (socketfd == -1) { - spdlog::error("Hyprland IPC: socketfd failed"); - return; + spdlog::trace("Closing socket"); + if (close(socketfd_) == -1) { + spdlog::error("Hyprland IPC: Couldn't close socket"); } + } + ipcThread_.join(); +} - addr.sun_family = AF_UNIX; +IPC& IPC::inst() { + static IPC ipc; + return ipc; +} - auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock"; - strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1); +void IPC::socketListener() { + // check for hyprland + const char* his = getenv("HYPRLAND_INSTANCE_SIGNATURE"); - addr.sun_path[sizeof(addr.sun_path) - 1] = 0; + if (his == nullptr) { + spdlog::warn("Hyprland is not running, Hyprland IPC will not be available."); + return; + } - int l = sizeof(struct sockaddr_un); + if (!modulesReady) return; - if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) { - spdlog::error("Hyprland IPC: Unable to connect?"); - return; - } + spdlog::info("Hyprland IPC starting"); - auto* file = fdopen(socketfd, "r"); + struct sockaddr_un addr; + socketfd_ = socket(AF_UNIX, SOCK_STREAM, 0); - while (true) { - std::array buffer; // Hyprland socket2 events are max 1024 bytes + if (socketfd_ == -1) { + spdlog::error("Hyprland IPC: socketfd failed"); + return; + } - auto* receivedCharPtr = fgets(buffer.data(), buffer.size(), file); + addr.sun_family = AF_UNIX; - if (receivedCharPtr == nullptr) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - continue; - } + auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock"; + strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1); - std::string messageReceived(buffer.data()); - messageReceived = messageReceived.substr(0, messageReceived.find_first_of('\n')); - spdlog::debug("hyprland IPC received {}", messageReceived); + addr.sun_path[sizeof(addr.sun_path) - 1] = 0; - try { - parseIPC(messageReceived); - } catch (std::exception& e) { - spdlog::warn("Failed to parse IPC message: {}, reason: {}", messageReceived, e.what()); - } catch (...) { - throw; - } + int l = sizeof(struct sockaddr_un); + if (connect(socketfd_, (struct sockaddr*)&addr, l) == -1) { + spdlog::error("Hyprland IPC: Unable to connect?"); + return; + } + auto* file = fdopen(socketfd_, "r"); + if (file == nullptr) { + spdlog::error("Hyprland IPC: Couldn't open file descriptor"); + return; + } + while (running_) { + std::array buffer; // Hyprland socket2 events are max 1024 bytes + + auto* receivedCharPtr = fgets(buffer.data(), buffer.size(), file); + + if (receivedCharPtr == nullptr) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; } - }).detach(); + + std::string messageReceived(buffer.data()); + messageReceived = messageReceived.substr(0, messageReceived.find_first_of('\n')); + spdlog::debug("hyprland IPC received {}", messageReceived); + + try { + parseIPC(messageReceived); + } catch (std::exception& e) { + spdlog::warn("Failed to parse IPC message: {}, reason: {}", messageReceived, e.what()); + } catch (...) { + throw; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + spdlog::debug("Hyprland IPC stopped"); } void IPC::parseIPC(const std::string& ev) { diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index d86393af..da56e578 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -10,13 +10,9 @@ namespace waybar::modules::hyprland { Language::Language(const std::string& id, const Bar& bar, const Json::Value& config) - : ALabel(config, "language", id, "{}", 0, true), bar_(bar) { + : ALabel(config, "language", id, "{}", 0, true), bar_(bar), m_ipc(IPC::inst()) { modulesReady = true; - if (!gIPC) { - gIPC = std::make_unique(); - } - // get the active layout when open initLanguage(); @@ -24,11 +20,11 @@ Language::Language(const std::string& id, const Bar& bar, const Json::Value& con update(); // register for hyprland ipc - gIPC->registerForIPC("activelayout", this); + m_ipc.registerForIPC("activelayout", this); } Language::~Language() { - gIPC->unregisterForIPC(this); + m_ipc.unregisterForIPC(this); // wait for possible event handler to finish std::lock_guard lg(mutex_); } @@ -85,7 +81,7 @@ void Language::onEvent(const std::string& ev) { } void Language::initLanguage() { - const auto inputDevices = gIPC->getSocket1Reply("devices"); + const auto inputDevices = m_ipc.getSocket1Reply("devices"); const auto kbName = config_["keyboard-name"].asString(); diff --git a/src/modules/hyprland/submap.cpp b/src/modules/hyprland/submap.cpp index 96677d12..97c4bb62 100644 --- a/src/modules/hyprland/submap.cpp +++ b/src/modules/hyprland/submap.cpp @@ -7,15 +7,11 @@ namespace waybar::modules::hyprland { Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config) - : ALabel(config, "submap", id, "{}", 0, true), bar_(bar) { + : ALabel(config, "submap", id, "{}", 0, true), bar_(bar), m_ipc(IPC::inst()) { modulesReady = true; parseConfig(config); - if (!gIPC) { - gIPC = std::make_unique(); - } - label_.hide(); ALabel::update(); @@ -27,12 +23,12 @@ Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config) } // register for hyprland ipc - gIPC->registerForIPC("submap", this); + m_ipc.registerForIPC("submap", this); dp.emit(); } Submap::~Submap() { - gIPC->unregisterForIPC(this); + m_ipc.unregisterForIPC(this); // wait for possible event handler to finish std::lock_guard lg(mutex_); } diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index 08c99363..874d619c 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -19,22 +19,18 @@ namespace waybar::modules::hyprland { std::shared_mutex windowIpcSmtx; Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) - : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) { + : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), m_ipc(IPC::inst()) { std::unique_lock windowIpcUniqueLock(windowIpcSmtx); modulesReady = true; separateOutputs_ = config["separate-outputs"].asBool(); - if (!gIPC) { - gIPC = std::make_unique(); - } - // register for hyprland ipc - gIPC->registerForIPC("activewindow", this); - gIPC->registerForIPC("closewindow", this); - gIPC->registerForIPC("movewindow", this); - gIPC->registerForIPC("changefloatingmode", this); - gIPC->registerForIPC("fullscreen", this); + m_ipc.registerForIPC("activewindow", this); + m_ipc.registerForIPC("closewindow", this); + m_ipc.registerForIPC("movewindow", this); + m_ipc.registerForIPC("changefloatingmode", this); + m_ipc.registerForIPC("fullscreen", this); windowIpcUniqueLock.unlock(); @@ -45,7 +41,7 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) Window::~Window() { std::unique_lock windowIpcUniqueLock(windowIpcSmtx); - gIPC->unregisterForIPC(this); + m_ipc.unregisterForIPC(this); } auto Window::update() -> void { @@ -114,7 +110,7 @@ auto Window::update() -> void { } auto Window::getActiveWorkspace() -> Workspace { - const auto workspace = gIPC->getSocket1JsonReply("activeworkspace"); + const auto workspace = IPC::inst().getSocket1JsonReply("activeworkspace"); if (workspace.isObject()) { return Workspace::parse(workspace); @@ -124,24 +120,33 @@ auto Window::getActiveWorkspace() -> Workspace { } auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace { - const auto monitors = gIPC->getSocket1JsonReply("monitors"); + const auto monitors = IPC::inst().getSocket1JsonReply("monitors"); if (monitors.isArray()) { - auto monitor = std::find_if(monitors.begin(), monitors.end(), [&](Json::Value monitor) { - return monitor["name"] == monitorName; - }); + auto monitor = std::ranges::find_if( + monitors, [&](Json::Value monitor) { return monitor["name"] == monitorName; }); if (monitor == std::end(monitors)) { spdlog::warn("Monitor not found: {}", monitorName); - return Workspace{-1, 0, "", ""}; + return Workspace{ + .id = -1, + .windows = 0, + .last_window = "", + .last_window_title = "", + }; } const int id = (*monitor)["activeWorkspace"]["id"].asInt(); - const auto workspaces = gIPC->getSocket1JsonReply("workspaces"); + const auto workspaces = IPC::inst().getSocket1JsonReply("workspaces"); if (workspaces.isArray()) { - auto workspace = std::find_if(workspaces.begin(), workspaces.end(), - [&](Json::Value workspace) { return workspace["id"] == id; }); + auto workspace = std::ranges::find_if( + workspaces, [&](Json::Value workspace) { return workspace["id"] == id; }); if (workspace == std::end(workspaces)) { spdlog::warn("No workspace with id {}", id); - return Workspace{-1, 0, "", ""}; + return Workspace{ + .id = -1, + .windows = 0, + .last_window = "", + .last_window_title = "", + }; } return Workspace::parse(*workspace); }; @@ -152,18 +157,22 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace { auto Window::Workspace::parse(const Json::Value& value) -> Window::Workspace { return Workspace{ - value["id"].asInt(), - value["windows"].asInt(), - value["lastwindow"].asString(), - value["lastwindowtitle"].asString(), + .id = value["id"].asInt(), + .windows = value["windows"].asInt(), + .last_window = value["lastwindow"].asString(), + .last_window_title = value["lastwindowtitle"].asString(), }; } auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData { - return WindowData{value["floating"].asBool(), value["monitor"].asInt(), - value["class"].asString(), value["initialClass"].asString(), - value["title"].asString(), value["initialTitle"].asString(), - value["fullscreen"].asBool(), !value["grouped"].empty()}; + return WindowData{.floating = value["floating"].asBool(), + .monitor = value["monitor"].asInt(), + .class_name = value["class"].asString(), + .initial_class_name = value["initialClass"].asString(), + .title = value["title"].asString(), + .initial_title = value["initialTitle"].asString(), + .fullscreen = value["fullscreen"].asBool(), + .grouped = !value["grouped"].empty()}; } void Window::queryActiveWorkspace() { @@ -177,11 +186,10 @@ void Window::queryActiveWorkspace() { focused_ = true; if (workspace_.windows > 0) { - const auto clients = gIPC->getSocket1JsonReply("clients"); + const auto clients = m_ipc.getSocket1JsonReply("clients"); if (clients.isArray()) { - auto activeWindow = std::find_if(clients.begin(), clients.end(), [&](Json::Value window) { - return window["address"] == workspace_.last_window; - }); + auto activeWindow = std::ranges::find_if( + clients, [&](Json::Value window) { return window["address"] == workspace_.last_window; }); if (activeWindow == std::end(clients)) { focused_ = false; @@ -191,22 +199,19 @@ void Window::queryActiveWorkspace() { windowData_ = WindowData::parse(*activeWindow); updateAppIconName(windowData_.class_name, windowData_.initial_class_name); std::vector workspaceWindows; - std::copy_if(clients.begin(), clients.end(), std::back_inserter(workspaceWindows), - [&](Json::Value window) { - return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool(); - }); - swallowing_ = - std::any_of(workspaceWindows.begin(), workspaceWindows.end(), [&](Json::Value window) { - return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0"; - }); + std::ranges::copy_if(clients, std::back_inserter(workspaceWindows), [&](Json::Value window) { + return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool(); + }); + swallowing_ = std::ranges::any_of(workspaceWindows, [&](Json::Value window) { + return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0"; + }); std::vector visibleWindows; - std::copy_if(workspaceWindows.begin(), workspaceWindows.end(), - std::back_inserter(visibleWindows), - [&](Json::Value window) { return !window["hidden"].asBool(); }); + std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows), + [&](Json::Value window) { return !window["hidden"].asBool(); }); solo_ = 1 == std::count_if(visibleWindows.begin(), visibleWindows.end(), [&](Json::Value window) { return !window["floating"].asBool(); }); - allFloating_ = std::all_of(visibleWindows.begin(), visibleWindows.end(), - [&](Json::Value window) { return window["floating"].asBool(); }); + allFloating_ = std::ranges::all_of( + visibleWindows, [&](Json::Value window) { return window["floating"].asBool(); }); fullscreen_ = windowData_.fullscreen; // Fullscreen windows look like they are solo @@ -219,7 +224,7 @@ void Window::queryActiveWorkspace() { } else { soloClass_ = ""; } - }; + } } else { focused_ = false; windowData_ = WindowData{}; diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index e575d1c4..4655096f 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -18,7 +18,8 @@ Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_ma m_windows(workspace_data["windows"].asInt()), m_isActive(true), m_isPersistentRule(workspace_data["persistent-rule"].asBool()), - m_isPersistentConfig(workspace_data["persistent-config"].asBool()) { + m_isPersistentConfig(workspace_data["persistent-config"].asBool()), + m_ipc(IPC::inst()) { if (m_name.starts_with("name:")) { m_name = m_name.substr(5); } else if (m_name.starts_with("special")) { @@ -58,20 +59,20 @@ bool Workspace::handleClicked(GdkEventButton *bt) const { try { if (id() > 0) { // normal if (m_workspaceManager.moveToMonitor()) { - gIPC->getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id())); + m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id())); } else { - gIPC->getSocket1Reply("dispatch workspace " + std::to_string(id())); + m_ipc.getSocket1Reply("dispatch workspace " + std::to_string(id())); } } else if (!isSpecial()) { // named (this includes persistent) if (m_workspaceManager.moveToMonitor()) { - gIPC->getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name()); + m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name()); } else { - gIPC->getSocket1Reply("dispatch workspace name:" + name()); + m_ipc.getSocket1Reply("dispatch workspace name:" + name()); } } else if (id() != -99) { // named special - gIPC->getSocket1Reply("dispatch togglespecialworkspace " + name()); + m_ipc.getSocket1Reply("dispatch togglespecialworkspace " + name()); } else { // special - gIPC->getSocket1Reply("dispatch togglespecialworkspace"); + m_ipc.getSocket1Reply("dispatch togglespecialworkspace"); } return true; } catch (const std::exception &e) { diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 047703cc..02d85cd9 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -13,7 +13,10 @@ namespace waybar::modules::hyprland { Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) - : AModule(config, "workspaces", id, false, false), m_bar(bar), m_box(bar.orientation, 0) { + : AModule(config, "workspaces", id, false, false), + m_bar(bar), + m_box(bar.orientation, 0), + m_ipc(IPC::inst()) { modulesReady = true; parseConfig(config); @@ -24,23 +27,19 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value m_box.get_style_context()->add_class(MODULE_CLASS); event_box_.add(m_box); - if (!gIPC) { - gIPC = std::make_unique(); - } - setCurrentMonitorId(); init(); registerIpc(); } Workspaces::~Workspaces() { - gIPC->unregisterForIPC(this); + m_ipc.unregisterForIPC(this); // wait for possible event handler to finish std::lock_guard lg(m_mutex); } void Workspaces::init() { - m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString(); + m_activeWorkspaceName = (m_ipc.getSocket1JsonReply("activeworkspace"))["name"].asString(); initializeWorkspaces(); dp.emit(); @@ -161,7 +160,7 @@ std::string Workspaces::getRewrite(std::string window_class, std::string window_ std::vector Workspaces::getVisibleWorkspaces() { std::vector visibleWorkspaces; - auto monitors = gIPC->getSocket1JsonReply("monitors"); + auto monitors = IPC::inst().getSocket1JsonReply("monitors"); for (const auto &monitor : monitors) { auto ws = monitor["activeWorkspace"]; if (ws.isObject() && ws["name"].isString()) { @@ -185,8 +184,8 @@ void Workspaces::initializeWorkspaces() { } // get all current workspaces - auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces"); - auto const clientsJson = gIPC->getSocket1JsonReply("clients"); + auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); + auto const clientsJson = m_ipc.getSocket1JsonReply("clients"); for (Json::Value workspaceJson : workspacesJson) { std::string workspaceName = workspaceJson["name"].asString(); @@ -285,7 +284,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &clientsJson) { spdlog::info("Loading persistent workspaces from Hyprland workspace rules"); - auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules"); + auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules"); for (Json::Value const &rule : workspaceRules) { if (!rule["workspaceString"].isString()) { spdlog::warn("Workspace rules: invalid workspaceString, skipping: {}", rule); @@ -365,10 +364,10 @@ void Workspaces::onWorkspaceDestroyed(std::string const &payload) { void Workspaces::onWorkspaceCreated(std::string const &workspaceName, Json::Value const &clientsData) { spdlog::debug("Workspace created: {}", workspaceName); - auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces"); + auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); if (!isWorkspaceIgnored(workspaceName)) { - auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules"); + auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules"); for (Json::Value workspaceJson : workspacesJson) { std::string name = workspaceJson["name"].asString(); if (name == workspaceName) { @@ -397,7 +396,7 @@ void Workspaces::onWorkspaceMoved(std::string const &payload) { spdlog::debug("Workspace moved: {}", payload); // Update active workspace - m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString(); + m_activeWorkspaceName = (m_ipc.getSocket1JsonReply("activeworkspace"))["name"].asString(); if (allOutputs()) return; @@ -405,7 +404,7 @@ void Workspaces::onWorkspaceMoved(std::string const &payload) { std::string monitorName = payload.substr(payload.find(',') + 1); if (m_bar.output->name == monitorName) { - Json::Value clientsData = gIPC->getSocket1JsonReply("clients"); + Json::Value clientsData = m_ipc.getSocket1JsonReply("clients"); onWorkspaceCreated(workspaceName, clientsData); } else { spdlog::debug("Removing workspace because it was moved to another monitor: {}"); @@ -434,7 +433,7 @@ void Workspaces::onMonitorFocused(std::string const &payload) { spdlog::trace("Monitor focused: {}", payload); m_activeWorkspaceName = payload.substr(payload.find(',') + 1); - for (Json::Value &monitor : gIPC->getSocket1JsonReply("monitors")) { + for (Json::Value &monitor : m_ipc.getSocket1JsonReply("monitors")) { if (monitor["name"].asString() == payload.substr(0, payload.find(','))) { auto name = monitor["specialWorkspace"]["name"].asString(); m_activeSpecialWorkspaceName = !name.starts_with("special:") ? name : name.substr(8); @@ -542,7 +541,7 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { } if (inserter.has_value()) { - Json::Value clientsData = gIPC->getSocket1JsonReply("clients"); + Json::Value clientsData = m_ipc.getSocket1JsonReply("clients"); std::string jsonWindowAddress = fmt::format("0x{}", payload); auto client = @@ -660,24 +659,24 @@ void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payloa } auto Workspaces::registerIpc() -> void { - gIPC->registerForIPC("workspace", this); - gIPC->registerForIPC("activespecial", this); - gIPC->registerForIPC("createworkspace", this); - gIPC->registerForIPC("destroyworkspace", this); - gIPC->registerForIPC("focusedmon", this); - gIPC->registerForIPC("moveworkspace", this); - gIPC->registerForIPC("renameworkspace", this); - gIPC->registerForIPC("openwindow", this); - gIPC->registerForIPC("closewindow", this); - gIPC->registerForIPC("movewindow", this); - gIPC->registerForIPC("urgent", this); - gIPC->registerForIPC("configreloaded", this); + m_ipc.registerForIPC("workspace", this); + m_ipc.registerForIPC("activespecial", this); + m_ipc.registerForIPC("createworkspace", this); + m_ipc.registerForIPC("destroyworkspace", this); + m_ipc.registerForIPC("focusedmon", this); + m_ipc.registerForIPC("moveworkspace", this); + m_ipc.registerForIPC("renameworkspace", this); + m_ipc.registerForIPC("openwindow", this); + m_ipc.registerForIPC("closewindow", this); + m_ipc.registerForIPC("movewindow", this); + m_ipc.registerForIPC("urgent", this); + m_ipc.registerForIPC("configreloaded", this); if (windowRewriteConfigUsesTitle()) { spdlog::info( "Registering for Hyprland's 'windowtitle' events because a user-defined window " "rewrite rule uses the 'title' field."); - gIPC->registerForIPC("windowtitle", this); + m_ipc.registerForIPC("windowtitle", this); } } @@ -712,7 +711,7 @@ void Workspaces::removeWorkspace(std::string const &name) { void Workspaces::setCurrentMonitorId() { // get monitor ID from name (used by persistent workspaces) m_monitorId = 0; - auto monitors = gIPC->getSocket1JsonReply("monitors"); + auto monitors = m_ipc.getSocket1JsonReply("monitors"); auto currentMonitor = std::find_if( monitors.begin(), monitors.end(), [this](const Json::Value &m) { return m["name"].asString() == m_bar.output->name; }); @@ -788,7 +787,7 @@ void Workspaces::sortWorkspaces() { } void Workspaces::setUrgentWorkspace(std::string const &windowaddress) { - const Json::Value clientsJson = gIPC->getSocket1JsonReply("clients"); + const Json::Value clientsJson = m_ipc.getSocket1JsonReply("clients"); int workspaceId = -1; for (Json::Value clientJson : clientsJson) { @@ -812,7 +811,7 @@ auto Workspaces::update() -> void { } void Workspaces::updateWindowCount() { - const Json::Value workspacesJson = gIPC->getSocket1JsonReply("workspaces"); + const Json::Value workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); for (auto &workspace : m_workspaces) { auto workspaceJson = std::find_if(workspacesJson.begin(), workspacesJson.end(), [&](Json::Value const &x) { @@ -859,7 +858,7 @@ bool Workspaces::updateWindowsToCreate() { void Workspaces::updateWorkspaceStates() { const std::vector visibleWorkspaces = getVisibleWorkspaces(); - auto updatedWorkspaces = gIPC->getSocket1JsonReply("workspaces"); + auto updatedWorkspaces = m_ipc.getSocket1JsonReply("workspaces"); for (auto &workspace : m_workspaces) { workspace->setActive(workspace->name() == m_activeWorkspaceName || workspace->name() == m_activeSpecialWorkspaceName); From a4989cedae1990dfe79a7ace4165ba234fc8f282 Mon Sep 17 00:00:00 2001 From: zjeffer <4633209+zjeffer@users.noreply.github.com> Date: Wed, 19 Feb 2025 21:02:59 +0100 Subject: [PATCH 159/780] formatting --- src/modules/hyprland/window.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index 874d619c..6d4331ad 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -72,11 +72,12 @@ auto Window::update() -> void { tooltip_format = config_["tooltip-format"].asString(); } if (!tooltip_format.empty()) { - label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format), fmt::arg("title", windowName), - fmt::arg("initialTitle", windowData_.initial_title), - fmt::arg("class", windowData_.class_name), - fmt::arg("initialClass", windowData_.initial_class_name))); - } else if (!label_text.empty()){ + label_.set_tooltip_text( + fmt::format(fmt::runtime(tooltip_format), fmt::arg("title", windowName), + fmt::arg("initialTitle", windowData_.initial_title), + fmt::arg("class", windowData_.class_name), + fmt::arg("initialClass", windowData_.initial_class_name))); + } else if (!label_text.empty()) { label_.set_tooltip_text(label_text); } } From 37c6cd42f5ddb0d8e3d59fc5349f5b126e5cd6bf Mon Sep 17 00:00:00 2001 From: zjeffer <4633209+zjeffer@users.noreply.github.com> Date: Wed, 19 Feb 2025 21:15:18 +0100 Subject: [PATCH 160/780] fix freebsd compilation --- include/modules/hyprland/backend.hpp | 3 +-- src/modules/hyprland/backend.cpp | 2 +- src/modules/hyprland/window.cpp | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp index 52f1c3a7..cfd0b258 100644 --- a/include/modules/hyprland/backend.hpp +++ b/include/modules/hyprland/backend.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -38,7 +37,7 @@ class IPC { void socketListener(); void parseIPC(const std::string&); - std::jthread ipcThread_; + std::thread ipcThread_; std::mutex callbackMutex_; util::JsonParser parser_; std::list> callbacks_; diff --git a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp index 0561fb61..2bd3b509 100644 --- a/src/modules/hyprland/backend.cpp +++ b/src/modules/hyprland/backend.cpp @@ -45,7 +45,7 @@ std::filesystem::path IPC::getSocketFolder(const char* instanceSig) { IPC::IPC() { // will start IPC and relay events to parseIPC - ipcThread_ = std::jthread([this]() { socketListener(); }); + ipcThread_ = std::thread([this]() { socketListener(); }); } IPC::~IPC() { diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index 6d4331ad..815fbad8 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include "modules/hyprland/backend.hpp" From bcee548f5e40ca5721e25e7697f5f35d57df09e6 Mon Sep 17 00:00:00 2001 From: zjeffer <4633209+zjeffer@users.noreply.github.com> Date: Thu, 20 Feb 2025 00:06:05 +0100 Subject: [PATCH 161/780] Fix workspacerules not taking into account defaultName --- src/modules/hyprland/workspaces.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 047703cc..66851c22 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -294,7 +294,8 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c if (!rule["persistent"].asBool()) { continue; } - auto const &workspace = rule["workspaceString"].asString(); + auto const &workspace = rule.isMember("defaultName") ? rule["defaultName"].asString() + : rule["workspaceString"].asString(); auto const &monitor = rule["monitor"].asString(); // create this workspace persistently if: // 1. the allOutputs config option is enabled @@ -375,7 +376,10 @@ void Workspaces::onWorkspaceCreated(std::string const &workspaceName, if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && (showSpecial() || !name.starts_with("special")) && !isDoubleSpecial(workspaceName)) { for (Json::Value const &rule : workspaceRules) { - if (rule["workspaceString"].asString() == workspaceName) { + auto ruleWorkspaceName = rule.isMember("defaultName") + ? rule["defaultName"].asString() + : rule["workspaceString"].asString(); + if (ruleWorkspaceName == workspaceName) { workspaceJson["persistent-rule"] = rule["persistent"].asBool(); break; } From a3ee5f1125b6fc5de17a255f55b85f21d16bbf2b Mon Sep 17 00:00:00 2001 From: Alexis Rouillard Date: Fri, 21 Feb 2025 09:04:22 +0100 Subject: [PATCH 162/780] Update clang-format.yml --- .github/workflows/clang-format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 4a774dbd..3c62819e 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -17,4 +17,4 @@ jobs: source: "." extensions: "hpp,h,cpp,c" style: "file:.clang-format" - clangFormatVersion: 18 + clangFormatVersion: 19 From 8490a1d9b9a5c8770dd63e9faca0a5e01acedcae Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 21 Feb 2025 09:04:53 +0100 Subject: [PATCH 163/780] chore: 0.12.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 726d492b..5524b49c 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'waybar', 'cpp', 'c', - version: '0.11.0', + version: '0.12.0', license: 'MIT', meson_version: '>= 0.59.0', default_options : [ From 212c676251d44939d1f89dc3abbb50e8f75f8588 Mon Sep 17 00:00:00 2001 From: Harishankar G Date: Wed, 26 Feb 2025 15:59:33 +0530 Subject: [PATCH 164/780] Provide an option to show ipv4 or ipv6 or both of them --- include/modules/network.hpp | 5 ++++ man/waybar-network.5.scd | 2 +- src/modules/network.cpp | 54 ++++++++++++++++++++++++++++++++----- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index df0ba9c3..dcf7d499 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -16,6 +16,8 @@ #include "util/rfkill.hpp" #endif +enum ip_addr_pref : uint8_t { IPV4, IPV6, IPV4_6 }; + namespace waybar::modules { class Network : public ALabel { @@ -50,6 +52,7 @@ class Network : public ALabel { std::optional> readBandwidthUsage(); int ifid_; + ip_addr_pref addr_pref_; struct sockaddr_nl nladdr_ = {0}; struct nl_sock* sock_ = nullptr; struct nl_sock* ev_sock_ = nullptr; @@ -73,9 +76,11 @@ class Network : public ALabel { bool carrier_; std::string ifname_; std::string ipaddr_; + std::string ipaddr6_; std::string gwaddr_; std::string netmask_; int cidr_; + int cidr6_; int32_t signal_strength_dbm_; uint8_t signal_strength_; std::string signal_strength_app_; diff --git a/man/waybar-network.5.scd b/man/waybar-network.5.scd index ee409d0a..c2dfc544 100644 --- a/man/waybar-network.5.scd +++ b/man/waybar-network.5.scd @@ -24,7 +24,7 @@ Addressed by *network* *family*: ++ typeof: string ++ default: *ipv4* ++ - The address family that is used for the format replacement {ipaddr} and to determine if a network connection is present. + The address family that is used for the format replacement {ipaddr} and to determine if a network connection is present. Set it to ipv4_6 to display both. *format*: ++ typeof: string ++ diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 393b4296..ce40340c 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -80,6 +80,7 @@ waybar::modules::Network::readBandwidthUsage() { waybar::modules::Network::Network(const std::string &id, const Json::Value &config) : ALabel(config, "network", id, DEFAULT_FORMAT, 60), ifid_(-1), + addr_pref_(IPV4), efd_(-1), ev_fd_(-1), want_route_dump_(false), @@ -88,6 +89,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf dump_in_progress_(false), is_p2p_(false), cidr_(0), + cidr6_(0), signal_strength_dbm_(0), signal_strength_(0), #ifdef WANT_RFKILL @@ -101,6 +103,12 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf // the module start with no text, but the event_box_ is shown. label_.set_markup(""); + if (config_["family"] == "ipv6") { + addr_pref_ = IPV6; + } else if (config["family"] == "ipv4_6") { + addr_pref_ = IPV4_6; + } + auto bandwidth = readBandwidthUsage(); if (bandwidth.has_value()) { bandwidth_down_total_ = (*bandwidth).first; @@ -270,7 +278,7 @@ const std::string waybar::modules::Network::getNetworkState() const { return "disconnected"; } if (!carrier_) return "disconnected"; - if (ipaddr_.empty()) return "linked"; + if (ipaddr_.empty() && ipaddr6_.empty()) return "linked"; if (essid_.empty()) return "ethernet"; return "wifi"; } @@ -316,11 +324,22 @@ auto waybar::modules::Network::update() -> void { } getState(signal_strength_); + std::string final_ipaddr_; + if (addr_pref_ == ip_addr_pref::IPV4) { + final_ipaddr_ = ipaddr_; + } else if (addr_pref_ == ip_addr_pref::IPV6) { + final_ipaddr_ = ipaddr6_; + } else if (addr_pref_ == ip_addr_pref::IPV4_6) { + final_ipaddr_ = ipaddr_; + final_ipaddr_ += " | "; + final_ipaddr_ += ipaddr6_; + } + auto text = fmt::format( fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), - fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), + fmt::arg("netmask", netmask_), fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), 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")), @@ -352,8 +371,9 @@ auto waybar::modules::Network::update() -> void { fmt::runtime(tooltip_format), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), - fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), - fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), + fmt::arg("netmask", netmask_), fmt::arg("ipaddr", final_ipaddr_), + fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), + 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")), @@ -394,10 +414,12 @@ void waybar::modules::Network::clearIface() { essid_.clear(); bssid_.clear(); ipaddr_.clear(); + ipaddr6_.clear(); gwaddr_.clear(); netmask_.clear(); carrier_ = false; cidr_ = 0; + cidr6_ = 0; signal_strength_dbm_ = 0; signal_strength_ = 0; signal_strength_app_.clear(); @@ -516,12 +538,16 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { return NL_OK; } + if ((ifa->ifa_family != AF_INET && net->addr_pref_ == ip_addr_pref::IPV4) || + (ifa->ifa_family != AF_INET6 && net->addr_pref_ == ip_addr_pref::IPV6)) { + return NL_OK; + } + // We ignore address mark as scope for the link or host, // which should leave scope global addresses. if (ifa->ifa_scope >= RT_SCOPE_LINK) { return NL_OK; } - for (; RTA_OK(ifa_rta, attrlen); ifa_rta = RTA_NEXT(ifa_rta, attrlen)) { switch (ifa_rta->rta_type) { case IFA_ADDRESS: @@ -529,8 +555,20 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { case IFA_LOCAL: char ipaddr[INET6_ADDRSTRLEN]; if (!is_del_event) { - net->ipaddr_ = inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)); - net->cidr_ = ifa->ifa_prefixlen; + if ((net->addr_pref_ == ip_addr_pref::IPV4 || + net->addr_pref_ == ip_addr_pref::IPV4_6) && + net->cidr_ == 0 && ifa->ifa_family == AF_INET) { + net->ipaddr_ = + inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)); + net->cidr_ = ifa->ifa_prefixlen; + } else if ((net->addr_pref_ == ip_addr_pref::IPV6 || + net->addr_pref_ == ip_addr_pref::IPV4_6) && + net->cidr6_ == 0 && ifa->ifa_family == AF_INET6) { + net->ipaddr6_ = + inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)); + net->cidr6_ = ifa->ifa_prefixlen; + } + switch (ifa->ifa_family) { case AF_INET: { struct in_addr netmask; @@ -551,7 +589,9 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { spdlog::debug("network: {}, new addr {}/{}", net->ifname_, net->ipaddr_, net->cidr_); } else { net->ipaddr_.clear(); + net->ipaddr6_.clear(); net->cidr_ = 0; + net->cidr6_ = 0; net->netmask_.clear(); spdlog::debug("network: {} addr deleted {}/{}", net->ifname_, inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)), From 8bd0285c889244c7c730053c111c8a2431dc64e0 Mon Sep 17 00:00:00 2001 From: Harishankar G Date: Wed, 26 Feb 2025 16:06:58 +0530 Subject: [PATCH 165/780] Remove redundant if condition --- src/modules/network.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index ce40340c..2f5d19c8 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -538,11 +538,6 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { return NL_OK; } - if ((ifa->ifa_family != AF_INET && net->addr_pref_ == ip_addr_pref::IPV4) || - (ifa->ifa_family != AF_INET6 && net->addr_pref_ == ip_addr_pref::IPV6)) { - return NL_OK; - } - // We ignore address mark as scope for the link or host, // which should leave scope global addresses. if (ifa->ifa_scope >= RT_SCOPE_LINK) { From 26a344b131d23caf972955fd7aac9550a5082d27 Mon Sep 17 00:00:00 2001 From: Matt White Date: Fri, 10 Jan 2025 10:01:40 -0700 Subject: [PATCH 166/780] feat(hyprland): support createworkspacev2 --- src/modules/hyprland/workspaces.cpp | 39 ++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index ef057d6d..03a7c44e 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -322,7 +322,7 @@ void Workspaces::onEvent(const std::string &ev) { onSpecialWorkspaceActivated(payload); } else if (eventName == "destroyworkspace") { onWorkspaceDestroyed(payload); - } else if (eventName == "createworkspace") { + } else if (eventName == "createworkspacev2") { onWorkspaceCreated(payload); } else if (eventName == "focusedmon") { onMonitorFocused(payload); @@ -362,18 +362,31 @@ void Workspaces::onWorkspaceDestroyed(std::string const &payload) { } } -void Workspaces::onWorkspaceCreated(std::string const &workspaceName, +void Workspaces::onWorkspaceCreated(std::string const &payload, Json::Value const &clientsData) { - spdlog::debug("Workspace created: {}", workspaceName); - auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); + spdlog::debug("Workspace created: {}", payload); + std::string workspaceIdStr = payload.substr(0, payload.find(',')); + std::string workspaceName = payload.substr(workspaceIdStr.size() + 1); + int workspaceId = std::stoi(workspaceIdStr); - if (!isWorkspaceIgnored(workspaceName)) { - auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules"); + auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces"); + + auto workspaceIgnored = isWorkspaceIgnored(workspaceName); + if (!workspaceIgnored) { + auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules"); for (Json::Value workspaceJson : workspacesJson) { - std::string name = workspaceJson["name"].asString(); - if (name == workspaceName) { + int currentId = workspaceJson["id"].asInt(); + std::string currentName = workspaceJson["name"].asString(); + if (currentId == workspaceId) { + // The workspace may have been renamed since creation + // Check if the configured ignore rules apply to the new name + workspaceIgnored = isWorkspaceIgnored(currentName); + if (workspaceIgnored) { + break; + } + if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && - (showSpecial() || !name.starts_with("special")) && !isDoubleSpecial(workspaceName)) { + (showSpecial() || !currentName.starts_with("special")) && !isDoubleSpecial(currentName)) { for (Json::Value const &rule : workspaceRules) { auto ruleWorkspaceName = rule.isMember("defaultName") ? rule["defaultName"].asString() @@ -388,11 +401,13 @@ void Workspaces::onWorkspaceCreated(std::string const &workspaceName, break; } } else { - extendOrphans(workspaceJson["id"].asInt(), clientsData); + extendOrphans(workspaceId, clientsData); } } - } else { - spdlog::trace("Not creating workspace because it is ignored: {}", workspaceName); + } + if (workspaceIgnored) { + spdlog::trace("Not creating workspace because it is ignored: id={} name={}", workspaceId, + workspaceName); } } From 0c6ca8321c9d083c3a2ae73f0f94b2193a1619ef Mon Sep 17 00:00:00 2001 From: Matt White Date: Fri, 10 Jan 2025 11:27:40 -0700 Subject: [PATCH 167/780] feat(hyprland): support destroyworkspacev2 --- src/modules/hyprland/workspaces.cpp | 81 +++++++++++++++++------------ 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 03a7c44e..894d77a8 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -180,7 +180,7 @@ void Workspaces::initializeWorkspaces() { // if the workspace rules changed since last initialization, make sure we reset everything: for (auto &workspace : m_workspaces) { - m_workspacesToRemove.push_back(workspace->name()); + m_workspacesToRemove.push_back(std::to_string(workspace->id())); } // get all current workspaces @@ -320,7 +320,7 @@ void Workspaces::onEvent(const std::string &ev) { onWorkspaceActivated(payload); } else if (eventName == "activespecial") { onSpecialWorkspaceActivated(payload); - } else if (eventName == "destroyworkspace") { + } else if (eventName == "destroyworkspacev2") { onWorkspaceDestroyed(payload); } else if (eventName == "createworkspacev2") { onWorkspaceCreated(payload); @@ -357,8 +357,10 @@ void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) { } void Workspaces::onWorkspaceDestroyed(std::string const &payload) { - if (!isDoubleSpecial(payload)) { - m_workspacesToRemove.push_back(payload); + std::string workspaceIdStr = payload.substr(0, payload.find(',')); + std::string workspaceName = payload.substr(workspaceIdStr.size() + 1); + if (!isDoubleSpecial(workspaceName)) { + m_workspacesToRemove.push_back(workspaceIdStr); } } @@ -366,24 +368,21 @@ void Workspaces::onWorkspaceCreated(std::string const &payload, Json::Value const &clientsData) { spdlog::debug("Workspace created: {}", payload); std::string workspaceIdStr = payload.substr(0, payload.find(',')); - std::string workspaceName = payload.substr(workspaceIdStr.size() + 1); int workspaceId = std::stoi(workspaceIdStr); + auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules"); auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces"); - auto workspaceIgnored = isWorkspaceIgnored(workspaceName); - if (!workspaceIgnored) { - auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules"); - for (Json::Value workspaceJson : workspacesJson) { - int currentId = workspaceJson["id"].asInt(); - std::string currentName = workspaceJson["name"].asString(); - if (currentId == workspaceId) { - // The workspace may have been renamed since creation - // Check if the configured ignore rules apply to the new name - workspaceIgnored = isWorkspaceIgnored(currentName); - if (workspaceIgnored) { - break; - } + for (Json::Value workspaceJson : workspacesJson) { + int currentId = workspaceJson["id"].asInt(); + if (currentId == workspaceId) { + std::string name = workspaceJson["name"].asString(); + // This workspace name is more up-to-date than the one in the event payload. + if (isWorkspaceIgnored(name)) { + spdlog::trace("Not creating workspace because it is ignored: id={} name={}", workspaceId, + name); + break; + } if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && (showSpecial() || !currentName.starts_with("special")) && !isDoubleSpecial(currentName)) { @@ -397,18 +396,13 @@ void Workspaces::onWorkspaceCreated(std::string const &payload, } } - m_workspacesToCreate.emplace_back(workspaceJson, clientsData); - break; - } - } else { - extendOrphans(workspaceId, clientsData); + m_workspacesToCreate.emplace_back(workspaceJson, clientsData); + break; } + } else { + extendOrphans(workspaceId, clientsData); } } - if (workspaceIgnored) { - spdlog::trace("Not creating workspace because it is ignored: id={} name={}", workspaceId, - workspaceName); - } } void Workspaces::onWorkspaceMoved(std::string const &payload) { @@ -700,17 +694,38 @@ auto Workspaces::registerIpc() -> void { } void Workspaces::removeWorkspacesToRemove() { - for (const auto &workspaceName : m_workspacesToRemove) { - removeWorkspace(workspaceName); + for (const auto &workspaceString: m_workspacesToRemove) { + removeWorkspace(workspaceString); } m_workspacesToRemove.clear(); } -void Workspaces::removeWorkspace(std::string const &name) { - spdlog::debug("Removing workspace {}", name); +void Workspaces::removeWorkspace(std::string const &workspaceString) { + spdlog::debug("Removing workspace {}", workspaceString); + + int id = -100; // workspace IDs range from -99 upwards, so -100 is a good "invalid" value + std::string name; + + // TODO: we need to support workspace selectors here + // https://wiki.hyprland.org/Configuring/Workspace-Rules/#workspace-selectors + try { + id = std::stoi(workspaceString); + } catch (const std::exception &e) { + if (workspaceString.starts_with("special:")) { + name = workspaceString.substr(8); + } else if (workspaceString.starts_with("name:")) { + name = workspaceString.substr(5); + } else { + name = workspaceString; + } + } + auto workspace = std::find_if(m_workspaces.begin(), m_workspaces.end(), [&](std::unique_ptr &x) { - return (name.starts_with("special:") && name.substr(8) == x->name()) || name == x->name(); + if (name.empty()) { + return id == x->id(); + } + return name == x->name(); }); if (workspace == m_workspaces.end()) { @@ -719,7 +734,7 @@ void Workspaces::removeWorkspace(std::string const &name) { } if ((*workspace)->isPersistentConfig()) { - spdlog::trace("Not removing config persistent workspace {}", name); + spdlog::trace("Not removing config persistent workspace {}", (*workspace)->name()); return; } From 17cee0d8766c4dc8a5d39c429ef1d0820ec0a41c Mon Sep 17 00:00:00 2001 From: Matt White Date: Fri, 10 Jan 2025 11:27:40 -0700 Subject: [PATCH 168/780] feat(hyprland): support workspacev2 --- include/modules/hyprland/workspaces.hpp | 2 +- src/modules/hyprland/workspaces.cpp | 78 +++++++++++++++++++++---- 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 3f0252c8..364e55dd 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -138,7 +138,7 @@ class Workspaces : public AModule, public EventHandler { bool m_withIcon; uint64_t m_monitorId; - std::string m_activeWorkspaceName; + int m_activeWorkspaceId; std::string m_activeSpecialWorkspaceName; std::vector> m_workspaces; std::vector> m_workspacesToCreate; diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 894d77a8..03ab4c56 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -39,7 +39,13 @@ Workspaces::~Workspaces() { } void Workspaces::init() { +<<<<<<< HEAD m_activeWorkspaceName = (m_ipc.getSocket1JsonReply("activeworkspace"))["name"].asString(); +||||||| parent of 24d391b9 (feat(hyprland): support workspacev2) + m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString(); +======= + m_activeWorkspaceId = (gIPC->getSocket1JsonReply("activeworkspace"))["id"].asInt(); +>>>>>>> 24d391b9 (feat(hyprland): support workspacev2) initializeWorkspaces(); dp.emit(); @@ -306,6 +312,7 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c workspaceData["persistent-rule"] = true; m_workspacesToCreate.emplace_back(workspaceData, clientsJson); } else { + // This can be any workspace selector. m_workspacesToRemove.emplace_back(workspace); } } @@ -316,7 +323,7 @@ void Workspaces::onEvent(const std::string &ev) { std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>')); std::string payload = ev.substr(eventName.size() + 2); - if (eventName == "workspace") { + if (eventName == "workspacev2") { onWorkspaceActivated(payload); } else if (eventName == "activespecial") { onSpecialWorkspaceActivated(payload); @@ -348,7 +355,8 @@ void Workspaces::onEvent(const std::string &ev) { } void Workspaces::onWorkspaceActivated(std::string const &payload) { - m_activeWorkspaceName = payload; + std::string workspaceIdStr = payload.substr(0, payload.find(',')); + m_activeWorkspaceId = std::stoi(workspaceIdStr); } void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) { @@ -409,7 +417,13 @@ void Workspaces::onWorkspaceMoved(std::string const &payload) { spdlog::debug("Workspace moved: {}", payload); // Update active workspace +<<<<<<< HEAD m_activeWorkspaceName = (m_ipc.getSocket1JsonReply("activeworkspace"))["name"].asString(); +||||||| parent of 24d391b9 (feat(hyprland): support workspacev2) + m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString(); +======= + m_activeWorkspaceId = (gIPC->getSocket1JsonReply("activeworkspace"))["id"].asInt(); +>>>>>>> 24d391b9 (feat(hyprland): support workspacev2) if (allOutputs()) return; @@ -432,9 +446,6 @@ void Workspaces::onWorkspaceRenamed(std::string const &payload) { std::string newName = payload.substr(payload.find(',') + 1); for (auto &workspace : m_workspaces) { if (workspace->id() == workspaceId) { - if (workspace->name() == m_activeWorkspaceName) { - m_activeWorkspaceName = newName; - } workspace->setName(newName); break; } @@ -444,7 +455,16 @@ void Workspaces::onWorkspaceRenamed(std::string const &payload) { void Workspaces::onMonitorFocused(std::string const &payload) { spdlog::trace("Monitor focused: {}", payload); - m_activeWorkspaceName = payload.substr(payload.find(',') + 1); + + std::string workspaceName = payload.substr(payload.find(',') + 1); + + // TODO this will be in the payload when we upgrade to focusedmonv2 + for (auto &workspace : m_workspaces) { + if (workspace->name() == workspaceName) { + m_activeWorkspaceId = workspace->id(); + break; + } + } for (Json::Value &monitor : m_ipc.getSocket1JsonReply("monitors")) { if (monitor["name"].asString() == payload.substr(0, payload.find(','))) { @@ -672,6 +692,7 @@ void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payloa } auto Workspaces::registerIpc() -> void { +<<<<<<< HEAD m_ipc.registerForIPC("workspace", this); m_ipc.registerForIPC("activespecial", this); m_ipc.registerForIPC("createworkspace", this); @@ -684,6 +705,33 @@ auto Workspaces::registerIpc() -> void { m_ipc.registerForIPC("movewindow", this); m_ipc.registerForIPC("urgent", this); m_ipc.registerForIPC("configreloaded", this); +||||||| parent of 24d391b9 (feat(hyprland): support workspacev2) + gIPC->registerForIPC("workspace", this); + gIPC->registerForIPC("activespecial", this); + gIPC->registerForIPC("createworkspacev2", this); + gIPC->registerForIPC("destroyworkspacev2", this); + gIPC->registerForIPC("focusedmon", this); + gIPC->registerForIPC("moveworkspace", this); + gIPC->registerForIPC("renameworkspace", this); + gIPC->registerForIPC("openwindow", this); + gIPC->registerForIPC("closewindow", this); + gIPC->registerForIPC("movewindow", this); + gIPC->registerForIPC("urgent", this); + gIPC->registerForIPC("configreloaded", this); +======= + gIPC->registerForIPC("workspacev2", this); + gIPC->registerForIPC("activespecial", this); + gIPC->registerForIPC("createworkspacev2", this); + gIPC->registerForIPC("destroyworkspacev2", this); + gIPC->registerForIPC("focusedmon", this); + gIPC->registerForIPC("moveworkspace", this); + gIPC->registerForIPC("renameworkspace", this); + gIPC->registerForIPC("openwindow", this); + gIPC->registerForIPC("closewindow", this); + gIPC->registerForIPC("movewindow", this); + gIPC->registerForIPC("urgent", this); + gIPC->registerForIPC("configreloaded", this); +>>>>>>> 24d391b9 (feat(hyprland): support workspacev2) if (windowRewriteConfigUsesTitle()) { spdlog::info( @@ -703,14 +751,16 @@ void Workspaces::removeWorkspacesToRemove() { void Workspaces::removeWorkspace(std::string const &workspaceString) { spdlog::debug("Removing workspace {}", workspaceString); - int id = -100; // workspace IDs range from -99 upwards, so -100 is a good "invalid" value + int id; std::string name; - // TODO: we need to support workspace selectors here - // https://wiki.hyprland.org/Configuring/Workspace-Rules/#workspace-selectors try { + // If this succeeds, we have a workspace ID. id = std::stoi(workspaceString); } catch (const std::exception &e) { + // TODO: At some point we want to support all workspace selectors + // This is just a subset. + // https://wiki.hyprland.org/Configuring/Workspace-Rules/#workspace-selectors if (workspaceString.starts_with("special:")) { name = workspaceString.substr(8); } else if (workspaceString.starts_with("name:")) { @@ -734,7 +784,7 @@ void Workspaces::removeWorkspace(std::string const &workspaceString) { } if ((*workspace)->isPersistentConfig()) { - spdlog::trace("Not removing config persistent workspace {}", (*workspace)->name()); + spdlog::trace("Not removing config persistent workspace id={} name={}", (*workspace)->id(), (*workspace)->name()); return; } @@ -894,9 +944,15 @@ void Workspaces::updateWorkspaceStates() { const std::vector visibleWorkspaces = getVisibleWorkspaces(); auto updatedWorkspaces = m_ipc.getSocket1JsonReply("workspaces"); for (auto &workspace : m_workspaces) { - workspace->setActive(workspace->name() == m_activeWorkspaceName || + workspace->setActive(workspace->id() == m_activeWorkspaceId || workspace->name() == m_activeSpecialWorkspaceName); +<<<<<<< HEAD if (workspace->isActive() && workspace->isUrgent()) { +||||||| parent of 24d391b9 (feat(hyprland): support workspacev2) + if (workspace->name() == m_activeWorkspaceName && workspace->isUrgent()) { +======= + if (workspace->id() == m_activeWorkspaceId && workspace->isUrgent()) { +>>>>>>> 24d391b9 (feat(hyprland): support workspacev2) workspace->setUrgent(false); } workspace->setVisible(std::find(visibleWorkspaces.begin(), visibleWorkspaces.end(), From 9f71de5227620dc2d884cfda946c8d7e5966337a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 1 Mar 2025 00:11:29 +0000 Subject: [PATCH 169/780] 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/9d3ae807ebd2981d593cddd0080856873139aa40?narHash=sha256-NGqpVVxNAHwIicXpgaVqJEJWeyqzoQJ9oc8lnK9%2BWC4%3D' (2025-01-29) → 'github:NixOS/nixpkgs/5135c59491985879812717f4c9fea69604e7f26f?narHash=sha256-Vr3Qi346M%2B8CjedtbyUevIGDZW8LcA1fTG0ugPY/Hic%3D' (2025-02-26) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index c4bb5dee..e64cbe2c 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1738142207, - "narHash": "sha256-NGqpVVxNAHwIicXpgaVqJEJWeyqzoQJ9oc8lnK9+WC4=", + "lastModified": 1740560979, + "narHash": "sha256-Vr3Qi346M+8CjedtbyUevIGDZW8LcA1fTG0ugPY/Hic=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9d3ae807ebd2981d593cddd0080856873139aa40", + "rev": "5135c59491985879812717f4c9fea69604e7f26f", "type": "github" }, "original": { From 4a6c417ef5e3b511b40f13057b401cabe90ff197 Mon Sep 17 00:00:00 2001 From: Harishankar G Date: Tue, 4 Mar 2025 19:09:21 +0530 Subject: [PATCH 170/780] Add format replacements For cidr6, netmask6 --- include/modules/network.hpp | 1 + man/waybar-network.5.scd | 8 ++++++-- src/modules/network.cpp | 19 +++++++++++-------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index dcf7d499..5fd0c180 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -79,6 +79,7 @@ class Network : public ALabel { std::string ipaddr6_; std::string gwaddr_; std::string netmask_; + std::string netmask6_; int cidr_; int cidr6_; int32_t signal_strength_dbm_; diff --git a/man/waybar-network.5.scd b/man/waybar-network.5.scd index c2dfc544..15f15395 100644 --- a/man/waybar-network.5.scd +++ b/man/waybar-network.5.scd @@ -155,9 +155,13 @@ Addressed by *network* *{gwaddr}*: The default gateway for the interface -*{netmask}*: The subnetmask corresponding to the IP. +*{netmask}*: The subnetmask corresponding to the IP(V4). -*{cidr}*: The subnetmask corresponding to the IP in CIDR notation. +*{netmask6}*: The subnetmask corresponding to the IP(V6). + +*{cidr}*: The subnetmask corresponding to the IP(V4) in CIDR notation. + +*{cidr6}*: The subnetmask corresponding to the IP(V6) in CIDR notation. *{essid}*: Name (SSID) of the wireless network. diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 2f5d19c8..716cd497 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -339,8 +339,9 @@ auto waybar::modules::Network::update() -> void { fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), - fmt::arg("netmask", netmask_), fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), - fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), + fmt::arg("netmask", netmask_), fmt::arg("netmask6", netmask6_), + 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")), @@ -371,9 +372,9 @@ auto waybar::modules::Network::update() -> void { fmt::runtime(tooltip_format), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), - fmt::arg("netmask", netmask_), fmt::arg("ipaddr", final_ipaddr_), - fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), - fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), + fmt::arg("netmask", netmask_), fmt::arg("netmask6", netmask6_), + 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")), @@ -417,6 +418,7 @@ void waybar::modules::Network::clearIface() { ipaddr6_.clear(); gwaddr_.clear(); netmask_.clear(); + netmask6_.clear(); carrier_ = false; cidr_ = 0; cidr6_ = 0; @@ -571,14 +573,14 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof(ipaddr)); } case AF_INET6: { - struct in6_addr netmask; + struct in6_addr netmask6; for (int i = 0; i < 16; i++) { int v = (i + 1) * 8 - ifa->ifa_prefixlen; if (v < 0) v = 0; if (v > 8) v = 8; - netmask.s6_addr[i] = ~0 << v; + netmask6.s6_addr[i] = ~0 << v; } - net->netmask_ = inet_ntop(ifa->ifa_family, &netmask, ipaddr, sizeof(ipaddr)); + net->netmask6_ = inet_ntop(ifa->ifa_family, &netmask6, ipaddr, sizeof(ipaddr)); } } spdlog::debug("network: {}, new addr {}/{}", net->ifname_, net->ipaddr_, net->cidr_); @@ -588,6 +590,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { net->cidr_ = 0; net->cidr6_ = 0; net->netmask_.clear(); + net->netmask6_.clear(); spdlog::debug("network: {} addr deleted {}/{}", net->ifname_, inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)), ifa->ifa_prefixlen); From f7b4451564dd860bfacfc293d0bbd414e6bb4e54 Mon Sep 17 00:00:00 2001 From: Matthew White Date: Wed, 26 Feb 2025 18:34:18 -0700 Subject: [PATCH 171/780] fix(hyprland): support additional v2 events --- include/modules/hyprland/workspaces.hpp | 15 +- src/modules/hyprland/workspaces.cpp | 421 ++++++++++++------------ 2 files changed, 220 insertions(+), 216 deletions(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 364e55dd..6b33baea 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -55,7 +56,7 @@ class Workspaces : public AModule, public EventHandler { static Json::Value createMonitorWorkspaceData(std::string const& name, std::string const& monitor); - void removeWorkspace(std::string const& name); + void removeWorkspace(std::string const& workspaceString); void setUrgentWorkspace(std::string const& windowaddress); // Config @@ -74,10 +75,11 @@ class Workspaces : public AModule, public EventHandler { void onWorkspaceActivated(std::string const& payload); void onSpecialWorkspaceActivated(std::string const& payload); void onWorkspaceDestroyed(std::string const& payload); - void onWorkspaceCreated(std::string const& workspaceName, + void onWorkspaceCreated(std::string const& payload, Json::Value const& clientsData = Json::Value::nullRef); void onWorkspaceMoved(std::string const& payload); void onWorkspaceRenamed(std::string const& payload); + static std::optional parseWorkspaceId(std::string const& workspaceIdStr); // monitor events void onMonitorFocused(std::string const& payload); @@ -93,11 +95,18 @@ class Workspaces : public AModule, public EventHandler { int windowRewritePriorityFunction(std::string const& window_rule); + // event payload management + template + static std::string makePayload(Args const&... args); + static std::pair splitDoublePayload(std::string const& payload); + static std::tuple splitTriplePayload( + std::string const& payload); + // Update methods void doUpdate(); void removeWorkspacesToRemove(); void createWorkspacesToCreate(); - static std::vector getVisibleWorkspaces(); + static std::vector getVisibleWorkspaces(); void updateWorkspaceStates(); bool updateWindowsToCreate(); diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 03ab4c56..c7d397e3 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -39,13 +40,7 @@ Workspaces::~Workspaces() { } void Workspaces::init() { -<<<<<<< HEAD - m_activeWorkspaceName = (m_ipc.getSocket1JsonReply("activeworkspace"))["name"].asString(); -||||||| parent of 24d391b9 (feat(hyprland): support workspacev2) - m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString(); -======= - m_activeWorkspaceId = (gIPC->getSocket1JsonReply("activeworkspace"))["id"].asInt(); ->>>>>>> 24d391b9 (feat(hyprland): support workspacev2) + m_activeWorkspaceId = m_ipc.getSocket1JsonReply("activeworkspace")["id"].asInt(); initializeWorkspaces(); dp.emit(); @@ -55,13 +50,12 @@ Json::Value Workspaces::createMonitorWorkspaceData(std::string const &name, std::string const &monitor) { spdlog::trace("Creating persistent workspace: {} on monitor {}", name, monitor); Json::Value workspaceData; - try { - // numbered persistent workspaces get the name as ID - workspaceData["id"] = name == "special" ? -99 : std::stoi(name); - } catch (const std::exception &e) { - // named persistent workspaces start with ID=0 - workspaceData["id"] = 0; + + auto workspaceId = parseWorkspaceId(name); + if (!workspaceId.has_value()) { + workspaceId = 0; } + workspaceData["id"] = *workspaceId; workspaceData["name"] = name; workspaceData["monitor"] = monitor; workspaceData["windows"] = 0; @@ -74,9 +68,8 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data, spdlog::debug("Creating workspace {}", workspaceName); // avoid recreating existing workspaces - auto workspace = std::find_if( - m_workspaces.begin(), m_workspaces.end(), - [workspaceName](std::unique_ptr const &w) { + auto workspace = + std::ranges::find_if(m_workspaces, [workspaceName](std::unique_ptr const &w) { return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) || workspaceName == w->name(); }); @@ -164,18 +157,18 @@ std::string Workspaces::getRewrite(std::string window_class, std::string window_ fmt::arg("title", window_title)); } -std::vector Workspaces::getVisibleWorkspaces() { - std::vector visibleWorkspaces; +std::vector Workspaces::getVisibleWorkspaces() { + std::vector visibleWorkspaces; auto monitors = IPC::inst().getSocket1JsonReply("monitors"); for (const auto &monitor : monitors) { auto ws = monitor["activeWorkspace"]; - if (ws.isObject() && ws["name"].isString()) { - visibleWorkspaces.push_back(ws["name"].asString()); + if (ws.isObject() && ws["id"].isInt()) { + visibleWorkspaces.push_back(ws["id"].asInt()); } auto sws = monitor["specialWorkspace"]; auto name = sws["name"].asString(); - if (sws.isObject() && sws["name"].isString() && !name.empty()) { - visibleWorkspaces.push_back(!name.starts_with("special:") ? name : name.substr(8)); + if (sws.isObject() && sws["id"].isInt() && !name.empty()) { + visibleWorkspaces.push_back(sws["id"].asInt()); } } return visibleWorkspaces; @@ -253,7 +246,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs int amount = value.asInt(); spdlog::debug("Creating {} persistent workspaces for monitor {}", amount, currentMonitor); for (int i = 0; i < amount; i++) { - persistentWorkspacesToCreate.emplace_back(std::to_string(m_monitorId * amount + i + 1)); + persistentWorkspacesToCreate.emplace_back(std::to_string((m_monitorId * amount) + i + 1)); } } } else if (value.isArray() && !value.empty()) { @@ -331,21 +324,21 @@ void Workspaces::onEvent(const std::string &ev) { onWorkspaceDestroyed(payload); } else if (eventName == "createworkspacev2") { onWorkspaceCreated(payload); - } else if (eventName == "focusedmon") { + } else if (eventName == "focusedmonv2") { onMonitorFocused(payload); - } else if (eventName == "moveworkspace") { + } else if (eventName == "moveworkspacev2") { onWorkspaceMoved(payload); } else if (eventName == "openwindow") { onWindowOpened(payload); } else if (eventName == "closewindow") { onWindowClosed(payload); - } else if (eventName == "movewindow") { + } else if (eventName == "movewindowv2") { onWindowMoved(payload); } else if (eventName == "urgent") { setUrgentWorkspace(payload); } else if (eventName == "renameworkspace") { onWorkspaceRenamed(payload); - } else if (eventName == "windowtitle") { + } else if (eventName == "windowtitlev2") { onWindowTitleEvent(payload); } else if (eventName == "configreloaded") { onConfigReloaded(); @@ -355,8 +348,11 @@ void Workspaces::onEvent(const std::string &ev) { } void Workspaces::onWorkspaceActivated(std::string const &payload) { - std::string workspaceIdStr = payload.substr(0, payload.find(',')); - m_activeWorkspaceId = std::stoi(workspaceIdStr); + const auto [workspaceIdStr, workspaceName] = splitDoublePayload(payload); + const auto workspaceId = parseWorkspaceId(workspaceIdStr); + if (workspaceId.has_value()) { + m_activeWorkspaceId = *workspaceId; + } } void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) { @@ -365,50 +361,54 @@ void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) { } void Workspaces::onWorkspaceDestroyed(std::string const &payload) { - std::string workspaceIdStr = payload.substr(0, payload.find(',')); - std::string workspaceName = payload.substr(workspaceIdStr.size() + 1); + const auto [workspaceId, workspaceName] = splitDoublePayload(payload); if (!isDoubleSpecial(workspaceName)) { - m_workspacesToRemove.push_back(workspaceIdStr); + m_workspacesToRemove.push_back(workspaceId); } } -void Workspaces::onWorkspaceCreated(std::string const &payload, - Json::Value const &clientsData) { +void Workspaces::onWorkspaceCreated(std::string const &payload, Json::Value const &clientsData) { spdlog::debug("Workspace created: {}", payload); - std::string workspaceIdStr = payload.substr(0, payload.find(',')); - int workspaceId = std::stoi(workspaceIdStr); - auto const workspaceRules = gIPC->getSocket1JsonReply("workspacerules"); - auto const workspacesJson = gIPC->getSocket1JsonReply("workspaces"); + const auto [workspaceIdStr, _] = splitDoublePayload(payload); + + const auto workspaceId = parseWorkspaceId(workspaceIdStr); + if (!workspaceId.has_value()) { + return; + } + + auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules"); + auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); for (Json::Value workspaceJson : workspacesJson) { - int currentId = workspaceJson["id"].asInt(); - if (currentId == workspaceId) { - std::string name = workspaceJson["name"].asString(); + const auto currentId = workspaceJson["id"].asInt(); + if (currentId == *workspaceId) { + std::string workspaceName = workspaceJson["name"].asString(); // This workspace name is more up-to-date than the one in the event payload. - if (isWorkspaceIgnored(name)) { - spdlog::trace("Not creating workspace because it is ignored: id={} name={}", workspaceId, - name); + if (isWorkspaceIgnored(workspaceName)) { + spdlog::trace("Not creating workspace because it is ignored: id={} name={}", *workspaceId, + workspaceName); break; } - if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && - (showSpecial() || !currentName.starts_with("special")) && !isDoubleSpecial(currentName)) { - for (Json::Value const &rule : workspaceRules) { - auto ruleWorkspaceName = rule.isMember("defaultName") - ? rule["defaultName"].asString() - : rule["workspaceString"].asString(); - if (ruleWorkspaceName == workspaceName) { - workspaceJson["persistent-rule"] = rule["persistent"].asBool(); - break; - } + if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && + (showSpecial() || !workspaceName.starts_with("special")) && + !isDoubleSpecial(workspaceName)) { + for (Json::Value const &rule : workspaceRules) { + auto ruleWorkspaceName = rule.isMember("defaultName") + ? rule["defaultName"].asString() + : rule["workspaceString"].asString(); + if (ruleWorkspaceName == workspaceName) { + workspaceJson["persistent-rule"] = rule["persistent"].asBool(); + break; } + } m_workspacesToCreate.emplace_back(workspaceJson, clientsData); break; } } else { - extendOrphans(workspaceId, clientsData); + extendOrphans(*workspaceId, clientsData); } } } @@ -417,35 +417,34 @@ void Workspaces::onWorkspaceMoved(std::string const &payload) { spdlog::debug("Workspace moved: {}", payload); // Update active workspace -<<<<<<< HEAD - m_activeWorkspaceName = (m_ipc.getSocket1JsonReply("activeworkspace"))["name"].asString(); -||||||| parent of 24d391b9 (feat(hyprland): support workspacev2) - m_activeWorkspaceName = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString(); -======= - m_activeWorkspaceId = (gIPC->getSocket1JsonReply("activeworkspace"))["id"].asInt(); ->>>>>>> 24d391b9 (feat(hyprland): support workspacev2) + m_activeWorkspaceId = (m_ipc.getSocket1JsonReply("activeworkspace"))["id"].asInt(); if (allOutputs()) return; - std::string workspaceName = payload.substr(0, payload.find(',')); - std::string monitorName = payload.substr(payload.find(',') + 1); + const auto [workspaceIdStr, workspaceName, monitorName] = splitTriplePayload(payload); + + const auto subPayload = makePayload(workspaceIdStr, workspaceName); if (m_bar.output->name == monitorName) { Json::Value clientsData = m_ipc.getSocket1JsonReply("clients"); - onWorkspaceCreated(workspaceName, clientsData); + onWorkspaceCreated(subPayload, clientsData); } else { - spdlog::debug("Removing workspace because it was moved to another monitor: {}"); - onWorkspaceDestroyed(workspaceName); + spdlog::debug("Removing workspace because it was moved to another monitor: {}", subPayload); + onWorkspaceDestroyed(subPayload); } } void Workspaces::onWorkspaceRenamed(std::string const &payload) { spdlog::debug("Workspace renamed: {}", payload); - std::string workspaceIdStr = payload.substr(0, payload.find(',')); - int workspaceId = workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr); - std::string newName = payload.substr(payload.find(',') + 1); + const auto [workspaceIdStr, newName] = splitDoublePayload(payload); + + const auto workspaceId = parseWorkspaceId(workspaceIdStr); + if (!workspaceId.has_value()) { + return; + } + for (auto &workspace : m_workspaces) { - if (workspace->id() == workspaceId) { + if (workspace->id() == *workspaceId) { workspace->setName(newName); break; } @@ -456,19 +455,18 @@ void Workspaces::onWorkspaceRenamed(std::string const &payload) { void Workspaces::onMonitorFocused(std::string const &payload) { spdlog::trace("Monitor focused: {}", payload); - std::string workspaceName = payload.substr(payload.find(',') + 1); + const auto [monitorName, workspaceIdStr] = splitDoublePayload(payload); - // TODO this will be in the payload when we upgrade to focusedmonv2 - for (auto &workspace : m_workspaces) { - if (workspace->name() == workspaceName) { - m_activeWorkspaceId = workspace->id(); - break; - } + const auto workspaceId = parseWorkspaceId(workspaceIdStr); + if (!workspaceId.has_value()) { + return; } + m_activeWorkspaceId = *workspaceId; + for (Json::Value &monitor : m_ipc.getSocket1JsonReply("monitors")) { - if (monitor["name"].asString() == payload.substr(0, payload.find(','))) { - auto name = monitor["specialWorkspace"]["name"].asString(); + if (monitor["name"].asString() == monitorName) { + const auto name = monitor["specialWorkspace"]["name"].asString(); m_activeSpecialWorkspaceName = !name.starts_with("special:") ? name : name.substr(8); } } @@ -507,11 +505,7 @@ void Workspaces::onWindowClosed(std::string const &addr) { void Workspaces::onWindowMoved(std::string const &payload) { spdlog::trace("Window moved: {}", payload); updateWindowCount(); - size_t lastCommaIdx = 0; - size_t nextCommaIdx = payload.find(','); - std::string windowAddress = payload.substr(lastCommaIdx, nextCommaIdx - lastCommaIdx); - - std::string workspaceName = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx); + auto [windowAddress, _, workspaceName] = splitTriplePayload(payload); std::string windowRepr; @@ -547,13 +541,15 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { spdlog::trace("Window title changed: {}", payload); std::optional> inserter; + const auto [windowAddress, _] = splitDoublePayload(payload); + // If the window was an orphan, rename it at the orphan's vector - if (m_orphanWindowMap.contains(payload)) { + if (m_orphanWindowMap.contains(windowAddress)) { inserter = [this](WindowCreationPayload wcp) { this->registerOrphanWindow(std::move(wcp)); }; } else { - auto windowWorkspace = - std::find_if(m_workspaces.begin(), m_workspaces.end(), - [payload](auto &workspace) { return workspace->containsWindow(payload); }); + auto windowWorkspace = std::ranges::find_if(m_workspaces, [windowAddress](auto &workspace) { + return workspace->containsWindow(windowAddress); + }); // If the window exists on a workspace, rename it at the workspace's window // map @@ -562,9 +558,9 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { (*windowWorkspace)->insertWindow(std::move(wcp)); }; } else { - auto queuedWindow = std::find_if( - m_windowsToCreate.begin(), m_windowsToCreate.end(), - [payload](auto &windowPayload) { return windowPayload.getAddress() == payload; }); + auto queuedWindow = std::ranges::find_if(m_windowsToCreate, [payload](auto &windowPayload) { + return windowPayload.getAddress() == payload; + }); // If the window was queued, rename it in the queue if (queuedWindow != m_windowsToCreate.end()) { @@ -692,57 +688,29 @@ void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payloa } auto Workspaces::registerIpc() -> void { -<<<<<<< HEAD - m_ipc.registerForIPC("workspace", this); + m_ipc.registerForIPC("workspacev2", this); m_ipc.registerForIPC("activespecial", this); - m_ipc.registerForIPC("createworkspace", this); - m_ipc.registerForIPC("destroyworkspace", this); - m_ipc.registerForIPC("focusedmon", this); - m_ipc.registerForIPC("moveworkspace", this); + m_ipc.registerForIPC("createworkspacev2", this); + m_ipc.registerForIPC("destroyworkspacev2", this); + m_ipc.registerForIPC("focusedmonv2", this); + m_ipc.registerForIPC("moveworkspacev2", this); m_ipc.registerForIPC("renameworkspace", this); m_ipc.registerForIPC("openwindow", this); m_ipc.registerForIPC("closewindow", this); - m_ipc.registerForIPC("movewindow", this); + m_ipc.registerForIPC("movewindowv2", this); m_ipc.registerForIPC("urgent", this); m_ipc.registerForIPC("configreloaded", this); -||||||| parent of 24d391b9 (feat(hyprland): support workspacev2) - gIPC->registerForIPC("workspace", this); - gIPC->registerForIPC("activespecial", this); - gIPC->registerForIPC("createworkspacev2", this); - gIPC->registerForIPC("destroyworkspacev2", this); - gIPC->registerForIPC("focusedmon", this); - gIPC->registerForIPC("moveworkspace", this); - gIPC->registerForIPC("renameworkspace", this); - gIPC->registerForIPC("openwindow", this); - gIPC->registerForIPC("closewindow", this); - gIPC->registerForIPC("movewindow", this); - gIPC->registerForIPC("urgent", this); - gIPC->registerForIPC("configreloaded", this); -======= - gIPC->registerForIPC("workspacev2", this); - gIPC->registerForIPC("activespecial", this); - gIPC->registerForIPC("createworkspacev2", this); - gIPC->registerForIPC("destroyworkspacev2", this); - gIPC->registerForIPC("focusedmon", this); - gIPC->registerForIPC("moveworkspace", this); - gIPC->registerForIPC("renameworkspace", this); - gIPC->registerForIPC("openwindow", this); - gIPC->registerForIPC("closewindow", this); - gIPC->registerForIPC("movewindow", this); - gIPC->registerForIPC("urgent", this); - gIPC->registerForIPC("configreloaded", this); ->>>>>>> 24d391b9 (feat(hyprland): support workspacev2) if (windowRewriteConfigUsesTitle()) { spdlog::info( - "Registering for Hyprland's 'windowtitle' events because a user-defined window " + "Registering for Hyprland's 'windowtitlev2' events because a user-defined window " "rewrite rule uses the 'title' field."); - m_ipc.registerForIPC("windowtitle", this); + m_ipc.registerForIPC("windowtitlev2", this); } } void Workspaces::removeWorkspacesToRemove() { - for (const auto &workspaceString: m_workspacesToRemove) { + for (const auto &workspaceString : m_workspacesToRemove) { removeWorkspace(workspaceString); } m_workspacesToRemove.clear(); @@ -751,32 +719,27 @@ void Workspaces::removeWorkspacesToRemove() { void Workspaces::removeWorkspace(std::string const &workspaceString) { spdlog::debug("Removing workspace {}", workspaceString); - int id; - std::string name; + // If this succeeds, we have a workspace ID. + const auto workspaceId = parseWorkspaceId(workspaceString); - try { - // If this succeeds, we have a workspace ID. - id = std::stoi(workspaceString); - } catch (const std::exception &e) { - // TODO: At some point we want to support all workspace selectors - // This is just a subset. - // https://wiki.hyprland.org/Configuring/Workspace-Rules/#workspace-selectors - if (workspaceString.starts_with("special:")) { - name = workspaceString.substr(8); - } else if (workspaceString.starts_with("name:")) { - name = workspaceString.substr(5); - } else { - name = workspaceString; - } + std::string name; + // TODO: At some point we want to support all workspace selectors + // This is just a subset. + // https://wiki.hyprland.org/Configuring/Workspace-Rules/#workspace-selectors + if (workspaceString.starts_with("special:")) { + name = workspaceString.substr(8); + } else if (workspaceString.starts_with("name:")) { + name = workspaceString.substr(5); + } else { + name = workspaceString; } - auto workspace = - std::find_if(m_workspaces.begin(), m_workspaces.end(), [&](std::unique_ptr &x) { - if (name.empty()) { - return id == x->id(); - } - return name == x->name(); - }); + const auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr &x) { + if (workspaceId.has_value()) { + return *workspaceId == x->id(); + } + return name == x->name(); + }); if (workspace == m_workspaces.end()) { // happens when a workspace on another monitor is destroyed @@ -784,7 +747,8 @@ void Workspaces::removeWorkspace(std::string const &workspaceString) { } if ((*workspace)->isPersistentConfig()) { - spdlog::trace("Not removing config persistent workspace id={} name={}", (*workspace)->id(), (*workspace)->name()); + spdlog::trace("Not removing config persistent workspace id={} name={}", (*workspace)->id(), + (*workspace)->name()); return; } @@ -808,62 +772,63 @@ void Workspaces::setCurrentMonitorId() { } void Workspaces::sortWorkspaces() { - std::sort(m_workspaces.begin(), m_workspaces.end(), - [&](std::unique_ptr &a, std::unique_ptr &b) { - // Helper comparisons - auto isIdLess = a->id() < b->id(); - auto isNameLess = a->name() < b->name(); + std::ranges::sort( // + m_workspaces, [&](std::unique_ptr &a, std::unique_ptr &b) { + // Helper comparisons + auto isIdLess = a->id() < b->id(); + auto isNameLess = a->name() < b->name(); - switch (m_sortBy) { - case SortMethod::ID: - return isIdLess; - case SortMethod::NAME: - return isNameLess; - case SortMethod::NUMBER: - try { - return std::stoi(a->name()) < std::stoi(b->name()); - } catch (const std::invalid_argument &) { - // Handle the exception if necessary. - break; - } - case SortMethod::DEFAULT: - default: - // Handle the default case here. - // normal -> named persistent -> named -> special -> named special + switch (m_sortBy) { + case SortMethod::ID: + return isIdLess; + case SortMethod::NAME: + return isNameLess; + case SortMethod::NUMBER: + try { + return std::stoi(a->name()) < std::stoi(b->name()); + } catch (const std::invalid_argument &) { + // Handle the exception if necessary. + break; + } + case SortMethod::DEFAULT: + default: + // Handle the default case here. + // normal -> named persistent -> named -> special -> named special - // both normal (includes numbered persistent) => sort by ID - if (a->id() > 0 && b->id() > 0) { - return isIdLess; - } + // both normal (includes numbered persistent) => sort by ID + if (a->id() > 0 && b->id() > 0) { + return isIdLess; + } - // one normal, one special => normal first - if ((a->isSpecial()) ^ (b->isSpecial())) { - return b->isSpecial(); - } + // one normal, one special => normal first + if ((a->isSpecial()) ^ (b->isSpecial())) { + return b->isSpecial(); + } - // only one normal, one named - if ((a->id() > 0) ^ (b->id() > 0)) { - return a->id() > 0; - } + // only one normal, one named + if ((a->id() > 0) ^ (b->id() > 0)) { + return a->id() > 0; + } - // both special - if (a->isSpecial() && b->isSpecial()) { - // if one is -99 => put it last - if (a->id() == -99 || b->id() == -99) { - return b->id() == -99; - } - // both are 0 (not yet named persistents) / named specials (-98 <= ID <= -1) - return isNameLess; - } - - // sort non-special named workspaces by name (ID <= -1377) - return isNameLess; - break; + // both special + if (a->isSpecial() && b->isSpecial()) { + // if one is -99 => put it last + if (a->id() == -99 || b->id() == -99) { + return b->id() == -99; } + // both are 0 (not yet named persistents) / named specials + // (-98 <= ID <= -1) + return isNameLess; + } - // Return a default value if none of the cases match. - return isNameLess; // You can adjust this to your specific needs. - }); + // sort non-special named workspaces by name (ID <= -1377) + return isNameLess; + break; + } + + // Return a default value if none of the cases match. + return isNameLess; // You can adjust this to your specific needs. + }); for (size_t i = 0; i < m_workspaces.size(); ++i) { m_box.reorder_child(m_workspaces[i]->button(), i); @@ -941,22 +906,17 @@ bool Workspaces::updateWindowsToCreate() { } void Workspaces::updateWorkspaceStates() { - const std::vector visibleWorkspaces = getVisibleWorkspaces(); + const std::vector visibleWorkspaces = getVisibleWorkspaces(); auto updatedWorkspaces = m_ipc.getSocket1JsonReply("workspaces"); for (auto &workspace : m_workspaces) { - workspace->setActive(workspace->id() == m_activeWorkspaceId || - workspace->name() == m_activeSpecialWorkspaceName); -<<<<<<< HEAD + workspace->setActive( + workspace->id() == m_activeWorkspaceId || + (workspace->isSpecial() && workspace->name() == m_activeSpecialWorkspaceName)); if (workspace->isActive() && workspace->isUrgent()) { -||||||| parent of 24d391b9 (feat(hyprland): support workspacev2) - if (workspace->name() == m_activeWorkspaceName && workspace->isUrgent()) { -======= - if (workspace->id() == m_activeWorkspaceId && workspace->isUrgent()) { ->>>>>>> 24d391b9 (feat(hyprland): support workspacev2) workspace->setUrgent(false); } - workspace->setVisible(std::find(visibleWorkspaces.begin(), visibleWorkspaces.end(), - workspace->name()) != visibleWorkspaces.end()); + workspace->setVisible(std::ranges::find(visibleWorkspaces, workspace->id()) != + visibleWorkspaces.end()); std::string &workspaceIcon = m_iconsMap[""]; if (m_withIcon) { workspaceIcon = workspace->selectIcon(m_iconsMap); @@ -994,4 +954,39 @@ int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) { return 0; } +template +std::string Workspaces::makePayload(Args const &...args) { + std::ostringstream result; + bool first = true; + ((result << (first ? "" : ",") << args, first = false), ...); + return result.str(); +} + +std::pair Workspaces::splitDoublePayload(std::string const &payload) { + const std::string part1 = payload.substr(0, payload.find(',')); + const std::string part2 = payload.substr(part1.size() + 1); + return {part1, part2}; +} + +std::tuple Workspaces::splitTriplePayload( + std::string const &payload) { + const size_t firstComma = payload.find(','); + const size_t secondComma = payload.find(',', firstComma + 1); + + const std::string part1 = payload.substr(0, firstComma); + const std::string part2 = payload.substr(firstComma + 1, secondComma - (firstComma + 1)); + const std::string part3 = payload.substr(secondComma + 1); + + return {part1, part2, part3}; +} + +std::optional Workspaces::parseWorkspaceId(std::string const &workspaceIdStr) { + try { + return workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr); + } catch (std::exception const &e) { + spdlog::error("Failed to parse workspace ID: {}", e.what()); + return std::nullopt; + } +} + } // namespace waybar::modules::hyprland From 5e4dac1c0aebd6c4ad1f358f09e1cfd06a95d529 Mon Sep 17 00:00:00 2001 From: Harishankar G Date: Wed, 5 Mar 2025 15:27:28 +0530 Subject: [PATCH 172/780] Newline as a seperator when displaying IPv4 and 6 at the same time --- src/modules/network.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 716cd497..0a77c00e 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -331,7 +331,7 @@ auto waybar::modules::Network::update() -> void { final_ipaddr_ = ipaddr6_; } else if (addr_pref_ == ip_addr_pref::IPV4_6) { final_ipaddr_ = ipaddr_; - final_ipaddr_ += " | "; + final_ipaddr_ += '\n'; final_ipaddr_ += ipaddr6_; } From 906170400efd29b8151cbbac7e744485b48457a2 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 5 Mar 2025 16:53:56 -0800 Subject: [PATCH 173/780] cffi: always return config values as JSON Previously, string JSON values were special cased to be provided as bare strings, which means that CFFI modules have to either know what type each value is expected to be, or use a heuristic such as trying to decode and then treating the value as a string on failure. Instead, we can always return JSON, and let the downstream consumer handle deserialising the value into whatever type is expected. The new behaviour is gated on a new ABI version 2: modules built against version 1 will continue to get the old behaviour. --- resources/custom_modules/cffi_example/main.c | 4 ++-- .../custom_modules/cffi_example/waybar_cffi_module.h | 10 ++++++++-- src/modules/cffi.cpp | 12 ++++++++---- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/resources/custom_modules/cffi_example/main.c b/resources/custom_modules/cffi_example/main.c index ba2c8cf4..7618de58 100644 --- a/resources/custom_modules/cffi_example/main.c +++ b/resources/custom_modules/cffi_example/main.c @@ -17,7 +17,7 @@ void onclicked(GtkButton* button) { } // You must -const size_t wbcffi_version = 1; +const size_t wbcffi_version = 2; void* wbcffi_init(const wbcffi_init_info* init_info, const wbcffi_config_entry* config_entries, size_t config_entries_len) { @@ -67,4 +67,4 @@ void wbcffi_refresh(void* instance, int signal) { void wbcffi_doaction(void* instance, const char* name) { printf("cffi_example inst=%p: doAction(%s)\n", instance, name); -} \ No newline at end of file +} diff --git a/resources/custom_modules/cffi_example/waybar_cffi_module.h b/resources/custom_modules/cffi_example/waybar_cffi_module.h index a7886bea..c1a82f59 100644 --- a/resources/custom_modules/cffi_example/waybar_cffi_module.h +++ b/resources/custom_modules/cffi_example/waybar_cffi_module.h @@ -7,7 +7,7 @@ extern "C" { #endif -/// Waybar ABI version. 1 is the latest version +/// Waybar ABI version. 2 is the latest version extern const size_t wbcffi_version; /// Private Waybar CFFI module @@ -35,7 +35,13 @@ typedef struct { typedef struct { /// Entry key const char* key; - /// Entry value as string. JSON object and arrays are serialized. + /// Entry value + /// + /// In ABI version 1, this may be either a bare string if the value is a + /// string, or the JSON representation of any other JSON object as a string. + /// + /// From ABI version 2 onwards, this is always the JSON representation of the + /// value as a string. const char* value; } wbcffi_config_entry; diff --git a/src/modules/cffi.cpp b/src/modules/cffi.cpp index e560659b..5c095f46 100644 --- a/src/modules/cffi.cpp +++ b/src/modules/cffi.cpp @@ -28,7 +28,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co } // Fetch functions - if (*wbcffi_version == 1) { + if (*wbcffi_version == 1 || *wbcffi_version == 2) { // Mandatory functions hooks_.init = reinterpret_cast(dlsym(handle, "wbcffi_init")); if (!hooks_.init) { @@ -58,10 +58,14 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co const auto& keys = config.getMemberNames(); for (size_t i = 0; i < keys.size(); i++) { const auto& value = config[keys[i]]; - if (value.isConvertibleTo(Json::ValueType::stringValue)) { - config_entries_stringstor.push_back(config[keys[i]].asString()); + if (*wbcffi_version == 1) { + if (value.isConvertibleTo(Json::ValueType::stringValue)) { + config_entries_stringstor.push_back(value.asString()); + } else { + config_entries_stringstor.push_back(value.toStyledString()); + } } else { - config_entries_stringstor.push_back(config[keys[i]].toStyledString()); + config_entries_stringstor.push_back(value.toStyledString()); } } From f631d5eaf90f0e366d1ae73911cbc0cf67ad6ffa Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Wed, 5 Mar 2025 22:44:55 -0600 Subject: [PATCH 174/780] nix/default: disable version check Downstream added version check, causes this flake to fail building. --- nix/default.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nix/default.nix b/nix/default.nix index a9ff180b..fc564f0a 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -27,6 +27,8 @@ in # downstream patch should not affect upstream patches = []; + # nixpkgs checks version, no need when building locally + nativeInstallCheckInputs = []; buildInputs = (builtins.filter (p: p.pname != "wireplumber") oldAttrs.buildInputs) ++ [ pkgs.wireplumber From 8a15cbad5cf1e116c331108e9ed168e20ef041b4 Mon Sep 17 00:00:00 2001 From: Lars Niesen Date: Mon, 13 Jan 2025 09:10:25 +0100 Subject: [PATCH 175/780] Fixes: Add stretching of modules and modules-center toggling Thanks to tmccombs this commit fixes some inconsitencies in #3730. These inconsitencies were: - Fixed the oversight of missing the implementation of expand_center for center_ and right_ - Removes a last minut printf debugging statment I missed. --- src/bar.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/bar.cpp b/src/bar.cpp index b7737d36..3c3ab690 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -545,7 +545,6 @@ auto waybar::Bar::setupWidgets() -> void { if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) { box_.set_center_widget(center_); } else { - spdlog::error("No fixed center_"); box_.pack_start(center_, true, expand_center); } } @@ -569,13 +568,13 @@ auto waybar::Bar::setupWidgets() -> void { if (!no_center) { for (auto const& module : modules_center_) { - center_.pack_start(*module, false, false); + center_.pack_start(*module, module->expandEnabled(), module->expandEnabled()); } } std::reverse(modules_right_.begin(), modules_right_.end()); for (auto const& module : modules_right_) { - right_.pack_end(*module, false, false); + right_.pack_end(*module, module->expandEnabled(), module->expandEnabled()); } } From feb6dfb9d66006889018ba250807e1e0dfa69ff8 Mon Sep 17 00:00:00 2001 From: Jeevitha Kannan K S Date: Fri, 21 Mar 2025 16:23:39 +0530 Subject: [PATCH 176/780] feat(power-profiles-daemon): split driver into cpu driver and platform driver --- include/modules/power_profiles_daemon.hpp | 5 +++-- man/waybar-power-profiles-daemon.5.scd | 6 ++--- resources/config.jsonc | 2 +- src/modules/power_profiles_daemon.cpp | 27 ++++++++++++++++++----- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/include/modules/power_profiles_daemon.hpp b/include/modules/power_profiles_daemon.hpp index a2bd3858..c43ccd78 100644 --- a/include/modules/power_profiles_daemon.hpp +++ b/include/modules/power_profiles_daemon.hpp @@ -9,9 +9,10 @@ namespace waybar::modules { struct Profile { std::string name; - std::string driver; + std::string cpuDriver; + std::string platformDriver; - Profile(std::string n, std::string d) : name(std::move(n)), driver(std::move(d)) {} + Profile(std::string n, std::string cd, std::string pd) : name(std::move(n)), cpuDriver(std::move(cd)), platformDriver(std::move(pd)) {} }; class PowerProfilesDaemon : public ALabel { diff --git a/man/waybar-power-profiles-daemon.5.scd b/man/waybar-power-profiles-daemon.5.scd index 6488767b..c1b2f5b8 100644 --- a/man/waybar-power-profiles-daemon.5.scd +++ b/man/waybar-power-profiles-daemon.5.scd @@ -25,7 +25,7 @@ $XDG_CONFIG_HOME/waybar/config :[ Message displayed on the bar. {icon} and {profile} are respectively substituted with the icon representing the active profile and its full name. |[ *tooltip-format* :[ string -:[ "Power profile: {profile}\\nDriver: {driver}" +:[ "Power profile: {profile}\\nCPU driver: {cpu_driver}\\nPlatform driver: {platform_driver}" :[ Messaged displayed in the module tooltip. {icon} and {profile} are respectively substituted with the icon representing the active profile and its full name. |[ *tooltip* :[ bool @@ -51,7 +51,7 @@ Compact display (default config): ``` "power-profiles-daemon": { "format": "{icon}", - "tooltip-format": "Power profile: {profile}\nDriver: {driver}", + "tooltip-format": "Power profile: {profile}\nCPU driver: {cpu_driver}\nPlatform driver: {platform_driver}", "tooltip": true, "format-icons": { "default": "", @@ -67,7 +67,7 @@ Display the full profile name: ``` "power-profiles-daemon": { "format": "{icon} {profile}", - "tooltip-format": "Power profile: {profile}\nDriver: {driver}", + "tooltip-format": "Power profile: {profile}\nCPU driver: {cpu_driver}\nPlatform driver: {platform_driver}", "tooltip": true, "format-icons": { "default": "", diff --git a/resources/config.jsonc b/resources/config.jsonc index 6ac1aa50..3b1ae160 100644 --- a/resources/config.jsonc +++ b/resources/config.jsonc @@ -151,7 +151,7 @@ }, "power-profiles-daemon": { "format": "{icon}", - "tooltip-format": "Power profile: {profile}\nDriver: {driver}", + "tooltip-format": "Power profile: {profile}\nCPU driver: {cpu_driver}\nPlatform driver: {platform_driver}", "tooltip": true, "format-icons": { "default": "", diff --git a/src/modules/power_profiles_daemon.cpp b/src/modules/power_profiles_daemon.cpp index 3ae3ae83..b2876c9e 100644 --- a/src/modules/power_profiles_daemon.cpp +++ b/src/modules/power_profiles_daemon.cpp @@ -12,7 +12,7 @@ PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Valu if (config_["tooltip-format"].isString()) { tooltipFormat_ = config_["tooltip-format"].asString(); } else { - tooltipFormat_ = "Power profile: {profile}\nDriver: {driver}"; + tooltipFormat_ = "Power profile: {profile}\nCpuDriver: {cpu_driver}"; } // Fasten your seatbelt, we're up for quite a ride. The rest of the // init is performed asynchronously. There's 2 callbacks involved. @@ -95,15 +95,29 @@ void PowerProfilesDaemon::populateInitState() { powerProfilesProxy_->get_cached_property(profilesVariant, "Profiles"); for (auto& variantDict : profilesVariant.get()) { Glib::ustring name; - Glib::ustring driver; + Glib::ustring cpuDriver; + Glib::ustring platformDriver; if (auto p = variantDict.find("Profile"); p != variantDict.end()) { name = p->second.get(); } - if (auto d = variantDict.find("Driver"); d != variantDict.end()) { - driver = d->second.get(); + if (auto cd = variantDict.find("CpuDriver"); cd != variantDict.end()) { + cpuDriver = cd->second.get(); } + if (auto pd = variantDict.find("PlatformDriver"); pd != variantDict.end()) { + platformDriver = pd->second.get(); + } + + if (cpuDriver.empty()) { + cpuDriver = "Unavailable"; + spdlog::warn("Cannot find power profiles daemon cpu driver."); + } + if (platformDriver.empty()) { + platformDriver = "Unavailable"; + spdlog::warn("Cannot find power profiles daemon platform driver."); + } + if (!name.empty()) { - availableProfiles_.emplace_back(std::move(name), std::move(driver)); + availableProfiles_.emplace_back(std::move(name), std::move(cpuDriver), std::move(platformDriver)); } else { spdlog::error( "Power profiles daemon: power-profiles-daemon sent us an empty power profile name. " @@ -153,7 +167,8 @@ auto PowerProfilesDaemon::update() -> void { // Set label fmt::dynamic_format_arg_store store; store.push_back(fmt::arg("profile", profile.name)); - store.push_back(fmt::arg("driver", profile.driver)); + store.push_back(fmt::arg("cpu_driver", profile.cpuDriver)); + store.push_back(fmt::arg("platform_driver", profile.platformDriver)); store.push_back(fmt::arg("icon", getIcon(0, profile.name))); label_.set_markup(fmt::vformat(format_, store)); if (tooltipEnabled()) { From 6fd859c0c4230cb45fe4503446ba548d09d64158 Mon Sep 17 00:00:00 2001 From: Angelo Dureghello Date: Sat, 22 Mar 2025 18:33:03 +0100 Subject: [PATCH 177/780] add login-proxy option There are cases where systemd-logind is not used/running. Result is that bcklight module will not run. Add an option that, when set to false, allows backlight module to work without systemd-logind. --- meson.build | 4 ++++ meson_options.txt | 1 + src/util/backlight_backend.cpp | 2 ++ 3 files changed, 7 insertions(+) diff --git a/meson.build b/meson.build index 5524b49c..1bc1f656 100644 --- a/meson.build +++ b/meson.build @@ -333,6 +333,10 @@ if get_option('niri') ) endif +if get_option('login-proxy') + add_project_arguments('-DHAVE_LOGIN_PROXY', language: 'cpp') +endif + if libnl.found() and libnlgen.found() add_project_arguments('-DHAVE_LIBNL', language: 'cpp') src_files += files('src/modules/network.cpp') diff --git a/meson_options.txt b/meson_options.txt index 303ef038..d83fe01f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -20,3 +20,4 @@ option('jack', type: 'feature', value: 'auto', description: 'Enable support for option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber') option('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava') option('niri', type: 'boolean', description: 'Enable support for niri') +option('login-proxy', type: 'boolean', description: 'Enable interfacing with dbus login interface') diff --git a/src/util/backlight_backend.cpp b/src/util/backlight_backend.cpp index 41236462..863896d5 100644 --- a/src/util/backlight_backend.cpp +++ b/src/util/backlight_backend.cpp @@ -150,6 +150,7 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval, throw std::runtime_error("No backlight found"); } +#ifdef HAVE_LOGIN_PROXY // Connect to the login interface login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync( Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1", @@ -160,6 +161,7 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval, Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1", "/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session"); } +#endif udev_thread_ = [this] { std::unique_ptr udev{udev_new()}; From 4ba1947a50d9af7578dc4febdc99bfdc9f88ec8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Bartoletti?= Date: Mon, 24 Mar 2025 15:30:57 +0100 Subject: [PATCH 178/780] fix(FreeBSD): Use dev.cpu temperature sysctl --- src/modules/temperature.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index 89d3db2d..93f427ee 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -114,13 +114,17 @@ float waybar::modules::Temperature::getTemperature() { auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0; - if (sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size, - NULL, 0) != 0) { - throw std::runtime_error(fmt::format( - "sysctl hw.acpi.thermal.tz{}.temperature or dev.cpu.{}.temperature failed", zone, zone)); + // First, try with dev.cpu + if ( (sysctlbyname(fmt::format("dev.cpu.{}.temperature", zone).c_str(), &temp, &size, + NULL, 0) == 0) || + (sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size, + NULL, 0) == 0) ) { + auto temperature_c = ((float)temp - 2732) / 10; + return temperature_c; } - auto temperature_c = ((float)temp - 2732) / 10; - return temperature_c; + + throw std::runtime_error(fmt::format( + "sysctl hw.acpi.thermal.tz{}.temperature and dev.cpu.{}.temperature failed", zone, zone)); #else // Linux std::ifstream temp(file_path_); @@ -148,4 +152,4 @@ bool waybar::modules::Temperature::isWarning(uint16_t temperature_c) { bool waybar::modules::Temperature::isCritical(uint16_t temperature_c) { return config_["critical-threshold"].isInt() && temperature_c >= config_["critical-threshold"].asInt(); -} \ No newline at end of file +} From 567ae16a680dfc8196f493d4318e9d0cd36e2580 Mon Sep 17 00:00:00 2001 From: tea Date: Fri, 28 Mar 2025 09:42:48 +0100 Subject: [PATCH 179/780] fix incorrect type for `weeks-pos` in waybar-clock man page --- man/waybar-clock.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/waybar-clock.5.scd b/man/waybar-clock.5.scd index f0a88d24..50a5fc07 100644 --- a/man/waybar-clock.5.scd +++ b/man/waybar-clock.5.scd @@ -117,7 +117,7 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe :[ 3 :[ Relevant for *mode=year*. Count of months per row |[ *weeks-pos* -:[ integer +:[ string :[ :[ The position where week numbers should be displayed. Disabled when is empty. Possible values: left|right From 9d2b137594313f8bfd79e6c6c2ecfef2f798e035 Mon Sep 17 00:00:00 2001 From: "Rene D. Obermueller" Date: Mon, 31 Mar 2025 18:36:12 +0200 Subject: [PATCH 180/780] fix manpage for backlight/slider --- man/waybar-backlight-slider.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/waybar-backlight-slider.5.scd b/man/waybar-backlight-slider.5.scd index 8d8353c3..c6f027a1 100644 --- a/man/waybar-backlight-slider.5.scd +++ b/man/waybar-backlight-slider.5.scd @@ -40,7 +40,7 @@ The brightness can be controlled by dragging the slider across the bar or clicki ``` "modules-right": [ - "backlight-slider", + "backlight/slider", ], "backlight/slider": { "min": 0, From c0b8c4d46865bedf721988f73d64b76e41cd6f41 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 1 Apr 2025 00:12:37 +0000 Subject: [PATCH 181/780] 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/5135c59491985879812717f4c9fea69604e7f26f?narHash=sha256-Vr3Qi346M%2B8CjedtbyUevIGDZW8LcA1fTG0ugPY/Hic%3D' (2025-02-26) → 'github:NixOS/nixpkgs/52faf482a3889b7619003c0daec593a1912fddc1?narHash=sha256-6hl6L/tRnwubHcA4pfUUtk542wn2Om%2BD4UnDhlDW9BE%3D' (2025-03-30) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index e64cbe2c..27290894 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1740560979, - "narHash": "sha256-Vr3Qi346M+8CjedtbyUevIGDZW8LcA1fTG0ugPY/Hic=", + "lastModified": 1743315132, + "narHash": "sha256-6hl6L/tRnwubHcA4pfUUtk542wn2Om+D4UnDhlDW9BE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5135c59491985879812717f4c9fea69604e7f26f", + "rev": "52faf482a3889b7619003c0daec593a1912fddc1", "type": "github" }, "original": { From c5bc3bc59a19fdc77c4066ec5132617289206d0f Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Thu, 3 Apr 2025 23:11:33 -0500 Subject: [PATCH 182/780] hyprland/workspaces: fix crash --- src/modules/hyprland/workspaces.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index c7d397e3..770947ea 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -578,7 +578,7 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { return client["address"].asString() == jsonWindowAddress; }); - if (!client->empty()) { + if (client != clientsData.end() && !client->empty()) { (*inserter)({*client}); } } From 91ef6e51ed5e24d89827d9e8c127a96fef4a2c60 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Thu, 3 Apr 2025 23:23:42 -0500 Subject: [PATCH 183/780] hyprland/workspaces: range find lint cleanup --- src/modules/hyprland/workspaces.cpp | 45 ++++++++++++++--------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 770947ea..0e225935 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -79,14 +79,14 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data, const auto keys = workspace_data.getMemberNames(); const auto *k = "persistent-rule"; - if (std::find(keys.begin(), keys.end(), k) != keys.end()) { + if (std::ranges::find(keys, k) != keys.end()) { spdlog::debug("Set dynamic persistency of workspace {} to: {}", workspaceName, workspace_data[k].asBool() ? "true" : "false"); (*workspace)->setPersistentRule(workspace_data[k].asBool()); } k = "persistent-config"; - if (std::find(keys.begin(), keys.end(), k) != keys.end()) { + if (std::ranges::find(keys, k) != keys.end()) { spdlog::debug("Set config persistency of workspace {} to: {}", workspaceName, workspace_data[k].asBool() ? "true" : "false"); (*workspace)->setPersistentConfig(workspace_data[k].asBool()); @@ -231,7 +231,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs std::vector persistentWorkspacesToCreate; const std::string currentMonitor = m_bar.output->name; - const bool monitorInConfig = std::find(keys.begin(), keys.end(), currentMonitor) != keys.end(); + const bool monitorInConfig = std::ranges::find(keys, currentMonitor) != keys.end(); for (const std::string &key : keys) { // only add if either: // 1. key is the current monitor name @@ -573,10 +573,9 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { Json::Value clientsData = m_ipc.getSocket1JsonReply("clients"); std::string jsonWindowAddress = fmt::format("0x{}", payload); - auto client = - std::find_if(clientsData.begin(), clientsData.end(), [jsonWindowAddress](auto &client) { - return client["address"].asString() == jsonWindowAddress; - }); + auto client = std::ranges::find_if(clientsData, [jsonWindowAddress](auto &client) { + return client["address"].asString() == jsonWindowAddress; + }); if (client != clientsData.end() && !client->empty()) { (*inserter)({*client}); @@ -760,9 +759,9 @@ void Workspaces::setCurrentMonitorId() { // get monitor ID from name (used by persistent workspaces) m_monitorId = 0; auto monitors = m_ipc.getSocket1JsonReply("monitors"); - auto currentMonitor = std::find_if( - monitors.begin(), monitors.end(), - [this](const Json::Value &m) { return m["name"].asString() == m_bar.output->name; }); + auto currentMonitor = std::ranges::find_if(monitors, [this](const Json::Value &m) { + return m["name"].asString() == m_bar.output->name; + }); if (currentMonitor == monitors.end()) { spdlog::error("Monitor '{}' does not have an ID? Using 0", m_bar.output->name); } else { @@ -846,9 +845,9 @@ void Workspaces::setUrgentWorkspace(std::string const &windowaddress) { } } - auto workspace = - std::find_if(m_workspaces.begin(), m_workspaces.end(), - [workspaceId](std::unique_ptr &x) { return x->id() == workspaceId; }); + auto workspace = std::ranges::find_if(m_workspaces, [workspaceId](std::unique_ptr &x) { + return x->id() == workspaceId; + }); if (workspace != m_workspaces.end()) { workspace->get()->setUrgent(); } @@ -862,11 +861,10 @@ auto Workspaces::update() -> void { void Workspaces::updateWindowCount() { const Json::Value workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); for (auto &workspace : m_workspaces) { - auto workspaceJson = - std::find_if(workspacesJson.begin(), workspacesJson.end(), [&](Json::Value const &x) { - return x["name"].asString() == workspace->name() || - (workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name()); - }); + auto workspaceJson = std::ranges::find_if(workspacesJson, [&](Json::Value const &x) { + return x["name"].asString() == workspace->name() || + (workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name()); + }); uint32_t count = 0; if (workspaceJson != workspacesJson.end()) { try { @@ -921,12 +919,11 @@ void Workspaces::updateWorkspaceStates() { if (m_withIcon) { workspaceIcon = workspace->selectIcon(m_iconsMap); } - auto updatedWorkspace = std::find_if( - updatedWorkspaces.begin(), updatedWorkspaces.end(), [&workspace](const auto &w) { - auto wNameRaw = w["name"].asString(); - auto wName = wNameRaw.starts_with("special:") ? wNameRaw.substr(8) : wNameRaw; - return wName == workspace->name(); - }); + auto updatedWorkspace = std::ranges::find_if(updatedWorkspaces, [&workspace](const auto &w) { + auto wNameRaw = w["name"].asString(); + auto wName = wNameRaw.starts_with("special:") ? wNameRaw.substr(8) : wNameRaw; + return wName == workspace->name(); + }); if (updatedWorkspace != updatedWorkspaces.end()) { workspace->setOutput((*updatedWorkspace)["monitor"].asString()); } From 84162ec60437756fd3ad089ba1b8596bdad64b63 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Thu, 3 Apr 2025 23:48:01 -0500 Subject: [PATCH 184/780] .github/workflows/clang-format: bump github action --- .github/workflows/clang-format.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 3c62819e..b5bfbe36 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -11,7 +11,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: DoozyX/clang-format-lint-action@v0.16.2 + # TODO: bump to clang 19 release + # - uses: DoozyX/clang-format-lint-action@v0.18.2 + - uses: DoozyX/clang-format-lint-action@558090054b3f39e3d6af24f0cd73b319535da809 name: clang-format with: source: "." From 5ff6b0ad0ff1072a15fb7ed16a5595d083e48661 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Thu, 3 Apr 2025 23:56:32 -0500 Subject: [PATCH 185/780] .github/workflows: tweak job names They didn't seem to correspond to the workflow, properly. Making triggering them locally weird. --- .github/workflows/clang-format.yml | 2 +- .github/workflows/clang-tidy.yml.bak | 2 +- .github/workflows/freebsd.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index b5bfbe36..0fad47c4 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -7,7 +7,7 @@ concurrency: cancel-in-progress: true jobs: - build: + lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/clang-tidy.yml.bak b/.github/workflows/clang-tidy.yml.bak index a39bd23d..ec67fb7e 100644 --- a/.github/workflows/clang-tidy.yml.bak +++ b/.github/workflows/clang-tidy.yml.bak @@ -7,7 +7,7 @@ concurrency: cancel-in-progress: true jobs: - build: + lint: runs-on: ubuntu-latest container: image: alexays/waybar:debian diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index bbb97198..ca0dcbc8 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -7,7 +7,7 @@ concurrency: cancel-in-progress: true jobs: - clang: + build: # Run actions in a FreeBSD VM on the ubuntu runner # https://github.com/actions/runner/issues/385 - for FreeBSD runner support runs-on: ubuntu-latest From 9ca52a48c86c362c28aa3b9102192756daf82a16 Mon Sep 17 00:00:00 2001 From: "Rene D. Obermueller" Date: Sun, 6 Apr 2025 09:46:06 +0200 Subject: [PATCH 186/780] wireplumber: fix potential nullpointer deref --- src/modules/wireplumber.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index c25b3b51..106ca403 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -299,7 +299,7 @@ void waybar::modules::Wireplumber::onMixerApiLoaded(WpObject* p, GAsyncResult* r gboolean success = FALSE; g_autoptr(GError) error = nullptr; - success = wp_core_load_component_finish(self->wp_core_, res, nullptr); + success = wp_core_load_component_finish(self->wp_core_, res, &error); if (success == FALSE) { spdlog::error("[{}]: mixer API load failed", self->name_); From 2faad27cadb6d86c064debc82fcea90dcaf1d0a2 Mon Sep 17 00:00:00 2001 From: fkobi Date: Mon, 7 Apr 2025 14:45:31 +0000 Subject: [PATCH 187/780] meson_options: improve sndio's description Correct me if I am wrong but I think that is it's only use. If that is the case the description would be less generic and thus better. --- meson_options.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson_options.txt b/meson_options.txt index d83fe01f..42827c02 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -12,7 +12,7 @@ option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable supp option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') option('mpd', type: 'feature', value: 'auto', description: 'Enable support for the Music Player Daemon') option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL') -option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio') +option('sndio', type: 'feature', value: 'auto', description: 'Enable support for volume control via sndio') option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind') option('tests', type: 'feature', value: 'auto', description: 'Enable tests') option('experimental', type : 'boolean', value : false, description: 'Enable experimental features') From e92b0a86b5595a3c130f9c2fded724c71ac89d35 Mon Sep 17 00:00:00 2001 From: Clemens Horn Date: Mon, 7 Apr 2025 20:33:18 +0200 Subject: [PATCH 188/780] wlr/taskbar: find icon by title as fallback --- src/modules/wlr/taskbar.cpp | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 30e4ee48..3c188dd0 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -382,9 +382,40 @@ std::string Task::state_string(bool shortened) const { return res.substr(0, res.size() - 1); } -void Task::handle_title(const char *title) { - title_ = title; - hide_if_ignored(); +void Task::handle_title(const char* title) { + title_ = title; + hide_if_ignored(); + + // Skip if we already have app info or no title + if (app_info_ || title_.empty()) { + return; + } + + // Try exact title match + app_info_ = get_desktop_app_info(title_); + + // Try lowercase version if still needed + if (!app_info_) { + std::string lower_title = title_; + std::transform(lower_title.begin(), lower_title.end(), lower_title.begin(), ::tolower); + app_info_ = get_desktop_app_info(lower_title); + } + + // If we found a match, update name and icon + if (app_info_) { + name_ = app_info_->get_display_name(); + spdlog::info("Found desktop file via title fallback: {}", name_); + + if (with_icon_) { + const int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16; + for (auto& icon_theme : tbar_->icon_themes()) { + if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) { + icon_.show(); + break; + } + } + } + } } void Task::set_minimize_hint() { From addf44d9457ae6f17e73cb4b428b7d3be242390c Mon Sep 17 00:00:00 2001 From: Clemens Horn Date: Mon, 7 Apr 2025 20:51:35 +0200 Subject: [PATCH 189/780] test --- src/modules/wlr/taskbar.cpp | 59 ++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 3c188dd0..1169425a 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -383,39 +383,38 @@ std::string Task::state_string(bool shortened) const { } void Task::handle_title(const char* title) { - title_ = title; - hide_if_ignored(); + if (title_.empty()) { + spdlog::debug(fmt::format("Task ({}) setting title to {}", id_, title_)); + } else { + spdlog::debug(fmt::format("Task ({}) overwriting title '{}' with '{}'", id_, title_, title)); + } + title_ = title; + hide_if_ignored(); - // Skip if we already have app info or no title - if (app_info_ || title_.empty()) { - return; + if (!with_icon_ && !with_name_ || app_info_) { + return; + } + + set_app_info_from_app_id_list(title_); + name_ = app_info_ ? app_info_->get_display_name() : title; + + if (!with_icon_) { + return; + } + + int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16; + bool found = false; + for (auto &icon_theme : tbar_->icon_themes()) { + if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) { + found = true; + break; } + } - // Try exact title match - app_info_ = get_desktop_app_info(title_); - - // Try lowercase version if still needed - if (!app_info_) { - std::string lower_title = title_; - std::transform(lower_title.begin(), lower_title.end(), lower_title.begin(), ::tolower); - app_info_ = get_desktop_app_info(lower_title); - } - - // If we found a match, update name and icon - if (app_info_) { - name_ = app_info_->get_display_name(); - spdlog::info("Found desktop file via title fallback: {}", name_); - - if (with_icon_) { - const int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16; - for (auto& icon_theme : tbar_->icon_themes()) { - if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) { - icon_.show(); - break; - } - } - } - } + if (found) + icon_.show(); + else + spdlog::debug("Couldn't find icon for {}", title_); } void Task::set_minimize_hint() { From 633bf9e00fab791084682d4442a47f5d739d9b82 Mon Sep 17 00:00:00 2001 From: Corey Doughty Date: Thu, 10 Apr 2025 06:56:00 -0400 Subject: [PATCH 190/780] Hyprland submap allow pango markup. --- src/modules/hyprland/submap.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/hyprland/submap.cpp b/src/modules/hyprland/submap.cpp index 97c4bb62..9ded789b 100644 --- a/src/modules/hyprland/submap.cpp +++ b/src/modules/hyprland/submap.cpp @@ -68,8 +68,9 @@ void Submap::onEvent(const std::string& ev) { return; } - auto submapName = ev.substr(ev.find_last_of('>') + 1); - submapName = waybar::util::sanitize_string(submapName); + //auto submapName = ev.substr(ev.find_last_of('>') + 1); + //submapName = waybar::util::sanitize_string(submapName); + auto submapName = ev.substr(ev.find_first_of('>') + 2 ); if (!submap_.empty()) { label_.get_style_context()->remove_class(submap_); From 682492f7a93331e930f9f57dd82dd681e0f78f49 Mon Sep 17 00:00:00 2001 From: Corey Doughty Date: Thu, 10 Apr 2025 07:05:45 -0400 Subject: [PATCH 191/780] This commit fixes #4023 --- src/modules/hyprland/submap.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/hyprland/submap.cpp b/src/modules/hyprland/submap.cpp index 9ded789b..33de79f5 100644 --- a/src/modules/hyprland/submap.cpp +++ b/src/modules/hyprland/submap.cpp @@ -68,8 +68,6 @@ void Submap::onEvent(const std::string& ev) { return; } - //auto submapName = ev.substr(ev.find_last_of('>') + 1); - //submapName = waybar::util::sanitize_string(submapName); auto submapName = ev.substr(ev.find_first_of('>') + 2 ); if (!submap_.empty()) { From afb1ee54221838c98c8583ec6da8fa4ad7913aa9 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Fri, 11 Apr 2025 14:05:39 -0500 Subject: [PATCH 192/780] audio_backend: fix crash Getting crashes when called before we have proper information. --- src/util/audio_backend.cpp | 138 +++++++++++++++++++++++++++++-------- 1 file changed, 110 insertions(+), 28 deletions(-) diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index 807b5dc7..860168fd 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -24,6 +24,8 @@ AudioBackend::AudioBackend(std::function on_updated_cb, private_construc source_volume_(0), source_muted_(false), on_updated_cb_(std::move(on_updated_cb)) { + // Initialize pa_volume_ with safe defaults + pa_cvolume_init(&pa_volume_); mainloop_ = pa_threaded_mainloop_new(); if (mainloop_ == nullptr) { throw std::runtime_error("pa_mainloop_new() failed."); @@ -131,7 +133,12 @@ void AudioBackend::subscribeCb(pa_context *context, pa_subscription_event_type_t void AudioBackend::volumeModifyCb(pa_context *c, int success, void *data) { auto *backend = static_cast(data); if (success != 0) { - pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_, sinkInfoCb, data); + if ((backend->context_ != nullptr) && + pa_context_get_state(backend->context_) == PA_CONTEXT_READY) { + pa_context_get_sink_info_by_index(backend->context_, backend->sink_idx_, sinkInfoCb, data); + } + } else { + spdlog::debug("Volume modification failed"); } } @@ -180,11 +187,20 @@ void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, i } if (backend->current_sink_name_ == i->name) { - backend->pa_volume_ = i->volume; - float volume = - static_cast(pa_cvolume_avg(&(backend->pa_volume_))) / float{PA_VOLUME_NORM}; - backend->sink_idx_ = i->index; - backend->volume_ = std::round(volume * 100.0F); + // Safely copy the volume structure + if (pa_cvolume_valid(&i->volume) != 0) { + backend->pa_volume_ = i->volume; + float volume = + static_cast(pa_cvolume_avg(&(backend->pa_volume_))) / float{PA_VOLUME_NORM}; + backend->sink_idx_ = i->index; + backend->volume_ = std::round(volume * 100.0F); + } else { + spdlog::error("Invalid volume structure received from PulseAudio"); + // Initialize with safe defaults + pa_cvolume_init(&backend->pa_volume_); + backend->volume_ = 0; + } + backend->muted_ = i->mute != 0; backend->desc_ = i->description; backend->monitor_ = i->monitor_source_name; @@ -230,43 +246,109 @@ void AudioBackend::serverInfoCb(pa_context *context, const pa_server_info *i, vo } void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t max_volume) { - double volume_tick = static_cast(PA_VOLUME_NORM) / 100; - pa_cvolume pa_volume = pa_volume_; + // Early return if context is not ready + if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) { + spdlog::error("PulseAudio context not ready"); + return; + } + // Prepare volume structure + pa_cvolume pa_volume; + + pa_cvolume_init(&pa_volume); + + // Use existing volume structure if valid, otherwise create a safe default + if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) { + pa_volume = pa_volume_; + } else { + // Set stereo as a safe default + pa_volume.channels = 2; + spdlog::debug("Using default stereo volume structure"); + } + + // Set the volume safely volume = std::clamp(volume, min_volume, max_volume); - pa_cvolume_set(&pa_volume, pa_volume_.channels, volume * volume_tick); + pa_volume_t vol = volume * (static_cast(PA_VOLUME_NORM) / 100); + // Set all channels to the same volume manually to avoid pa_cvolume_set + for (uint8_t i = 0; i < pa_volume.channels; i++) { + pa_volume.values[i] = vol; + } + + // Apply the volume change pa_threaded_mainloop_lock(mainloop_); pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); pa_threaded_mainloop_unlock(mainloop_); } void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t max_volume) { - double volume_tick = static_cast(PA_VOLUME_NORM) / 100; - pa_volume_t change = volume_tick; - pa_cvolume pa_volume = pa_volume_; + // Early return if context is not ready + if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) { + spdlog::error("PulseAudio context not ready"); + return; + } + // Prepare volume structure + pa_cvolume pa_volume; + pa_cvolume_init(&pa_volume); + + // Use existing volume structure if valid, otherwise create a safe default + if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) { + pa_volume = pa_volume_; + } else { + // Set stereo as a safe default + pa_volume.channels = 2; + spdlog::debug("Using default stereo volume structure"); + + // Initialize all channels to current volume level + double volume_tick = static_cast(PA_VOLUME_NORM) / 100; + pa_volume_t vol = volume_ * volume_tick; + for (uint8_t i = 0; i < pa_volume.channels; i++) { + pa_volume.values[i] = vol; + } + + // No need to continue with volume change if we had to create a new structure + pa_threaded_mainloop_lock(mainloop_); + pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); + pa_threaded_mainloop_unlock(mainloop_); + return; + } + + // Calculate volume change + double volume_tick = static_cast(PA_VOLUME_NORM) / 100; + pa_volume_t change; max_volume = std::min(max_volume, static_cast(PA_VOLUME_UI_MAX)); - if (change_type == ChangeType::Increase) { - if (volume_ < max_volume) { - if (volume_ + step > max_volume) { - change = round((max_volume - volume_) * volume_tick); - } else { - change = round(step * volume_tick); - } - pa_cvolume_inc(&pa_volume, change); + if (change_type == ChangeType::Increase && volume_ < max_volume) { + // Calculate how much to increase + if (volume_ + step > max_volume) { + change = round((max_volume - volume_) * volume_tick); + } else { + change = round(step * volume_tick); } - } else if (change_type == ChangeType::Decrease) { - if (volume_ > 0) { - if (volume_ - step < 0) { - change = round(volume_ * volume_tick); - } else { - change = round(step * volume_tick); - } - pa_cvolume_dec(&pa_volume, change); + + // Manually increase each channel's volume + for (uint8_t i = 0; i < pa_volume.channels; i++) { + pa_volume.values[i] = std::min(pa_volume.values[i] + change, PA_VOLUME_MAX); } + } else if (change_type == ChangeType::Decrease && volume_ > 0) { + // Calculate how much to decrease + if (volume_ - step < 0) { + change = round(volume_ * volume_tick); + } else { + change = round(step * volume_tick); + } + + // Manually decrease each channel's volume + for (uint8_t i = 0; i < pa_volume.channels; i++) { + pa_volume.values[i] = (pa_volume.values[i] > change) ? (pa_volume.values[i] - change) : 0; + } + } else { + // No change needed + return; } + + // Apply the volume change pa_threaded_mainloop_lock(mainloop_); pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); pa_threaded_mainloop_unlock(mainloop_); From 7e845f506e6fe666e6f5ba9e2907511adb373740 Mon Sep 17 00:00:00 2001 From: Almarhoon Ibraheem Date: Sat, 12 Apr 2025 18:31:34 +0300 Subject: [PATCH 193/780] sway workspace: fix workspace button not shown in nested layouts --- src/modules/sway/workspaces.cpp | 34 +++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index dec5cddf..b8ed73d4 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -494,16 +494,34 @@ std::string Workspaces::trimWorkspaceName(std::string name) { return name; } +bool is_focused_recursive(const Json::Value& node) { + // If a workspace has a focused container then get_tree will say + // that the workspace itself isn't focused. Therefore we need to + // check if any of its nodes are focused as well. + // some layouts like tabbed have many nested nodes + // all nested nodes must be checked for focused flag + if (node["focused"].asBool()) { + return true; + } + + for (const auto& child : node["nodes"]) { + if (is_focused_recursive(child)) { + return true; + } + } + + for (const auto& child : node["floating_nodes"]) { + if (is_focused_recursive(child)) { + return true; + } + } + + return false; +} + void Workspaces::onButtonReady(const Json::Value &node, Gtk::Button &button) { if (config_["current-only"].asBool()) { - // If a workspace has a focused container then get_tree will say - // that the workspace itself isn't focused. Therefore we need to - // check if any of its nodes are focused as well. - bool focused = node["focused"].asBool() || - std::any_of(node["nodes"].begin(), node["nodes"].end(), - [](const auto &child) { return child["focused"].asBool(); }); - - if (focused) { + if (is_focused_recursive(node)) { button.show(); } else { button.hide(); From b03ecb3d744a6d60d01ff1ace0c01b08eef01307 Mon Sep 17 00:00:00 2001 From: literallyvoid Date: Sat, 12 Apr 2025 17:21:57 -0700 Subject: [PATCH 194/780] Move signal handling to main thread --- src/main.cpp | 130 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 105 insertions(+), 25 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 045b2cd4..94355e1b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -7,6 +8,7 @@ #include #include "client.hpp" +#include "util/SafeSignal.hpp" std::mutex reap_mtx; std::list reap; @@ -70,37 +72,115 @@ void startSignalThread() { } } +static int signal_pipe_write_fd; + +// Write a single signal to `signal_pipe_write_fd`. +// This function is set as a signal handler, so it must be async-signal-safe. +static void writeSignalToPipe(int signum) { + ssize_t amt = write(signal_pipe_write_fd, &signum, sizeof(int)); + + // There's not much we can safely do inside of a signal handler. + // Let's just ignore any errors. + (void) amt; +} + +// This initializes `signal_pipe_write_fd`, and sets up signal handlers. +// +// This function will run forever, emitting every `SIGUSR1`, `SIGUSR2`, +// `SIGINT`, and `SIGRTMIN + 1`...`SIGRTMAX` signal received to +// `signal_handler`. +static void catchSignals(waybar::SafeSignal &signal_handler) { + int fd[2]; + pipe(fd); + + int signal_pipe_read_fd = fd[0]; + signal_pipe_write_fd = fd[1]; + + // This pipe should be able to buffer ~thousands of signals. If it fills up, + // we'll drop signals instead of blocking. + + // We can't allow the write end to block because we'll be writing to it in a + // signal handler, which could interrupt the loop that's reading from it and + // deadlock. + + fcntl(signal_pipe_write_fd, F_SETFL, O_NONBLOCK); + + std::signal(SIGUSR1, writeSignalToPipe); + std::signal(SIGUSR2, writeSignalToPipe); + std::signal(SIGINT, writeSignalToPipe); + + for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) { + std::signal(sig, writeSignalToPipe); + } + + while (true) { + int signum; + ssize_t amt = read(signal_pipe_read_fd, &signum, sizeof(int)); + if (amt < 0) { + spdlog::error("read from signal pipe failed with error {}, closing thread", strerror(errno)); + break; + } + + if (amt != sizeof(int)) { + continue; + } + + signal_handler.emit(signum); + } +} + +// Must be called on the main thread. +static void handleSignalMainThread(int signum) { + if (signum >= SIGRTMIN + 1 && signum <= SIGRTMAX) { + for (auto& bar : waybar::Client::inst()->bars) { + bar->handleSignal(signum); + } + + return; + } + + switch (signum) { + case SIGUSR1: + spdlog::debug("Visibility toggled"); + for (auto& bar : waybar::Client::inst()->bars) { + bar->toggle(); + } + break; + case SIGUSR2: + spdlog::info("Reloading..."); + reload = true; + waybar::Client::inst()->reset(); + break; + case SIGINT: + spdlog::info("Quitting."); + reload = false; + waybar::Client::inst()->reset(); + break; + default: + spdlog::debug("Received signal with number {}, but not handling", signum); + break; + } +} + int main(int argc, char* argv[]) { try { auto* client = waybar::Client::inst(); - std::signal(SIGUSR1, [](int /*signal*/) { - for (auto& bar : waybar::Client::inst()->bars) { - bar->toggle(); - } - }); - - std::signal(SIGUSR2, [](int /*signal*/) { - spdlog::info("Reloading..."); - reload = true; - waybar::Client::inst()->reset(); - }); - - std::signal(SIGINT, [](int /*signal*/) { - spdlog::info("Quitting."); - reload = false; - waybar::Client::inst()->reset(); - }); - - for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) { - std::signal(sig, [](int sig) { - for (auto& bar : waybar::Client::inst()->bars) { - bar->handleSignal(sig); - } - }); - } startSignalThread(); + waybar::SafeSignal posix_signal_received; + posix_signal_received.connect([&](int signum) { + handleSignalMainThread(signum); + }); + + std::thread signal_thread([&]() { + catchSignals(posix_signal_received); + }); + + // Every `std::thread` must be joined or detached. + // This thread should run forever, so detach it. + signal_thread.detach(); + auto ret = 0; do { reload = false; From 97591c825a394fc3bedde47b26bed9d605459cdc Mon Sep 17 00:00:00 2001 From: literallyvoid Date: Sat, 12 Apr 2025 16:41:18 -0700 Subject: [PATCH 195/780] Remove `signalThread` and move reaping to `catchSignals` --- src/main.cpp | 78 +++++++++++----------------------------------------- 1 file changed, 16 insertions(+), 62 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 94355e1b..ba6e00dc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,64 +14,6 @@ std::mutex reap_mtx; std::list reap; volatile bool reload; -void* signalThread(void* args) { - int err; - int signum; - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGCHLD); - - while (true) { - err = sigwait(&mask, &signum); - if (err != 0) { - spdlog::error("sigwait failed: {}", strerror(errno)); - continue; - } - - switch (signum) { - case SIGCHLD: - spdlog::debug("Received SIGCHLD in signalThread"); - if (!reap.empty()) { - reap_mtx.lock(); - for (auto it = reap.begin(); it != reap.end(); ++it) { - if (waitpid(*it, nullptr, WNOHANG) == *it) { - spdlog::debug("Reaped child with PID: {}", *it); - it = reap.erase(it); - } - } - reap_mtx.unlock(); - } - break; - default: - spdlog::debug("Received signal with number {}, but not handling", signum); - break; - } - } -} - -void startSignalThread() { - int err; - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGCHLD); - - // Block SIGCHLD so it can be handled by the signal thread - // Any threads created by this one (the main thread) should not - // modify their signal mask to unblock SIGCHLD - err = pthread_sigmask(SIG_BLOCK, &mask, nullptr); - if (err != 0) { - spdlog::error("pthread_sigmask failed in startSignalThread: {}", strerror(err)); - exit(1); - } - - pthread_t thread_id; - err = pthread_create(&thread_id, nullptr, signalThread, nullptr); - if (err != 0) { - spdlog::error("pthread_create failed in startSignalThread: {}", strerror(err)); - exit(1); - } -} - static int signal_pipe_write_fd; // Write a single signal to `signal_pipe_write_fd`. @@ -87,8 +29,8 @@ static void writeSignalToPipe(int signum) { // This initializes `signal_pipe_write_fd`, and sets up signal handlers. // // This function will run forever, emitting every `SIGUSR1`, `SIGUSR2`, -// `SIGINT`, and `SIGRTMIN + 1`...`SIGRTMAX` signal received to -// `signal_handler`. +// `SIGINT`, `SIGCHLD`, and `SIGRTMIN + 1`...`SIGRTMAX` signal received +// to `signal_handler`. static void catchSignals(waybar::SafeSignal &signal_handler) { int fd[2]; pipe(fd); @@ -108,6 +50,7 @@ static void catchSignals(waybar::SafeSignal &signal_handler) { std::signal(SIGUSR1, writeSignalToPipe); std::signal(SIGUSR2, writeSignalToPipe); std::signal(SIGINT, writeSignalToPipe); + std::signal(SIGCHLD, writeSignalToPipe); for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) { std::signal(sig, writeSignalToPipe); @@ -156,6 +99,19 @@ static void handleSignalMainThread(int signum) { reload = false; waybar::Client::inst()->reset(); break; + case SIGCHLD: + spdlog::debug("Received SIGCHLD in signalThread"); + if (!reap.empty()) { + reap_mtx.lock(); + for (auto it = reap.begin(); it != reap.end(); ++it) { + if (waitpid(*it, nullptr, WNOHANG) == *it) { + spdlog::debug("Reaped child with PID: {}", *it); + it = reap.erase(it); + } + } + reap_mtx.unlock(); + } + break; default: spdlog::debug("Received signal with number {}, but not handling", signum); break; @@ -166,8 +122,6 @@ int main(int argc, char* argv[]) { try { auto* client = waybar::Client::inst(); - startSignalThread(); - waybar::SafeSignal posix_signal_received; posix_signal_received.connect([&](int signum) { handleSignalMainThread(signum); From dbd3ffd73217506ce519179f460820a870b334b6 Mon Sep 17 00:00:00 2001 From: literallyvoid Date: Sat, 12 Apr 2025 17:48:16 -0700 Subject: [PATCH 196/780] Convert `reload` to a local --- src/main.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index ba6e00dc..ce493ca1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,7 +12,6 @@ std::mutex reap_mtx; std::list reap; -volatile bool reload; static int signal_pipe_write_fd; @@ -73,7 +72,10 @@ static void catchSignals(waybar::SafeSignal &signal_handler) { } // Must be called on the main thread. -static void handleSignalMainThread(int signum) { +// +// If this signal should restart or close the bar, this function will write +// `true` or `false`, respectively, into `reload`. +static void handleSignalMainThread(int signum, bool &reload) { if (signum >= SIGRTMIN + 1 && signum <= SIGRTMAX) { for (auto& bar : waybar::Client::inst()->bars) { bar->handleSignal(signum); @@ -122,9 +124,11 @@ int main(int argc, char* argv[]) { try { auto* client = waybar::Client::inst(); + bool reload; + waybar::SafeSignal posix_signal_received; posix_signal_received.connect([&](int signum) { - handleSignalMainThread(signum); + handleSignalMainThread(signum, reload); }); std::thread signal_thread([&]() { From 252e4f78bfb18d515c99c55afbe47a3987deb2d7 Mon Sep 17 00:00:00 2001 From: Kaiyang Wu Date: Sun, 13 Apr 2025 22:11:41 -0700 Subject: [PATCH 197/780] fix: support libcava 0.10.4 Signed-off-by: Kaiyang Wu --- include/modules/cava.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/include/modules/cava.hpp b/include/modules/cava.hpp index b4d2325b..1a88c7b7 100644 --- a/include/modules/cava.hpp +++ b/include/modules/cava.hpp @@ -5,7 +5,16 @@ 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 From 517eb7651e3ea6c7d731e7d9e21d216350d39150 Mon Sep 17 00:00:00 2001 From: literallyvoid Date: Mon, 14 Apr 2025 12:30:08 -0700 Subject: [PATCH 198/780] Run `clang-format` on main.cpp --- src/main.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index ce493ca1..6e7650a9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,7 +22,7 @@ static void writeSignalToPipe(int signum) { // There's not much we can safely do inside of a signal handler. // Let's just ignore any errors. - (void) amt; + (void)amt; } // This initializes `signal_pipe_write_fd`, and sets up signal handlers. @@ -30,7 +30,7 @@ static void writeSignalToPipe(int signum) { // This function will run forever, emitting every `SIGUSR1`, `SIGUSR2`, // `SIGINT`, `SIGCHLD`, and `SIGRTMIN + 1`...`SIGRTMAX` signal received // to `signal_handler`. -static void catchSignals(waybar::SafeSignal &signal_handler) { +static void catchSignals(waybar::SafeSignal& signal_handler) { int fd[2]; pipe(fd); @@ -72,10 +72,10 @@ static void catchSignals(waybar::SafeSignal &signal_handler) { } // Must be called on the main thread. -// +// // If this signal should restart or close the bar, this function will write // `true` or `false`, respectively, into `reload`. -static void handleSignalMainThread(int signum, bool &reload) { +static void handleSignalMainThread(int signum, bool& reload) { if (signum >= SIGRTMIN + 1 && signum <= SIGRTMAX) { for (auto& bar : waybar::Client::inst()->bars) { bar->handleSignal(signum); @@ -114,7 +114,7 @@ static void handleSignalMainThread(int signum, bool &reload) { reap_mtx.unlock(); } break; - default: + default: spdlog::debug("Received signal with number {}, but not handling", signum); break; } @@ -127,13 +127,9 @@ int main(int argc, char* argv[]) { bool reload; waybar::SafeSignal posix_signal_received; - posix_signal_received.connect([&](int signum) { - handleSignalMainThread(signum, reload); - }); + posix_signal_received.connect([&](int signum) { handleSignalMainThread(signum, reload); }); - std::thread signal_thread([&]() { - catchSignals(posix_signal_received); - }); + std::thread signal_thread([&]() { catchSignals(posix_signal_received); }); // Every `std::thread` must be joined or detached. // This thread should run forever, so detach it. From e85025f8052fbf25730c4153548677222d935490 Mon Sep 17 00:00:00 2001 From: Viktar Lukashonak Date: Tue, 15 Apr 2025 16:33:07 +0300 Subject: [PATCH 199/780] libCava bump: 0.10.4 --- meson.build | 2 +- subprojects/cava.wrap | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/meson.build b/meson.build index 1bc1f656..7f9854d5 100644 --- a/meson.build +++ b/meson.build @@ -486,7 +486,7 @@ if get_option('experimental') endif cava = dependency('cava', - version : '>=0.10.3', + version : '>=0.10.4', required: get_option('cava'), fallback : ['cava', 'cava_dep'], not_found_message: 'cava is not found. Building waybar without cava') diff --git a/subprojects/cava.wrap b/subprojects/cava.wrap index f0309bf5..f220207c 100644 --- a/subprojects/cava.wrap +++ b/subprojects/cava.wrap @@ -1,7 +1,7 @@ [wrap-file] -directory = cava-0.10.3 -source_url = https://github.com/LukashonakV/cava/archive/0.10.3.tar.gz -source_filename = cava-0.10.3.tar.gz -source_hash = aab0a4ed3f999e8461ad9de63ef8a77f28b6b2011f7dd0c69ba81819d442f6f9 +directory = cava-0.10.4 +source_url = https://github.com/LukashonakV/cava/archive/0.10.4.tar.gz +source_filename = cava-0.10.4.tar.gz +source_hash =7bc1c1f9535f2bcc5cd2ae8a2434a2e3a05f5670b1c96316df304137ffc65756 [provide] cava = cava_dep From bf4f3ab064a4b8aa3b37868a1a6a8abede373e6e Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Tue, 15 Apr 2025 12:06:41 -0500 Subject: [PATCH 200/780] nix: cava bump --- nix/default.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nix/default.nix b/nix/default.nix index fc564f0a..2e621272 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -5,12 +5,12 @@ }: let libcava = rec { - version = "0.10.3"; + version = "0.10.4"; src = pkgs.fetchFromGitHub { owner = "LukashonakV"; repo = "cava"; - rev = version; - hash = "sha256-ZDFbI69ECsUTjbhlw2kHRufZbQMu+FQSMmncCJ5pagg="; + tag = version; + hash = "sha256-9eTDqM+O1tA/3bEfd1apm8LbEcR9CVgELTIspSVPMKM="; }; }; in From 5c48373cfedb32300a51bb0e567fb9eeedc48253 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Thu, 3 Apr 2025 23:43:47 -0500 Subject: [PATCH 201/780] flake.nix: add treefmt formatter Easier to format everything properly. --- flake.nix | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/flake.nix b/flake.nix index 571c4934..d19d4a1f 100644 --- a/flake.nix +++ b/flake.nix @@ -45,12 +45,41 @@ # overrides for local development nativeBuildInputs = pkgs.waybar.nativeBuildInputs ++ (with pkgs; [ + nixfmt-rfc-style clang-tools gdb ]); }; }); + 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" ]; + }; + }; + tree-root-file = ".git/index"; + } + ]; + } + ); + overlays = { default = self.overlays.waybar; waybar = final: prev: { From 55f52c345775535649d8861b83a0e936a207830c Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Tue, 15 Apr 2025 14:56:28 -0500 Subject: [PATCH 202/780] treewide: clang and nix format --- default.nix | 19 +++---- flake.nix | 106 ++++++++++++++++++++++-------------- nix/default.nix | 53 +++++++++--------- src/modules/sni/item.cpp | 12 ++-- src/modules/sni/tray.cpp | 3 +- src/modules/temperature.cpp | 8 +-- src/modules/wlr/taskbar.cpp | 2 +- 7 files changed, 113 insertions(+), 90 deletions(-) diff --git a/default.nix b/default.nix index 2cccff28..6466507b 100644 --- a/default.nix +++ b/default.nix @@ -1,10 +1,9 @@ -(import - ( - let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in - fetchTarball { - url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; - sha256 = lock.nodes.flake-compat.locked.narHash; - } - ) - { src = ./.; } -).defaultNix +(import ( + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } +) { src = ./.; }).defaultNix diff --git a/flake.nix b/flake.nix index d19d4a1f..7c7a2281 100644 --- a/flake.nix +++ b/flake.nix @@ -9,48 +9,68 @@ }; }; - outputs = { self, nixpkgs, ... }: + outputs = + { self, nixpkgs, ... }: let inherit (nixpkgs) lib; - genSystems = func: lib.genAttrs [ - "x86_64-linux" - "aarch64-linux" - ] - (system: func (import nixpkgs { - inherit system; - overlays = with self.overlays; [ - waybar - ]; - })); + genSystems = + func: + lib.genAttrs + [ + "x86_64-linux" + "aarch64-linux" + ] + ( + system: + func ( + import nixpkgs { + inherit system; + overlays = with self.overlays; [ + waybar + ]; + } + ) + ); - mkDate = longDate: (lib.concatStringsSep "-" [ - (builtins.substring 0 4 longDate) - (builtins.substring 4 2 longDate) - (builtins.substring 6 2 longDate) - ]); + mkDate = + longDate: + (lib.concatStringsSep "-" [ + (builtins.substring 0 4 longDate) + (builtins.substring 4 2 longDate) + (builtins.substring 6 2 longDate) + ]); in { - devShells = genSystems - (pkgs: - { - default = - pkgs.mkShell - { - name = "waybar-shell"; + devShells = genSystems (pkgs: { + default = pkgs.mkShell { + name = "waybar-shell"; - # inherit attributes from upstream nixpkgs derivation - inherit (pkgs.waybar) buildInputs depsBuildBuild depsBuildBuildPropagated depsBuildTarget - depsBuildTargetPropagated depsHostHost depsHostHostPropagated depsTargetTarget - depsTargetTargetPropagated propagatedBuildInputs propagatedNativeBuildInputs strictDeps; + # inherit attributes from upstream nixpkgs derivation + inherit (pkgs.waybar) + buildInputs + depsBuildBuild + depsBuildBuildPropagated + depsBuildTarget + depsBuildTargetPropagated + depsHostHost + depsHostHostPropagated + depsTargetTarget + depsTargetTargetPropagated + propagatedBuildInputs + propagatedNativeBuildInputs + strictDeps + ; - # overrides for local development - nativeBuildInputs = pkgs.waybar.nativeBuildInputs ++ (with pkgs; [ - nixfmt-rfc-style - clang-tools - gdb - ]); - }; - }); + # overrides for local development + nativeBuildInputs = + pkgs.waybar.nativeBuildInputs + ++ (with pkgs; [ + nixfmt-rfc-style + clang-tools + gdb + ]); + }; + }); formatter = genSystems ( pkgs: @@ -61,7 +81,7 @@ clang-format = { options = [ "-i" ]; command = lib.getExe' pkgs.clang-tools "clang-format"; - excludes = []; + excludes = [ ]; includes = [ "*.c" "*.cpp" @@ -87,11 +107,15 @@ waybar = prev.waybar; # take the first "version: '...'" from meson.build version = - (builtins.head (builtins.split "'" - (builtins.elemAt - (builtins.split " version: '" (builtins.readFile ./meson.build)) - 2))) - + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty"); + (builtins.head ( + builtins.split "'" ( + builtins.elemAt (builtins.split " version: '" (builtins.readFile ./meson.build)) 2 + ) + )) + + "+date=" + + (mkDate (self.lastModifiedDate or "19700101")) + + "_" + + (self.shortRev or "dirty"); }; }; }; diff --git a/nix/default.nix b/nix/default.nix index 2e621272..4cfd75c0 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -1,7 +1,8 @@ -{ lib -, pkgs -, waybar -, version +{ + lib, + pkgs, + waybar, + version, }: let libcava = rec { @@ -14,31 +15,29 @@ let }; }; in -(waybar.overrideAttrs ( - oldAttrs: { - inherit version; +(waybar.overrideAttrs (oldAttrs: { + inherit version; - src = lib.cleanSourceWith { - filter = name: type: type != "regular" || !lib.hasSuffix ".nix" name; - src = lib.cleanSource ../.; - }; + src = lib.cleanSourceWith { + filter = name: type: type != "regular" || !lib.hasSuffix ".nix" name; + src = lib.cleanSource ../.; + }; - mesonFlags = lib.remove "-Dgtk-layer-shell=enabled" oldAttrs.mesonFlags; + mesonFlags = lib.remove "-Dgtk-layer-shell=enabled" oldAttrs.mesonFlags; - # downstream patch should not affect upstream - patches = []; - # nixpkgs checks version, no need when building locally - nativeInstallCheckInputs = []; + # downstream patch should not affect upstream + patches = [ ]; + # nixpkgs checks version, no need when building locally + nativeInstallCheckInputs = [ ]; - buildInputs = (builtins.filter (p: p.pname != "wireplumber") oldAttrs.buildInputs) ++ [ - pkgs.wireplumber - ]; + buildInputs = (builtins.filter (p: p.pname != "wireplumber") oldAttrs.buildInputs) ++ [ + pkgs.wireplumber + ]; - postUnpack = '' - pushd "$sourceRoot" - cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava-${libcava.version} - patchShebangs . - popd - ''; - } -)) + postUnpack = '' + pushd "$sourceRoot" + cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava-${libcava.version} + patchShebangs . + popd + ''; +})) diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 978e8b07..9dc13158 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -1,16 +1,16 @@ #include "modules/sni/item.hpp" -#include "modules/sni/icon_manager.hpp" #include #include #include #include +#include #include #include -#include #include "gdk/gdk.h" +#include "modules/sni/icon_manager.hpp" #include "util/format.hpp" #include "util/gtk_icon.hpp" @@ -206,10 +206,10 @@ void Item::setCustomIcon(const std::string& id) { std::string custom_icon = IconManager::instance().getIconForApp(id); if (!custom_icon.empty()) { if (std::filesystem::exists(custom_icon)) { - Glib::RefPtr custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon); - icon_name = ""; // icon_name has priority over pixmap - icon_pixmap = custom_pixbuf; - } else { // if file doesn't exist it's most likely an icon_name + Glib::RefPtr custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon); + icon_name = ""; // icon_name has priority over pixmap + icon_pixmap = custom_pixbuf; + } else { // if file doesn't exist it's most likely an icon_name icon_name = custom_icon; } } diff --git a/src/modules/sni/tray.cpp b/src/modules/sni/tray.cpp index abd23ebd..f657c855 100644 --- a/src/modules/sni/tray.cpp +++ b/src/modules/sni/tray.cpp @@ -1,8 +1,9 @@ #include "modules/sni/tray.hpp" -#include "modules/sni/icon_manager.hpp" #include +#include "modules/sni/icon_manager.hpp" + namespace waybar::modules::SNI { Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index 93f427ee..a3e1c1ee 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -115,10 +115,10 @@ float waybar::modules::Temperature::getTemperature() { auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0; // First, try with dev.cpu - if ( (sysctlbyname(fmt::format("dev.cpu.{}.temperature", zone).c_str(), &temp, &size, - NULL, 0) == 0) || - (sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size, - NULL, 0) == 0) ) { + if ((sysctlbyname(fmt::format("dev.cpu.{}.temperature", zone).c_str(), &temp, &size, NULL, 0) == + 0) || + (sysctlbyname(fmt::format("hw.acpi.thermal.tz{}.temperature", zone).c_str(), &temp, &size, + NULL, 0) == 0)) { auto temperature_c = ((float)temp - 2732) / 10; return temperature_c; } diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 1169425a..8e3b2542 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -382,7 +382,7 @@ std::string Task::state_string(bool shortened) const { return res.substr(0, res.size() - 1); } -void Task::handle_title(const char* title) { +void Task::handle_title(const char *title) { if (title_.empty()) { spdlog::debug(fmt::format("Task ({}) setting title to {}", id_, title_)); } else { From 45ebf4534316c75c318aece87440e46d8ef1b21f Mon Sep 17 00:00:00 2001 From: Lena Date: Fri, 20 Sep 2024 17:40:43 +0200 Subject: [PATCH 203/780] Start GPS module --- include/modules/gps.hpp | 31 ++++++++++++ man/waybar-gps.5.scd | 41 ++++++++++++++++ meson.build | 10 +++- meson_options.txt | 1 + src/factory.cpp | 8 ++++ src/modules/gps.cpp | 101 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 include/modules/gps.hpp create mode 100644 man/waybar-gps.5.scd create mode 100644 src/modules/gps.cpp diff --git a/include/modules/gps.hpp b/include/modules/gps.hpp new file mode 100644 index 00000000..20bf02e4 --- /dev/null +++ b/include/modules/gps.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include +#include + +#include "ALabel.hpp" +#include "util/format.hpp" +#include "util/sleeper_thread.hpp" + +namespace waybar::modules { + + class Gps : public ALabel { + public: + Gps(const std::string&, const Json::Value&); + virtual ~Gps(); + auto update() -> void override; + + private: + const int getFixState() const; + const std::string getFixStateName() const; + const std::string getFixStateString() const; + + util::SleeperThread thread_; + gps_data_t gps_data_; + std::string state_; + }; + +} // namespace waybar::modules diff --git a/man/waybar-gps.5.scd b/man/waybar-gps.5.scd new file mode 100644 index 00000000..dfa91c27 --- /dev/null +++ b/man/waybar-gps.5.scd @@ -0,0 +1,41 @@ +waybar-gps(5) "waybar-gps" "User Manual" + +# NAME + +waybar - gps module + +# DESCRIPTION + +*gps* module for gpsd. + + +# FILES + +$XDG_CONFIG_HOME/waybar/config ++ + Per user configuration file + +# ADDITIONAL FILES + +libgps lives in: + +. /usr/lib/libgps.so or /usr/lib64/libgps.so +. /usr/lib/pkgconfig/libgps.pc or /usr/lib64/pkgconfig/libgps.pc +. /usr/include/gps + +# CONFIGURATION + + + +# EXAMPLES + +``` +"gps": { + +}, +``` +# STYLE + +- *#gps* +- *#gps.fix-none* Applied when GPS is present, but there is no fix. +- *#gps.fix-2d* Applied when there is a 2D fix. +- *#gps.fix-3d* Applied when there is a 3D fix. diff --git a/meson.build b/meson.build index 7f9854d5..a9d6b6e1 100644 --- a/meson.build +++ b/meson.build @@ -93,6 +93,7 @@ libmpdclient = dependency('libmpdclient', required: get_option('mpd')) xkbregistry = dependency('xkbregistry') libjack = dependency('jack', required: get_option('jack')) libwireplumber = dependency('wireplumber-0.5', required: get_option('wireplumber')) +libgps = dependency('libgps', required: get_option('gps')) libsndio = compiler.find_library('sndio', required: get_option('sndio')) if libsndio.found() @@ -497,6 +498,12 @@ if cava.found() man_files += files('man/waybar-cava.5.scd') endif +if libgps.found() + add_project_arguments('-DHAVE_LIBGPS', language: 'cpp') + src_files += files('src/modules/gps.cpp') + man_files += files('man/waybar-gps.5.scd') +endif + subdir('protocol') app_resources = [] @@ -535,7 +542,8 @@ executable( libsndio, tz_dep, xkbregistry, - cava + cava, + libgps ], include_directories: inc_dirs, install: true, diff --git a/meson_options.txt b/meson_options.txt index d83fe01f..c0f63f28 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -21,3 +21,4 @@ option('wireplumber', type: 'feature', value: 'auto', description: 'Enable suppo option('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava') option('niri', type: 'boolean', description: 'Enable support for niri') option('login-proxy', type: 'boolean', description: 'Enable interfacing with dbus login interface') +option('gps', type: 'feature', value: 'auto', description: 'Enable support for gps') diff --git a/src/factory.cpp b/src/factory.cpp index 1483397d..cfc12fbe 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -109,6 +109,9 @@ #ifdef HAVE_SYSTEMD_MONITOR #include "modules/systemd_failed_units.hpp" #endif +#ifdef HAVE_LIBGPS +#include "modules/gps.hpp" +#endif #include "modules/cffi.hpp" #include "modules/custom.hpp" #include "modules/image.hpp" @@ -331,6 +334,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, if (ref == "systemd-failed-units") { return new waybar::modules::SystemdFailedUnits(id, config_[name]); } +#endif +#ifdef HAVE_LIBCAVA + if (ref == "gps") { + return new waybar::modules::Gps(id, config_[name]); + } #endif if (ref == "temperature") { return new waybar::modules::Temperature(id, config_[name]); diff --git a/src/modules/gps.cpp b/src/modules/gps.cpp new file mode 100644 index 00000000..52217e0a --- /dev/null +++ b/src/modules/gps.cpp @@ -0,0 +1,101 @@ +#include "modules/gps.hpp" + +#include + +// In the 80000 version of fmt library authors decided to optimize imports +// and moved declarations required for fmt::dynamic_format_arg_store in new +// header fmt/args.h +#if (FMT_VERSION >= 80000) +#include +#else +#include +#endif + +namespace { + using namespace waybar::util; + constexpr const char *DEFAULT_FORMAT = "{status}"; +} // namespace + + +waybar::modules::Gps::Gps(const std::string& id, const Json::Value& config) +: ALabel(config, "gps", id, "{}", 30) { + thread_ = [this] { + dp.emit(); + thread_.sleep_for(interval_); + }; + // TODO: connect to gpsd socket + + if (0 != gps_open("localhost", "2947", &gps_data_)) { + throw std::runtime_error("Can't open gpsd socket"); + } + +} + +const int waybar::modules::Gps::getFixState() const { +0 +} + +const std::string waybar::modules::Gps::getFixStateName() const { + switch (getFixState()) { + case 1: + return "fix-2d"; + case 2: + return "fix-3d"; + default: + return "fix-none"; + } +} + +const std::string waybar::modules::Gps::getFixStateString() const { + switch (getFixState()) { + case 1: + return "2D Fix"; + case 2: + return "3D Fix"; + default: + return "No fix"; + } +} + +auto waybar::modules::Gps::update() -> void { + + std::string tooltip_format; + + if (!alt_) { + auto state = getFixStateName(); + if (!state_.empty() && label_.get_style_context()->has_class(state_)) { + label_.get_style_context()->remove_class(state_); + } + if (config_["format-" + state].isString()) { + default_format_ = config_["format-" + state].asString(); + } else if (config_["format"].isString()) { + default_format_ = config_["format"].asString(); + } else { + default_format_ = DEFAULT_FORMAT; + } + if (config_["tooltip-format-" + state].isString()) { + tooltip_format = config_["tooltip-format-" + state].asString(); + } + if (!label_.get_style_context()->has_class(state)) { + label_.get_style_context()->add_class(state); + } + format_ = default_format_; + state_ = state; + } + + + auto format = format_; + + fmt::dynamic_format_arg_store store; + store.push_back(fmt::arg("status", getFixStateString())); + + label_.set_markup(fmt::vformat(format, store)); +// Call parent update +ALabel::update(); +} + +waybar::modules::Gps::~Gps() { + gps_close(&gps_data_); +} + + From dfa40d6a00439c0d3cf57dba51ca5b30bb4d01dc Mon Sep 17 00:00:00 2001 From: Lena Date: Fri, 20 Sep 2024 19:05:26 +0200 Subject: [PATCH 204/780] add all arguments --- include/modules/gps.hpp | 7 +-- src/modules/gps.cpp | 106 ++++++++++++++++++++++++++++++++-------- 2 files changed, 90 insertions(+), 23 deletions(-) diff --git a/include/modules/gps.hpp b/include/modules/gps.hpp index 20bf02e4..a3335a10 100644 --- a/include/modules/gps.hpp +++ b/include/modules/gps.hpp @@ -19,9 +19,10 @@ namespace waybar::modules { auto update() -> void override; private: - const int getFixState() const; - const std::string getFixStateName() const; - const std::string getFixStateString() const; + const std::string getFixModeName() const; + const std::string getFixModeString() const; + + const std::string getFixStatusString() const; util::SleeperThread thread_; gps_data_t gps_data_; diff --git a/src/modules/gps.cpp b/src/modules/gps.cpp index 52217e0a..e2367c41 100644 --- a/src/modules/gps.cpp +++ b/src/modules/gps.cpp @@ -1,6 +1,8 @@ #include "modules/gps.hpp" +#include #include +#include // In the 80000 version of fmt library authors decided to optimize imports // and moved declarations required for fmt::dynamic_format_arg_store in new @@ -13,56 +15,84 @@ namespace { using namespace waybar::util; - constexpr const char *DEFAULT_FORMAT = "{status}"; + constexpr const char *DEFAULT_FORMAT = "{mode}"; } // namespace waybar::modules::Gps::Gps(const std::string& id, const Json::Value& config) -: ALabel(config, "gps", id, "{}", 30) { +: ALabel(config, "gps", id, "{}", 1) { thread_ = [this] { dp.emit(); thread_.sleep_for(interval_); }; - // TODO: connect to gpsd socket if (0 != gps_open("localhost", "2947", &gps_data_)) { throw std::runtime_error("Can't open gpsd socket"); } + gps_stream(&gps_data_, WATCH_ENABLE, NULL); } -const int waybar::modules::Gps::getFixState() const { -0 -} - -const std::string waybar::modules::Gps::getFixStateName() const { - switch (getFixState()) { - case 1: +const std::string waybar::modules::Gps::getFixModeName() const { + switch (gps_data_.fix.mode) { + case MODE_NO_FIX: + return "fix-none"; + case MODE_2D: return "fix-2d"; - case 2: + case MODE_3D: return "fix-3d"; default: - return "fix-none"; + return "disconnected"; } } -const std::string waybar::modules::Gps::getFixStateString() const { - switch (getFixState()) { - case 1: +const std::string waybar::modules::Gps::getFixModeString() const { + switch (gps_data_.fix.mode) { + case MODE_NO_FIX: + return "No fix"; + case MODE_2D: return "2D Fix"; - case 2: + case MODE_3D: return "3D Fix"; default: - return "No fix"; + return "Disconnected"; + } +} + +const std::string waybar::modules::Gps::getFixStatusString() const { + switch (gps_data_.fix.status) { + case STATUS_UNK: + return "Unknown"; + case STATUS_GPS: + return "GPS"; + case STATUS_DGPS: + return "DGPS"; + case STATUS_RTK_FIX: + return "RTK Fixed"; + case STATUS_RTK_FLT: + return "RTK Float"; + case STATUS_DR: + return "Dead Reckoning"; + case STATUS_GNSSDR: + return "GNSS + Dead Reckoning"; + case STATUS_TIME: + return "Time Only"; + case STATUS_PPS_FIX: + return "PPS Fix"; + default: + return "Unknown"; } } auto waybar::modules::Gps::update() -> void { + if (gps_read(&gps_data_, NULL, 0) == -1) { + throw std::runtime_error("Can't read data from gpsd."); + } std::string tooltip_format; if (!alt_) { - auto state = getFixStateName(); + auto state = getFixModeName(); if (!state_.empty() && label_.get_style_context()->has_class(state_)) { label_.get_style_context()->remove_class(state_); } @@ -87,14 +117,50 @@ auto waybar::modules::Gps::update() -> void { auto format = format_; fmt::dynamic_format_arg_store store; - store.push_back(fmt::arg("status", getFixStateString())); + store.push_back(fmt::arg("mode", getFixModeString())); + store.push_back(fmt::arg("status", getFixStatusString())); - label_.set_markup(fmt::vformat(format, store)); + store.push_back(fmt::arg("latitude", gps_data_.fix.latitude)); + store.push_back(fmt::arg("latitude_error", gps_data_.fix.epy)); + + store.push_back(fmt::arg("longitude", gps_data_.fix.longitude)); + store.push_back(fmt::arg("longitude_error", gps_data_.fix.epx)); + + store.push_back(fmt::arg("altitude_hae", gps_data_.fix.altHAE)); + store.push_back(fmt::arg("altitude_msl", gps_data_.fix.altMSL)); + store.push_back(fmt::arg("altitude_error", gps_data_.fix.epv)); + + store.push_back(fmt::arg("speed", gps_data_.fix.speed)); + store.push_back(fmt::arg("speed_error", gps_data_.fix.eps)); + + store.push_back(fmt::arg("climb", gps_data_.fix.climb)); + store.push_back(fmt::arg("climb_error", gps_data_.fix.epc)); + + store.push_back(fmt::arg("satellites_used", gps_data_.satellites_used)); + store.push_back(fmt::arg("satellites_visible", gps_data_.satellites_visible)); + + auto text = fmt::vformat(format, store); + + if (tooltipEnabled()) { + if (tooltip_format.empty() && config_["tooltip-format"].isString()) { + tooltip_format = config_["tooltip-format"].asString(); + } + if (!tooltip_format.empty()) { + auto tooltip_text = fmt::vformat(tooltip_format, store); + if (label_.get_tooltip_text() != tooltip_text) { + label_.set_tooltip_markup(tooltip_text); + } + } else if (label_.get_tooltip_text() != text) { + label_.set_tooltip_markup(text); + } + } + label_.set_markup(text); // Call parent update ALabel::update(); } waybar::modules::Gps::~Gps() { + gps_stream(&gps_data_, WATCH_DISABLE, NULL); gps_close(&gps_data_); } From d331331b76a6ff0e45a371a0ff29d7931e11fada Mon Sep 17 00:00:00 2001 From: Lena Date: Fri, 20 Sep 2024 19:56:16 +0200 Subject: [PATCH 205/780] add manpage --- include/modules/gps.hpp | 3 ++ man/waybar-gps.5.scd | 71 +++++++++++++++++++++++++++++++++++++++-- src/modules/gps.cpp | 18 ++++++++++- 3 files changed, 89 insertions(+), 3 deletions(-) diff --git a/include/modules/gps.hpp b/include/modules/gps.hpp index a3335a10..6c2e980e 100644 --- a/include/modules/gps.hpp +++ b/include/modules/gps.hpp @@ -27,6 +27,9 @@ namespace waybar::modules { util::SleeperThread thread_; gps_data_t gps_data_; std::string state_; + + bool hideDisconnected = true; + bool hideNoFix = false; }; } // namespace waybar::modules diff --git a/man/waybar-gps.5.scd b/man/waybar-gps.5.scd index dfa91c27..cf5faf42 100644 --- a/man/waybar-gps.5.scd +++ b/man/waybar-gps.5.scd @@ -24,14 +24,81 @@ libgps lives in: # CONFIGURATION +*format*: ++ + typeof: string ++ + default: {glyph} ++ + The text format. +*tooltip*: ++ + typeof: bool ++ + default: true ++ + Option to disable tooltip on hover. + +*tooltip-format*: ++ + typeof: string ++ + default: Games running: {glyph} ++ + The text format of the tooltip. + +*interval*: ++ + typeof: integer ++ + default: 5 ++ + The interval in which the GPS information gets polled (e.g. fix status). + +*hide-disconnected*: ++ + typeof: bool ++ + default: true ++ + Defines if the module should be hidden if there is no GPS receiver. + +*hide-no-fix*: ++ + typeof: bool ++ + default: false ++ + Defines if the module should be hidden if there is no GPS fix. + +# FORMAT REPLACEMENTS + +*{mode}*: Fix mode + +*{status}*: Technology used for GPS fix. Not all GPS receivers report this. + +*{latitude}*: Latitude, decimal degrees. Can be NaN. + +*{latitude_error}*: Latitude uncertainty, meters. Can be NaN. + +*{longitude}*: Longitude, decimal degrees. Can be NaN. + +*{longitude_error}*: Longitude uncertainty, meters. Can be NaN. + +*{altitude_hae}*: Altitude, height above ellipsoid, meters. Can be NaN. + +*{altitude_msl}*: Longitude, MSL, meters. Can be NaN. + +*{altitude_error}*: Altitude uncertainty, meters. Can be NaN. + +*{speed}*: Speed over ground, meters/sec. Can be NaN. + +*{speed_error}*: Speed uncertainty, meters/sec. Can be NaN. + +*{climb}*: Vertical speed, meters/sec. Can be NaN. + +*{climb_error}*: Vertical speed uncertainty, meters/sec. Can be NaN. + +*{satellites_visible}*: Number of satellites visible from the GPS receiver. + +*{satellites_used}*: Number of satellites used for the GPS fix. # EXAMPLES ``` "gps": { - -}, + "format": "{mode}", + "format-no-fix": "No fix", + "format-fix-3d": "{status}", + "tooltip-format": "{mode}", + "tooltip-format-no-fix": "{satellites_visible} satellites visible", + "tooltip-format-fix-2d": "{satellites_used}/{satellites_visible} satellites used", + "tooltip-format-fix-3d": "Altitude: {altitude_hae}m", + "hide-disconnected": false +} ``` # STYLE diff --git a/src/modules/gps.cpp b/src/modules/gps.cpp index e2367c41..12e01ac4 100644 --- a/src/modules/gps.cpp +++ b/src/modules/gps.cpp @@ -20,7 +20,7 @@ namespace { waybar::modules::Gps::Gps(const std::string& id, const Json::Value& config) -: ALabel(config, "gps", id, "{}", 1) { +: ALabel(config, "gps", id, "{}", 5) { thread_ = [this] { dp.emit(); thread_.sleep_for(interval_); @@ -30,6 +30,14 @@ waybar::modules::Gps::Gps(const std::string& id, const Json::Value& config) throw std::runtime_error("Can't open gpsd socket"); } + if (config_["hide-disconnected"].isBool()) { + hideDisconnected = config_["hide-disconnected"].asBool(); + } + + if (config_["hide-no-fix"].isBool()) { + hideNoFix = config_["hide-no-fix"].asBool(); + } + gps_stream(&gps_data_, WATCH_ENABLE, NULL); } @@ -89,6 +97,14 @@ auto waybar::modules::Gps::update() -> void { throw std::runtime_error("Can't read data from gpsd."); } + if ((gps_data_.fix.mode == MODE_NOT_SEEN && hideDisconnected) || (gps_data_.fix.mode == MODE_NO_FIX && hideNoFix)) { + event_box_.set_visible(false); + return; + } + + // Show the module + if (!event_box_.get_visible()) event_box_.set_visible(true); + std::string tooltip_format; if (!alt_) { From 22ec8e077036b69ba691537acb1d330d85358be0 Mon Sep 17 00:00:00 2001 From: Lena Date: Fri, 8 Nov 2024 16:38:51 +0100 Subject: [PATCH 206/780] fix factory copy&paste typo --- src/factory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/factory.cpp b/src/factory.cpp index cfc12fbe..11ca14fd 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -335,7 +335,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, return new waybar::modules::SystemdFailedUnits(id, config_[name]); } #endif -#ifdef HAVE_LIBCAVA +#ifdef HAVE_LIBGPS if (ref == "gps") { return new waybar::modules::Gps(id, config_[name]); } From 956e39d3d3eedd287b14aa82fbc6313246a64b9e Mon Sep 17 00:00:00 2001 From: Lena Date: Fri, 8 Nov 2024 23:40:12 +0100 Subject: [PATCH 207/780] make gpsd receiver not poll --- include/modules/gps.hpp | 2 +- man/waybar-gps.5.scd | 3 ++- src/modules/gps.cpp | 28 ++++++++++++++++++++++++---- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/include/modules/gps.hpp b/include/modules/gps.hpp index 6c2e980e..500614a4 100644 --- a/include/modules/gps.hpp +++ b/include/modules/gps.hpp @@ -24,7 +24,7 @@ namespace waybar::modules { const std::string getFixStatusString() const; - util::SleeperThread thread_; + util::SleeperThread thread_, gps_thread_; gps_data_t gps_data_; std::string state_; diff --git a/man/waybar-gps.5.scd b/man/waybar-gps.5.scd index cf5faf42..3b2c2cac 100644 --- a/man/waybar-gps.5.scd +++ b/man/waybar-gps.5.scd @@ -42,7 +42,8 @@ libgps lives in: *interval*: ++ typeof: integer ++ default: 5 ++ - The interval in which the GPS information gets polled (e.g. fix status). + The interval in which the GPS information gets polled (e.g. current speed). + Significant updates (e.g. the current fix mode) are updated immediately. *hide-disconnected*: ++ typeof: bool ++ diff --git a/src/modules/gps.cpp b/src/modules/gps.cpp index 12e01ac4..85726876 100644 --- a/src/modules/gps.cpp +++ b/src/modules/gps.cpp @@ -1,4 +1,5 @@ #include "modules/gps.hpp" +#include #include #include @@ -38,7 +39,28 @@ waybar::modules::Gps::Gps(const std::string& id, const Json::Value& config) hideNoFix = config_["hide-no-fix"].asBool(); } - gps_stream(&gps_data_, WATCH_ENABLE, NULL); + gps_thread_ = [this] { + dp.emit(); + gps_stream(&gps_data_, WATCH_ENABLE, NULL); + int last_gps_mode = 0; + + while (gps_waiting(&gps_data_, 5000000)) { + if (gps_read(&gps_data_, NULL, 0) == -1) { + throw std::runtime_error("Can't read data from gpsd."); + } + + if (MODE_SET != (MODE_SET & gps_data_.set)) { + // did not even get mode, nothing to see here + continue; + } + + if (gps_data_.fix.mode != last_gps_mode) { + // significant update + dp.emit(); + } + last_gps_mode = gps_data_.fix.mode; + } + }; } const std::string waybar::modules::Gps::getFixModeName() const { @@ -93,9 +115,7 @@ const std::string waybar::modules::Gps::getFixStatusString() const { } auto waybar::modules::Gps::update() -> void { - if (gps_read(&gps_data_, NULL, 0) == -1) { - throw std::runtime_error("Can't read data from gpsd."); - } + sleep(0); // Wait for gps status change if ((gps_data_.fix.mode == MODE_NOT_SEEN && hideDisconnected) || (gps_data_.fix.mode == MODE_NO_FIX && hideNoFix)) { event_box_.set_visible(false); From d0c6e9109449851b1eb082c00254b6aa6dbf87fc Mon Sep 17 00:00:00 2001 From: Lena Date: Sat, 9 Nov 2024 11:58:08 +0100 Subject: [PATCH 208/780] gps: add rfkill support --- include/modules/gps.hpp | 9 +++++++-- man/waybar-gps.5.scd | 2 ++ src/modules/gps.cpp | 20 +++++++++++++++++--- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/include/modules/gps.hpp b/include/modules/gps.hpp index 500614a4..3701634b 100644 --- a/include/modules/gps.hpp +++ b/include/modules/gps.hpp @@ -3,11 +3,13 @@ #include #include -#include +#ifdef WANT_RFKILL +#include "util/rfkill.hpp" +#endif + #include #include "ALabel.hpp" -#include "util/format.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { @@ -19,6 +21,9 @@ namespace waybar::modules { auto update() -> void override; private: + #ifdef WANT_RFKILL + util::Rfkill rfkill_; + #endif const std::string getFixModeName() const; const std::string getFixModeString() const; diff --git a/man/waybar-gps.5.scd b/man/waybar-gps.5.scd index 3b2c2cac..e7473601 100644 --- a/man/waybar-gps.5.scd +++ b/man/waybar-gps.5.scd @@ -92,6 +92,7 @@ libgps lives in: ``` "gps": { "format": "{mode}", + "format-disabled": "", // an empty format will hide the module "format-no-fix": "No fix", "format-fix-3d": "{status}", "tooltip-format": "{mode}", @@ -104,6 +105,7 @@ libgps lives in: # STYLE - *#gps* +- *#gps.disabled* Applied when GPS is disabled. - *#gps.fix-none* Applied when GPS is present, but there is no fix. - *#gps.fix-2d* Applied when there is a 2D fix. - *#gps.fix-3d* Applied when there is a 3D fix. diff --git a/src/modules/gps.cpp b/src/modules/gps.cpp index 85726876..f075b44c 100644 --- a/src/modules/gps.cpp +++ b/src/modules/gps.cpp @@ -21,7 +21,11 @@ namespace { waybar::modules::Gps::Gps(const std::string& id, const Json::Value& config) -: ALabel(config, "gps", id, "{}", 5) { +: ALabel(config, "gps", id, "{}", 5) +#ifdef WANT_RFKILL +,rfkill_{RFKILL_TYPE_GPS} +#endif +{ thread_ = [this] { dp.emit(); thread_.sleep_for(interval_); @@ -61,6 +65,10 @@ waybar::modules::Gps::Gps(const std::string& id, const Json::Value& config) last_gps_mode = gps_data_.fix.mode; } }; + + #ifdef WANT_RFKILL + rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Gps::update))); + #endif } const std::string waybar::modules::Gps::getFixModeName() const { @@ -72,6 +80,9 @@ const std::string waybar::modules::Gps::getFixModeName() const { case MODE_3D: return "fix-3d"; default: + #ifdef WANT_RFKILL + if (rfkill_.getState()) return "disabled"; + #endif return "disconnected"; } } @@ -91,8 +102,6 @@ const std::string waybar::modules::Gps::getFixModeString() const { const std::string waybar::modules::Gps::getFixStatusString() const { switch (gps_data_.fix.status) { - case STATUS_UNK: - return "Unknown"; case STATUS_GPS: return "GPS"; case STATUS_DGPS: @@ -110,6 +119,11 @@ const std::string waybar::modules::Gps::getFixStatusString() const { case STATUS_PPS_FIX: return "PPS Fix"; default: + + #ifdef WANT_RFKILL + if (rfkill_.getState()) return "Disabled"; + #endif + return "Unknown"; } } From 20642f47df9fbdd02fa869f83348c99e431a1092 Mon Sep 17 00:00:00 2001 From: Rimsoo <50300684+Rimsoo@users.noreply.github.com> Date: Tue, 22 Apr 2025 11:41:45 +0200 Subject: [PATCH 209/780] Fixed variant bug --- src/modules/hyprland/language.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index da56e578..2371a397 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -66,7 +66,7 @@ auto Language::update() -> void { void Language::onEvent(const std::string& ev) { std::lock_guard lg(mutex_); std::string kbName(begin(ev) + ev.find_last_of('>') + 1, begin(ev) + ev.find_first_of(',')); - auto layoutName = ev.substr(ev.find_last_of(',') + 1); + auto layoutName = ev.substr(ev.find_first_of(',') + 1); if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString()) return; // ignore From ba8ea3d952c95311a25758a5e9f5420f83e58236 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 24 Apr 2025 09:29:40 +0000 Subject: [PATCH 210/780] 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/52faf482a3889b7619003c0daec593a1912fddc1?narHash=sha256-6hl6L/tRnwubHcA4pfUUtk542wn2Om%2BD4UnDhlDW9BE%3D' (2025-03-30) → 'github:NixOS/nixpkgs/8a2f738d9d1f1d986b5a4cd2fd2061a7127237d7?narHash=sha256-sPwcCYuiEopaafePqlG826tBhctuJsLx/mhKKM5Fmjo%3D' (2025-04-23) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 27290894..480f004f 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1743315132, - "narHash": "sha256-6hl6L/tRnwubHcA4pfUUtk542wn2Om+D4UnDhlDW9BE=", + "lastModified": 1745391562, + "narHash": "sha256-sPwcCYuiEopaafePqlG826tBhctuJsLx/mhKKM5Fmjo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "52faf482a3889b7619003c0daec593a1912fddc1", + "rev": "8a2f738d9d1f1d986b5a4cd2fd2061a7127237d7", "type": "github" }, "original": { From afeea62214872689fa39068655c8c6ca663b78a5 Mon Sep 17 00:00:00 2001 From: Rimsoo <50300684+Rimsoo@users.noreply.github.com> Date: Thu, 24 Apr 2025 23:32:47 +0200 Subject: [PATCH 211/780] fixes #3676 including #3224 --- src/modules/hyprland/language.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index 2371a397..1dbdfeba 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -66,7 +66,11 @@ auto Language::update() -> void { void Language::onEvent(const std::string& ev) { std::lock_guard lg(mutex_); std::string kbName(begin(ev) + ev.find_last_of('>') + 1, begin(ev) + ev.find_first_of(',')); - auto layoutName = ev.substr(ev.find_first_of(',') + 1); + + // Last comma before variants parenthesis, eg: + // activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl., with dead keys) + std::string beforParenthesis(begin(ev), begin(ev) + ev.find_last_of('(')); + auto layoutName = ev.substr(beforParenthesis.find_last_of(',') + 1); if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString()) return; // ignore From 9b7a8a8c21a43f94a421a0db65244e8277b4a603 Mon Sep 17 00:00:00 2001 From: hecate cantus Date: Sun, 27 Apr 2025 20:30:14 -0700 Subject: [PATCH 212/780] implement albumArtist todo testing --- include/modules/mpris/mpris.hpp | 3 +++ src/modules/mpris/mpris.cpp | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/include/modules/mpris/mpris.hpp b/include/modules/mpris/mpris.hpp index ad4dac1e..a07a6358 100644 --- a/include/modules/mpris/mpris.hpp +++ b/include/modules/mpris/mpris.hpp @@ -38,6 +38,7 @@ class Mpris : public ALabel { std::optional artist; std::optional album; + std::optional album_artist; std::optional title; std::optional length; // as HH:MM:SS std::optional position; // same format @@ -76,6 +77,8 @@ class Mpris : public ALabel { std::string player_; std::vector ignored_players_; + bool prefer_album_artist_; + PlayerctlPlayerManager* manager; PlayerctlPlayer* player; std::string lastStatus; diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index ed383b0c..2492f3b5 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -31,6 +31,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) dynamic_separator_(" - "), truncate_hours_(true), tooltip_len_limits_(false), + prefer_album_artist_(false), // this character is used in Gnome so it's fine to use it here ellipsis_("\u2026"), player_("playerctld"), @@ -68,6 +69,9 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) if (config_["enable-tooltip-len-limits"].isBool()) { tooltip_len_limits_ = config["enable-tooltip-len-limits"].asBool(); } + if (config_["prefer-album-artist"].isBool()) { + prefer_album_artist_ = config["prefer-album-artist"].asBool(); + } } if (config["artist-len"].isUInt()) { @@ -244,6 +248,12 @@ void truncate(std::string& s, const std::string& ellipsis, size_t max_len) { auto Mpris::getArtistStr(const PlayerInfo& info, bool truncated) -> std::string { auto artist = info.artist.value_or(std::string()); + if (prefer_album_artist_) { + auto album_artist = info.album_artist.value_or(std::string()); + if (!album_artist.empty()) { + artist = album_artist; + } + } if (truncated && artist_len_ >= 0) truncate(artist, ellipsis_, artist_len_); return artist; } @@ -516,6 +526,7 @@ auto Mpris::getPlayerInfo() -> std::optional { .status_string = player_status, .artist = std::nullopt, .album = std::nullopt, + .album_artist = std::nullopt, .title = std::nullopt, .length = std::nullopt, }; @@ -527,6 +538,12 @@ auto Mpris::getPlayerInfo() -> std::optional { } if (error) goto errorexit; + if (auto* album_artist_ = playerctl_player_print_metadata_prop(player, "xesam:albumArtist", &error)) { + spdlog::debug("mpris[{}]: albumArtist = {}", info.name, album_artist_); + info.album_artist = album_artist_; + g_free(album_artist_); + } + if (auto* album_ = playerctl_player_get_album(player, &error)) { spdlog::debug("mpris[{}]: album = {}", info.name, album_); info.album = album_; From 451d458545157351ea4fbdb60ab807d34d8645e4 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Tue, 29 Apr 2025 20:12:59 +0200 Subject: [PATCH 213/780] Fix compilation errors after merge --- src/modules/wlr/taskbar.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index c16a32d0..d555649b 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -202,7 +202,7 @@ void Task::handle_title(const char *title) { return; } - set_app_info_from_app_id_list(title_); + app_info_ = IconLoader::get_app_info_from_app_id_list(title_); name_ = app_info_ ? app_info_->get_display_name() : title; if (!with_icon_) { @@ -210,15 +210,7 @@ void Task::handle_title(const char *title) { } int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16; - bool found = false; - for (auto &icon_theme : tbar_->icon_themes()) { - if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) { - found = true; - break; - } - } - - if (found) + if (tbar_->icon_loader().image_load_icon(icon_, app_info_, icon_size)) icon_.show(); else spdlog::debug("Couldn't find icon for {}", title_); From a816812f814d0f17c0506215f6f78b97183a9a26 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Wed, 30 Apr 2025 16:20:11 +0200 Subject: [PATCH 214/780] Run clang-format --- src/modules/sway/workspaces.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index b8ed73d4..86c2029b 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -494,7 +494,7 @@ std::string Workspaces::trimWorkspaceName(std::string name) { return name; } -bool is_focused_recursive(const Json::Value& node) { +bool is_focused_recursive(const Json::Value &node) { // If a workspace has a focused container then get_tree will say // that the workspace itself isn't focused. Therefore we need to // check if any of its nodes are focused as well. @@ -504,13 +504,13 @@ bool is_focused_recursive(const Json::Value& node) { return true; } - for (const auto& child : node["nodes"]) { + for (const auto &child : node["nodes"]) { if (is_focused_recursive(child)) { return true; } } - for (const auto& child : node["floating_nodes"]) { + for (const auto &child : node["floating_nodes"]) { if (is_focused_recursive(child)) { return true; } From 59c270ec0667c90e7b0386daa5003c5621b8376e Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Thu, 1 May 2025 19:26:44 +0200 Subject: [PATCH 215/780] Respect format-window-separator if workspace-taskbar is enabled --- src/modules/hyprland/workspace.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 70953247..325fb83f 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -247,7 +247,15 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { } } + bool isFirst = true; for (const auto &window_repr : m_windowMap) { + if (isFirst) { + isFirst = false; + } else { + auto windowSeparator = Gtk::make_managed(m_workspaceManager.getWindowSeparator()); + 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"); From 72404a77f0a62cfae7ba530a4cf56404e72a6e89 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Thu, 1 May 2025 20:22:18 +0200 Subject: [PATCH 216/780] Initial implementation of active window TODO: - Sometimes the active event arrives before the create, in which case the window is not activated. - The window title event also looks unreliable in some cases, will need to investigate --- .../hyprland/windowcreationpayload.hpp | 4 ++ include/modules/hyprland/workspace.hpp | 1 + include/modules/hyprland/workspaces.hpp | 2 + .../hyprland/windowcreationpayload.cpp | 4 +- src/modules/hyprland/workspace.cpp | 13 +++++++ src/modules/hyprland/workspaces.cpp | 38 +++++++++++++++++-- 6 files changed, 57 insertions(+), 5 deletions(-) diff --git a/include/modules/hyprland/windowcreationpayload.hpp b/include/modules/hyprland/windowcreationpayload.hpp index 3bd30c8e..50b709f8 100644 --- a/include/modules/hyprland/windowcreationpayload.hpp +++ b/include/modules/hyprland/windowcreationpayload.hpp @@ -31,9 +31,11 @@ struct WindowRepr { std::string window_class; std::string window_title; std::string repr_rewrite; + bool isActive = false; public: bool empty() const { return address.empty(); } + void setActive(bool value) { isActive = value; } }; class WindowCreationPayload { @@ -48,6 +50,7 @@ class WindowCreationPayload { bool isEmpty(Workspaces& workspace_manager); bool reprIsReady() const { return std::holds_alternative(m_window); } WindowRepr repr(Workspaces& workspace_manager); + void setActive(bool value) { m_isActive = value; } std::string getWorkspaceName() const { return m_workspaceName; } WindowAddress getAddress() const { return m_windowAddress; } @@ -64,6 +67,7 @@ class WindowCreationPayload { WindowAddress m_windowAddress; std::string m_workspaceName; + bool m_isActive = false; int m_timeSpentUncreated = 0; }; diff --git a/include/modules/hyprland/workspace.hpp b/include/modules/hyprland/workspace.hpp index 4280cc39..f61ef00e 100644 --- a/include/modules/hyprland/workspace.hpp +++ b/include/modules/hyprland/workspace.hpp @@ -60,6 +60,7 @@ class Workspace { }; void insertWindow(WindowCreationPayload create_window_paylod); void initializeWindowMap(const Json::Value& clients_data); + void setActiveWindow(WindowAddress const& addr); bool onWindowOpened(WindowCreationPayload const& create_window_paylod); std::optional closeWindow(WindowAddress const& addr); diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 757492be..02599dd5 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -103,6 +103,7 @@ class Workspaces : public AModule, public EventHandler { void onWindowMoved(std::string const& payload); void onWindowTitleEvent(std::string const& payload); + void onActiveWindowChanged(WindowAddress const& payload); void onConfigReloaded(); @@ -170,6 +171,7 @@ class Workspaces : public AModule, public EventHandler { IconLoader m_iconLoader; bool m_enableTaskbar = false; + bool m_updateActiveWindow = false; bool m_taskbarWithIcon = false; bool m_taskbarWithTitle = false; std::string m_taskbarFormatBefore; diff --git a/src/modules/hyprland/windowcreationpayload.cpp b/src/modules/hyprland/windowcreationpayload.cpp index 5e587d51..0cdf4a1a 100644 --- a/src/modules/hyprland/windowcreationpayload.cpp +++ b/src/modules/hyprland/windowcreationpayload.cpp @@ -97,9 +97,9 @@ WindowRepr WindowCreationPayload::repr(Workspaces &workspace_manager) { return std::get(m_window); } if (std::holds_alternative(m_window)) { - auto [window_class, window_title] = std::get(m_window); + auto const &[window_class, window_title] = std::get(m_window); return {m_windowAddress, window_class, window_title, - workspace_manager.getRewrite(window_class, window_title)}; + workspace_manager.getRewrite(window_class, window_title), m_isActive}; } // Unreachable spdlog::error("WorkspaceWindow::repr: Unreachable"); diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 325fb83f..3350e302 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -103,6 +103,16 @@ void Workspace::initializeWindowMap(const Json::Value &clients_data) { } } +void Workspace::setActiveWindow(WindowAddress const &addr) { + for (auto &window : m_windowMap) { + if (window.address == addr) { + window.setActive(true); + } else { + window.setActive(false); + } + } +} + void Workspace::insertWindow(WindowCreationPayload create_window_paylod) { if (!create_window_paylod.isEmpty(m_workspaceManager)) { auto repr = create_window_paylod.repr(m_workspaceManager); @@ -259,6 +269,9 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { 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"); + if (window_repr.isActive) { + window_box->get_style_context()->add_class("active"); + } auto event_box = Gtk::manage(new Gtk::EventBox()); event_box->add(*window_box); if (m_workspaceManager.onClickWindow() != "") { diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index a694e030..a6cd213e 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -341,6 +341,8 @@ void Workspaces::onEvent(const std::string &ev) { onWorkspaceRenamed(payload); } else if (eventName == "windowtitlev2") { onWindowTitleEvent(payload); + } else if (eventName == "activewindowv2") { + onActiveWindowChanged(payload); } else if (eventName == "configreloaded") { onConfigReloaded(); } @@ -561,9 +563,10 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { (*windowWorkspace)->insertWindow(std::move(wcp)); }; } else { - auto queuedWindow = std::ranges::find_if(m_windowsToCreate, [payload](auto &windowPayload) { - return windowPayload.getAddress() == payload; - }); + auto queuedWindow = + std::ranges::find_if(m_windowsToCreate, [&windowAddress](auto &windowPayload) { + return windowPayload.getAddress() == windowAddress; + }); // If the window was queued, rename it in the queue if (queuedWindow != m_windowsToCreate.end()) { @@ -586,6 +589,28 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { } } +void Workspaces::onActiveWindowChanged(WindowAddress const &activeWindowAddress) { + spdlog::trace("Active window changed: {}", activeWindowAddress); + + for (auto &[address, window] : m_orphanWindowMap) { + if (address == activeWindowAddress) { + window.setActive(true); + } else { + window.setActive(false); + } + } + for (auto const &workspace : m_workspaces) { + workspace->setActiveWindow(activeWindowAddress); + } + for (auto &window : m_windowsToCreate) { + if (window.getAddress() == activeWindowAddress) { + window.setActive(true); + } else { + window.setActive(false); + } + } +} + void Workspaces::onConfigReloaded() { spdlog::info("Hyprland config reloaded, reinitializing hyprland/workspaces module..."); init(); @@ -701,6 +726,7 @@ auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> vo } populateBoolConfig(workspaceTaskbar, "enable", m_enableTaskbar); + populateBoolConfig(workspaceTaskbar, "update-active-window", m_updateActiveWindow); if (workspaceTaskbar["format"].isString()) { /* The user defined a format string, use it */ @@ -765,6 +791,12 @@ auto Workspaces::registerIpc() -> void { "rewrite rule uses the 'title' field."); m_ipc.registerForIPC("windowtitlev2", this); } + if (m_updateActiveWindow) { + spdlog::info( + "Registering for Hyprland's 'activewindowv2' events because 'update-active-window' is set " + "to true."); + m_ipc.registerForIPC("activewindowv2", this); + } } void Workspaces::removeWorkspacesToRemove() { From 998fd7a1928fb0b372341e4075b12504042a47f7 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Thu, 1 May 2025 20:51:12 +0200 Subject: [PATCH 217/780] Fix window title not being updated properly --- src/modules/hyprland/workspaces.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index a6cd213e..e6295607 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -577,7 +577,7 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { if (inserter.has_value()) { Json::Value clientsData = m_ipc.getSocket1JsonReply("clients"); - std::string jsonWindowAddress = fmt::format("0x{}", payload); + std::string jsonWindowAddress = fmt::format("0x{}", windowAddress); auto client = std::ranges::find_if(clientsData, [jsonWindowAddress](auto &client) { return client["address"].asString() == jsonWindowAddress; From 61c5dad895ce484c5823ac07edfec8b700930f0d Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Thu, 1 May 2025 21:03:46 +0200 Subject: [PATCH 218/780] Fix some windows not being marked as active when opened In some cases, the active event is arriving before the create event. We need to store the currently active address and initialize the windows accordingly --- include/modules/hyprland/windowcreationpayload.hpp | 2 +- include/modules/hyprland/workspaces.hpp | 1 + src/modules/hyprland/windowcreationpayload.cpp | 5 +++-- src/modules/hyprland/workspaces.cpp | 4 +++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/include/modules/hyprland/windowcreationpayload.hpp b/include/modules/hyprland/windowcreationpayload.hpp index 50b709f8..98526348 100644 --- a/include/modules/hyprland/windowcreationpayload.hpp +++ b/include/modules/hyprland/windowcreationpayload.hpp @@ -43,7 +43,7 @@ class WindowCreationPayload { WindowCreationPayload(std::string workspace_name, WindowAddress window_address, WindowRepr window_repr); WindowCreationPayload(std::string workspace_name, WindowAddress window_address, - std::string window_class, std::string window_title); + std::string window_class, std::string window_title, bool is_active); WindowCreationPayload(Json::Value const& client_data); int incrementTimeSpentUncreated(); diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 02599dd5..240047e5 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -179,6 +179,7 @@ class Workspaces : public AModule, public EventHandler { int m_taskbarIconSize = 16; Gtk::Orientation m_taskbarOrientation = Gtk::ORIENTATION_HORIZONTAL; std::string m_onClickWindow; + std::string m_currentActiveWindowAddress; std::vector m_ignoreWorkspaces; diff --git a/src/modules/hyprland/windowcreationpayload.cpp b/src/modules/hyprland/windowcreationpayload.cpp index 0cdf4a1a..48c4047f 100644 --- a/src/modules/hyprland/windowcreationpayload.cpp +++ b/src/modules/hyprland/windowcreationpayload.cpp @@ -30,10 +30,11 @@ WindowCreationPayload::WindowCreationPayload(std::string workspace_name, WindowCreationPayload::WindowCreationPayload(std::string workspace_name, WindowAddress window_address, std::string window_class, - std::string window_title) + std::string window_title, bool is_active) : m_window(std::make_pair(std::move(window_class), std::move(window_title))), m_windowAddress(std::move(window_address)), - m_workspaceName(std::move(workspace_name)) { + m_workspaceName(std::move(workspace_name)), + m_isActive(is_active) { clearAddr(); clearWorkspaceName(); } diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index e6295607..752f298c 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -492,7 +492,8 @@ void Workspaces::onWindowOpened(std::string const &payload) { std::string windowTitle = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx); - m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle); + bool isActive = m_currentActiveWindowAddress == windowAddress; + m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle, isActive); } void Workspaces::onWindowClosed(std::string const &addr) { @@ -591,6 +592,7 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { void Workspaces::onActiveWindowChanged(WindowAddress const &activeWindowAddress) { spdlog::trace("Active window changed: {}", activeWindowAddress); + m_currentActiveWindowAddress = activeWindowAddress; for (auto &[address, window] : m_orphanWindowMap) { if (address == activeWindowAddress) { From 72184b2205355452037725c96a6f7ce681635af2 Mon Sep 17 00:00:00 2001 From: "Rene D. Obermueller" Date: Fri, 2 May 2025 09:53:11 +0200 Subject: [PATCH 219/780] Issue 3981: try and fix memory leak --- src/modules/privacy/privacy_item.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/modules/privacy/privacy_item.cpp b/src/modules/privacy/privacy_item.cpp index 54e61b43..36efd3e5 100644 --- a/src/modules/privacy/privacy_item.cpp +++ b/src/modules/privacy/privacy_item.cpp @@ -1,5 +1,7 @@ #include "modules/privacy/privacy_item.hpp" +#include + #include #include "glibmm/main.h" @@ -94,22 +96,22 @@ PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privac } void PrivacyItem::update_tooltip() { + spdlog::trace("update privacy tooltip"); // Removes all old nodes for (auto *child : tooltip_window.get_children()) { - delete child; + tooltip_window.remove(*child); } - for (auto *node : *nodes) { Gtk::Box *box = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4); // Set device icon - Gtk::Image *node_icon = new Gtk::Image(); + Gtk::Image *node_icon = Gtk::make_managed(); node_icon->set_pixel_size(tooltipIconSize); node_icon->set_from_icon_name(node->getIconName(), Gtk::ICON_SIZE_INVALID); box->add(*node_icon); // Set model - auto *nodeName = new Gtk::Label(node->getName()); + auto *nodeName = Gtk::make_managed(node->getName()); box->add(*nodeName); tooltip_window.add(*box); From c9215ad818123206600f619ff89519dfd3bb7098 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Fri, 2 May 2025 14:29:27 +0200 Subject: [PATCH 220/780] Minor code cleanup --- src/modules/hyprland/workspace.cpp | 9 +++------ src/modules/hyprland/workspaces.cpp | 14 +++----------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 3350e302..30496069 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -105,11 +105,7 @@ void Workspace::initializeWindowMap(const Json::Value &clients_data) { void Workspace::setActiveWindow(WindowAddress const &addr) { for (auto &window : m_windowMap) { - if (window.address == addr) { - window.setActive(true); - } else { - window.setActive(false); - } + window.setActive(window.address == addr); } } @@ -244,6 +240,7 @@ void Workspace::update(const std::string &workspace_icon) { m_labelBefore.set_markup(fmt::format(fmt::runtime(formatBefore), fmt::arg("id", id()), fmt::arg("name", name()), fmt::arg("icon", workspace_icon), fmt::arg("windows", windows))); + m_labelBefore.get_style_context()->add_class("workspace-label"); if (m_workspaceManager.enableTaskbar()) { updateTaskbar(workspace_icon); @@ -261,7 +258,7 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { for (const auto &window_repr : m_windowMap) { if (isFirst) { isFirst = false; - } else { + } else if (m_workspaceManager.getWindowSeparator() != "") { auto windowSeparator = Gtk::make_managed(m_workspaceManager.getWindowSeparator()); m_content.pack_start(*windowSeparator, false, false); windowSeparator->show(); diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 752f298c..8c99e706 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -595,21 +595,13 @@ void Workspaces::onActiveWindowChanged(WindowAddress const &activeWindowAddress) m_currentActiveWindowAddress = activeWindowAddress; for (auto &[address, window] : m_orphanWindowMap) { - if (address == activeWindowAddress) { - window.setActive(true); - } else { - window.setActive(false); - } + window.setActive(address == activeWindowAddress); } for (auto const &workspace : m_workspaces) { workspace->setActiveWindow(activeWindowAddress); } for (auto &window : m_windowsToCreate) { - if (window.getAddress() == activeWindowAddress) { - window.setActive(true); - } else { - window.setActive(false); - } + window.setActive(window.getAddress() == activeWindowAddress); } } @@ -953,7 +945,7 @@ auto Workspaces::update() -> void { void Workspaces::updateWindowCount() { const Json::Value workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); - for (auto &workspace : m_workspaces) { + for (auto const &workspace : m_workspaces) { auto workspaceJson = std::ranges::find_if(workspacesJson, [&](Json::Value const &x) { return x["name"].asString() == workspace->name() || (workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name()); From 4ae2b6f1ba42271e8751ccd02d535fb0300d0bc6 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Fri, 2 May 2025 15:48:34 +0200 Subject: [PATCH 221/780] Implement ignore-list --- include/modules/hyprland/workspace.hpp | 1 + include/modules/hyprland/workspaces.hpp | 2 ++ src/modules/hyprland/workspace.cpp | 12 ++++++++++++ src/modules/hyprland/workspaces.cpp | 11 +++++++++++ 4 files changed, 26 insertions(+) diff --git a/include/modules/hyprland/workspace.hpp b/include/modules/hyprland/workspace.hpp index f61ef00e..25377185 100644 --- a/include/modules/hyprland/workspace.hpp +++ b/include/modules/hyprland/workspace.hpp @@ -90,6 +90,7 @@ class Workspace { void updateTaskbar(const std::string& workspace_icon); bool handleClick(const GdkEventButton* event_button, WindowAddress const& addr) const; + bool shouldSkipWindow(const WindowRepr& window_repr) const; IPC& m_ipc; }; diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 240047e5..516f1151 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -51,6 +51,7 @@ class Workspaces : public AModule, public EventHandler { auto taskbarIconSize() const -> int { return m_taskbarIconSize; } auto taskbarOrientation() const -> Gtk::Orientation { return m_taskbarOrientation; } auto onClickWindow() const -> std::string { return m_onClickWindow; } + auto getIgnoredWindows() const -> std::vector { return m_ignoreWindows; } std::string getRewrite(std::string window_class, std::string window_title); std::string& getWindowSeparator() { return m_formatWindowSeparator; } @@ -182,6 +183,7 @@ class Workspaces : public AModule, public EventHandler { std::string m_currentActiveWindowAddress; std::vector m_ignoreWorkspaces; + std::vector m_ignoreWindows; std::mutex m_mutex; const Bar& m_bar; diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 30496069..e1b6ba0e 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -256,6 +256,9 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { bool isFirst = true; for (const auto &window_repr : m_windowMap) { + if (shouldSkipWindow(window_repr)) { + continue; + } if (isFirst) { isFirst = false; } else if (m_workspaceManager.getWindowSeparator() != "") { @@ -326,4 +329,13 @@ bool Workspace::handleClick(const GdkEventButton *event_button, WindowAddress co return true; } +bool Workspace::shouldSkipWindow(const WindowRepr &window_repr) const { + auto ignore_list = m_workspaceManager.getIgnoredWindows(); + auto it = std::ranges::find_if(ignore_list, [&window_repr](const auto &ignoreItem) { + return std::regex_match(window_repr.window_class, ignoreItem) || + std::regex_match(window_repr.window_title, ignoreItem); + }); + return it != ignore_list.end(); +} + } // namespace waybar::modules::hyprland diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 8c99e706..370c064b 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -757,6 +757,17 @@ auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> vo if (workspaceTaskbar["on-click-window"].isString()) { m_onClickWindow = workspaceTaskbar["on-click-window"].asString(); } + + if (workspaceTaskbar["ignore-list"].isArray()) { + for (auto &windowRegex : workspaceTaskbar["ignore-list"]) { + std::string ruleString = windowRegex.asString(); + try { + m_ignoreWindows.emplace_back(ruleString, std::regex_constants::icase); + } catch (const std::regex_error &e) { + spdlog::error("Invalid rule {}: {}", ruleString, e.what()); + } + } + } } void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payload) { From ff4ed826931209ac333cb270f2463027718c668f Mon Sep 17 00:00:00 2001 From: "Rene D. Obermueller" Date: Sat, 3 May 2025 11:26:03 +0200 Subject: [PATCH 222/780] memory leak: 2nd attempt --- src/modules/privacy/privacy_item.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/modules/privacy/privacy_item.cpp b/src/modules/privacy/privacy_item.cpp index 36efd3e5..536c9180 100644 --- a/src/modules/privacy/privacy_item.cpp +++ b/src/modules/privacy/privacy_item.cpp @@ -100,12 +100,15 @@ void PrivacyItem::update_tooltip() { // Removes all old nodes for (auto *child : tooltip_window.get_children()) { tooltip_window.remove(*child); + // despite the remove, still needs a delete to prevent memory leak. Speculating that this might + // work differently in GTK4. + delete child; } for (auto *node : *nodes) { - Gtk::Box *box = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4); + auto *box = Gtk::make_managed(Gtk::ORIENTATION_HORIZONTAL, 4); // Set device icon - Gtk::Image *node_icon = Gtk::make_managed(); + auto *node_icon = Gtk::make_managed(); node_icon->set_pixel_size(tooltipIconSize); node_icon->set_from_icon_name(node->getIconName(), Gtk::ICON_SIZE_INVALID); box->add(*node_icon); From 9bf8c8277add598e62e7d16a8676505cfc09eab0 Mon Sep 17 00:00:00 2001 From: Fengerros <83514190+Fengerros@users.noreply.github.com> Date: Sat, 3 May 2025 15:52:05 +0200 Subject: [PATCH 223/780] Update mediaplayer.py - Fix artist name display in mediaplayer.py Fixed an issue where artist names like Earth, Wind & Fire were not displayed correctly. The change ensures that artist names containing commas or special characters are now shown properly. --- resources/custom_modules/mediaplayer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/custom_modules/mediaplayer.py b/resources/custom_modules/mediaplayer.py index d1bb72b4..524d4d2a 100755 --- a/resources/custom_modules/mediaplayer.py +++ b/resources/custom_modules/mediaplayer.py @@ -112,6 +112,7 @@ class PlayerManager: logger.debug(f"Metadata changed for player {player.props.player_name}") player_name = player.props.player_name artist = player.get_artist() + artist = artist.replace("&", "&") title = player.get_title() title = title.replace("&", "&") From 7b5206128ce7960791b97fe0212f5e95e8575a88 Mon Sep 17 00:00:00 2001 From: Aidan Epstein Date: Sat, 3 May 2025 10:41:07 -0700 Subject: [PATCH 224/780] Add idle_inhibitor style docs. --- man/waybar-idle-inhibitor.5.scd | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/man/waybar-idle-inhibitor.5.scd b/man/waybar-idle-inhibitor.5.scd index 6d5a2170..405c8fc5 100644 --- a/man/waybar-idle-inhibitor.5.scd +++ b/man/waybar-idle-inhibitor.5.scd @@ -125,3 +125,9 @@ screensaver, also known as "presentation mode". "timeout": 30.5 } ``` + +# STYLE + +- *#idle_inhibitor* +- *#idle_inhibitor.activated* +- *#idle_inhibitor.deactivated* From f4b68f41e6d58e9659705d58d1fef7bd154ada38 Mon Sep 17 00:00:00 2001 From: "Rene D. Obermueller" Date: Sun, 4 May 2025 07:26:55 +0200 Subject: [PATCH 225/780] feat: sway/window: provide {marks} format replacement --- include/modules/sway/window.hpp | 6 ++-- man/waybar-sway-window.5.scd | 7 ++++ src/modules/sway/window.cpp | 57 ++++++++++++++++++++++----------- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/include/modules/sway/window.hpp b/include/modules/sway/window.hpp index 427c2e81..60cd2679 100644 --- a/include/modules/sway/window.hpp +++ b/include/modules/sway/window.hpp @@ -19,10 +19,11 @@ class Window : public AAppIconLabel, public sigc::trackable { auto update() -> void override; private: - void setClass(std::string classname, bool enable); + void setClass(const std::string& classname, bool enable); void onEvent(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&); - std::tuple + std::tuple getFocusedNode(const Json::Value& nodes, std::string& output); void getTree(); @@ -35,6 +36,7 @@ class Window : public AAppIconLabel, public sigc::trackable { std::string old_app_id_; std::size_t app_nb_; std::string shell_; + std::string marks_; int floating_count_; util::JsonParser parser_; std::mutex mutex_; diff --git a/man/waybar-sway-window.5.scd b/man/waybar-sway-window.5.scd index a7eb4f05..6fa9170b 100644 --- a/man/waybar-sway-window.5.scd +++ b/man/waybar-sway-window.5.scd @@ -89,6 +89,11 @@ Addressed by *sway/window* default: false ++ If the workspace itself is focused and the workspace contains nodes or floating_nodes, show the workspace name. If not set, text remains empty but styles according to nodes in the workspace are still applied. +*show-hidden-marks*: ++ + typeof: bool ++ + default: false ++ + For the *{marks}* format replacement, include hidden marks that start with an underscore. + *rewrite*: ++ typeof: object ++ Rules to rewrite the module format output. See *rewrite rules*. @@ -117,6 +122,8 @@ Addressed by *sway/window* *{shell}*: The shell of the focused window. It's 'xwayland' when the window is running through xwayland, otherwise, it's 'xdg-shell'. +*{marks}*: Marks of the window. + # REWRITE RULES *rewrite* is an object where keys are regular expressions and values are diff --git a/src/modules/sway/window.cpp b/src/modules/sway/window.cpp index 25e430a7..68655a76 100644 --- a/src/modules/sway/window.cpp +++ b/src/modules/sway/window.cpp @@ -41,8 +41,8 @@ void Window::onCmd(const struct Ipc::ipc_response& res) { std::lock_guard lock(mutex_); auto payload = parser_.parse(res.payload); auto output = payload["output"].isString() ? payload["output"].asString() : ""; - std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_) = - getFocusedNode(payload["nodes"], output); + std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_, + marks_) = getFocusedNode(payload["nodes"], output); updateAppIconName(app_id_, app_class_); dp.emit(); } catch (const std::exception& e) { @@ -96,7 +96,7 @@ auto Window::update() -> void { label_.set_markup(waybar::util::rewriteString( fmt::format(fmt::runtime(format_), fmt::arg("title", window_), fmt::arg("app_id", app_id_), - fmt::arg("shell", shell_)), + fmt::arg("shell", shell_), fmt::arg("marks", marks_)), config_["rewrite"])); if (tooltipEnabled()) { label_.set_tooltip_text(window_); @@ -108,7 +108,7 @@ auto Window::update() -> void { AAppIconLabel::update(); } -void Window::setClass(std::string classname, bool enable) { +void Window::setClass(const std::string& classname, bool enable) { if (enable) { if (!bar_.window.get_style_context()->has_class(classname)) { bar_.window.get_style_context()->add_class(classname); @@ -169,17 +169,31 @@ std::optional> getSingleChildNode( return {getSingleChildNode(child)}; } -std::tuple getWindowInfo(const Json::Value& node) { +std::tuple getWindowInfo( + const Json::Value& node, bool showHidden) { const auto app_id = node["app_id"].isString() ? node["app_id"].asString() : node["window_properties"]["instance"].asString(); const auto app_class = node["window_properties"]["class"].isString() ? node["window_properties"]["class"].asString() : ""; const auto shell = node["shell"].isString() ? node["shell"].asString() : ""; - return {app_id, app_class, shell}; + std::string marks = ""; + if (node["marks"].isArray()) { + for (const auto& m : node["marks"]) { + if (!m.isString() || (!showHidden && m.asString().at(0) == '_')) { + continue; + } + if (!marks.empty()) { + marks += ','; + } + marks += m.asString(); + } + } + return {app_id, app_class, shell, marks}; } -std::tuple +std::tuple gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Value& config_, const Bar& bar_, Json::Value& parentWorkspace, const Json::Value& immediateParent) { @@ -207,7 +221,8 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu "", "", "", - node["layout"].asString()}; + node["layout"].asString(), + ""}; } parentWorkspace = node; } else if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") && @@ -215,7 +230,8 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu // found node spdlog::trace("actual output {}, output found {}, node (focused) found {}", bar_.output->name, output, node["name"].asString()); - const auto [app_id, app_class, shell] = getWindowInfo(node); + const auto [app_id, app_class, shell, marks] = + getWindowInfo(node, config_["show-hidden-marks"].asBool()); int nb = node.size(); int floating_count = 0; std::string workspace_layout = ""; @@ -232,20 +248,21 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu app_id, app_class, shell, - workspace_layout}; + workspace_layout, + marks}; } // iterate - auto [nb, f, id, name, app_id, app_class, shell, workspace_layout] = + auto [nb, f, id, name, app_id, app_class, shell, workspace_layout, marks] = gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace, node); - auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2] = + auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2, marks2] = gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace, node); // if ((id > 0 || ((id2 < 0 || name2.empty()) && id > -1)) && !name.empty()) { if ((id > 0) || (id2 < 0 && id > -1)) { - return {nb, f, id, name, app_id, app_class, shell, workspace_layout}; + return {nb, f, id, name, app_id, app_class, shell, workspace_layout, marks}; } else if (id2 > 0 && !name2.empty()) { - return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2}; + return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2, marks2}; } } @@ -258,10 +275,12 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu std::string app_id = ""; std::string app_class = ""; std::string workspace_layout = ""; + std::string marks = ""; if (all_leaf_nodes.first == 1) { const auto single_child = getSingleChildNode(immediateParent); if (single_child.has_value()) { - std::tie(app_id, app_class, workspace_layout) = getWindowInfo(single_child.value()); + std::tie(app_id, app_class, workspace_layout, marks) = + getWindowInfo(single_child.value(), config_["show-hidden-marks"].asBool()); } } return {all_leaf_nodes.first, @@ -273,13 +292,15 @@ gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Valu app_id, app_class, workspace_layout, - immediateParent["layout"].asString()}; + immediateParent["layout"].asString(), + marks}; } - return {0, 0, -1, "", "", "", "", ""}; + return {0, 0, -1, "", "", "", "", "", ""}; } -std::tuple +std::tuple Window::getFocusedNode(const Json::Value& nodes, std::string& output) { Json::Value placeholder = Json::Value::null; return gfnWithWorkspace(nodes, output, config_, bar_, placeholder, placeholder); From 5ab852ff5245596f5f153b584d4c9557bb3a2112 Mon Sep 17 00:00:00 2001 From: Alex Denes Date: Sat, 8 Feb 2025 01:35:32 +0000 Subject: [PATCH 226/780] dwl/window: allow hiding of title when either inactive or empty --- include/modules/dwl/window.hpp | 2 ++ man/waybar-dwl-window.5.scd | 10 ++++++++++ src/modules/dwl/window.cpp | 24 ++++++++++++++++++++---- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/include/modules/dwl/window.hpp b/include/modules/dwl/window.hpp index 619360a5..212fb513 100644 --- a/include/modules/dwl/window.hpp +++ b/include/modules/dwl/window.hpp @@ -32,6 +32,8 @@ class Window : public AAppIconLabel, public sigc::trackable { std::string appid_; std::string layout_symbol_; bool active_; + bool hide_inactive_; + bool hide_empty_; uint32_t layout_; struct zdwl_ipc_output_v2 *output_status_; diff --git a/man/waybar-dwl-window.5.scd b/man/waybar-dwl-window.5.scd index c7f70908..907d5332 100644 --- a/man/waybar-dwl-window.5.scd +++ b/man/waybar-dwl-window.5.scd @@ -17,6 +17,16 @@ Addressed by *dwl/window* default: {title} ++ The format, how information should be displayed. +*hide-empty*: ++ + typeof: bool ++ + default: false ++ + Option to hide the module when the content would be empty. + +*hide-inactive*: ++ + typeof: bool ++ + default: false ++ + Option to hide the module when the window is unfocused. + *rotate*: ++ typeof: integer ++ Positive value to rotate the text label (in 90 degree increments). diff --git a/src/modules/dwl/window.cpp b/src/modules/dwl/window.cpp index 82d92003..b5dd377c 100644 --- a/src/modules/dwl/window.cpp +++ b/src/modules/dwl/window.cpp @@ -76,7 +76,15 @@ static const wl_registry_listener registry_listener_impl = {.global = handle_glo Window::Window(const std::string &id, const Bar &bar, const Json::Value &config) : AAppIconLabel(config, "window", id, "{}", 0, true), bar_(bar), - active_(false) { + active_(false), + hide_inactive_(false), + hide_empty_(false) { + if (config_["hide-inactive"].isBool()) { + hide_inactive_ = config["hide-inactive"].asBool(); + } + if (config_["hide-empty"].isBool()) { + hide_empty_ = config["hide-empty"].asBool(); + } struct wl_display *display = Client::inst()->wl_display; struct wl_registry *registry = wl_display_get_registry(display); @@ -122,10 +130,18 @@ void Window::handle_frame() { if (tooltipEnabled()) { label_.set_tooltip_text(title_); } - if (active_) { - box_.get_style_context()->add_class("active"); + if (hide_empty_ && title_.empty()) { + box_.set_visible(false); } else { - box_.get_style_context()->remove_class("active"); + if (active_) { + box_.get_style_context()->add_class("active"); + box_.set_visible(true); + } else { + box_.get_style_context()->remove_class("active"); + if (hide_inactive_) { + box_.set_visible(false); + } + } } } From 0340760e12829239078c0d21fa6d227561e30ff2 Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Wed, 7 May 2025 16:57:54 +0800 Subject: [PATCH 227/780] Use load_symbolic for gtk icon instead of load_icon. --- include/util/gtk_icon.hpp | 4 +++- src/modules/sni/item.cpp | 15 +++++++++------ src/util/gtk_icon.cpp | 15 ++++++++++++--- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/include/util/gtk_icon.hpp b/include/util/gtk_icon.hpp index 44555f65..06f15abe 100644 --- a/include/util/gtk_icon.hpp +++ b/include/util/gtk_icon.hpp @@ -10,5 +10,7 @@ class DefaultGtkIconThemeWrapper { public: static bool has_icon(const std::string&); - static Glib::RefPtr load_icon(const char*, int, Gtk::IconLookupFlags); + static Glib::RefPtr load_icon( + const char*, int, Gtk::IconLookupFlags, + Glib::RefPtr style = Glib::RefPtr()); }; diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 9dc13158..407d7e72 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -388,14 +388,17 @@ Glib::RefPtr Item::getIconPixbuf() { Glib::RefPtr Item::getIconByName(const std::string& name, int request_size) { icon_theme->rescan_if_needed(); - if (!icon_theme_path.empty() && - icon_theme->lookup_icon(name.c_str(), request_size, - Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE)) { - return icon_theme->load_icon(name.c_str(), request_size, - Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE); + if (!icon_theme_path.empty()) { + auto icon_info = icon_theme->lookup_icon(name.c_str(), request_size, + Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE); + if (icon_info) { + bool is_sym = false; + return icon_info.load_symbolic(event_box.get_style_context(), is_sym); + } } return DefaultGtkIconThemeWrapper::load_icon(name.c_str(), request_size, - Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE); + Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE, + event_box.get_style_context()); } double Item::getScaledIconSize() { diff --git a/src/util/gtk_icon.cpp b/src/util/gtk_icon.cpp index 5dd741f9..4b4d3d69 100644 --- a/src/util/gtk_icon.cpp +++ b/src/util/gtk_icon.cpp @@ -15,11 +15,20 @@ bool DefaultGtkIconThemeWrapper::has_icon(const std::string& value) { return Gtk::IconTheme::get_default()->has_icon(value); } -Glib::RefPtr DefaultGtkIconThemeWrapper::load_icon(const char* name, int tmp_size, - Gtk::IconLookupFlags flags) { +Glib::RefPtr DefaultGtkIconThemeWrapper::load_icon( + const char* name, int tmp_size, Gtk::IconLookupFlags flags, + Glib::RefPtr style) { const std::lock_guard lock(default_theme_mutex); auto default_theme = Gtk::IconTheme::get_default(); default_theme->rescan_if_needed(); - return default_theme->load_icon(name, tmp_size, flags); + + auto icon_info = default_theme->lookup_icon(name, tmp_size, flags); + + if (style.get() == nullptr) { + return icon_info.load_icon(); + } + + bool is_sym = false; + return icon_info.load_symbolic(style, is_sym); } From f3ae13e44600f3585b32bf9b50258b3c3f19a1dc Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 13 May 2025 21:40:39 +0200 Subject: [PATCH 228/780] cpu: add load{1,5,15} format variables --- man/waybar-cpu.5.scd | 6 ++++++ src/modules/cpu.cpp | 3 +++ 2 files changed, 9 insertions(+) diff --git a/man/waybar-cpu.5.scd b/man/waybar-cpu.5.scd index 287bf123..3e06f35c 100644 --- a/man/waybar-cpu.5.scd +++ b/man/waybar-cpu.5.scd @@ -91,6 +91,12 @@ The *cpu* module displays the current CPU utilization. *{load}*: Current CPU load. +*{load1}*: CPU load average over the last minute. + +*{load5}*: CPU load average over the last 5 minutes. + +*{load15}*: CPU load average over the last 15 minutes. + *{usage}*: Current overall CPU usage. *{usage*{n}*}*: Current CPU core n usage. Cores are numbered from zero, so first core will be {usage0} and 4th will be {usage3}. diff --git a/src/modules/cpu.cpp b/src/modules/cpu.cpp index 0703eaf7..45035a81 100644 --- a/src/modules/cpu.cpp +++ b/src/modules/cpu.cpp @@ -43,6 +43,9 @@ auto waybar::modules::Cpu::update() -> void { auto icons = std::vector{state}; fmt::dynamic_format_arg_store store; store.push_back(fmt::arg("load", load1)); + store.push_back(fmt::arg("load1", load1)); + store.push_back(fmt::arg("load5", load5)); + store.push_back(fmt::arg("load15", load15)); store.push_back(fmt::arg("usage", total_usage)); store.push_back(fmt::arg("icon", getIcon(total_usage, icons))); store.push_back(fmt::arg("max_frequency", max_frequency)); From 47e0f4252338cd51544a6229a495c24c35b442cf Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 13 May 2025 21:34:24 +0200 Subject: [PATCH 229/780] niri: add support for urgency indicators to workspaces --- man/waybar-niri-workspaces.5.scd | 2 ++ src/modules/niri/backend.cpp | 11 +++++++++++ src/modules/niri/workspaces.cpp | 8 ++++++++ 3 files changed, 21 insertions(+) diff --git a/man/waybar-niri-workspaces.5.scd b/man/waybar-niri-workspaces.5.scd index 0c0249ca..3afd1172 100644 --- a/man/waybar-niri-workspaces.5.scd +++ b/man/waybar-niri-workspaces.5.scd @@ -70,6 +70,7 @@ Additional to workspace name matching, the following *format-icons* can be set. - *default*: Will be shown, when no string matches are found. - *focused*: Will be shown, when workspace is focused. - *active*: Will be shown, when workspace is active on its output. +- *urgent*: Will be shown, when workspace has urgent windows. # EXAMPLES @@ -95,6 +96,7 @@ Additional to workspace name matching, the following *format-icons* can be set. - *#workspaces button* - *#workspaces button.focused*: The single focused workspace. - *#workspaces button.active*: The workspace is active (visible) on its output. +- *#workspaces button.urgent*: The workspace has one or more urgent windows. - *#workspaces button.empty*: The workspace is empty. - *#workspaces button.current_output*: The workspace is from the same output as the bar that it is displayed on. diff --git a/src/modules/niri/backend.cpp b/src/modules/niri/backend.cpp index 383bf113..fa4dc287 100644 --- a/src/modules/niri/backend.cpp +++ b/src/modules/niri/backend.cpp @@ -147,6 +147,17 @@ void IPC::parseIPC(const std::string &line) { } else { spdlog::error("Active window changed on unknown workspace"); } + } else if (const auto &payload = ev["WorkspaceUrgencyChanged"]) { + const auto id = payload["id"].asUInt64(); + const auto urgent = payload["urgent"].asBool(); + auto it = std::find_if(workspaces_.begin(), workspaces_.end(), + [id](const auto &ws) { return ws["id"].asUInt64() == id; }); + if (it != workspaces_.end()) { + auto &ws = *it; + ws["is_urgent"] = urgent; + } else { + spdlog::error("Urgency changed for unknown workspace"); + } } else if (const auto &payload = ev["KeyboardLayoutsChanged"]) { const auto &layouts = payload["keyboard_layouts"]; const auto &names = layouts["names"]; diff --git a/src/modules/niri/workspaces.cpp b/src/modules/niri/workspaces.cpp index d2fcad5d..02e9d57a 100644 --- a/src/modules/niri/workspaces.cpp +++ b/src/modules/niri/workspaces.cpp @@ -20,6 +20,7 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value gIPC->registerForIPC("WorkspacesChanged", this); gIPC->registerForIPC("WorkspaceActivated", this); gIPC->registerForIPC("WorkspaceActiveWindowChanged", this); + gIPC->registerForIPC("WorkspaceUrgencyChanged", this); dp.emit(); } @@ -67,6 +68,11 @@ void Workspaces::doUpdate() { else style_context->remove_class("active"); + if (ws["is_urgent"].asBool()) + style_context->add_class("urgent"); + else + style_context->remove_class("urgent"); + if (ws["output"]) { if (ws["output"].asString() == bar_.output->name) style_context->add_class("current_output"); @@ -166,6 +172,8 @@ std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws) const auto &icons = config_["format-icons"]; if (!icons) return value; + if (ws["is_urgent"].asBool() && icons["urgent"]) return icons["urgent"].asString(); + if (ws["is_focused"].asBool() && icons["focused"]) return icons["focused"].asString(); if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString(); From 19d243051646f0772e14295d8f4204900cea2f0f Mon Sep 17 00:00:00 2001 From: Illia Ostapyshyn Date: Mon, 19 May 2025 23:01:19 +0200 Subject: [PATCH 230/780] niri/workspaces: Add empty icon --- man/waybar-niri-workspaces.5.scd | 1 + src/modules/niri/workspaces.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/man/waybar-niri-workspaces.5.scd b/man/waybar-niri-workspaces.5.scd index 0c0249ca..48022096 100644 --- a/man/waybar-niri-workspaces.5.scd +++ b/man/waybar-niri-workspaces.5.scd @@ -70,6 +70,7 @@ Additional to workspace name matching, the following *format-icons* can be set. - *default*: Will be shown, when no string matches are found. - *focused*: Will be shown, when workspace is focused. - *active*: Will be shown, when workspace is active on its output. +- *empty*: Will be shown, when workspace is empty. # EXAMPLES diff --git a/src/modules/niri/workspaces.cpp b/src/modules/niri/workspaces.cpp index d2fcad5d..db4ac32b 100644 --- a/src/modules/niri/workspaces.cpp +++ b/src/modules/niri/workspaces.cpp @@ -166,6 +166,8 @@ std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws) const auto &icons = config_["format-icons"]; if (!icons) return value; + if (ws["active_window_id"].isNull() && icons["empty"]) return icons["empty"].asString(); + if (ws["is_focused"].asBool() && icons["focused"]) return icons["focused"].asString(); if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString(); From cdf3ca910d0d9db4e5fb14f7d12fcc369981ed23 Mon Sep 17 00:00:00 2001 From: belcaik Date: Mon, 19 May 2025 22:52:01 -0400 Subject: [PATCH 231/780] feat: Add support for side buttons mouse navigation in handleToggle mapped buttons config on-click-backward and on-click-forward on mpris module to previous/next handle GTK codes are 9 and 8 --- src/modules/mpris/mpris.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index ed383b0c..59600ca0 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -14,7 +14,7 @@ extern "C" { #include #include - +#include // <-- agrega esta línea namespace waybar::modules::mpris { const std::string DEFAULT_FORMAT = "{player} ({status}): {dynamic}"; @@ -614,6 +614,18 @@ bool Mpris::handleToggle(GdkEventButton* const& e) { } playerctl_player_next(player, &error); break; + case 8: // side button mouse back on browser + if (config_["on-click-backward"].isString()) { + return ALabel::handleToggle(e); + } + playerctl_player_previous(player, &error); + break; + case 9: // side button mouse forward on browser + if (config_["on-click-forward"].isString()) { + return ALabel::handleToggle(e); + } + playerctl_player_next(player, &error); + break; } } if (error) { From d41a66477956ed2d9f1045250ede1f2ebb44da9b Mon Sep 17 00:00:00 2001 From: belcaik Date: Mon, 19 May 2025 23:12:42 -0400 Subject: [PATCH 232/780] refactor: use command pattern for button actions --- src/modules/mpris/mpris.cpp | 66 +++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 59600ca0..f57d4765 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -14,7 +14,6 @@ extern "C" { #include #include -#include // <-- agrega esta línea namespace waybar::modules::mpris { const std::string DEFAULT_FORMAT = "{player} ({status}): {dynamic}"; @@ -584,50 +583,45 @@ errorexit: } bool Mpris::handleToggle(GdkEventButton* const& e) { + if (!e || e->type != GdkEventType::GDK_BUTTON_PRESS) { + return false; + } + + auto info = getPlayerInfo(); + if (!info) return false; + + struct ButtonAction { + guint button; + const char* config_key; + std::function builtin_action; + }; + GError* error = nullptr; - waybar::util::ScopeGuard error_deleter([error]() { + waybar::util::ScopeGuard error_deleter([&error]() { if (error) { g_error_free(error); } }); - auto info = getPlayerInfo(); - if (!info) return false; + // Command pattern: encapsulate each button's action + const ButtonAction actions[] = { + {1, "on-click", [&]() { playerctl_player_play_pause(player, &error); }}, + {2, "on-click-middle", [&]() { playerctl_player_previous(player, &error); }}, + {3, "on-click-right", [&]() { playerctl_player_next(player, &error); }}, + {8, "on-click-backward", [&]() { playerctl_player_previous(player, &error); }}, + {9, "on-click-forward", [&]() { playerctl_player_next(player, &error); }}, + }; - if (e->type == GdkEventType::GDK_BUTTON_PRESS) { - switch (e->button) { - case 1: // left-click - if (config_["on-click"].isString()) { - return ALabel::handleToggle(e); - } - playerctl_player_play_pause(player, &error); - break; - case 2: // middle-click - if (config_["on-click-middle"].isString()) { - return ALabel::handleToggle(e); - } - playerctl_player_previous(player, &error); - break; - case 3: // right-click - if (config_["on-click-right"].isString()) { - return ALabel::handleToggle(e); - } - playerctl_player_next(player, &error); - break; - case 8: // side button mouse back on browser - if (config_["on-click-backward"].isString()) { - return ALabel::handleToggle(e); - } - playerctl_player_previous(player, &error); - break; - case 9: // side button mouse forward on browser - if (config_["on-click-forward"].isString()) { - return ALabel::handleToggle(e); - } - playerctl_player_next(player, &error); - break; + for (const auto& action : actions) { + if (e->button == action.button) { + if (config_[action.config_key].isString()) { + return ALabel::handleToggle(e); + } + action.builtin_action(); + break; } } + if (error) { spdlog::error("mpris[{}]: error running builtin on-click action: {}", (*info).name, error->message); From 4f55d7da9004a40dbac704c5053186b7ea6fc423 Mon Sep 17 00:00:00 2001 From: gred Date: Tue, 20 May 2025 22:45:36 +0300 Subject: [PATCH 233/780] Add persistent-only setting for hyprland/workspaces --- include/modules/hyprland/workspaces.hpp | 2 ++ src/modules/hyprland/workspace.cpp | 4 ++++ src/modules/hyprland/workspaces.cpp | 1 + 3 files changed, 7 insertions(+) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 6b33baea..db0f5114 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -37,6 +37,7 @@ class Workspaces : public AModule, public EventHandler { auto showSpecial() const -> bool { return m_showSpecial; } auto activeOnly() const -> bool { return m_activeOnly; } auto specialVisibleOnly() const -> bool { return m_specialVisibleOnly; } + auto persistentOnly() const -> bool { return m_persistentOnly; } auto moveToMonitor() const -> bool { return m_moveToMonitor; } auto getBarOutput() const -> std::string { return m_bar.output->name; } @@ -122,6 +123,7 @@ class Workspaces : public AModule, public EventHandler { bool m_showSpecial = false; bool m_activeOnly = false; bool m_specialVisibleOnly = false; + bool m_persistentOnly = false; bool m_moveToMonitor = false; Json::Value m_persistentWorkspaceConfig; diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 4655096f..fdbb96aa 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -173,6 +173,10 @@ std::string &Workspace::selectIcon(std::map &icons_map } void Workspace::update(const std::string &format, const std::string &icon) { + if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) { + m_button.hide(); + return; + } // clang-format off if (this->m_workspaceManager.activeOnly() && \ !this->isActive() && \ diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 0e225935..734f4a3a 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -600,6 +600,7 @@ auto Workspaces::parseConfig(const Json::Value &config) -> void { populateBoolConfig(config, "all-outputs", m_allOutputs); populateBoolConfig(config, "show-special", m_showSpecial); populateBoolConfig(config, "special-visible-only", m_specialVisibleOnly); + populateBoolConfig(config, "persistent-only", m_persistentOnly); populateBoolConfig(config, "active-only", m_activeOnly); populateBoolConfig(config, "move-to-monitor", m_moveToMonitor); From 24a30b7ffd9d9907cd912ee9c533ff330d21a252 Mon Sep 17 00:00:00 2001 From: gred Date: Wed, 21 May 2025 01:01:23 +0300 Subject: [PATCH 234/780] Add sorting with centered special workspaces --- include/modules/hyprland/workspaces.hpp | 4 +++- src/modules/hyprland/workspaces.cpp | 32 +++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 6b33baea..21a9dc22 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -50,6 +50,7 @@ class Workspaces : public AModule, public EventHandler { private: void onEvent(const std::string& e) override; void updateWindowCount(); + void sortSpecialCentered(); void sortWorkspaces(); void createWorkspace(Json::Value const& workspace_data, Json::Value const& clients_data = Json::Value::nullRef); @@ -130,12 +131,13 @@ class Workspaces : public AModule, public EventHandler { // and doesn't share windows accross bars (a.k.a `all-outputs` = false) std::map m_orphanWindowMap; - enum class SortMethod { ID, NAME, NUMBER, DEFAULT }; + enum class SortMethod { ID, NAME, NUMBER, SPECIAL_CENTERED, DEFAULT }; util::EnumParser m_enumParser; SortMethod m_sortBy = SortMethod::DEFAULT; std::map m_sortMap = {{"ID", SortMethod::ID}, {"NAME", SortMethod::NAME}, {"NUMBER", SortMethod::NUMBER}, + {"SPECIAL-CENTERED", SortMethod::SPECIAL_CENTERED}, {"DEFAULT", SortMethod::DEFAULT}}; std::string m_format; diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 0e225935..d8ae3c32 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -770,7 +770,36 @@ void Workspaces::setCurrentMonitorId() { } } +void Workspaces::sortSpecialCentered() { + std::vector> specialWorkspaces; + std::vector> normalWorkspaces; + + for (auto &workspace : m_workspaces) { + if (workspace->isSpecial()) { + specialWorkspaces.push_back(std::move(workspace)); + } else { + normalWorkspaces.push_back(std::move(workspace)); + } + } + m_workspaces.clear(); + + size_t center = normalWorkspaces.size() / 2; + + m_workspaces.insert(m_workspaces.end(), + std::make_move_iterator(normalWorkspaces.begin()), + std::make_move_iterator(normalWorkspaces.begin() + center)); + + m_workspaces.insert(m_workspaces.end(), + std::make_move_iterator(specialWorkspaces.begin()), + std::make_move_iterator(specialWorkspaces.end())); + + m_workspaces.insert(m_workspaces.end(), + std::make_move_iterator(normalWorkspaces.begin() + center), + std::make_move_iterator(normalWorkspaces.end())); +} + void Workspaces::sortWorkspaces() { + std::ranges::sort( // m_workspaces, [&](std::unique_ptr &a, std::unique_ptr &b) { // Helper comparisons @@ -828,6 +857,9 @@ void Workspaces::sortWorkspaces() { // Return a default value if none of the cases match. return isNameLess; // You can adjust this to your specific needs. }); + if (m_sortBy == SortMethod::SPECIAL_CENTERED) { + this->sortSpecialCentered(); + } for (size_t i = 0; i < m_workspaces.size(); ++i) { m_box.reorder_child(m_workspaces[i]->button(), i); From 15f54cd6ef63c0c1507961582f161661339be01b Mon Sep 17 00:00:00 2001 From: gred Date: Wed, 21 May 2025 01:38:03 +0300 Subject: [PATCH 235/780] Fix hidden buttons --- src/modules/hyprland/workspaces.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index d8ae3c32..9caf6c6e 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -772,30 +772,39 @@ void Workspaces::setCurrentMonitorId() { void Workspaces::sortSpecialCentered() { std::vector> specialWorkspaces; + std::vector> hidedWorkspaces; std::vector> normalWorkspaces; for (auto &workspace : m_workspaces) { if (workspace->isSpecial()) { specialWorkspaces.push_back(std::move(workspace)); } else { - normalWorkspaces.push_back(std::move(workspace)); + if (workspace->button().is_visible()) { + normalWorkspaces.push_back(std::move(workspace)); + } else { + hidedWorkspaces.push_back(std::move(workspace)); + } } } m_workspaces.clear(); size_t center = normalWorkspaces.size() / 2; - m_workspaces.insert(m_workspaces.end(), - std::make_move_iterator(normalWorkspaces.begin()), + m_workspaces.insert(m_workspaces.end(), + std::make_move_iterator(normalWorkspaces.begin()), std::make_move_iterator(normalWorkspaces.begin() + center)); m_workspaces.insert(m_workspaces.end(), - std::make_move_iterator(specialWorkspaces.begin()), + std::make_move_iterator(specialWorkspaces.begin()), std::make_move_iterator(specialWorkspaces.end())); - m_workspaces.insert(m_workspaces.end(), - std::make_move_iterator(normalWorkspaces.begin() + center), + m_workspaces.insert(m_workspaces.end(), + std::make_move_iterator(normalWorkspaces.begin() + center), std::make_move_iterator(normalWorkspaces.end())); + + m_workspaces.insert(m_workspaces.end(), + std::make_move_iterator(hidedWorkspaces.begin()), + std::make_move_iterator(hidedWorkspaces.end())); } void Workspaces::sortWorkspaces() { From d53135f834fea98fa0ef140f30d048e7cfa0d228 Mon Sep 17 00:00:00 2001 From: Max Gautier Date: Sat, 31 May 2025 23:07:26 +0200 Subject: [PATCH 236/780] Fix Description= in systemd service file Description= should be a noun phrase, and not a full sentence, according to man 5 systemd.unit. In particular, using a dot at the end result in messages like this in journalctl when running as a user service (not the superfluous dot at the end): May 31 16:03:38 framework systemd[1180]: Started Highly customizable Wayland bar for Sway and Wlroots based compositors.. May 31 16:20:39 framework systemd[1180]: Stopping Highly customizable Wayland bar for Sway and Wlroots based compositors.... May 31 16:20:39 framework systemd[1180]: Stopped Highly customizable Wayland bar for Sway and Wlroots based compositors.. --- resources/waybar.service.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/waybar.service.in b/resources/waybar.service.in index 81ac6779..18bac54c 100644 --- a/resources/waybar.service.in +++ b/resources/waybar.service.in @@ -1,5 +1,5 @@ [Unit] -Description=Highly customizable Wayland bar for Sway and Wlroots based compositors. +Description=Highly customizable Wayland bar for Sway and Wlroots based compositors Documentation=https://github.com/Alexays/Waybar/wiki/ PartOf=graphical-session.target After=graphical-session.target From c0e7aad60e69381062fb8d2b82c42dba6bc11726 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 1 Jun 2025 00:14:43 +0000 Subject: [PATCH 237/780] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'flake-compat': 'github:edolstra/flake-compat/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec?narHash=sha256-NeCCThCEP3eCl2l/%2B27kNNK7QrwZB1IJCrXfrbv5oqU%3D' (2024-12-04) → 'github:edolstra/flake-compat/9100a0f413b0c601e0533d1d94ffd501ce2e7885?narHash=sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX%2BfjA8Xf8PUmqCY%3D' (2025-05-12) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/8a2f738d9d1f1d986b5a4cd2fd2061a7127237d7?narHash=sha256-sPwcCYuiEopaafePqlG826tBhctuJsLx/mhKKM5Fmjo%3D' (2025-04-23) → 'github:NixOS/nixpkgs/96ec055edbe5ee227f28cdbc3f1ddf1df5965102?narHash=sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg%3D' (2025-05-28) --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 480f004f..34545ed4 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1733328505, - "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", + "lastModified": 1747046372, + "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", "owner": "edolstra", "repo": "flake-compat", - "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", + "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", "type": "github" }, "original": { @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1745391562, - "narHash": "sha256-sPwcCYuiEopaafePqlG826tBhctuJsLx/mhKKM5Fmjo=", + "lastModified": 1748460289, + "narHash": "sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8a2f738d9d1f1d986b5a4cd2fd2061a7127237d7", + "rev": "96ec055edbe5ee227f28cdbc3f1ddf1df5965102", "type": "github" }, "original": { From 05cfd738045d5da918a9d8bbbe65cb69da4d8854 Mon Sep 17 00:00:00 2001 From: Mateus Eto Date: Sun, 1 Jun 2025 21:27:37 +0900 Subject: [PATCH 238/780] Fix calendar extra padding if there are wide characters --- src/modules/clock.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 269fa765..0ef63c7b 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -1,5 +1,6 @@ #include "modules/clock.hpp" +#include #include #include @@ -358,10 +359,23 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea } } - os << Glib::ustring::format((cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right, - std::setfill(L' '), - std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ : 0)), - getCalendarLine(today, ymTmp, line, firstdow, &m_locale_)); + // Count wide characters to avoid extra padding + size_t wideCharCount = 0; + std::string calendarLine = getCalendarLine(today, ymTmp, line, firstdow, &m_locale_); + if (line < 2) { + for (gchar *data = calendarLine.data(), *end = data + calendarLine.size(); + data != nullptr;) { + gunichar c = g_utf8_get_char_validated(data, end - data); + if (g_unichar_iswide(c)) { + wideCharCount++; + } + data = g_utf8_find_next_char(data, end); + } + } + os << Glib::ustring::format( + (cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right, std::setfill(L' '), + std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ - wideCharCount : 0)), + calendarLine); // Week numbers on the right if (cldWPos_ == WS::RIGHT && line > 0) { From 34484919d6c2b1c808cdf43f9bd8cb9a32d44412 Mon Sep 17 00:00:00 2001 From: Davide Manini Date: Mon, 2 Jun 2025 17:23:06 +0300 Subject: [PATCH 239/780] AIconLabel: honour `rotation' option; add `swap-icon-label' option --- src/AIconLabel.cpp | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/AIconLabel.cpp b/src/AIconLabel.cpp index d7ee666e..130ba60c 100644 --- a/src/AIconLabel.cpp +++ b/src/AIconLabel.cpp @@ -1,6 +1,7 @@ #include "AIconLabel.hpp" #include +#include namespace waybar { @@ -17,14 +18,37 @@ AIconLabel::AIconLabel(const Json::Value &config, const std::string &name, const box_.get_style_context()->add_class(id); } - box_.set_orientation(Gtk::Orientation::ORIENTATION_HORIZONTAL); + int rot = 0; + + if (config_["rotate"].isUInt()) { + rot = config["rotate"].asUInt() % 360; + if ((rot % 90) != 00) + rot = 0; + rot /= 90; + } + + if ((rot % 2) == 0) + box_.set_orientation(Gtk::Orientation::ORIENTATION_HORIZONTAL); + else + box_.set_orientation(Gtk::Orientation::ORIENTATION_VERTICAL); box_.set_name(name); int spacing = config_["icon-spacing"].isInt() ? config_["icon-spacing"].asInt() : 8; box_.set_spacing(spacing); - box_.add(image_); - box_.add(label_); + bool swap_icon_label = false; + if (not config_["swap-icon-label"].isBool()) + spdlog::warn("'swap-icon-label' must be a bool."); + else + swap_icon_label = config_["swap-icon-label"].asBool(); + + if ( (rot == 0 || rot == 3) ^ swap_icon_label ) { + box_.add(image_); + box_.add(label_); + } else { + box_.add(label_); + box_.add(image_); + } event_box_.add(box_); } From c26978eca8e2e0e7e609085a7af0c9097d73717b Mon Sep 17 00:00:00 2001 From: Davide Manini Date: Mon, 2 Jun 2025 20:38:49 +0300 Subject: [PATCH 240/780] Update documentation --- man/waybar.5.scd.in | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 5bb62724..a9b76f8d 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -272,6 +272,17 @@ When positioning Waybar on the left or right side of the screen, sometimes it's Valid options for the "rotate" property are: 0, 90, 180, and 270. +## Swapping icon and label + +If a module displays both a label and an icon, it might be desirable to swap them (for instance, for panels on the left or right of the screen, or for user adopting a right-to-left script). This can be achieved with the "swap-icon-label" property, taking a boolean. Example: +``` +{ + "sway/window": { + "swap-icon-label": true + } +} +``` + ## Grouping modules Module groups allow stacking modules in any direction. By default, when the bar is positioned on the top or bottom of the screen, modules in a group are stacked vertically. Likewise, when positioned on the left or right, modules in a group are stacked horizontally. This can be changed with the "orientation" property. From 81fb0daad2a666eb34ebe7b52453b94be0a9263e Mon Sep 17 00:00:00 2001 From: Adrian Perez Date: Tue, 3 Jun 2025 16:12:30 -0700 Subject: [PATCH 241/780] feat: add a way to override electron app tray icons --- src/modules/sni/item.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 9dc13158..3b9a3dbd 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -1,3 +1,4 @@ +#include #include "modules/sni/item.hpp" #include @@ -140,7 +141,25 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { category = get_variant(value); } else if (name == "Id") { id = get_variant(value); - setCustomIcon(id); + + /* + * HACK: Electron apps seem to have the same ID, but tooltip seems correct, so use that as ID + * to pass as the custom icon option. I'm avoiding being disruptive and setting that to the ID + * itself as I've no idea what this would affect. + * The tooltip text is converted to lowercase since that's what (most?) themes expect? + * I still haven't found a way for it to pick from theme automatically, although + * it might be my theme. + */ + if (id == "chrome_status_icon_1") { + Glib::VariantBase value; + this->proxy_->get_cached_property(value, "ToolTip"); + tooltip = get_variant(value); + if (!tooltip.text.empty()) { + setCustomIcon(tooltip.text.lowercase()); + } + } else { + setCustomIcon(id); + } } else if (name == "Title") { title = get_variant(value); if (tooltip.text.empty()) { @@ -203,6 +222,8 @@ void Item::setStatus(const Glib::ustring& value) { } void Item::setCustomIcon(const std::string& id) { + spdlog::debug("SNI tray id: {}", id); + std::string custom_icon = IconManager::instance().getIconForApp(id); if (!custom_icon.empty()) { if (std::filesystem::exists(custom_icon)) { From 715503ec3ed14683114df50496267a28968009fd Mon Sep 17 00:00:00 2001 From: gred Date: Sat, 7 Jun 2025 10:50:29 +0300 Subject: [PATCH 242/780] Rename vector to hiddenWorkspaces --- src/modules/hyprland/workspaces.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 9caf6c6e..50727d61 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -772,7 +772,7 @@ void Workspaces::setCurrentMonitorId() { void Workspaces::sortSpecialCentered() { std::vector> specialWorkspaces; - std::vector> hidedWorkspaces; + std::vector> hiddenWorkspaces; std::vector> normalWorkspaces; for (auto &workspace : m_workspaces) { @@ -782,7 +782,7 @@ void Workspaces::sortSpecialCentered() { if (workspace->button().is_visible()) { normalWorkspaces.push_back(std::move(workspace)); } else { - hidedWorkspaces.push_back(std::move(workspace)); + hiddenWorkspaces.push_back(std::move(workspace)); } } } @@ -803,8 +803,8 @@ void Workspaces::sortSpecialCentered() { std::make_move_iterator(normalWorkspaces.end())); m_workspaces.insert(m_workspaces.end(), - std::make_move_iterator(hidedWorkspaces.begin()), - std::make_move_iterator(hidedWorkspaces.end())); + std::make_move_iterator(hiddenWorkspaces.begin()), + std::make_move_iterator(hiddenWorkspaces.end())); } void Workspaces::sortWorkspaces() { From b36a283f8324d7cd3772acc2bed1a4dfc817369a Mon Sep 17 00:00:00 2001 From: gred Date: Sat, 7 Jun 2025 11:06:06 +0300 Subject: [PATCH 243/780] Update man --- man/waybar-hyprland-workspaces.5.scd | 1 + 1 file changed, 1 insertion(+) diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index 18c39898..17b6e3fe 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -76,6 +76,7 @@ Addressed by *hyprland/workspaces* If set to number, workspaces will sort by number. If set to name, workspaces will sort by name. If set to id, workspaces will sort by id. + If set to special-centered, workspaces will sort by default with special workspaces in the center. If none of those, workspaces will sort with default behavior. *expand*: ++ From 35c6e9c21c6d2e0c8200a2177f5ab977f5ecbc86 Mon Sep 17 00:00:00 2001 From: gred Date: Sat, 7 Jun 2025 11:23:19 +0300 Subject: [PATCH 244/780] Update man page --- man/waybar-hyprland-workspaces.5.scd | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index 18c39898..85446715 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -48,6 +48,11 @@ Addressed by *hyprland/workspaces* default: false ++ If this and show-special are to true, special workspaces will be shown only if visible. +*persistent-only*: ++ + typeof: bool ++ + default: false ++ + TIf set to true only persistent workspaces will be shown on bar. + *all-outputs*: ++ typeof: bool ++ default: false ++ From e3e80773422b4b56166967f9aca0f8fc4a1fcc63 Mon Sep 17 00:00:00 2001 From: gred Date: Sat, 7 Jun 2025 12:11:22 +0300 Subject: [PATCH 245/780] Add hide-active --- include/modules/hyprland/workspaces.hpp | 2 ++ src/modules/hyprland/workspace.cpp | 9 +++++++++ src/modules/hyprland/workspaces.cpp | 1 + 3 files changed, 12 insertions(+) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 6b33baea..7618f35c 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -36,6 +36,7 @@ class Workspaces : public AModule, public EventHandler { auto allOutputs() const -> bool { return m_allOutputs; } auto showSpecial() const -> bool { return m_showSpecial; } auto activeOnly() const -> bool { return m_activeOnly; } + auto hideActive() const -> bool { return m_hideActive; } auto specialVisibleOnly() const -> bool { return m_specialVisibleOnly; } auto moveToMonitor() const -> bool { return m_moveToMonitor; } @@ -121,6 +122,7 @@ class Workspaces : public AModule, public EventHandler { bool m_allOutputs = false; bool m_showSpecial = false; bool m_activeOnly = false; + bool m_hideActive = false; bool m_specialVisibleOnly = false; bool m_moveToMonitor = false; Json::Value m_persistentWorkspaceConfig; diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 4655096f..e0fed6cc 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -173,6 +173,15 @@ std::string &Workspace::selectIcon(std::map &icons_map } void Workspace::update(const std::string &format, const std::string &icon) { + // clang-format off + if (this->m_workspaceManager.hideActive() && \ + this->isActive() && \ + !this->isPersistent() && \ + !this->isSpecial()) { + // clang-format on + m_button.hide(); + return; + } // clang-format off if (this->m_workspaceManager.activeOnly() && \ !this->isActive() && \ diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 0e225935..ebfc942b 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -601,6 +601,7 @@ auto Workspaces::parseConfig(const Json::Value &config) -> void { populateBoolConfig(config, "show-special", m_showSpecial); populateBoolConfig(config, "special-visible-only", m_specialVisibleOnly); populateBoolConfig(config, "active-only", m_activeOnly); + populateBoolConfig(config, "hide-active", m_hideActive); populateBoolConfig(config, "move-to-monitor", m_moveToMonitor); m_persistentWorkspaceConfig = config.get("persistent-workspaces", Json::Value()); From 35b0d591a60440828add7bfdc6b24d44acead5b0 Mon Sep 17 00:00:00 2001 From: gred Date: Sat, 7 Jun 2025 12:14:40 +0300 Subject: [PATCH 246/780] Update man page --- man/waybar-hyprland-workspaces.5.scd | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index 18c39898..79ee1d26 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -58,6 +58,11 @@ Addressed by *hyprland/workspaces* default: false ++ If set to true, only the active workspace will be shown. +*hide-active*: ++ + typeof: bool ++ + default: false ++ + If set to true, the active workspace will be hidded. Unless a workspace is persistent or special. + *move-to-monitor*: ++ typeof: bool ++ default: false ++ From fca159ad014b0189c4d530a5ec783f07c64f8b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= <88423841+Roc25@users.noreply.github.com> Date: Sat, 7 Jun 2025 12:19:13 +0300 Subject: [PATCH 247/780] Update man page Co-authored-by: Tuur Vanhoutte <4633209+zjeffer@users.noreply.github.com> --- man/waybar-hyprland-workspaces.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index 85446715..b76be108 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -51,7 +51,7 @@ Addressed by *hyprland/workspaces* *persistent-only*: ++ typeof: bool ++ default: false ++ - TIf set to true only persistent workspaces will be shown on bar. + If set to true, only persistent workspaces will be shown on bar. *all-outputs*: ++ typeof: bool ++ From 5a075646d27255cb66358e84ef3f86e01b7693a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= <88423841+Roc25@users.noreply.github.com> Date: Sat, 7 Jun 2025 12:19:52 +0300 Subject: [PATCH 248/780] Update man Co-authored-by: Tuur Vanhoutte <4633209+zjeffer@users.noreply.github.com> --- man/waybar-hyprland-workspaces.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index 79ee1d26..73ccd9f3 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -61,7 +61,7 @@ Addressed by *hyprland/workspaces* *hide-active*: ++ typeof: bool ++ default: false ++ - If set to true, the active workspace will be hidded. Unless a workspace is persistent or special. + If set to true, the active workspace will be hidden. Unless a workspace is persistent or special. *move-to-monitor*: ++ typeof: bool ++ From 703be13b002342625debbab9b3393d7cb8e50e16 Mon Sep 17 00:00:00 2001 From: Gregor Kleen <20089782+gkleen@users.noreply.github.com> Date: Sat, 17 Aug 2024 21:11:07 +0200 Subject: [PATCH 249/780] privacy: introduce `ignore` option --- include/modules/privacy/privacy.hpp | 1 + src/modules/privacy/privacy.cpp | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/include/modules/privacy/privacy.hpp b/include/modules/privacy/privacy.hpp index 6179098c..3ea41d12 100644 --- a/include/modules/privacy/privacy.hpp +++ b/include/modules/privacy/privacy.hpp @@ -31,6 +31,7 @@ class Privacy : public AModule { uint iconSpacing = 4; uint iconSize = 20; uint transition_duration = 250; + std::set> ignore; std::shared_ptr backend = nullptr; }; diff --git a/src/modules/privacy/privacy.cpp b/src/modules/privacy/privacy.cpp index 48bba888..42875976 100644 --- a/src/modules/privacy/privacy.cpp +++ b/src/modules/privacy/privacy.cpp @@ -74,6 +74,18 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientat } } + for (const auto& ignore_item : config_["ignore"]) { + if (!ignore_item.isObject() || !ignore_item["type"].isString() || !ignore_item["name"].isString()) continue; + const std::string type = ignore_item["type"].asString(); + const std::string name = ignore_item["name"].asString(); + + auto iter = typeMap.find(type); + if (iter != typeMap.end()) { + auto& [_, nodeType] = iter->second; + ignore.emplace(nodeType, std::move(name)); + } + } + backend = util::PipewireBackend::PipewireBackend::getInstance(); backend->privacy_nodes_changed_signal_event.connect( sigc::mem_fun(*this, &Privacy::onPrivacyNodesChanged)); @@ -88,6 +100,10 @@ void Privacy::onPrivacyNodesChanged() { nodes_screenshare.clear(); for (auto& node : backend->privacy_nodes) { + auto iter = ignore.find(std::pair(node.second->type, node.second->node_name)); + if (iter != ignore.end()) + continue; + switch (node.second->state) { case PW_NODE_STATE_RUNNING: switch (node.second->type) { From 831602a9134deced66c3d4b8c4caa8a068a80cba Mon Sep 17 00:00:00 2001 From: Gregor Kleen <20089782+gkleen@users.noreply.github.com> Date: Tue, 20 Aug 2024 22:09:22 +0200 Subject: [PATCH 250/780] privacy: default to ignoring all stream.monitor pw nodes --- include/modules/privacy/privacy.hpp | 1 + include/util/pipewire/privacy_node_info.hpp | 1 + src/modules/privacy/privacy.cpp | 7 +++++++ src/util/pipewire/privacy_node_info.cpp | 2 ++ 4 files changed, 11 insertions(+) diff --git a/include/modules/privacy/privacy.hpp b/include/modules/privacy/privacy.hpp index 3ea41d12..cb6a34da 100644 --- a/include/modules/privacy/privacy.hpp +++ b/include/modules/privacy/privacy.hpp @@ -32,6 +32,7 @@ class Privacy : public AModule { uint iconSize = 20; uint transition_duration = 250; std::set> ignore; + bool ignore_monitor = true; std::shared_ptr backend = nullptr; }; diff --git a/include/util/pipewire/privacy_node_info.hpp b/include/util/pipewire/privacy_node_info.hpp index 7b8df018..54da7d16 100644 --- a/include/util/pipewire/privacy_node_info.hpp +++ b/include/util/pipewire/privacy_node_info.hpp @@ -25,6 +25,7 @@ class PrivacyNodeInfo { std::string media_name; std::string node_name; std::string application_name; + bool is_monitor = false; std::string pipewire_access_portal_app_id; std::string application_icon_name; diff --git a/src/modules/privacy/privacy.cpp b/src/modules/privacy/privacy.cpp index 42875976..6f6d1395 100644 --- a/src/modules/privacy/privacy.cpp +++ b/src/modules/privacy/privacy.cpp @@ -86,6 +86,10 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientat } } + if (config_["ignore-monitor"].isBool()) { + ignore_monitor = config_["ignore-monitor"].asBool(); + } + backend = util::PipewireBackend::PipewireBackend::getInstance(); backend->privacy_nodes_changed_signal_event.connect( sigc::mem_fun(*this, &Privacy::onPrivacyNodesChanged)); @@ -100,6 +104,9 @@ void Privacy::onPrivacyNodesChanged() { nodes_screenshare.clear(); for (auto& node : backend->privacy_nodes) { + if (ignore_monitor && node.second->is_monitor) + continue; + auto iter = ignore.find(std::pair(node.second->type, node.second->node_name)); if (iter != ignore.end()) continue; diff --git a/src/util/pipewire/privacy_node_info.cpp b/src/util/pipewire/privacy_node_info.cpp index 739dc528..ec110b86 100644 --- a/src/util/pipewire/privacy_node_info.cpp +++ b/src/util/pipewire/privacy_node_info.cpp @@ -49,6 +49,8 @@ void PrivacyNodeInfo::handleNodeEventInfo(const struct pw_node_info *info) { pipewire_access_portal_app_id = item->value; } else if (strcmp(item->key, PW_KEY_APP_ICON_NAME) == 0) { application_icon_name = item->value; + } else if (strcmp(item->key, "stream.monitor") == 0) { + is_monitor = strcmp(item->value, "true") == 0; } } } From 6cfaf4ff63b9f30bdcd9c0fc3247ff537ae78966 Mon Sep 17 00:00:00 2001 From: Gregor Kleen <20089782+gkleen@users.noreply.github.com> Date: Fri, 13 Sep 2024 14:18:04 +0200 Subject: [PATCH 251/780] privacy: document ignore options --- man/waybar-privacy.5.scd | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/man/waybar-privacy.5.scd b/man/waybar-privacy.5.scd index 946fe136..28b55f1a 100644 --- a/man/waybar-privacy.5.scd +++ b/man/waybar-privacy.5.scd @@ -37,6 +37,17 @@ the screen or playing audio. default: false ++ Enables this module to consume all left over space dynamically. +*ignore-monitor* ++ + typeof: bool ++ + default: true ++ + Ignore streams with *stream.monitor* property. + +*ignore* ++ + typeof: array of objects ++ + default: [] ++ + Additional streams to be ignored. See *IGNORE CONFIGURATION* for++ + more information. + # MODULES CONFIGURATION *type*: ++ @@ -54,6 +65,14 @@ the screen or playing audio. default: 24 ++ The size of each icon in the tooltip. +# IGNORE CONFIGURATION + +*type*: ++ + typeof: string + +*name*: ++ + typeof: string + # EXAMPLES ``` From f73d26722c58b9a27b8390cfb37c5a07333a2732 Mon Sep 17 00:00:00 2001 From: Sonter <108224581+S0nter@users.noreply.github.com> Date: Sun, 8 Jun 2025 12:39:10 +0300 Subject: [PATCH 252/780] privacy: add example configuration --- man/waybar-privacy.5.scd | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/man/waybar-privacy.5.scd b/man/waybar-privacy.5.scd index 28b55f1a..1c4ef59d 100644 --- a/man/waybar-privacy.5.scd +++ b/man/waybar-privacy.5.scd @@ -96,6 +96,17 @@ the screen or playing audio. "tooltip": true, "tooltip-icon-size": 24 } + ], + "ignore-monitor": true, + "ignore": [ + { + "type": "audio-in", + "name": "cava" + }, + { + "type": "screenshare", + "name": "obs" + } ] }, ``` From 4d9403601a621b947aba81bc27b89431b173e709 Mon Sep 17 00:00:00 2001 From: Sonter <108224581+S0nter@users.noreply.github.com> Date: Sun, 8 Jun 2025 12:53:46 +0300 Subject: [PATCH 253/780] privacy: format with clang-format --- src/modules/privacy/privacy.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modules/privacy/privacy.cpp b/src/modules/privacy/privacy.cpp index 6f6d1395..904c8fd9 100644 --- a/src/modules/privacy/privacy.cpp +++ b/src/modules/privacy/privacy.cpp @@ -75,7 +75,9 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientat } for (const auto& ignore_item : config_["ignore"]) { - if (!ignore_item.isObject() || !ignore_item["type"].isString() || !ignore_item["name"].isString()) continue; + if (!ignore_item.isObject() || !ignore_item["type"].isString() || + !ignore_item["name"].isString()) + continue; const std::string type = ignore_item["type"].asString(); const std::string name = ignore_item["name"].asString(); @@ -104,12 +106,10 @@ void Privacy::onPrivacyNodesChanged() { nodes_screenshare.clear(); for (auto& node : backend->privacy_nodes) { - if (ignore_monitor && node.second->is_monitor) - continue; + if (ignore_monitor && node.second->is_monitor) continue; auto iter = ignore.find(std::pair(node.second->type, node.second->node_name)); - if (iter != ignore.end()) - continue; + if (iter != ignore.end()) continue; switch (node.second->state) { case PW_NODE_STATE_RUNNING: From 3ebf2d96e529c0ccba05273805693adbd863527d Mon Sep 17 00:00:00 2001 From: Taimase Date: Wed, 11 Jun 2025 01:12:08 -0600 Subject: [PATCH 254/780] fix continuousWorker in the custom module by capturing the buffer by reference. --- src/modules/custom.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index e023aaf6..052247e0 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -62,7 +62,7 @@ void waybar::modules::Custom::continuousWorker() { } thread_ = [this, cmd] { char* buff = nullptr; - waybar::util::ScopeGuard buff_deleter([buff]() { + waybar::util::ScopeGuard buff_deleter([&buff]() { if (buff) { free(buff); } From 5e14698b4eb824189513bdbbd57484316453c6b4 Mon Sep 17 00:00:00 2001 From: peelz Date: Thu, 12 Jun 2025 17:01:05 -0400 Subject: [PATCH 255/780] fix: network frequency is reported in GHz --- man/waybar-network.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/waybar-network.5.scd b/man/waybar-network.5.scd index 15f15395..3b63e3ee 100644 --- a/man/waybar-network.5.scd +++ b/man/waybar-network.5.scd @@ -171,7 +171,7 @@ Addressed by *network* *{signaldBm}*: Signal strength of the wireless network in dBm. -*{frequency}*: Frequency of the wireless network in MHz. +*{frequency}*: Frequency of the wireless network in GHz. *{bandwidthUpBits}*: Instant up speed in bits/seconds. From 07468357f478c42fabc85e5ca849bba9e4c2df70 Mon Sep 17 00:00:00 2001 From: peelz Date: Thu, 12 Jun 2025 19:19:43 -0400 Subject: [PATCH 256/780] fix: network module not displaying rfkill state --- src/modules/network.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 0a77c00e..c46b8188 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -223,8 +223,8 @@ void waybar::modules::Network::worker() { std::lock_guard lock(mutex_); if (ifid_ > 0) { getInfo(); - dp.emit(); } + dp.emit(); } thread_timer_.sleep_for(interval_); }; @@ -271,10 +271,10 @@ void waybar::modules::Network::worker() { } const std::string waybar::modules::Network::getNetworkState() const { - if (ifid_ == -1) { #ifdef WANT_RFKILL if (rfkill_.getState()) return "disabled"; #endif + if (ifid_ == -1) { return "disconnected"; } if (!carrier_) return "disconnected"; From f4496c9648bf651b871eb6fa61ddb115c25cd90a Mon Sep 17 00:00:00 2001 From: peelz Date: Fri, 13 Jun 2025 22:51:03 -0400 Subject: [PATCH 257/780] fix: length_error thrown in handleOutputDescription --- src/client.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index 63a9276a..b4fe2a8b 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -106,11 +106,11 @@ void waybar::Client::handleOutputDescription(void *data, struct zxdg_output_v1 * auto *client = waybar::Client::inst(); try { auto &output = client->getOutput(data); - const char *open_paren = strrchr(description, '('); // Description format: "identifier (name)" - size_t identifier_length = open_paren - description; - output.identifier = std::string(description, identifier_length - 1); + auto s = std::string(description); + auto pos = s.find(" ("); + output.identifier = pos != std::string::npos ? s.substr(0, pos) : s; } catch (const std::exception &e) { std::cerr << e.what() << '\n'; } From bd28bb959fdcd06685e9f4848c11dbf2b056d14b Mon Sep 17 00:00:00 2001 From: peelz Date: Fri, 13 Jun 2025 22:57:31 -0400 Subject: [PATCH 258/780] fix: use spdlog in zxdg_output_v1_listener callbacks --- src/client.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index b4fe2a8b..e363f236 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -86,7 +86,7 @@ void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_ } } } catch (const std::exception &e) { - std::cerr << e.what() << '\n'; + spdlog::warn("caught exception in zxdg_output_v1_listener::done: {}", e.what()); } } @@ -97,7 +97,7 @@ void waybar::Client::handleOutputName(void *data, struct zxdg_output_v1 * /*xdg_ auto &output = client->getOutput(data); output.name = name; } catch (const std::exception &e) { - std::cerr << e.what() << '\n'; + spdlog::warn("caught exception in zxdg_output_v1_listener::name: {}", e.what()); } } @@ -112,7 +112,7 @@ void waybar::Client::handleOutputDescription(void *data, struct zxdg_output_v1 * auto pos = s.find(" ("); output.identifier = pos != std::string::npos ? s.substr(0, pos) : s; } catch (const std::exception &e) { - std::cerr << e.what() << '\n'; + spdlog::warn("caught exception in zxdg_output_v1_listener::description: {}", e.what()); } } From 9b6590b972590672d465b1823c050b56fb4c288e Mon Sep 17 00:00:00 2001 From: burbschat Date: Sat, 14 Jun 2025 22:30:46 +0900 Subject: [PATCH 259/780] Add CSS classes for sway workspace indicators depending on associated output Allows for different styles of workspace indicators (buttons) depending on the output the workspace is located at. Using this one can for example color the indicators depending on the output making it easy to infer on which output a workspace is located. Example config: "sway/workspaces": { "all-outputs": true, "format": "{name}", "output-classes": { "DP-1": "output1", "LVDS-2": "output2" } } Example CSS: #workspaces button.output1{ background-color: green; } #workspaces button.output2{ background-color: blue; } --- man/waybar-sway-workspaces.5.scd | 7 +++++++ src/modules/sway/workspaces.cpp | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/man/waybar-sway-workspaces.5.scd b/man/waybar-sway-workspaces.5.scd index 1c4360e9..e4041f5a 100644 --- a/man/waybar-sway-workspaces.5.scd +++ b/man/waybar-sway-workspaces.5.scd @@ -105,6 +105,12 @@ warp-on-scroll: ++ default: false ++ Enables this module to consume all left over space dynamically. +*output-classes*: ++ + typeof: array ++ + Specify additional CSS classes to be added to workspace indicators based on the output on which the associated workspace is located. + Keys are output names and values are class names like *${output}: {output-class}*. + Assignment in config is used to keep the stylesheet independent of the available outputs. + # FORMAT REPLACEMENTS @@ -193,3 +199,4 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge - *#workspaces button.empty* - *#workspaces button.current_output* - *#workspaces button#sway-workspace-${name}* +- *#workspaces button.${output-class}* diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index b8ed73d4..fc122a03 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -321,6 +321,17 @@ auto Workspaces::update() -> void { button.get_style_context()->remove_class("empty"); } if ((*it)["output"].isString()) { + // Simply attempt to remove all output classes every time to reset output classes. This works + // even if a class has not been previously added to the style context. + for (const auto & oclass : config_["output-classes"]) { + button.get_style_context()->remove_class(oclass.asString()); + } + // If output-classes contains a class for output associated with current workspace button, add + // the class to its style context. + std::string output_name = (*it)["output"].asString(); + if (config_["output-classes"].isMember(output_name) && config_["output-classes"][output_name].isString()){ + button.get_style_context()->add_class(config_["output-classes"][output_name].asString()); + } if (((*it)["output"].asString()) == bar_.output->name) { button.get_style_context()->add_class("current_output"); } else { From d6f9dc911a9fd549893778e3e605cbea89ea3a98 Mon Sep 17 00:00:00 2001 From: burbschat Date: Sat, 14 Jun 2025 22:59:32 +0900 Subject: [PATCH 260/780] Apply format according to .clang-format --- src/modules/sway/workspaces.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index fc122a03..3b319623 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -323,13 +323,14 @@ auto Workspaces::update() -> void { if ((*it)["output"].isString()) { // Simply attempt to remove all output classes every time to reset output classes. This works // even if a class has not been previously added to the style context. - for (const auto & oclass : config_["output-classes"]) { + for (const auto &oclass : config_["output-classes"]) { button.get_style_context()->remove_class(oclass.asString()); } // If output-classes contains a class for output associated with current workspace button, add // the class to its style context. std::string output_name = (*it)["output"].asString(); - if (config_["output-classes"].isMember(output_name) && config_["output-classes"][output_name].isString()){ + if (config_["output-classes"].isMember(output_name) && + config_["output-classes"][output_name].isString()) { button.get_style_context()->add_class(config_["output-classes"][output_name].asString()); } if (((*it)["output"].asString()) == bar_.output->name) { From af9d61fa8f150317d2de908600a14ac856ee740d Mon Sep 17 00:00:00 2001 From: markx86 <25851052+markx86@users.noreply.github.com> Date: Thu, 19 Jun 2025 00:35:12 +0200 Subject: [PATCH 261/780] fix: `cldMonShift_` not getting initialized in `Clock()` constructor Initialize `cldMonShift_` member in the `clock` module constructor. This fixes a bug where the calendar tooltip would break after a reload, when in month mode. --- src/modules/clock.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 269fa765..f00d79fc 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -25,6 +25,7 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) m_tooltip_{new Gtk::Label()}, cldInTooltip_{m_tlpFmt_.find("{" + kCldPlaceholder + "}") != std::string::npos}, cldYearShift_{January / 1 / 1900}, + cldMonShift_{1900y / January}, tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos}, tzCurrIdx_{0}, ordInTooltip_{m_tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} { From bdcab011ee9daa316452a8764f67b2aca699103e Mon Sep 17 00:00:00 2001 From: Steffen Kothe Date: Thu, 19 Jun 2025 15:55:35 +0000 Subject: [PATCH 262/780] modules: memory: Add swapState format argument Add an argument to the memory module which displays the state of the swap configuration of the local system. Usage of swap does not necessarily indicate if swap is on or off. Signed-off-by: Steffen Kothe --- man/waybar-memory.5.scd | 2 ++ src/modules/memory/common.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/man/waybar-memory.5.scd b/man/waybar-memory.5.scd index cc42d5a3..567c2c72 100644 --- a/man/waybar-memory.5.scd +++ b/man/waybar-memory.5.scd @@ -120,6 +120,8 @@ Addressed by *memory* *{swapAvail}*: Amount of available swap in GiB. +*{swapState}*: Signals if swap is activated or not + # EXAMPLES ``` diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp index 544d7814..18600cd2 100644 --- a/src/modules/memory/common.cpp +++ b/src/modules/memory/common.cpp @@ -60,6 +60,7 @@ auto waybar::modules::Memory::update() -> void { fmt::arg("icon", getIcon(used_ram_percentage, icons)), fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), fmt::arg("percentage", used_ram_percentage), + fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"), fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes), fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes), fmt::arg("swapAvail", available_swap_gigabytes))); @@ -72,6 +73,7 @@ auto waybar::modules::Memory::update() -> void { fmt::runtime(tooltip_format), used_ram_percentage, fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), fmt::arg("percentage", used_ram_percentage), + fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"), fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes), fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes), fmt::arg("swapAvail", available_swap_gigabytes))); From 76d0b44214751cf334dae07072e1f7cff9a45661 Mon Sep 17 00:00:00 2001 From: Steffen Kothe Date: Thu, 19 Jun 2025 18:57:59 +0000 Subject: [PATCH 263/780] Dockerfiles: Use debian-slim for container Slim images of Debian tend to be smaller even during development. Hence replace the full-fledged variant with the slim one. Signed-off-by: Steffen Kothe --- Dockerfiles/debian | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfiles/debian b/Dockerfiles/debian index f479062d..c2584ccf 100644 --- a/Dockerfiles/debian +++ b/Dockerfiles/debian @@ -1,6 +1,6 @@ # vim: ft=Dockerfile -FROM debian:sid +FROM debian:sid-slim RUN apt update && \ apt install --no-install-recommends --no-install-suggests -y \ From c266befe0a9b80109d4b19c2a7482e19a16c0679 Mon Sep 17 00:00:00 2001 From: markx86 <25851052+markx86@users.noreply.github.com> Date: Fri, 20 Jun 2025 14:02:01 +0200 Subject: [PATCH 264/780] fix: MPRIS widget not hiding when no player is active --- src/modules/mpris/mpris.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index ed383b0c..47bb9c05 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -425,9 +425,11 @@ auto Mpris::onPlayerNameVanished(PlayerctlPlayerManager* manager, PlayerctlPlaye auto* mpris = static_cast(data); if (!mpris) return; - spdlog::debug("mpris: player-vanished callback: {}", player_name->name); + spdlog::debug("mpris: name-vanished callback: {}", player_name->name); - if (std::string(player_name->name) == mpris->player_) { + if (mpris->player_ == "playerctld") { + mpris->dp.emit(); + } else if (mpris->player_ == player_name->name) { mpris->player = nullptr; mpris->event_box_.set_visible(false); mpris->dp.emit(); @@ -499,6 +501,7 @@ auto Mpris::getPlayerInfo() -> std::optional { // https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249 players = g_list_first(players); if (players) player_name = static_cast(players->data)->name; + else return std::nullopt; // no players found, hide the widget } if (std::any_of(ignored_players_.begin(), ignored_players_.end(), From b8a985d606d8f33c92a8e539d4978c32ace3e9df Mon Sep 17 00:00:00 2001 From: peelz Date: Sat, 21 Jun 2025 10:54:16 -0400 Subject: [PATCH 265/780] style: fix formatting --- src/modules/network.cpp | 2 +- src/modules/sway/workspaces.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index c46b8188..955f9f1d 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -272,7 +272,7 @@ void waybar::modules::Network::worker() { const std::string waybar::modules::Network::getNetworkState() const { #ifdef WANT_RFKILL - if (rfkill_.getState()) return "disabled"; + if (rfkill_.getState()) return "disabled"; #endif if (ifid_ == -1) { return "disconnected"; diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index b8ed73d4..86c2029b 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -494,7 +494,7 @@ std::string Workspaces::trimWorkspaceName(std::string name) { return name; } -bool is_focused_recursive(const Json::Value& node) { +bool is_focused_recursive(const Json::Value &node) { // If a workspace has a focused container then get_tree will say // that the workspace itself isn't focused. Therefore we need to // check if any of its nodes are focused as well. @@ -504,13 +504,13 @@ bool is_focused_recursive(const Json::Value& node) { return true; } - for (const auto& child : node["nodes"]) { + for (const auto &child : node["nodes"]) { if (is_focused_recursive(child)) { return true; } } - for (const auto& child : node["floating_nodes"]) { + for (const auto &child : node["floating_nodes"]) { if (is_focused_recursive(child)) { return true; } From 37a6106d3e62efc19861f1ea80f04bf145d4252b Mon Sep 17 00:00:00 2001 From: Steffen Kothe Date: Sat, 21 Jun 2025 15:17:59 +0000 Subject: [PATCH 266/780] modules: systemd_failed_units: Introduce nr_failed as member Keeping nr_failed as member allows to split-out calculation of overall failed units into updateData. Signed-off-by: Steffen Kothe --- include/modules/systemd_failed_units.hpp | 2 +- src/modules/systemd_failed_units.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/modules/systemd_failed_units.hpp b/include/modules/systemd_failed_units.hpp index 9c3fbcee..df78a5bb 100644 --- a/include/modules/systemd_failed_units.hpp +++ b/include/modules/systemd_failed_units.hpp @@ -19,7 +19,7 @@ class SystemdFailedUnits : public ALabel { std::string format_ok; bool update_pending; - uint32_t nr_failed_system, nr_failed_user; + uint32_t nr_failed_system, nr_failed_user, nr_failed; std::string last_status; Glib::RefPtr system_proxy, user_proxy; diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index 56e624cf..e2cad8f9 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -16,6 +16,7 @@ SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value& update_pending(false), nr_failed_system(0), nr_failed_user(0), + nr_failed(0), last_status() { if (config["hide-on-ok"].isBool()) { hide_on_ok = config["hide-on-ok"].asBool(); @@ -100,7 +101,7 @@ void SystemdFailedUnits::updateData() { } auto SystemdFailedUnits::update() -> void { - uint32_t nr_failed = nr_failed_system + nr_failed_user; + nr_failed = nr_failed_system + nr_failed_user; // Hide if needed. if (nr_failed == 0 && hide_on_ok) { From dcbbe3bb97b973fe792e313dbddd4b8709f96d86 Mon Sep 17 00:00:00 2001 From: Steffen Kothe Date: Sat, 21 Jun 2025 15:19:43 +0000 Subject: [PATCH 267/780] modules: systemd_failed_units: Move nr_failed calculation to updateData Signed-off-by: Steffen Kothe --- src/modules/systemd_failed_units.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index e2cad8f9..2518b5cc 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -97,12 +97,12 @@ void SystemdFailedUnits::updateData() { if (user_proxy) { nr_failed_user = load("user", user_proxy); } + + nr_failed = nr_failed_system + nr_failed_user; dp.emit(); } auto SystemdFailedUnits::update() -> void { - nr_failed = nr_failed_system + nr_failed_user; - // Hide if needed. if (nr_failed == 0 && hide_on_ok) { event_box_.set_visible(false); From 5c2cf4c65c5e8138db7206664d292165b9aad32f Mon Sep 17 00:00:00 2001 From: Steffen Kothe Date: Sat, 21 Jun 2025 15:22:24 +0000 Subject: [PATCH 268/780] modules: systemd_failed_units: Fail early if state did not change Prefer early exit if last status matches the current status. Signed-off-by: Steffen Kothe --- src/modules/systemd_failed_units.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index 2518b5cc..8ca183ae 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -103,6 +103,10 @@ void SystemdFailedUnits::updateData() { } auto SystemdFailedUnits::update() -> void { + const std::string status = nr_failed == 0 ? "ok" : "degraded"; + + if (last_status == status) return; + // Hide if needed. if (nr_failed == 0 && hide_on_ok) { event_box_.set_visible(false); @@ -113,7 +117,6 @@ auto SystemdFailedUnits::update() -> void { } // Set state class. - const std::string status = nr_failed == 0 ? "ok" : "degraded"; if (!last_status.empty() && label_.get_style_context()->has_class(last_status)) { label_.get_style_context()->remove_class(last_status); } From 74255d0c7e6cd23bf29bfd51b1254576b65d01f5 Mon Sep 17 00:00:00 2001 From: Steffen Kothe Date: Sat, 21 Jun 2025 15:25:21 +0000 Subject: [PATCH 269/780] modules: systemd_failed_units: Move DBUS proxy check into lambda function Checking for the availability of a given proxy member can be done in the lambda function as well. Signed-off-by: Steffen Kothe --- src/modules/systemd_failed_units.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index 8ca183ae..3ff3c2ee 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -73,6 +73,7 @@ void SystemdFailedUnits::updateData() { auto load = [](const char* kind, Glib::RefPtr& proxy) -> uint32_t { try { + if (!proxy) return 0; auto parameters = Glib::VariantContainerBase( g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "NFailedUnits")); Glib::VariantContainerBase data = proxy->call_sync("Get", parameters); @@ -91,13 +92,8 @@ void SystemdFailedUnits::updateData() { return 0; }; - if (system_proxy) { - nr_failed_system = load("systemwide", system_proxy); - } - if (user_proxy) { - nr_failed_user = load("user", user_proxy); - } - + nr_failed_system = load("systemwide", system_proxy); + nr_failed_user = load("user", user_proxy); nr_failed = nr_failed_system + nr_failed_user; dp.emit(); } From 4bb06b86bccccdceaa55828a2de9c84c874ccf21 Mon Sep 17 00:00:00 2001 From: Steffen Kothe Date: Sat, 21 Jun 2025 15:27:43 +0000 Subject: [PATCH 270/780] modules: systemd_failed_units: Use explicit g_variant_get_uint32 Determining of failed units can be done by usage of explicit uint32 function with direct return due to auto lambda expression. Signed-off-by: Steffen Kothe --- src/modules/systemd_failed_units.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index 3ff3c2ee..a4457671 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -81,9 +81,7 @@ void SystemdFailedUnits::updateData() { Glib::VariantBase variant; g_variant_get(data.gobj_copy(), "(v)", &variant); if (variant && variant.is_of_type(Glib::VARIANT_TYPE_UINT32)) { - uint32_t value = 0; - g_variant_get(variant.gobj_copy(), "u", &value); - return value; + return g_variant_get_uint32(variant.gobj_copy()); } } } catch (Glib::Error& e) { From 07311176797cf476e7a51bb774a1eb4d4326ad8a Mon Sep 17 00:00:00 2001 From: Steffen Kothe Date: Sat, 21 Jun 2025 15:34:44 +0000 Subject: [PATCH 271/780] modules: systemd_failed_units: Introduce RequestFailedUnits member Split-out request of failed units from systemd into a separate member function. This increases the readability and extendability, but preserves the current functionality (non-functional change). Signed-off-by: Steffen Kothe --- include/modules/systemd_failed_units.hpp | 1 + src/modules/systemd_failed_units.cpp | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/include/modules/systemd_failed_units.hpp b/include/modules/systemd_failed_units.hpp index df78a5bb..7801a5d6 100644 --- a/include/modules/systemd_failed_units.hpp +++ b/include/modules/systemd_failed_units.hpp @@ -25,6 +25,7 @@ class SystemdFailedUnits : public ALabel { void notify_cb(const Glib::ustring &sender_name, const Glib::ustring &signal_name, const Glib::VariantContainerBase &arguments); + void RequestFailedUnits(); void updateData(); }; diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index a4457671..f3f04fae 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -68,9 +68,7 @@ auto SystemdFailedUnits::notify_cb(const Glib::ustring& sender_name, } } -void SystemdFailedUnits::updateData() { - update_pending = false; - +void SystemdFailedUnits::RequestFailedUnits() { auto load = [](const char* kind, Glib::RefPtr& proxy) -> uint32_t { try { if (!proxy) return 0; @@ -93,6 +91,11 @@ void SystemdFailedUnits::updateData() { nr_failed_system = load("systemwide", system_proxy); nr_failed_user = load("user", user_proxy); nr_failed = nr_failed_system + nr_failed_user; +} + +void SystemdFailedUnits::updateData() { + update_pending = false; + RequestFailedUnits(); dp.emit(); } From d5e3a9f894e5e5ed160a9329d198d5f0f81d494c Mon Sep 17 00:00:00 2001 From: Steffen Kothe Date: Sat, 21 Jun 2025 16:19:51 +0000 Subject: [PATCH 272/780] modules: systemd_failed_units: Enforce visibility of event box on every update Instead if guarding visibility in if condition, enforce visibility regardless of the state of the current update. Signed-off-by: Steffen Kothe --- src/modules/systemd_failed_units.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index f3f04fae..92f4105c 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -109,9 +109,8 @@ auto SystemdFailedUnits::update() -> void { event_box_.set_visible(false); return; } - if (!event_box_.get_visible()) { - event_box_.set_visible(true); - } + + event_box_.set_visible(true); // Set state class. if (!last_status.empty() && label_.get_style_context()->has_class(last_status)) { From cfb47790adaafd9508072632d35612e578f3ad84 Mon Sep 17 00:00:00 2001 From: Steffen Kothe Date: Sat, 21 Jun 2025 15:09:05 +0000 Subject: [PATCH 273/780] modules: systemd_failed_units: Introduce systemd state variables Systemd provides the status of a given user and system session as a human readable string. Retrieve this information via RequestSystemState and guard the retrieve of failed units depending on this request. The functionality is extended but does not change, which means that failed units in any granularity are displayed as before. Update documentation in the meantime. Signed-off-by: Steffen Kothe --- include/modules/systemd_failed_units.hpp | 2 + man/waybar-systemd-failed-units.5.scd | 6 +++ src/modules/systemd_failed_units.cpp | 50 +++++++++++++++++++----- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/include/modules/systemd_failed_units.hpp b/include/modules/systemd_failed_units.hpp index 7801a5d6..48b0074e 100644 --- a/include/modules/systemd_failed_units.hpp +++ b/include/modules/systemd_failed_units.hpp @@ -19,6 +19,7 @@ class SystemdFailedUnits : public ALabel { std::string format_ok; bool update_pending; + std::string system_state, user_state, overall_state; uint32_t nr_failed_system, nr_failed_user, nr_failed; std::string last_status; Glib::RefPtr system_proxy, user_proxy; @@ -26,6 +27,7 @@ class SystemdFailedUnits : public ALabel { void notify_cb(const Glib::ustring &sender_name, const Glib::ustring &signal_name, const Glib::VariantContainerBase &arguments); void RequestFailedUnits(); + void RequestSystemState(); void updateData(); }; diff --git a/man/waybar-systemd-failed-units.5.scd b/man/waybar-systemd-failed-units.5.scd index 92e74e9d..8d7c980a 100644 --- a/man/waybar-systemd-failed-units.5.scd +++ b/man/waybar-systemd-failed-units.5.scd @@ -62,6 +62,12 @@ Addressed by *systemd-failed-units* *{nr_failed}*: Number of total failed units. +*{systemd_state}:* State of the systemd system session + +*{user_state}:* State of the systemd user session + +*{overall_state}:* Overall state of the systemd and user session. ("Ok" or "Degraded") + # EXAMPLES ``` diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index 92f4105c..90f33be7 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -68,6 +68,34 @@ auto SystemdFailedUnits::notify_cb(const Glib::ustring& sender_name, } } +void SystemdFailedUnits::RequestSystemState() { + auto load = [](const char* kind, Glib::RefPtr& proxy) -> std::string { + try { + if (!proxy) return "unknown"; + auto parameters = Glib::VariantContainerBase( + g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "SystemState")); + Glib::VariantContainerBase data = proxy->call_sync("Get", parameters); + if (data && data.is_of_type(Glib::VariantType("(v)"))) { + Glib::VariantBase variant; + g_variant_get(data.gobj_copy(), "(v)", &variant); + if (variant && variant.is_of_type(Glib::VARIANT_TYPE_STRING)) { + return g_variant_get_string(variant.gobj_copy(), NULL); + } + } + } catch (Glib::Error& e) { + spdlog::error("Failed to get {} state: {}", kind, e.what().c_str()); + } + return "unknown"; + }; + + system_state = load("systemwide", system_proxy); + user_state = load("user", user_proxy); + if (system_state == "running" && user_state == "running") + overall_state = "ok"; + else + overall_state = "degraded"; +} + void SystemdFailedUnits::RequestFailedUnits() { auto load = [](const char* kind, Glib::RefPtr& proxy) -> uint32_t { try { @@ -95,17 +123,18 @@ void SystemdFailedUnits::RequestFailedUnits() { void SystemdFailedUnits::updateData() { update_pending = false; - RequestFailedUnits(); + + RequestSystemState(); + if (overall_state == "degraded") RequestFailedUnits(); + dp.emit(); } auto SystemdFailedUnits::update() -> void { - const std::string status = nr_failed == 0 ? "ok" : "degraded"; - - if (last_status == status) return; + if (last_status == overall_state) return; // Hide if needed. - if (nr_failed == 0 && hide_on_ok) { + if (overall_state == "ok" && hide_on_ok) { event_box_.set_visible(false); return; } @@ -116,14 +145,17 @@ auto SystemdFailedUnits::update() -> void { if (!last_status.empty() && label_.get_style_context()->has_class(last_status)) { label_.get_style_context()->remove_class(last_status); } - if (!label_.get_style_context()->has_class(status)) { - label_.get_style_context()->add_class(status); + if (!label_.get_style_context()->has_class(overall_state)) { + label_.get_style_context()->add_class(overall_state); } - last_status = status; + + last_status = overall_state; label_.set_markup(fmt::format( fmt::runtime(nr_failed == 0 ? format_ok : format_), fmt::arg("nr_failed", nr_failed), - fmt::arg("nr_failed_system", nr_failed_system), fmt::arg("nr_failed_user", nr_failed_user))); + fmt::arg("nr_failed_system", nr_failed_system), fmt::arg("nr_failed_user", nr_failed_user), + fmt::arg("system_state", system_state), fmt::arg("user_state", user_state), + fmt::arg("overall_state", overall_state))); ALabel::update(); } From c6fceb03c8dfb8d2254534d96b777d94a6b9d33a Mon Sep 17 00:00:00 2001 From: peelz Date: Sat, 21 Jun 2025 16:05:00 -0400 Subject: [PATCH 274/780] ci: relax gentoo gtkmm dependency requirement --- Dockerfiles/gentoo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfiles/gentoo b/Dockerfiles/gentoo index f2ec0dc9..f7023825 100644 --- a/Dockerfiles/gentoo +++ b/Dockerfiles/gentoo @@ -6,6 +6,6 @@ RUN export FEATURES="-ipc-sandbox -network-sandbox -pid-sandbox -sandbox -usersa emerge --sync && \ eselect news read --quiet new 1>/dev/null 2>&1 && \ emerge --verbose --update --deep --with-bdeps=y --backtrack=30 --newuse @world && \ - USE="wayland gtk3 gtk -doc X pulseaudio minimal" emerge dev-vcs/git dev-libs/wayland dev-libs/wayland-protocols =dev-cpp/gtkmm-3.24.6 x11-libs/libxkbcommon \ + USE="wayland gtk3 gtk -doc X pulseaudio minimal" emerge dev-vcs/git dev-libs/wayland dev-libs/wayland-protocols dev-cpp/gtkmm:3.0 x11-libs/libxkbcommon \ x11-libs/gtk+:3 dev-libs/libdbusmenu dev-libs/libnl sys-power/upower media-libs/libpulse dev-libs/libevdev media-libs/libmpdclient \ media-sound/sndio gui-libs/gtk-layer-shell app-text/scdoc media-sound/playerctl dev-libs/iniparser sci-libs/fftw From d4f61ad2717aa31ab2bf21e0b0175c5726f4ff8a Mon Sep 17 00:00:00 2001 From: peelz Date: Sat, 21 Jun 2025 16:05:00 -0400 Subject: [PATCH 275/780] ci: allow manual triggering of docker workflow --- .github/workflows/docker.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 66c465ba..c84b3c29 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,6 +1,7 @@ name: Build and Push Docker Image on: + workflow_dispatch: schedule: # run every night at midnight - cron: '0 0 * * *' @@ -8,7 +9,7 @@ on: jobs: build-and-push: runs-on: ubuntu-latest - if: github.repository == 'Alexays/Waybar' + if: github.event_name != 'schedule' || github.repository == 'Alexays/Waybar' strategy: fail-fast: false # don't fail the other jobs if one of the images fails to build matrix: From 84bd0d452ef80ade073632982081f519bb90c2d3 Mon Sep 17 00:00:00 2001 From: peelz Date: Sat, 21 Jun 2025 16:05:00 -0400 Subject: [PATCH 276/780] ci: run the docker workflow monthly instead of daily Rebuilding the gentoo docker image daily is most definitely going to bust through the free CI tier limits. --- .github/workflows/docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c84b3c29..0e7e2944 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -3,8 +3,8 @@ name: Build and Push Docker Image on: workflow_dispatch: schedule: - # run every night at midnight - - cron: '0 0 * * *' + # run monthly + - cron: '0 0 1 * *' jobs: build-and-push: From e4dd2ecc5a2ee8752b4ce664037aa7dee38501a9 Mon Sep 17 00:00:00 2001 From: peelz Date: Sun, 22 Jun 2025 00:50:25 -0400 Subject: [PATCH 277/780] refactor: avoid namespace pollution in util/date.hpp --- include/modules/clock.hpp | 44 +++++++++++++++++++-------------------- include/util/date.hpp | 4 +--- src/modules/clock.cpp | 9 ++++---- test/utils/date.cpp | 2 ++ 4 files changed, 30 insertions(+), 29 deletions(-) diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index 40b4f80e..e34b7a8e 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -38,39 +38,39 @@ 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 - months cldCurrShift_{0}; // calendar months shift - int cldShift_{1}; // calendar months shift factor - year_month_day cldYearShift_; // calendar Year mode. Cached ymd - std::string cldYearCached_; // calendar Year mode. Cached calendar - year_month cldMonShift_; // calendar Month mode. Cached ym - std::string cldMonCached_; // calendar Month mode. Cached calendar - 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 CldMode cldMode_{CldMode::MONTH}; - auto get_calendar(const year_month_day& today, const year_month_day& ymd, const time_zone* tz) - -> const std::string; + auto get_calendar(const date::year_month_day& today, const date::year_month_day& ymd, + const date::time_zone* tz) -> const std::string; // get local time zone - auto local_zone() -> const time_zone*; + auto local_zone() -> const date::time_zone*; // time zoned time in tooltip - const bool tzInTooltip_; // if need to print time zones text - std::vector tzList_; // time zones list - int tzCurrIdx_; // current time zone index for tzList_ - std::string tzText_{""}; // time zones text to print + const bool tzInTooltip_; // if need to print time zones text + std::vector tzList_; // time zones list + int tzCurrIdx_; // current time zone index for tzList_ + std::string tzText_{""}; // time zones text to print util::SleeperThread thread_; // ordinal date in tooltip const bool ordInTooltip_; std::string ordText_{""}; - auto get_ordinal_date(const year_month_day& today) -> std::string; + auto get_ordinal_date(const date::year_month_day& today) -> std::string; - auto getTZtext(sys_seconds now) -> std::string; - auto first_day_of_week() -> weekday; + auto getTZtext(date::sys_seconds now) -> std::string; + auto first_day_of_week() -> date::weekday; // Module actions void cldModeSwitch(); void cldShift_up(); diff --git a/include/util/date.hpp b/include/util/date.hpp index a467cc56..d8653faf 100644 --- a/include/util/date.hpp +++ b/include/util/date.hpp @@ -15,7 +15,7 @@ namespace date { #if HAVE_CHRONO_TIMEZONES using namespace std::chrono; -using namespace std; +using std::format; #else using system_clock = std::chrono::system_clock; @@ -73,5 +73,3 @@ struct fmt::formatter> { } }; #endif - -using namespace date; diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index f00d79fc..42c52dfd 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -16,6 +16,7 @@ #include #endif +using namespace date; namespace fmt_lib = waybar::util::date::format; waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) @@ -349,9 +350,9 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea m_locale_, fmtMap_[4], fmt_lib::make_format_args( (line == 2) - ? static_cast( + ? static_cast( zoned_seconds{tz, local_days{ymTmp / 1}}) - : static_cast(zoned_seconds{ + : static_cast(zoned_seconds{ tz, local_days{cldGetWeekForLine(ymTmp, firstdow, line)}}))) << ' '; } else @@ -372,9 +373,9 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea << fmt_lib::vformat( m_locale_, fmtMap_[4], fmt_lib::make_format_args( - (line == 2) ? static_cast( + (line == 2) ? static_cast( zoned_seconds{tz, local_days{ymTmp / 1}}) - : static_cast( + : static_cast( zoned_seconds{tz, local_days{cldGetWeekForLine( ymTmp, firstdow, line)}}))); else diff --git a/test/utils/date.cpp b/test/utils/date.cpp index d317f98a..576a4799 100644 --- a/test/utils/date.cpp +++ b/test/utils/date.cpp @@ -18,8 +18,10 @@ return #endif +using namespace date; using namespace std::literals::chrono_literals; namespace fmt_lib = waybar::util::date::format; + /* * Check that the date/time formatter with locale and timezone support is working as expected. */ From 25f432b0ceb8e89321f7be3b07e779176e3cb049 Mon Sep 17 00:00:00 2001 From: peelz Date: Sun, 22 Jun 2025 00:50:25 -0400 Subject: [PATCH 278/780] refactor: avoid Gio namespace pollution --- include/util/portal.hpp | 4 +--- src/util/portal.cpp | 6 ++---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/include/util/portal.hpp b/include/util/portal.hpp index 23619169..bff74b11 100644 --- a/include/util/portal.hpp +++ b/include/util/portal.hpp @@ -6,14 +6,12 @@ namespace waybar { -using namespace Gio; - enum class Appearance { UNKNOWN = 0, DARK = 1, LIGHT = 2, }; -class Portal : private DBus::Proxy { +class Portal : private Gio::DBus::Proxy { public: Portal(); void refreshAppearance(); diff --git a/src/util/portal.cpp b/src/util/portal.cpp index 5874871b..6df2a6b6 100644 --- a/src/util/portal.cpp +++ b/src/util/portal.cpp @@ -17,8 +17,6 @@ static constexpr const char* PORTAL_NAMESPACE = "org.freedesktop.appearance"; static constexpr const char* PORTAL_KEY = "color-scheme"; } // namespace waybar -using namespace Gio; - auto fmt::formatter::format(waybar::Appearance c, format_context& ctx) const { string_view name; switch (c) { @@ -36,8 +34,8 @@ auto fmt::formatter::format(waybar::Appearance c, format_con } waybar::Portal::Portal() - : DBus::Proxy(DBus::Connection::get_sync(DBus::BusType::BUS_TYPE_SESSION), PORTAL_BUS_NAME, - PORTAL_OBJ_PATH, PORTAL_INTERFACE), + : Gio::DBus::Proxy(Gio::DBus::Connection::get_sync(Gio::DBus::BusType::BUS_TYPE_SESSION), + PORTAL_BUS_NAME, PORTAL_OBJ_PATH, PORTAL_INTERFACE), currentMode(Appearance::UNKNOWN) { refreshAppearance(); }; From 8daaad1e1396d8084a2f05c9cd8cefca71e2ba60 Mon Sep 17 00:00:00 2001 From: peelz Date: Sun, 22 Jun 2025 00:56:56 -0400 Subject: [PATCH 279/780] fix: don't use c++20 chrono literals Unfortunately we can't use these yet because the freebsd build (clang) still uses HowardHinnant/date, which doesn't provide literal suffixes. --- src/modules/clock.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index f00d79fc..a1bd1d1f 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -25,7 +25,7 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) m_tooltip_{new Gtk::Label()}, cldInTooltip_{m_tlpFmt_.find("{" + kCldPlaceholder + "}") != std::string::npos}, cldYearShift_{January / 1 / 1900}, - cldMonShift_{1900y / January}, + cldMonShift_{year(1900) / January}, tzInTooltip_{m_tlpFmt_.find("{" + kTZPlaceholder + "}") != std::string::npos}, tzCurrIdx_{0}, ordInTooltip_{m_tlpFmt_.find("{" + kOrdPlaceholder + "}") != std::string::npos} { From 0bdea1e46fa9ced094fb6d0c6ab0df57e80f0471 Mon Sep 17 00:00:00 2001 From: peelz Date: Sun, 22 Jun 2025 01:02:53 -0400 Subject: [PATCH 280/780] ci: bump FreeBSD to 14.3 --- .github/workflows/freebsd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index ca0dcbc8..e45a8dc4 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -21,7 +21,7 @@ jobs: LDFLAGS: '-L/usr/local/lib' with: operating_system: freebsd - version: "14.1" + version: "14.3" environment_variables: CPPFLAGS LDFLAGS sync_files: runner-to-vm run: | From bef539e4de9a3ea5c62c2de34b88d55f13345fd1 Mon Sep 17 00:00:00 2001 From: Alexis Rouillard Date: Sun, 22 Jun 2025 09:30:46 +0200 Subject: [PATCH 281/780] Update privacy_item.cpp --- src/modules/privacy/privacy_item.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/privacy/privacy_item.cpp b/src/modules/privacy/privacy_item.cpp index 536c9180..6424da9e 100644 --- a/src/modules/privacy/privacy_item.cpp +++ b/src/modules/privacy/privacy_item.cpp @@ -96,7 +96,6 @@ PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privac } void PrivacyItem::update_tooltip() { - spdlog::trace("update privacy tooltip"); // Removes all old nodes for (auto *child : tooltip_window.get_children()) { tooltip_window.remove(*child); From d6b6158ae97651cb556ad388b365069843877ed0 Mon Sep 17 00:00:00 2001 From: Alexis Rouillard Date: Sun, 22 Jun 2025 09:42:14 +0200 Subject: [PATCH 282/780] Update custom.cpp --- src/modules/custom.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 3179e2bc..0220e348 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -35,8 +35,7 @@ waybar::modules::Custom::~Custom() { void waybar::modules::Custom::delayWorker() { thread_ = [this] { - for( int i : this->pid_children_ ) - { + for (int i: this->pid_children_) { int status; waitpid(i, &status, 0); } From 286cff2e3daf4234cce8f3f0b72ad38ee1b68249 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 22 Jun 2025 09:44:35 +0200 Subject: [PATCH 283/780] fix: lint --- src/AIconLabel.cpp | 5 ++--- src/modules/hyprland/submap.cpp | 2 +- src/modules/mpris/mpris.cpp | 6 ++++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/AIconLabel.cpp b/src/AIconLabel.cpp index 130ba60c..79cd5fe1 100644 --- a/src/AIconLabel.cpp +++ b/src/AIconLabel.cpp @@ -22,8 +22,7 @@ AIconLabel::AIconLabel(const Json::Value &config, const std::string &name, const if (config_["rotate"].isUInt()) { rot = config["rotate"].asUInt() % 360; - if ((rot % 90) != 00) - rot = 0; + if ((rot % 90) != 00) rot = 0; rot /= 90; } @@ -42,7 +41,7 @@ AIconLabel::AIconLabel(const Json::Value &config, const std::string &name, const else swap_icon_label = config_["swap-icon-label"].asBool(); - if ( (rot == 0 || rot == 3) ^ swap_icon_label ) { + if ((rot == 0 || rot == 3) ^ swap_icon_label) { box_.add(image_); box_.add(label_); } else { diff --git a/src/modules/hyprland/submap.cpp b/src/modules/hyprland/submap.cpp index d1060447..a2b3f460 100644 --- a/src/modules/hyprland/submap.cpp +++ b/src/modules/hyprland/submap.cpp @@ -68,7 +68,7 @@ void Submap::onEvent(const std::string& ev) { return; } - auto submapName = ev.substr(ev.find_first_of('>') + 2 ); + auto submapName = ev.substr(ev.find_first_of('>') + 2); if (!submap_.empty()) { label_.get_style_context()->remove_class(submap_); diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 47bb9c05..8782b9b2 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -500,8 +500,10 @@ auto Mpris::getPlayerInfo() -> std::optional { // > get the list of players [..] in order of activity // https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249 players = g_list_first(players); - if (players) player_name = static_cast(players->data)->name; - else return std::nullopt; // no players found, hide the widget + if (players) + player_name = static_cast(players->data)->name; + else + return std::nullopt; // no players found, hide the widget } if (std::any_of(ignored_players_.begin(), ignored_players_.end(), From 373fd77f7a89554fc254ac7825813c866dd30605 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 22 Jun 2025 09:54:41 +0200 Subject: [PATCH 284/780] chore: update cross-platform-actions/action --- .github/workflows/freebsd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index e45a8dc4..5b1515b2 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Test in FreeBSD VM - uses: cross-platform-actions/action@v0.25.0 + uses: cross-platform-actions/action@v0.28.0 timeout-minutes: 180 env: CPPFLAGS: '-isystem/usr/local/include' From df138e12c4dfe3f4f2a8d97cbc158283b0d77267 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 22 Jun 2025 09:55:57 +0200 Subject: [PATCH 285/780] fix: compat freebsd --- .github/workflows/freebsd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 5b1515b2..b9114c31 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -21,7 +21,7 @@ jobs: LDFLAGS: '-L/usr/local/lib' with: operating_system: freebsd - version: "14.3" + version: "14.2" environment_variables: CPPFLAGS LDFLAGS sync_files: runner-to-vm run: | From ee91d18ad9a7eee1305b5018d2010e44327bde91 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 22 Jun 2025 10:01:36 +0200 Subject: [PATCH 286/780] fix: lint --- include/modules/gps.hpp | 36 ++++++++++++++-------------- src/modules/custom.cpp | 2 +- src/modules/gps.cpp | 37 ++++++++++++++--------------- src/modules/hyprland/language.cpp | 5 ++-- src/modules/hyprland/workspaces.cpp | 12 ++++------ src/modules/mpris/mpris.cpp | 10 ++++---- src/modules/niri/workspaces.cpp | 1 - src/modules/wlr/taskbar.cpp | 2 +- 8 files changed, 50 insertions(+), 55 deletions(-) diff --git a/include/modules/gps.hpp b/include/modules/gps.hpp index 3701634b..80df12ba 100644 --- a/include/modules/gps.hpp +++ b/include/modules/gps.hpp @@ -14,27 +14,27 @@ namespace waybar::modules { - class Gps : public ALabel { - public: - Gps(const std::string&, const Json::Value&); - virtual ~Gps(); - auto update() -> void override; +class Gps : public ALabel { + public: + Gps(const std::string&, const Json::Value&); + virtual ~Gps(); + auto update() -> void override; - private: - #ifdef WANT_RFKILL - util::Rfkill rfkill_; - #endif - const std::string getFixModeName() const; - const std::string getFixModeString() const; + private: +#ifdef WANT_RFKILL + util::Rfkill rfkill_; +#endif + const std::string getFixModeName() const; + const std::string getFixModeString() const; - const std::string getFixStatusString() const; + const std::string getFixStatusString() const; - util::SleeperThread thread_, gps_thread_; - gps_data_t gps_data_; - std::string state_; + util::SleeperThread thread_, gps_thread_; + gps_data_t gps_data_; + std::string state_; - bool hideDisconnected = true; - bool hideNoFix = false; - }; + bool hideDisconnected = true; + bool hideNoFix = false; +}; } // namespace waybar::modules diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 0220e348..db5c6db3 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -35,7 +35,7 @@ waybar::modules::Custom::~Custom() { void waybar::modules::Custom::delayWorker() { thread_ = [this] { - for (int i: this->pid_children_) { + for (int i : this->pid_children_) { int status; waitpid(i, &status, 0); } diff --git a/src/modules/gps.cpp b/src/modules/gps.cpp index f075b44c..a7bab659 100644 --- a/src/modules/gps.cpp +++ b/src/modules/gps.cpp @@ -1,4 +1,5 @@ #include "modules/gps.hpp" + #include #include @@ -15,15 +16,15 @@ #endif namespace { - using namespace waybar::util; - constexpr const char *DEFAULT_FORMAT = "{mode}"; +using namespace waybar::util; +constexpr const char* DEFAULT_FORMAT = "{mode}"; } // namespace - waybar::modules::Gps::Gps(const std::string& id, const Json::Value& config) -: ALabel(config, "gps", id, "{}", 5) + : ALabel(config, "gps", id, "{}", 5) #ifdef WANT_RFKILL -,rfkill_{RFKILL_TYPE_GPS} + , + rfkill_{RFKILL_TYPE_GPS} #endif { thread_ = [this] { @@ -66,9 +67,9 @@ waybar::modules::Gps::Gps(const std::string& id, const Json::Value& config) } }; - #ifdef WANT_RFKILL +#ifdef WANT_RFKILL rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Gps::update))); - #endif +#endif } const std::string waybar::modules::Gps::getFixModeName() const { @@ -80,9 +81,9 @@ const std::string waybar::modules::Gps::getFixModeName() const { case MODE_3D: return "fix-3d"; default: - #ifdef WANT_RFKILL +#ifdef WANT_RFKILL if (rfkill_.getState()) return "disabled"; - #endif +#endif return "disconnected"; } } @@ -120,18 +121,19 @@ const std::string waybar::modules::Gps::getFixStatusString() const { return "PPS Fix"; default: - #ifdef WANT_RFKILL +#ifdef WANT_RFKILL if (rfkill_.getState()) return "Disabled"; - #endif +#endif return "Unknown"; } } auto waybar::modules::Gps::update() -> void { - sleep(0); // Wait for gps status change + sleep(0); // Wait for gps status change - if ((gps_data_.fix.mode == MODE_NOT_SEEN && hideDisconnected) || (gps_data_.fix.mode == MODE_NO_FIX && hideNoFix)) { + if ((gps_data_.fix.mode == MODE_NOT_SEEN && hideDisconnected) || + (gps_data_.fix.mode == MODE_NO_FIX && hideNoFix)) { event_box_.set_visible(false); return; } @@ -163,7 +165,6 @@ auto waybar::modules::Gps::update() -> void { state_ = state; } - auto format = format_; fmt::dynamic_format_arg_store store; @@ -191,7 +192,7 @@ auto waybar::modules::Gps::update() -> void { auto text = fmt::vformat(format, store); - if (tooltipEnabled()) { + if (tooltipEnabled()) { if (tooltip_format.empty() && config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } @@ -205,13 +206,11 @@ auto waybar::modules::Gps::update() -> void { } } label_.set_markup(text); -// Call parent update -ALabel::update(); + // Call parent update + ALabel::update(); } waybar::modules::Gps::~Gps() { gps_stream(&gps_data_, WATCH_DISABLE, NULL); gps_close(&gps_data_); } - - diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index 1dbdfeba..2989926a 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -66,9 +66,10 @@ auto Language::update() -> void { void Language::onEvent(const std::string& ev) { std::lock_guard lg(mutex_); std::string kbName(begin(ev) + ev.find_last_of('>') + 1, begin(ev) + ev.find_first_of(',')); - + // Last comma before variants parenthesis, eg: - // activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl., with dead keys) + // activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl., + // with dead keys) std::string beforParenthesis(begin(ev), begin(ev) + ev.find_last_of('(')); auto layoutName = ev.substr(beforParenthesis.find_last_of(',') + 1); diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index b5801759..bb03f707 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -791,25 +791,21 @@ void Workspaces::sortSpecialCentered() { size_t center = normalWorkspaces.size() / 2; - m_workspaces.insert(m_workspaces.end(), - std::make_move_iterator(normalWorkspaces.begin()), + m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(normalWorkspaces.begin()), std::make_move_iterator(normalWorkspaces.begin() + center)); - m_workspaces.insert(m_workspaces.end(), - std::make_move_iterator(specialWorkspaces.begin()), + m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(specialWorkspaces.begin()), std::make_move_iterator(specialWorkspaces.end())); m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(normalWorkspaces.begin() + center), std::make_move_iterator(normalWorkspaces.end())); - - m_workspaces.insert(m_workspaces.end(), - std::make_move_iterator(hiddenWorkspaces.begin()), + + m_workspaces.insert(m_workspaces.end(), std::make_move_iterator(hiddenWorkspaces.begin()), std::make_move_iterator(hiddenWorkspaces.end())); } void Workspaces::sortWorkspaces() { - std::ranges::sort( // m_workspaces, [&](std::unique_ptr &a, std::unique_ptr &b) { // Helper comparisons diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 30dd31ea..2b345fc5 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -610,11 +610,11 @@ bool Mpris::handleToggle(GdkEventButton* const& e) { // Command pattern: encapsulate each button's action const ButtonAction actions[] = { - {1, "on-click", [&]() { playerctl_player_play_pause(player, &error); }}, - {2, "on-click-middle", [&]() { playerctl_player_previous(player, &error); }}, - {3, "on-click-right", [&]() { playerctl_player_next(player, &error); }}, - {8, "on-click-backward", [&]() { playerctl_player_previous(player, &error); }}, - {9, "on-click-forward", [&]() { playerctl_player_next(player, &error); }}, + {1, "on-click", [&]() { playerctl_player_play_pause(player, &error); }}, + {2, "on-click-middle", [&]() { playerctl_player_previous(player, &error); }}, + {3, "on-click-right", [&]() { playerctl_player_next(player, &error); }}, + {8, "on-click-backward", [&]() { playerctl_player_previous(player, &error); }}, + {9, "on-click-forward", [&]() { playerctl_player_next(player, &error); }}, }; for (const auto& action : actions) { diff --git a/src/modules/niri/workspaces.cpp b/src/modules/niri/workspaces.cpp index 73f81dc0..7dfc0b35 100644 --- a/src/modules/niri/workspaces.cpp +++ b/src/modules/niri/workspaces.cpp @@ -172,7 +172,6 @@ std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws) const auto &icons = config_["format-icons"]; if (!icons) return value; - if (ws["is_urgent"].asBool() && icons["urgent"]) return icons["urgent"].asString(); if (ws["active_window_id"].isNull() && icons["empty"]) return icons["empty"].asString(); diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 5e497516..589638e8 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -712,7 +712,7 @@ void Task::update() { fmt::format(fmt::runtime(format_tooltip_), fmt::arg("title", title), fmt::arg("name", name), fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::arg("short_state", state_string(true))); - + txt = waybar::util::rewriteString(txt, config_["rewrite"]); if (markup) From 35d5203b4e774acf13b91d2c49e45db0d2776047 Mon Sep 17 00:00:00 2001 From: Alexis Rouillard Date: Sun, 22 Jun 2025 12:24:22 +0200 Subject: [PATCH 287/780] Revert "Bluetooth module: fetch battery percentage from upower if not found from bluez" --- src/modules/bluetooth.cpp | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp index 85486cc0..06475a2e 100644 --- a/src/modules/bluetooth.cpp +++ b/src/modules/bluetooth.cpp @@ -1,7 +1,6 @@ #include "modules/bluetooth.hpp" #include -#include #include #include @@ -393,30 +392,6 @@ auto waybar::modules::Bluetooth::getDeviceBatteryPercentage(GDBusObject* object) return battery_percentage; } - GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Device1")); - if (proxy_device != nullptr) { - auto serial = getStringProperty(proxy_device, "Address"); - std::transform(serial.begin(), serial.end(), serial.begin(), - [](unsigned char c) { return std::tolower(c); }); - - auto* client = up_client_new(); - if (client == nullptr) return std::nullopt; - - auto* devices = up_client_get_devices2(client); - UpDevice* dev; - char* udev_serial; - double percentage; - for (int i = 0; i < devices->len; i++) { - dev = (UpDevice*)g_ptr_array_index(devices, i); - g_object_get(dev, "serial", &udev_serial, nullptr); - if (serial == udev_serial) { - g_object_get(dev, "percentage", &percentage, nullptr); - g_ptr_array_unref(devices); - g_object_unref(client); - return percentage; - } - } - } return std::nullopt; } From 2b9601b9a44092b740448ee691a923573f1597ed Mon Sep 17 00:00:00 2001 From: yuannan <1994977-yuannan@users.noreply.gitlab.com> Date: Sun, 22 Jun 2025 13:15:18 +0100 Subject: [PATCH 288/780] added IPC fix and Nix GPS fix --- include/modules/hyprland/backend.hpp | 1 + nix/default.nix | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp index cfd0b258..d9f16526 100644 --- a/include/modules/hyprland/backend.hpp +++ b/include/modules/hyprland/backend.hpp @@ -46,4 +46,5 @@ class IPC { }; inline bool modulesReady = false; +inline std::unique_ptr gIPC; }; // namespace waybar::modules::hyprland diff --git a/nix/default.nix b/nix/default.nix index 4cfd75c0..2c97c20d 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -30,8 +30,12 @@ in # nixpkgs checks version, no need when building locally nativeInstallCheckInputs = [ ]; - buildInputs = (builtins.filter (p: p.pname != "wireplumber") oldAttrs.buildInputs) ++ [ + buildInputs = (builtins.filter (p: + p.pname != "wireplumber" && + p.pname != "gps" + ) oldAttrs.buildInputs) ++ [ pkgs.wireplumber + pkgs.gpsd ]; postUnpack = '' From 08da7b5282cfbcfa705cd069726c728a814700aa Mon Sep 17 00:00:00 2001 From: markx86 <25851052+markx86@users.noreply.github.com> Date: Fri, 20 Jun 2025 12:31:56 +0200 Subject: [PATCH 289/780] feat: Add support for battery related events --- include/modules/battery.hpp | 2 ++ src/ALabel.cpp | 2 +- src/modules/battery.cpp | 33 ++++++++++++++++++++++++++++++--- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/include/modules/battery.hpp b/include/modules/battery.hpp index 8e1a2ad2..c593e59b 100644 --- a/include/modules/battery.hpp +++ b/include/modules/battery.hpp @@ -35,6 +35,7 @@ class Battery : public ALabel { std::tuple getInfos(); const std::string formatTimeRemaining(float hoursRemaining); void setBarClass(std::string&); + void processEvents(std::string& state, std::string& status, uint8_t capacity); int global_watch; std::map batteries_; @@ -43,6 +44,7 @@ class Battery : public ALabel { int global_watch_fd_; std::mutex battery_list_mutex_; std::string old_status_; + std::string last_event_; bool warnFirstTime_{true}; const Bar& bar_; diff --git a/src/ALabel.cpp b/src/ALabel.cpp index c218e402..6df80e46 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -200,7 +200,7 @@ std::string ALabel::getState(uint8_t value, bool lesser) { } } // Sort states - std::sort(states.begin(), states.end(), [&lesser](auto& a, auto& b) { + std::ranges::sort(states.begin(), states.end(), [&lesser](auto& a, auto& b) { return lesser ? a.second < b.second : a.second > b.second; }); std::string valid_state; diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 44481448..5738d883 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -1,14 +1,15 @@ #include "modules/battery.hpp" +#include "util/command.hpp" #include +#include #if defined(__FreeBSD__) #include #endif #include -#include waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const Json::Value& config) - : ALabel(config, "battery", id, "{capacity}%", 60), bar_(bar) { + : ALabel(config, "battery", id, "{capacity}%", 60), last_event_(""), bar_(bar) { #if defined(__linux__) battery_watch_fd_ = inotify_init1(IN_CLOEXEC); if (battery_watch_fd_ == -1) { @@ -26,6 +27,7 @@ waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const J throw std::runtime_error("Could not watch for battery plug/unplug"); } #endif + spdlog::debug("battery: worker interval is {}", interval_.count()); worker(); } @@ -677,10 +679,11 @@ auto waybar::modules::Battery::update() -> void { } auto status_pretty = status; // Transform to lowercase and replace space with dash - std::transform(status.begin(), status.end(), status.begin(), + std::ranges::transform(status.begin(), status.end(), status.begin(), [](char ch) { return ch == ' ' ? '-' : std::tolower(ch); }); auto format = format_; auto state = getState(capacity, true); + processEvents(state, status, capacity); setBarClass(state); auto time_remaining_formatted = formatTimeRemaining(time_remaining); if (tooltipEnabled()) { @@ -767,3 +770,27 @@ void waybar::modules::Battery::setBarClass(std::string& state) { bar_.window.get_style_context()->add_class(new_class); } } + +void waybar::modules::Battery::processEvents(std::string& state, std::string& status, uint8_t capacity) { + // There are no events specified, skip + auto events = config_["events"]; + if (!events.isObject() || events.empty()) { + return; + } + std::string event_name = fmt::format( + "on-{}-{}", + status == "discharging" ? status : "charging", + state.empty() ? std::to_string(capacity) : state + ); + if (last_event_ != event_name) { + spdlog::debug("battery: triggering event {}", event_name); + if (events[event_name].isString()) { + std::string exec = events[event_name].asString(); + // Execute the command if it is not empty + if (!exec.empty()) { + util::command::exec(exec, ""); + } + } + last_event_ = event_name; + } +} From eae22f3273b5e83f70c24399bb5c3745d346b7ac Mon Sep 17 00:00:00 2001 From: markx86 <25851052+markx86@users.noreply.github.com> Date: Sun, 22 Jun 2025 12:45:28 +0200 Subject: [PATCH 290/780] battery: Document `events` in `waybar-battery` manpage --- man/waybar-battery.5.scd | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/man/waybar-battery.5.scd b/man/waybar-battery.5.scd index a3c21a20..2819aa68 100644 --- a/man/waybar-battery.5.scd +++ b/man/waybar-battery.5.scd @@ -116,7 +116,7 @@ The *battery* module displays the current capacity and state (eg. charging) of y *menu-file*: ++ typeof: string ++ Location of the menu descriptor file. There need to be an element of type - GtkMenu with id *menu* + GtkMenu with id *menu*. *menu-actions*: ++ typeof: array ++ @@ -127,6 +127,10 @@ The *battery* module displays the current capacity and state (eg. charging) of y default: false ++ Enables this module to consume all left over space dynamically. +*events*: ++ + typeof: object ++ + Specifies commands to be executed on specific battery states. See *EVENTS* section below. + # FORMAT REPLACEMENTS *{capacity}*: Capacity in percentage @@ -166,6 +170,19 @@ The *battery* module allows one to define custom formats based on up to two fact - The state can be addressed as a CSS class in the *style.css*. The name of the CSS class is the ** of the state. Each class gets activated when the current capacity is equal to or below the configured **. - Also each state can have its own *format*. Those can be configured via *format-*. Or if you want to differentiate a bit more even as *format--*. For more information see *custom-formats*. +# EVENTS + +Every entry in the *events* object consists of a ** (typeof: *string*) and a ** (typeof: *string*). ++ +** can be in one of the following formats: + +- *on--* +- *on--* + +Where: + +- ** is either *charging* or *discharging*, +- ** is the name of one of the states specified in the *states* object, +- ** is a battery level value (between *0-100*). # EXAMPLES @@ -178,6 +195,11 @@ The *battery* module allows one to define custom formats based on up to two fact "warning": 30, "critical": 15 }, + "events": { + "on-discharging-warning": "notify-send -u normal 'Low Battery'", + "on-discharging-critical": "notify-send -u critical 'Very Low Battery'", + "on-charging-100": "notify-send -u normal 'Battery Full!'" + }, "format": "{capacity}% {icon}", "format-icons": ["", "", "", "", ""], "max-length": 25 From 53d8d3cd2a365a4563b6cbdcef18582fd2fdb1f2 Mon Sep 17 00:00:00 2001 From: markx86 <25851052+markx86@users.noreply.github.com> Date: Sun, 22 Jun 2025 18:55:07 +0200 Subject: [PATCH 291/780] fix: Code formatting for clang-format/lint CI --- src/modules/battery.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 5738d883..3bc8c812 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -1,8 +1,9 @@ #include "modules/battery.hpp" -#include "util/command.hpp" #include #include + +#include "util/command.hpp" #if defined(__FreeBSD__) #include #endif @@ -680,7 +681,7 @@ auto waybar::modules::Battery::update() -> void { auto status_pretty = status; // Transform to lowercase and replace space with dash std::ranges::transform(status.begin(), status.end(), status.begin(), - [](char ch) { return ch == ' ' ? '-' : std::tolower(ch); }); + [](char ch) { return ch == ' ' ? '-' : std::tolower(ch); }); auto format = format_; auto state = getState(capacity, true); processEvents(state, status, capacity); @@ -771,17 +772,15 @@ void waybar::modules::Battery::setBarClass(std::string& state) { } } -void waybar::modules::Battery::processEvents(std::string& state, std::string& status, uint8_t capacity) { +void waybar::modules::Battery::processEvents(std::string& state, std::string& status, + uint8_t capacity) { // There are no events specified, skip auto events = config_["events"]; if (!events.isObject() || events.empty()) { return; } - std::string event_name = fmt::format( - "on-{}-{}", - status == "discharging" ? status : "charging", - state.empty() ? std::to_string(capacity) : state - ); + std::string event_name = fmt::format("on-{}-{}", status == "discharging" ? status : "charging", + state.empty() ? std::to_string(capacity) : state); if (last_event_ != event_name) { spdlog::debug("battery: triggering event {}", event_name); if (events[event_name].isString()) { From 0fcda9afa519eb84deda68c051f7938a6c6bdbce Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 23 Jun 2025 09:10:48 +0200 Subject: [PATCH 292/780] chore: 0.13.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 9481cc93..8832a55e 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'waybar', 'cpp', 'c', - version: '0.12.0', + version: '0.13.0', license: 'MIT', meson_version: '>= 0.59.0', default_options : [ From 460b19ba1b83b873795e2e65ce60efadc7a3a906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Maciulevi=C4=8Dius?= Date: Thu, 26 Jun 2025 20:34:18 +0300 Subject: [PATCH 293/780] Fix default icon in tray module --- src/util/gtk_icon.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/util/gtk_icon.cpp b/src/util/gtk_icon.cpp index 4b4d3d69..73f77284 100644 --- a/src/util/gtk_icon.cpp +++ b/src/util/gtk_icon.cpp @@ -25,6 +25,10 @@ Glib::RefPtr DefaultGtkIconThemeWrapper::load_icon( auto icon_info = default_theme->lookup_icon(name, tmp_size, flags); + if (icon_info == nullptr) { + return default_theme->load_icon(name, tmp_size, flags); + } + if (style.get() == nullptr) { return icon_info.load_icon(); } From aaea81421776eb3b5a97317948a42bd6e0c09290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Maciulevi=C4=8Dius?= Date: Sun, 9 Feb 2025 13:05:36 +0200 Subject: [PATCH 294/780] Idempotent bar show/hide via configurable user kill signals --- include/bar.hpp | 8 ++++++ include/util/kill_signal.hpp | 26 ++++++++++++++++++ src/bar.cpp | 31 ++++++++++++++++++++++ src/main.cpp | 51 ++++++++++++++++++++++++++++++------ src/util/enum.cpp | 1 + 5 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 include/util/kill_signal.hpp diff --git a/include/bar.hpp b/include/bar.hpp index 9b407abf..c2dc0ae1 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -14,6 +14,7 @@ #include "AModule.hpp" #include "group.hpp" +#include "util/kill_signal.hpp" #include "xdg-output-unstable-v1-client-protocol.h" namespace waybar { @@ -68,7 +69,11 @@ class Bar : public sigc::trackable { void setMode(const std::string &mode); void setVisible(bool value); void toggle(); + void show(); + void hide(); void handleSignal(int); + util::KillSignalAction getOnSigusr1Action(); + util::KillSignalAction getOnSigusr2Action(); struct waybar_output *output; Json::Value config; @@ -118,6 +123,9 @@ class Bar : public sigc::trackable { std::unique_ptr _ipc_client; #endif std::vector> modules_all_; + + waybar::util::KillSignalAction onSigusr1; + waybar::util::KillSignalAction onSigusr2; }; } // namespace waybar diff --git a/include/util/kill_signal.hpp b/include/util/kill_signal.hpp new file mode 100644 index 00000000..26d156e9 --- /dev/null +++ b/include/util/kill_signal.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include + +namespace waybar::util { + +enum class KillSignalAction : std::uint8_t { + TOGGLE, + RELOAD, + SHOW, + HIDE, + NOOP, +}; +const std::map userKillSignalActions = { + {"TOGGLE", KillSignalAction::TOGGLE}, + {"RELOAD", KillSignalAction::RELOAD}, + {"SHOW", KillSignalAction::SHOW}, + {"HIDE", KillSignalAction::HIDE}, + {"NOOP", KillSignalAction::NOOP}}; + +const KillSignalAction SIGNALACTION_DEFAULT_SIGUSR1 = KillSignalAction::TOGGLE; +const KillSignalAction SIGNALACTION_DEFAULT_SIGUSR2 = KillSignalAction::RELOAD; + +}; // namespace waybar::util diff --git a/src/bar.cpp b/src/bar.cpp index 3c3ab690..5ca2ec04 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -8,6 +8,8 @@ #include "client.hpp" #include "factory.hpp" #include "group.hpp" +#include "util/enum.hpp" +#include "util/kill_signal.hpp" #ifdef HAVE_SWAY #include "modules/sway/bar.hpp" @@ -277,6 +279,30 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) } #endif + waybar::util::EnumParser m_signalActionEnumParser; + const auto& configSigusr1 = config["on_sigusr1"]; + if (configSigusr1.isString()) { + auto strSigusr1 = configSigusr1.asString(); + try { + onSigusr1 = + m_signalActionEnumParser.parseStringToEnum(strSigusr1, util::userKillSignalActions); + } catch (const std::invalid_argument& e) { + onSigusr1 = util::SIGNALACTION_DEFAULT_SIGUSR1; + spdlog::warn("Invalid string representation for on_sigusr1. Falling back to default mode."); + } + } + const auto& configSigusr2 = config["on_sigusr2"]; + if (configSigusr2.isString()) { + auto strSigusr2 = configSigusr2.asString(); + try { + onSigusr2 = + m_signalActionEnumParser.parseStringToEnum(strSigusr2, util::userKillSignalActions); + } catch (const std::invalid_argument& e) { + onSigusr2 = util::SIGNALACTION_DEFAULT_SIGUSR2; + spdlog::warn("Invalid string representation for on_sigusr2. Falling back to default mode."); + } + } + setupWidgets(); window.show_all(); @@ -422,6 +448,8 @@ void waybar::Bar::setVisible(bool value) { } void waybar::Bar::toggle() { setVisible(!visible); } +void waybar::Bar::show() { setVisible(true); } +void waybar::Bar::hide() { setVisible(false); } // Converting string to button code rn as to avoid doing it later void waybar::Bar::setupAltFormatKeyForModule(const std::string& module_name) { @@ -479,6 +507,9 @@ void waybar::Bar::handleSignal(int signal) { } } +waybar::util::KillSignalAction waybar::Bar::getOnSigusr1Action() { return this->onSigusr1; } +waybar::util::KillSignalAction waybar::Bar::getOnSigusr2Action() { return this->onSigusr2; } + void waybar::Bar::getModules(const Factory& factory, const std::string& pos, waybar::Group* group = nullptr) { auto module_list = group != nullptr ? config[pos]["modules"] : config[pos]; diff --git a/src/main.cpp b/src/main.cpp index 6e7650a9..7ac948b4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,8 +7,10 @@ #include #include +#include "bar.hpp" #include "client.hpp" #include "util/SafeSignal.hpp" +#include "util/backend_common.hpp" std::mutex reap_mtx; std::list reap; @@ -71,6 +73,45 @@ static void catchSignals(waybar::SafeSignal& signal_handler) { } } +waybar::util::KillSignalAction getActionForBar(waybar::Bar* bar, int signal) { + switch (signal) { + case SIGUSR1: + return bar->getOnSigusr1Action(); + case SIGUSR2: + return bar->getOnSigusr2Action(); + default: + return waybar::util::KillSignalAction::NOOP; + } +} + +void handleUserSignal(int signal, bool& reload) { + int i = 0; + for (auto& bar : waybar::Client::inst()->bars) { + switch (getActionForBar(bar.get(), signal)) { + case waybar::util::KillSignalAction::HIDE: + spdlog::debug("Visibility 'hide' for bar ", i); + bar->hide(); + break; + case waybar::util::KillSignalAction::SHOW: + spdlog::debug("Visibility 'show' for bar ", i); + bar->show(); + break; + case waybar::util::KillSignalAction::TOGGLE: + spdlog::debug("Visibility 'toggle' for bar ", i); + bar->toggle(); + break; + case waybar::util::KillSignalAction::RELOAD: + spdlog::info("Reloading..."); + reload = true; + waybar::Client::inst()->reset(); + return; + case waybar::util::KillSignalAction::NOOP: + break; + } + i++; + } +} + // Must be called on the main thread. // // If this signal should restart or close the bar, this function will write @@ -80,21 +121,15 @@ static void handleSignalMainThread(int signum, bool& reload) { for (auto& bar : waybar::Client::inst()->bars) { bar->handleSignal(signum); } - return; } switch (signum) { case SIGUSR1: - spdlog::debug("Visibility toggled"); - for (auto& bar : waybar::Client::inst()->bars) { - bar->toggle(); - } + handleUserSignal(SIGUSR1, reload); break; case SIGUSR2: - spdlog::info("Reloading..."); - reload = true; - waybar::Client::inst()->reset(); + handleUserSignal(SIGUSR2, reload); break; case SIGINT: spdlog::info("Quitting."); diff --git a/src/util/enum.cpp b/src/util/enum.cpp index dc3eae0c..1e28d66e 100644 --- a/src/util/enum.cpp +++ b/src/util/enum.cpp @@ -41,5 +41,6 @@ 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; } // namespace waybar::util From 3c7a9bf43214c1e544fe3bd851a4f91a7ac579b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Maciulevi=C4=8Dius?= Date: Sat, 22 Feb 2025 09:28:32 +0200 Subject: [PATCH 295/780] Documentation for sigusr1 and sigusr2 config --- man/waybar.5.scd.in | 39 +++++++++++++++++++++++++++++++++++++-- src/bar.cpp | 6 ++++-- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 6ca0aa99..002cb50b 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -151,6 +151,20 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles( default: *false* ++ Option to enable reloading the css style if a modification is detected on the style sheet file or any imported css files. +*on_sigusr1* ++ + typeof: string ++ + default: *toggle* ++ + Action that is performed when receiving SIGUSR1 kill signal. ++ + Possible values: *show*, *hide*, *toggle*, *reload*, *noop*. ++ + Default value: *toggle*. + +*on_sigusr2* ++ + typeof: string ++ + default: *reload* ++ + Action that is performed when receiving SIGUSR2 kill signal. ++ + Possible values: *show*, *hide*, *toggle*, *reload*, *noop*. ++ + Default value: *reload*. + # MODULE FORMAT You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat). @@ -206,14 +220,35 @@ A minimal *config* file could look like this: Waybar accepts the following signals: *SIGUSR1* - Toggles the bar visibility (hides if shown, shows if hidden) + By default toggles the bar visibility (hides if shown, shows if hidden) *SIGUSR2* - Reloads (resets) the bar + By default reloads (resets) the bar *SIGINT* Quits the bar For example, to toggle the bar programmatically, you can invoke `killall -SIGUSR1 waybar`. +## User signal configuration + +Config parameters `on_sigusr1` and `on_sigusr2` change what happens when bars receive +`SIGUSR1` and `SIGUSR2` signals. + +This means that commands `killall -SIGUSR1 waybar` and `killall -SIGUSR2 waybar` +can perform user-configured action. + +It also means that if an external script has the PID of the bar then it can +perform more complex `show`/`hide`/`reload` logic for each instance of Waybar. +One can find the PID e.g. by doing `pgrep -a waybar` which could then match +by config name or other parameters. + +### Kill parameter meanings + +*show* Switches state to visible (per bar). +*hide* Switches state to hidden (per bar). +*toggle* Switches state between visible and hidden (per bar). +*reload* Reloads whole waybar. +*noop* Does nothing when the kill signal is received. + # MULTI OUTPUT CONFIGURATION ## Limit a configuration to some outputs diff --git a/src/bar.cpp b/src/bar.cpp index 5ca2ec04..6e6e76df 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -288,7 +288,8 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) m_signalActionEnumParser.parseStringToEnum(strSigusr1, util::userKillSignalActions); } catch (const std::invalid_argument& e) { onSigusr1 = util::SIGNALACTION_DEFAULT_SIGUSR1; - spdlog::warn("Invalid string representation for on_sigusr1. Falling back to default mode."); + spdlog::warn( + "Invalid string representation for on_sigusr1. Falling back to default mode (toggle)."); } } const auto& configSigusr2 = config["on_sigusr2"]; @@ -299,7 +300,8 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) m_signalActionEnumParser.parseStringToEnum(strSigusr2, util::userKillSignalActions); } catch (const std::invalid_argument& e) { onSigusr2 = util::SIGNALACTION_DEFAULT_SIGUSR2; - spdlog::warn("Invalid string representation for on_sigusr2. Falling back to default mode."); + spdlog::warn( + "Invalid string representation for on_sigusr2. Falling back to default mode (reload)."); } } From 125b59e23bc5b8520ad903829afaa13a50d01ffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Maciulevi=C4=8Dius?= Date: Sun, 2 Mar 2025 19:08:37 +0200 Subject: [PATCH 296/780] Add more details on how 'reload' works in signals --- man/waybar.5.scd.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 002cb50b..1fca6321 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -246,7 +246,8 @@ by config name or other parameters. *show* Switches state to visible (per bar). *hide* Switches state to hidden (per bar). *toggle* Switches state between visible and hidden (per bar). -*reload* Reloads whole waybar. +*reload* Reloads all waybars of current waybar process (basically equivalent to +restarting with updated config which sets initial visibility values). *noop* Does nothing when the kill signal is received. # MULTI OUTPUT CONFIGURATION From 7463be9b6a3777220377632cafb0447adb0f35cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Maciulevi=C4=8Dius?= Date: Fri, 7 Mar 2025 07:45:11 +0200 Subject: [PATCH 297/780] Fix initial null configuration; Fix doc compilation errors --- include/bar.hpp | 4 ++-- man/waybar.5.scd.in | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/bar.hpp b/include/bar.hpp index c2dc0ae1..9953b1f5 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -124,8 +124,8 @@ class Bar : public sigc::trackable { #endif std::vector> modules_all_; - waybar::util::KillSignalAction onSigusr1; - waybar::util::KillSignalAction onSigusr2; + waybar::util::KillSignalAction onSigusr1 = util::SIGNALACTION_DEFAULT_SIGUSR1; + waybar::util::KillSignalAction onSigusr2 = util::SIGNALACTION_DEFAULT_SIGUSR2; }; } // namespace waybar diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 1fca6321..a2212f04 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -230,8 +230,8 @@ For example, to toggle the bar programmatically, you can invoke `killall -SIGUSR ## User signal configuration -Config parameters `on_sigusr1` and `on_sigusr2` change what happens when bars receive -`SIGUSR1` and `SIGUSR2` signals. +Config parameters *on_sigusr1* and *on_sigusr2* change what happens when bars receive +*SIGUSR1* and *SIGUSR2* signals. This means that commands `killall -SIGUSR1 waybar` and `killall -SIGUSR2 waybar` can perform user-configured action. @@ -241,7 +241,7 @@ perform more complex `show`/`hide`/`reload` logic for each instance of Waybar. One can find the PID e.g. by doing `pgrep -a waybar` which could then match by config name or other parameters. -### Kill parameter meanings +## Kill parameter meanings *show* Switches state to visible (per bar). *hide* Switches state to hidden (per bar). From b7ff47fb053fb04c4e3bba4b235084dfded3d171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Maciulevi=C4=8Dius?= Date: Thu, 26 Jun 2025 18:41:23 +0300 Subject: [PATCH 298/780] User kill signal config keys: Use underscore instead of dash --- man/waybar.5.scd.in | 6 +++--- src/bar.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index a2212f04..566f7dc5 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -151,14 +151,14 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles( default: *false* ++ Option to enable reloading the css style if a modification is detected on the style sheet file or any imported css files. -*on_sigusr1* ++ +*on-sigusr1* ++ typeof: string ++ default: *toggle* ++ Action that is performed when receiving SIGUSR1 kill signal. ++ Possible values: *show*, *hide*, *toggle*, *reload*, *noop*. ++ Default value: *toggle*. -*on_sigusr2* ++ +*on-sigusr2* ++ typeof: string ++ default: *reload* ++ Action that is performed when receiving SIGUSR2 kill signal. ++ @@ -230,7 +230,7 @@ For example, to toggle the bar programmatically, you can invoke `killall -SIGUSR ## User signal configuration -Config parameters *on_sigusr1* and *on_sigusr2* change what happens when bars receive +Config parameters *on-sigusr1* and *on-sigusr2* change what happens when bars receive *SIGUSR1* and *SIGUSR2* signals. This means that commands `killall -SIGUSR1 waybar` and `killall -SIGUSR2 waybar` diff --git a/src/bar.cpp b/src/bar.cpp index 6e6e76df..bb91aa01 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -280,7 +280,7 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) #endif waybar::util::EnumParser m_signalActionEnumParser; - const auto& configSigusr1 = config["on_sigusr1"]; + const auto& configSigusr1 = config["on-sigusr1"]; if (configSigusr1.isString()) { auto strSigusr1 = configSigusr1.asString(); try { @@ -289,10 +289,10 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) } catch (const std::invalid_argument& e) { onSigusr1 = util::SIGNALACTION_DEFAULT_SIGUSR1; spdlog::warn( - "Invalid string representation for on_sigusr1. Falling back to default mode (toggle)."); + "Invalid string representation for on-sigusr1. Falling back to default mode (toggle)."); } } - const auto& configSigusr2 = config["on_sigusr2"]; + const auto& configSigusr2 = config["on-sigusr2"]; if (configSigusr2.isString()) { auto strSigusr2 = configSigusr2.asString(); try { @@ -301,7 +301,7 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) } catch (const std::invalid_argument& e) { onSigusr2 = util::SIGNALACTION_DEFAULT_SIGUSR2; spdlog::warn( - "Invalid string representation for on_sigusr2. Falling back to default mode (reload)."); + "Invalid string representation for on-sigusr2. Falling back to default mode (reload)."); } } From c8dc3e702402a936654d600b30727522eac8cb2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Maciulevi=C4=8Dius?= Date: Thu, 26 Jun 2025 21:20:18 +0300 Subject: [PATCH 299/780] Remove unused import --- src/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 7ac948b4..021f8e1a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,7 +10,6 @@ #include "bar.hpp" #include "client.hpp" #include "util/SafeSignal.hpp" -#include "util/backend_common.hpp" std::mutex reap_mtx; std::list reap; From 7505e2c3f3cc92bdbc1ef3a2650a2c32170f8b9e Mon Sep 17 00:00:00 2001 From: mexanoz Date: Fri, 27 Jun 2025 20:54:38 +0500 Subject: [PATCH 300/780] fix hyprland/language layout parsing --- src/modules/hyprland/language.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index 2989926a..3f141bbd 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -70,8 +70,14 @@ void Language::onEvent(const std::string& ev) { // Last comma before variants parenthesis, eg: // activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl., // with dead keys) - std::string beforParenthesis(begin(ev), begin(ev) + ev.find_last_of('(')); - auto layoutName = ev.substr(beforParenthesis.find_last_of(',') + 1); + std::string beforeParenthesis; + auto parenthesisPos = ev.find_last_of('('); + if (parenthesisPos == std::string::npos) { + beforeParenthesis = ev; + } else { + beforeParenthesis = std::string(begin(ev), begin(ev) + parenthesisPos); + } + auto layoutName = ev.substr(beforeParenthesis.find_last_of(',') + 1); if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString()) return; // ignore From 649a98a67952cb6f63547a504bff83d40daae220 Mon Sep 17 00:00:00 2001 From: Emiliano Deustua Date: Sat, 28 Jun 2025 16:33:14 -0500 Subject: [PATCH 301/780] 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 255c0ebe288f4ca3ab9812ed090e2d4c125c8cee Mon Sep 17 00:00:00 2001 From: Jens Peters Date: Fri, 11 Apr 2025 08:00:35 +0200 Subject: [PATCH 302/780] wlr/workspaces: port to ext-workspace-v1 Rework implementation to match new protocol behavior. --- include/modules/wlr/workspace_manager.hpp | 214 ++--- .../modules/wlr/workspace_manager_binding.hpp | 10 +- man/waybar-wlr-workspaces.5.scd | 24 +- meson.build | 22 +- protocol/ext-workspace-unstable-v1.xml | 306 ------- protocol/meson.build | 7 +- src/modules/wlr/workspace_manager.cpp | 859 ++++++++---------- src/modules/wlr/workspace_manager_binding.cpp | 107 ++- 8 files changed, 585 insertions(+), 964 deletions(-) delete mode 100644 protocol/ext-workspace-unstable-v1.xml diff --git a/include/modules/wlr/workspace_manager.hpp b/include/modules/wlr/workspace_manager.hpp index f7cc759e..7522134e 100644 --- a/include/modules/wlr/workspace_manager.hpp +++ b/include/modules/wlr/workspace_manager.hpp @@ -5,168 +5,140 @@ #include #include -#include #include #include #include #include "AModule.hpp" #include "bar.hpp" -#include "ext-workspace-unstable-v1-client-protocol.h" +#include "ext-workspace-v1-client-protocol.h" namespace waybar::modules::wlr { -class WorkspaceManager; class WorkspaceGroup; +class Workspace; -class Workspace { +class WorkspaceManager final : public AModule { public: - Workspace(const waybar::Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group, - zext_workspace_handle_v1 *workspace, uint32_t id, std::string name); - ~Workspace(); - auto update() -> void; + WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config); + ~WorkspaceManager() override; + void register_manager(wl_registry *registry, uint32_t name, uint32_t version); + void remove_workspace_group(uint32_t id); + void remove_workspace(uint32_t id); + void set_needs_sorting() { needs_sorting_ = true; } - auto id() const -> uint32_t { return id_; } - auto is_active() const -> bool { return state_ & static_cast(State::ACTIVE); } - auto is_urgent() const -> bool { return state_ & static_cast(State::URGENT); } - auto is_hidden() const -> bool { return state_ & static_cast(State::HIDDEN); } - auto is_empty() const -> bool { return state_ & static_cast(State::EMPTY); } - auto is_persistent() const -> bool { return persistent_; } - // wlr stuff - auto handle_name(const std::string &name) -> void; - auto handle_coordinates(const std::vector &coordinates) -> void; - auto handle_state(const std::vector &state) -> void; - auto handle_remove() -> void; - auto make_persistent() -> void; - auto handle_duplicate() -> void; + // wlr events + void handle_workspace_group(ext_workspace_group_handle_v1 *handle); + void handle_workspace(ext_workspace_handle_v1 *handle); + void handle_done(); + void handle_finished(); - auto handle_done() -> void; - auto handle_clicked(GdkEventButton *bt) -> bool; - auto show() -> void; - auto hide() -> void; - auto get_button_ref() -> Gtk::Button & { return button_; } - auto get_name() -> std::string & { return name_; } - auto get_coords() -> std::vector & { return coordinates_; } - - enum class State { - ACTIVE = (1 << 0), - URGENT = (1 << 1), - HIDDEN = (1 << 2), - EMPTY = (1 << 3), - }; + // wlr requests + void commit() const; private: - auto get_icon() -> std::string; + void update() override; + bool has_button(const Gtk::Button *button); + void sort_workspaces(); + void clear_buttons(); + void update_buttons(); - const Bar &bar_; - const Json::Value &config_; - WorkspaceGroup &workspace_group_; + static uint32_t group_global_id; + static uint32_t workspace_global_id; + uint32_t workspace_name = 0; - // wlr stuff - zext_workspace_handle_v1 *workspace_handle_; - uint32_t state_ = 0; + bool sort_by_id_ = false; + bool sort_by_name_ = true; + bool sort_by_coordinates_ = false; + bool active_only_ = false; + bool all_outputs_ = false; - uint32_t id_; - std::string name_; - std::vector coordinates_; - static std::map icons_map_; - std::string format_; - bool with_icon_ = false; - bool persistent_ = false; + const waybar::Bar &bar_; + Gtk::Box box_; - Gtk::Button button_; - Gtk::Box content_; - Gtk::Label label_; + ext_workspace_manager_v1 *ext_manager_ = nullptr; + std::vector> groups_; + std::vector> workspaces_; + + bool needs_sorting_ = false; }; class WorkspaceGroup { public: - WorkspaceGroup(const waybar::Bar &bar, Gtk::Box &box, const Json::Value &config, - WorkspaceManager &manager, zext_workspace_group_handle_v1 *workspace_group_handle, - uint32_t id); + WorkspaceGroup(WorkspaceManager &manager, ext_workspace_group_handle_v1 *handle, uint32_t id); ~WorkspaceGroup(); - auto update() -> void; - auto id() const -> uint32_t { return id_; } - auto is_visible() const -> bool; - auto remove_workspace(uint32_t id_) -> void; - auto active_only() const -> bool; - auto creation_delayed() const -> bool; - auto workspaces() -> std::vector> & { return workspaces_; } - auto persistent_workspaces() -> std::vector & { return persistent_workspaces_; } + u_int32_t id() const { return id_; } + bool has_output(const wl_output *output); + bool has_workspace(const ext_workspace_handle_v1 *workspace); - auto sort_workspaces() -> void; - auto set_need_to_sort() -> void { need_to_sort = true; } - auto add_button(Gtk::Button &button) -> void; - auto remove_button(Gtk::Button &button) -> void; - auto fill_persistent_workspaces() -> void; - auto create_persistent_workspaces() -> void; - - // wlr stuff - auto handle_workspace_create(zext_workspace_handle_v1 *workspace_handle) -> void; - auto handle_remove() -> void; - auto handle_output_enter(wl_output *output) -> void; - auto handle_output_leave() -> void; - auto handle_done() -> void; - auto commit() -> void; + // wlr events + void handle_capabilities(uint32_t capabilities); + void handle_output_enter(wl_output *output); + void handle_output_leave(wl_output *output); + void handle_workspace_enter(ext_workspace_handle_v1 *handle); + void handle_workspace_leave(ext_workspace_handle_v1 *handle); + void handle_removed(); private: - static uint32_t workspace_global_id; - const waybar::Bar &bar_; - Gtk::Box &box_; - const Json::Value &config_; - WorkspaceManager &workspace_manager_; - - // wlr stuff - zext_workspace_group_handle_v1 *workspace_group_handle_; - wl_output *output_ = nullptr; - + WorkspaceManager &workspaces_manager_; + ext_workspace_group_handle_v1 *ext_handle_; uint32_t id_; - std::vector> workspaces_; - bool need_to_sort = false; - std::vector persistent_workspaces_; - bool persistent_created_ = false; + std::vector outputs_; + std::vector workspaces_; }; -class WorkspaceManager : public AModule { +class Workspace { public: - WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config); - ~WorkspaceManager() override; - auto update() -> void override; + Workspace(const Json::Value &config, WorkspaceManager &manager, ext_workspace_handle_v1 *handle, + uint32_t id, const std::string &name); + ~Workspace(); - auto all_outputs() const -> bool { return all_outputs_; } - auto active_only() const -> bool { return active_only_; } - auto workspace_comparator() const - -> std::function &, std::unique_ptr &)>; - auto creation_delayed() const -> bool { return creation_delayed_; } + ext_workspace_handle_v1 *handle() const { return ext_handle_; } + u_int32_t id() const { return id_; } + std::string &workspace_id() { return workspace_id_; } + std::string &name() { return name_; } + std::vector &coordinates() { return coordinates_; } + Gtk::Button &button() { return button_; } + bool is_active() const; + bool is_urgent() const; + bool is_hidden() const; + void update(); - auto sort_workspaces() -> void; - auto remove_workspace_group(uint32_t id_) -> void; + // wlr events + void handle_id(const std::string &id); + void handle_name(const std::string &name); + void handle_coordinates(const std::vector &coordinates); + void handle_state(uint32_t state); + void handle_capabilities(uint32_t capabilities); + void handle_removed(); - // wlr stuff - auto register_manager(wl_registry *registry, uint32_t name, uint32_t version) -> void; - auto handle_workspace_group_create(zext_workspace_group_handle_v1 *workspace_group_handle) - -> void; - auto handle_done() -> void; - auto handle_finished() -> void; - auto commit() -> void; + // gdk events + bool handle_clicked(const GdkEventButton *button) const; private: - const waybar::Bar &bar_; - Gtk::Box box_; - std::vector> groups_; + std::string icon(); - // wlr stuff - zext_workspace_manager_v1 *workspace_manager_ = nullptr; + WorkspaceManager &workspace_manager_; + ext_workspace_handle_v1 *ext_handle_ = nullptr; + uint32_t id_; + uint32_t state_ = 0; + std::string workspace_id_; + std::string name_; + std::vector coordinates_; - static uint32_t group_global_id; + std::string format_; + bool with_icon_ = false; + static std::map icon_map_; + std::string on_click_action_; + std::string on_click_middle_action_; + std::string on_click_right_action_; - bool sort_by_name_ = true; - bool sort_by_coordinates_ = true; - bool sort_by_number_ = false; - bool all_outputs_ = false; - bool active_only_ = false; - bool creation_delayed_ = false; + Gtk::Button button_; + Gtk::Box content_; + Gtk::Label label_; + + bool needs_updating_ = false; }; } // namespace waybar::modules::wlr diff --git a/include/modules/wlr/workspace_manager_binding.hpp b/include/modules/wlr/workspace_manager_binding.hpp index cc242c94..a64d9b02 100644 --- a/include/modules/wlr/workspace_manager_binding.hpp +++ b/include/modules/wlr/workspace_manager_binding.hpp @@ -1,10 +1,10 @@ -#include "ext-workspace-unstable-v1-client-protocol.h" +#include "ext-workspace-v1-client-protocol.h" namespace waybar::modules::wlr { void add_registry_listener(void *data); -void add_workspace_listener(zext_workspace_handle_v1 *workspace_handle, void *data); -void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle, +void add_workspace_listener(ext_workspace_handle_v1 *workspace_handle, void *data); +void add_workspace_group_listener(ext_workspace_group_handle_v1 *workspace_group_handle, void *data); -zext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name, - uint32_t version, void *data); +ext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name, + uint32_t version, void *data); } // namespace waybar::modules::wlr diff --git a/man/waybar-wlr-workspaces.5.scd b/man/waybar-wlr-workspaces.5.scd index 62d3f636..433ded15 100644 --- a/man/waybar-wlr-workspaces.5.scd +++ b/man/waybar-wlr-workspaces.5.scd @@ -24,18 +24,18 @@ Addressed by *wlr/workspaces* *sort-by-name*: ++ typeof: bool ++ default: true ++ - Should workspaces be sorted by name. + Should workspaces be sorted by name. Workspace names will be sorted numerically when all names are numbers. *sort-by-coordinates*: ++ typeof: bool ++ - default: true ++ + default: false ++ Should workspaces be sorted by coordinates. ++ Note that if both *sort-by-name* and *sort-by-coordinates* are true sort-by name will be first. If both are false - sort by id will be performed. -*sort-by-number*: ++ +*sort-by-id*: ++ typeof: bool ++ default: false ++ - If set to true, workspace names will be sorted numerically. Takes precedence over any other sort-by option. + Should workspaces be sorted by ID. Workspace ID will be sorted numerically when all ID are numbers. Takes precedence over any other sort-by option. *all-outputs*: ++ typeof: bool ++ @@ -49,7 +49,9 @@ Addressed by *wlr/workspaces* # FORMAT REPLACEMENTS -*{name}*: Name of workspace assigned by compositor +*{name}*: Name of workspace assigned by compositor. + +*{id}*: ID of workspace assigned by compositor. *{icon}*: Icon, as defined in *format-icons*. @@ -71,16 +73,16 @@ In addition to workspace name matching, the following *format-icons* can be set. ``` "wlr/workspaces": { "format": "{name}: {icon}", + "on-click": "activate", "format-icons": { - "1": "", - "2": "", - "3": "", - "4": "", - "5": "", + "Workspace 1": "", + "Workspace 2": "", + "Workspace 3": "", + "Workspace 4": "", "active": "", "default": "" }, - "sort-by-number": true + "sort-by-id": true } ``` diff --git a/meson.build b/meson.build index 8832a55e..caf6a06b 100644 --- a/meson.build +++ b/meson.build @@ -276,6 +276,17 @@ if true man_files += files('man/waybar-wlr-taskbar.5.scd') endif +if wayland_protos.version().version_compare('>=1.39') + add_project_arguments('-DHAVE_WLR_WORKSPACES', language: 'cpp') + src_files += files( + 'src/modules/wlr/workspace_manager.cpp', + 'src/modules/wlr/workspace_manager_binding.cpp', + ) + man_files += files( + 'man/waybar-wlr-workspaces.5.scd', + ) +endif + if true add_project_arguments('-DHAVE_RIVER', language: 'cpp') src_files += files( @@ -485,17 +496,6 @@ else man_files += files('man/waybar-clock.5.scd') endif -if get_option('experimental') - add_project_arguments('-DHAVE_WLR_WORKSPACES', language: 'cpp') - src_files += files( - 'src/modules/wlr/workspace_manager.cpp', - 'src/modules/wlr/workspace_manager_binding.cpp', - ) - man_files += files( - 'man/waybar-wlr-workspaces.5.scd', - ) -endif - cava = dependency('cava', version : '>=0.10.4', required: get_option('cava'), diff --git a/protocol/ext-workspace-unstable-v1.xml b/protocol/ext-workspace-unstable-v1.xml deleted file mode 100644 index 24410b62..00000000 --- a/protocol/ext-workspace-unstable-v1.xml +++ /dev/null @@ -1,306 +0,0 @@ - - - - Copyright © 2019 Christopher Billington - Copyright © 2020 Ilia Bozhinov - - Permission to use, copy, modify, distribute, and sell this - software and its documentation for any purpose is hereby granted - without fee, provided that the above copyright notice appear in - all copies and that both that copyright notice and this permission - notice appear in supporting documentation, and that the name of - the copyright holders not be used in advertising or publicity - pertaining to distribution of the software without specific, - written prior permission. The copyright holders make no - representations about the suitability of this software for any - purpose. It is provided "as is" without express or implied - warranty. - - THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS - SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND - FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY - SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, - ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF - THIS SOFTWARE. - - - - - Workspaces, also called virtual desktops, are groups of surfaces. A - compositor with a concept of workspaces may only show some such groups of - surfaces (those of 'active' workspaces) at a time. 'Activating' a - workspace is a request for the compositor to display that workspace's - surfaces as normal, whereas the compositor may hide or otherwise - de-emphasise surfaces that are associated only with 'inactive' workspaces. - Workspaces are grouped by which sets of outputs they correspond to, and - may contain surfaces only from those outputs. In this way, it is possible - for each output to have its own set of workspaces, or for all outputs (or - any other arbitrary grouping) to share workspaces. Compositors may - optionally conceptually arrange each group of workspaces in an - N-dimensional grid. - - The purpose of this protocol is to enable the creation of taskbars and - docks by providing them with a list of workspaces and their properties, - and allowing them to activate and deactivate workspaces. - - After a client binds the zext_workspace_manager_v1, each workspace will be - sent via the workspace event. - - - - - This event is emitted whenever a new workspace group has been created. - - All initial details of the workspace group (workspaces, outputs) will be - sent immediately after this event via the corresponding events in - zext_workspace_group_handle_v1. - - - - - - - The client must send this request after it has finished sending other - requests. The compositor must process a series of requests preceding a - commit request atomically. - - This allows changes to the workspace properties to be seen as atomic, - even if they happen via multiple events, and even if they involve - multiple zext_workspace_handle_v1 objects, for example, deactivating one - workspace and activating another. - - - - - - This event is sent after all changes in all workspace groups have been - sent. - - This allows changes to one or more zext_workspace_group_handle_v1 - properties to be seen as atomic, even if they happen via multiple - events. In particular, an output moving from one workspace group to - another sends an output_enter event and an output_leave event to the two - zext_workspace_group_handle_v1 objects in question. The compositor sends - the done event only after updating the output information in both - workspace groups. - - - - - - This event indicates that the compositor is done sending events to the - zext_workspace_manager_v1. The server will destroy the object - immediately after sending this request, so it will become invalid and - the client should free any resources associated with it. - - - - - - Indicates the client no longer wishes to receive events for new - workspace groups. However the compositor may emit further workspace - events, until the finished event is emitted. - - The client must not send any more requests after this one. - - - - - - - A zext_workspace_group_handle_v1 object represents a a workspace group - that is assigned a set of outputs and contains a number of workspaces. - - The set of outputs assigned to the workspace group is conveyed to the client via - output_enter and output_leave events, and its workspaces are conveyed with - workspace events. - - For example, a compositor which has a set of workspaces for each output may - advertise a workspace group (and its workspaces) per output, whereas a compositor - where a workspace spans all outputs may advertise a single workspace group for all - outputs. - - - - - This event is emitted whenever an output is assigned to the workspace - group. - - - - - - - This event is emitted whenever an output is removed from the workspace - group. - - - - - - - This event is emitted whenever a new workspace has been created. - - All initial details of the workspace (name, coordinates, state) will - be sent immediately after this event via the corresponding events in - zext_workspace_handle_v1. - - - - - - - This event means the zext_workspace_group_handle_v1 has been destroyed. - It is guaranteed there won't be any more events for this - zext_workspace_group_handle_v1. The zext_workspace_group_handle_v1 becomes - inert so any requests will be ignored except the destroy request. - - The compositor must remove all workspaces belonging to a workspace group - before removing the workspace group. - - - - - - Request that the compositor create a new workspace with the given name. - - There is no guarantee that the compositor will create a new workspace, - or that the created workspace will have the provided name. - - - - - - - Destroys the zext_workspace_handle_v1 object. - - This request should be called either when the client does not want to - use the workspace object any more or after the remove event to finalize - the destruction of the object. - - - - - - - A zext_workspace_handle_v1 object represents a a workspace that handles a - group of surfaces. - - Each workspace has a name, conveyed to the client with the name event; a - list of states, conveyed to the client with the state event; and - optionally a set of coordinates, conveyed to the client with the - coordinates event. The client may request that the compositor activate or - deactivate the workspace. - - Each workspace can belong to only a single workspace group. - Depepending on the compositor policy, there might be workspaces with - the same name in different workspace groups, but these workspaces are still - separate (e.g. one of them might be active while the other is not). - - - - - This event is emitted immediately after the zext_workspace_handle_v1 is - created and whenever the name of the workspace changes. - - - - - - - This event is used to organize workspaces into an N-dimensional grid - within a workspace group, and if supported, is emitted immediately after - the zext_workspace_handle_v1 is created and whenever the coordinates of - the workspace change. Compositors may not send this event if they do not - conceptually arrange workspaces in this way. If compositors simply - number workspaces, without any geometric interpretation, they may send - 1D coordinates, which clients should not interpret as implying any - geometry. Sending an empty array means that the compositor no longer - orders the workspace geometrically. - - Coordinates have an arbitrary number of dimensions N with an uint32 - position along each dimension. By convention if N > 1, the first - dimension is X, the second Y, the third Z, and so on. The compositor may - chose to utilize these events for a more novel workspace layout - convention, however. No guarantee is made about the grid being filled or - bounded; there may be a workspace at coordinate 1 and another at - coordinate 1000 and none in between. Within a workspace group, however, - workspaces must have unique coordinates of equal dimensionality. - - - - - - - This event is emitted immediately after the zext_workspace_handle_v1 is - created and each time the workspace state changes, either because of a - compositor action or because of a request in this protocol. - - - - - - - The different states that a workspace can have. - - - - - - - The workspace is not visible in its workspace group, and clients - attempting to visualize the compositor workspace state should not - display such workspaces. - - - - - - - This event means the zext_workspace_handle_v1 has been destroyed. It is - guaranteed there won't be any more events for this - zext_workspace_handle_v1. The zext_workspace_handle_v1 becomes inert so - any requests will be ignored except the destroy request. - - - - - - Destroys the zext_workspace_handle_v1 object. - - This request should be called either when the client does not want to - use the workspace object any more or after the remove event to finalize - the destruction of the object. - - - - - - Request that this workspace be activated. - - There is no guarantee the workspace will be actually activated, and - behaviour may be compositor-dependent. For example, activating a - workspace may or may not deactivate all other workspaces in the same - group. - - - - - - Request that this workspace be deactivated. - - There is no guarantee the workspace will be actually deactivated. - - - - - - Request that this workspace be removed. - - There is no guarantee the workspace will be actually removed. - - - - diff --git a/protocol/meson.build b/protocol/meson.build index cd9a77b1..b16113b2 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -26,12 +26,17 @@ client_protocols = [ [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], ['wlr-foreign-toplevel-management-unstable-v1.xml'], - ['ext-workspace-unstable-v1.xml'], ['river-status-unstable-v1.xml'], ['river-control-unstable-v1.xml'], ['dwl-ipc-unstable-v2.xml'], ] +if wayland_protos.version().version_compare('>=1.39') + client_protocols += [ + [wl_protocol_dir, 'staging/ext-workspace/ext-workspace-v1.xml'] + ] +endif + client_protos_src = [] client_protos_headers = [] diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/wlr/workspace_manager.cpp index f556a161..9cc6d4ef 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/wlr/workspace_manager.cpp @@ -5,8 +5,7 @@ #include #include -#include -#include +#include #include #include "client.hpp" @@ -15,39 +14,52 @@ namespace waybar::modules::wlr { -uint32_t WorkspaceGroup::workspace_global_id = 0; +// WorkspaceManager + uint32_t WorkspaceManager::group_global_id = 0; -std::map Workspace::icons_map_; +uint32_t WorkspaceManager::workspace_global_id = 0; +std::map Workspace::icon_map_; WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config) : waybar::AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) { - auto config_sort_by_name = config_["sort-by-name"]; + add_registry_listener(this); + + // parse configuration + + const auto config_sort_by_number = config_["sort-by-number"]; + if (config_sort_by_number.isBool()) { + spdlog::warn("[wlr/workspaces]: Prefer sort-by-id instead of sort-by-number"); + sort_by_id_ = config_sort_by_number.asBool(); + } + + const auto config_sort_by_id = config_["sort-by-id"]; + if (config_sort_by_id.isBool()) { + sort_by_id_ = config_sort_by_id.asBool(); + } + + const auto config_sort_by_name = config_["sort-by-name"]; if (config_sort_by_name.isBool()) { sort_by_name_ = config_sort_by_name.asBool(); } - auto config_sort_by_coordinates = config_["sort-by-coordinates"]; + const auto config_sort_by_coordinates = config_["sort-by-coordinates"]; if (config_sort_by_coordinates.isBool()) { sort_by_coordinates_ = config_sort_by_coordinates.asBool(); } - auto config_sort_by_number = config_["sort-by-number"]; - if (config_sort_by_number.isBool()) { - sort_by_number_ = config_sort_by_number.asBool(); - } - - auto config_all_outputs = config_["all-outputs"]; + const auto config_all_outputs = config_["all-outputs"]; if (config_all_outputs.isBool()) { all_outputs_ = config_all_outputs.asBool(); } - auto config_active_only = config_["active-only"]; + const auto config_active_only = config_["active-only"]; if (config_active_only.isBool()) { active_only_ = config_active_only.asBool(); - creation_delayed_ = active_only_; } + // setup UI + box_.set_name("workspaces"); if (!id.empty()) { box_.get_style_context()->add_class(id); @@ -55,352 +67,297 @@ WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar box_.get_style_context()->add_class(MODULE_CLASS); event_box_.add(box_); - add_registry_listener(this); - if (!workspace_manager_) { - return; - } -} - -auto WorkspaceManager::workspace_comparator() const - -> std::function &, std::unique_ptr &)> { - return [=, this](std::unique_ptr &lhs, std::unique_ptr &rhs) { - auto is_name_less = lhs->get_name() < rhs->get_name(); - auto is_name_eq = lhs->get_name() == rhs->get_name(); - auto is_coords_less = lhs->get_coords() < rhs->get_coords(); - - if (sort_by_number_) { - try { - auto is_number_less = std::stoi(lhs->get_name()) < std::stoi(rhs->get_name()); - return is_number_less; - } catch (const std::invalid_argument &) { - } - } - - if (sort_by_name_) { - if (sort_by_coordinates_) { - return is_name_eq ? is_coords_less : is_name_less; - } else { - return is_name_less; - } - } - - if (sort_by_coordinates_) { - return is_coords_less; - } - - return lhs->id() < rhs->id(); - }; -} - -auto WorkspaceManager::sort_workspaces() -> void { - std::vector>> all_workspaces; - for (auto &group : groups_) { - auto &group_workspaces = group->workspaces(); - all_workspaces.reserve(all_workspaces.size() + - std::distance(group_workspaces.begin(), group_workspaces.end())); - if (!active_only()) { - all_workspaces.insert(all_workspaces.end(), group_workspaces.begin(), group_workspaces.end()); - continue; - } - - for (auto &workspace : group_workspaces) { - if (!workspace->is_active()) { - continue; - } - - all_workspaces.push_back(workspace); - } - } - - std::sort(all_workspaces.begin(), all_workspaces.end(), workspace_comparator()); - for (size_t i = 0; i < all_workspaces.size(); ++i) { - box_.reorder_child(all_workspaces[i].get()->get_button_ref(), i); - } -} - -auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version) - -> void { - if (workspace_manager_) { - spdlog::warn("Register workspace manager again although already registered!"); - return; - } - if (version != 1) { - spdlog::warn("Using different workspace manager protocol version: {}", version); - } - workspace_manager_ = workspace_manager_bind(registry, name, version, this); -} - -auto WorkspaceManager::handle_workspace_group_create( - zext_workspace_group_handle_v1 *workspace_group_handle) -> void { - auto new_id = ++group_global_id; - groups_.push_back( - std::make_unique(bar_, box_, config_, *this, workspace_group_handle, new_id)); - spdlog::debug("Workspace group {} created", new_id); -} - -auto WorkspaceManager::handle_finished() -> void { - zext_workspace_manager_v1_destroy(workspace_manager_); - workspace_manager_ = nullptr; -} - -auto WorkspaceManager::handle_done() -> void { - for (auto &group : groups_) { - group->handle_done(); - } - dp.emit(); -} - -auto WorkspaceManager::update() -> void { - for (auto &group : groups_) { - group->update(); - } - if (creation_delayed()) { - creation_delayed_ = false; - sort_workspaces(); - } - AModule::update(); + spdlog::debug("[wlr/workspaces]: Workspace manager created"); } WorkspaceManager::~WorkspaceManager() { - if (!workspace_manager_) { - return; + workspaces_.clear(); + groups_.clear(); + + if (ext_manager_ != nullptr) { + auto *display = Client::inst()->wl_display; + // Send `stop` request and wait for one roundtrip. + ext_workspace_manager_v1_stop(ext_manager_); + wl_display_roundtrip(display); } - wl_display *display = Client::inst()->wl_display; - - // Send `stop` request and wait for one roundtrip. This is not quite correct as - // the protocol encourages us to wait for the .finished event, but it should work - // with wlroots workspace manager implementation. - zext_workspace_manager_v1_stop(workspace_manager_); - wl_display_roundtrip(display); - - // If the .finished handler is still not executed, destroy the workspace manager here. - if (workspace_manager_) { - spdlog::warn("Foreign toplevel manager destroyed before .finished event"); - zext_workspace_manager_v1_destroy(workspace_manager_); - workspace_manager_ = nullptr; + if (ext_manager_ != nullptr) { + spdlog::warn("[wlr/workspaces]: Destroying workspace manager before .finished event"); + ext_workspace_manager_v1_destroy(ext_manager_); } + + spdlog::debug("[wlr/workspaces]: Workspace manager destroyed"); } -auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void { - auto it = std::find_if(groups_.begin(), groups_.end(), - [id](const std::unique_ptr &g) { return g->id() == id; }); +void WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version) { + if (ext_manager_ != nullptr) { + spdlog::warn("[wlr/workspaces]: Register workspace manager again although already registered!"); + return; + } + if (version != 1) { + spdlog::warn("[wlr/workspaces]: Using different workspace manager protocol version: {}", + version); + } + + ext_manager_ = workspace_manager_bind(registry, name, version, this); +} + +void WorkspaceManager::remove_workspace_group(uint32_t id) { + const auto it = + std::find_if(groups_.begin(), groups_.end(), [id](const auto &g) { return g->id() == id; }); if (it == groups_.end()) { - spdlog::warn("Can't find group with id {}", id); + spdlog::warn("[wlr/workspaces]: Can't find workspace group with id {}", id); return; } groups_.erase(it); } -auto WorkspaceManager::commit() -> void { zext_workspace_manager_v1_commit(workspace_manager_); } -WorkspaceGroup::WorkspaceGroup(const Bar &bar, Gtk::Box &box, const Json::Value &config, - WorkspaceManager &manager, - zext_workspace_group_handle_v1 *workspace_group_handle, uint32_t id) - : bar_(bar), - box_(box), - config_(config), - workspace_manager_(manager), - workspace_group_handle_(workspace_group_handle), - id_(id) { - add_workspace_group_listener(workspace_group_handle, this); -} - -auto WorkspaceGroup::fill_persistent_workspaces() -> void { - if (config_["persistent_workspaces"].isObject()) { - spdlog::warn( - "persistent_workspaces is deprecated. Please change config to use persistent-workspaces."); - } - - if ((config_["persistent-workspaces"].isObject() || - config_["persistent_workspaces"].isObject()) && - !workspace_manager_.all_outputs()) { - const Json::Value &p_workspaces = config_["persistent-workspaces"].isObject() - ? config_["persistent-workspaces"] - : config_["persistent_workspaces"]; - const std::vector p_workspaces_names = p_workspaces.getMemberNames(); - - for (const std::string &p_w_name : p_workspaces_names) { - const Json::Value &p_w = p_workspaces[p_w_name]; - if (p_w.isArray() && !p_w.empty()) { - // Adding to target outputs - for (const Json::Value &output : p_w) { - if (output.asString() == bar_.output->name) { - persistent_workspaces_.push_back(p_w_name); - break; - } - } - } else { - // Adding to all outputs - persistent_workspaces_.push_back(p_w_name); - } - } - } -} - -auto WorkspaceGroup::create_persistent_workspaces() -> void { - for (const std::string &p_w_name : persistent_workspaces_) { - auto new_id = ++workspace_global_id; - workspaces_.push_back( - std::make_unique(bar_, config_, *this, nullptr, new_id, p_w_name)); - spdlog::debug("Workspace {} created", new_id); - } -} - -auto WorkspaceGroup::active_only() const -> bool { return workspace_manager_.active_only(); } -auto WorkspaceGroup::creation_delayed() const -> bool { - return workspace_manager_.creation_delayed(); -} - -auto WorkspaceGroup::add_button(Gtk::Button &button) -> void { - box_.pack_start(button, false, false); -} - -WorkspaceGroup::~WorkspaceGroup() { - if (!workspace_group_handle_) { - return; - } - - zext_workspace_group_handle_v1_destroy(workspace_group_handle_); - workspace_group_handle_ = nullptr; -} - -auto WorkspaceGroup::handle_workspace_create(zext_workspace_handle_v1 *workspace) -> void { - auto new_id = ++workspace_global_id; - workspaces_.push_back(std::make_unique(bar_, config_, *this, workspace, new_id, "")); - spdlog::debug("Workspace {} created", new_id); - if (!persistent_created_) { - fill_persistent_workspaces(); - create_persistent_workspaces(); - persistent_created_ = true; - } -} - -auto WorkspaceGroup::handle_remove() -> void { - zext_workspace_group_handle_v1_destroy(workspace_group_handle_); - workspace_group_handle_ = nullptr; - workspace_manager_.remove_workspace_group(id_); -} - -auto WorkspaceGroup::handle_output_enter(wl_output *output) -> void { - spdlog::debug("Output {} assigned to {} group", (void *)output, id_); - output_ = output; - - if (!is_visible() || workspace_manager_.creation_delayed()) { - return; - } - - for (auto &workspace : workspaces_) { - add_button(workspace->get_button_ref()); - } -} - -auto WorkspaceGroup::is_visible() const -> bool { - return output_ != nullptr && - (workspace_manager_.all_outputs() || - output_ == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())); -} - -auto WorkspaceGroup::handle_output_leave() -> void { - spdlog::debug("Output {} remove from {} group", (void *)output_, id_); - output_ = nullptr; - - if (output_ != gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())) { - return; - } - - for (auto &workspace : workspaces_) { - remove_button(workspace->get_button_ref()); - } -} - -auto WorkspaceGroup::update() -> void { - for (auto &workspace : workspaces_) { - if (workspace_manager_.creation_delayed()) { - add_button(workspace->get_button_ref()); - if (is_visible() && (workspace->is_active() || workspace->is_urgent())) { - workspace->show(); - } - } - - workspace->update(); - } -} - -auto WorkspaceGroup::remove_workspace(uint32_t id) -> void { - auto it = std::find_if(workspaces_.begin(), workspaces_.end(), - [id](const std::unique_ptr &w) { return w->id() == id; }); +void WorkspaceManager::remove_workspace(uint32_t id) { + const auto it = std::find_if(workspaces_.begin(), workspaces_.end(), + [id](const auto &w) { return w->id() == id; }); if (it == workspaces_.end()) { - spdlog::warn("Can't find workspace with id {}", id); + spdlog::warn("[wlr/workspaces]: Can't find workspace with id {}", id); return; } workspaces_.erase(it); } -auto WorkspaceGroup::handle_done() -> void { - need_to_sort = false; - if (!is_visible()) { - return; - } +void WorkspaceManager::handle_workspace_group(ext_workspace_group_handle_v1 *handle) { + const auto new_id = ++group_global_id; + groups_.push_back(std::make_unique(*this, handle, new_id)); + spdlog::debug("[wlr/workspaces]: Workspace group {} created", new_id); +} - for (auto &workspace : workspaces_) { - workspace->handle_done(); - } +void WorkspaceManager::handle_workspace(ext_workspace_handle_v1 *handle) { + const auto new_id = ++workspace_global_id; + const auto new_name = std::to_string(++workspace_name); + workspaces_.push_back(std::make_unique(config_, *this, handle, new_id, new_name)); + set_needs_sorting(); + spdlog::debug("[wlr/workspaces]: Workspace {} created", new_id); +} - if (creation_delayed()) { - return; - } +void WorkspaceManager::handle_done() { dp.emit(); } - if (!workspace_manager_.all_outputs()) { +void WorkspaceManager::handle_finished() { + spdlog::debug("[wlr/workspaces]: Finishing workspace manager"); + ext_workspace_manager_v1_destroy(ext_manager_); + ext_manager_ = nullptr; +} + +void WorkspaceManager::commit() const { ext_workspace_manager_v1_commit(ext_manager_); } + +void WorkspaceManager::update() { + spdlog::debug("[wlr/workspaces]: Updating state"); + + if (needs_sorting_) { + clear_buttons(); sort_workspaces(); - } else { - workspace_manager_.sort_workspaces(); + needs_sorting_ = false; + } + + update_buttons(); + AModule::update(); +} + +bool WorkspaceManager::has_button(const Gtk::Button *button) { + const auto buttons = box_.get_children(); + return std::find(buttons.begin(), buttons.end(), button) != buttons.end(); +} + +void WorkspaceManager::sort_workspaces() { + // determine if workspace ID's and names can be sort numerically or literally + + auto is_numeric = [](const std::string &s) { + return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit); + }; + + auto sort_by_workspace_id_numerically = + std::all_of(workspaces_.begin(), workspaces_.end(), + [&](const auto &w) { return is_numeric(w->workspace_id()); }); + + auto sort_by_name_numerically = std::all_of(workspaces_.begin(), workspaces_.end(), + [&](const auto &w) { return is_numeric(w->name()); }); + + // sort based on configuration setting with sort-by-id as fallback + + std::sort(workspaces_.begin(), workspaces_.end(), [&](const auto &w1, const auto &w2) { + if (sort_by_id_ || (!sort_by_name_ && !sort_by_coordinates_)) { + if (w1->workspace_id() == w2->workspace_id()) { + return w1->id() < w2->id(); + } + if (sort_by_workspace_id_numerically) { + // the idea is that phonetic compare can be applied just to numbers + // with same number of digits + return w1->workspace_id().size() < w2->workspace_id().size() || + (w1->workspace_id().size() == w2->workspace_id().size() && + w1->workspace_id() < w2->workspace_id()); + } + return w1->workspace_id() < w2->workspace_id(); + } + + if (sort_by_name_) { + if (w1->name() == w2->name()) { + return w1->id() < w2->id(); + } + if (sort_by_name_numerically) { + // see above about numeric sorting + return w1->name().size() < w2->name().size() || + (w1->name().size() == w2->name().size() && w1->name() < w2->name()); + } + return w1->name() < w2->name(); + } + + if (sort_by_coordinates_) { + if (w1->coordinates() == w2->coordinates()) { + return w1->id() < w2->id(); + } + return w1->coordinates() < w2->coordinates(); + } + + return w1->id() < w2->id(); + }); +} + +void WorkspaceManager::clear_buttons() { + for (const auto &workspace : workspaces_) { + if (has_button(&workspace->button())) { + box_.remove(workspace->button()); + } } } -auto WorkspaceGroup::commit() -> void { workspace_manager_.commit(); } +void WorkspaceManager::update_buttons() { + const auto *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); -auto WorkspaceGroup::sort_workspaces() -> void { - std::sort(workspaces_.begin(), workspaces_.end(), workspace_manager_.workspace_comparator()); - for (size_t i = 0; i < workspaces_.size(); ++i) { - box_.reorder_child(workspaces_[i]->get_button_ref(), i); + // go through all workspace + + for (const auto &workspace : workspaces_) { + const bool workspace_on_any_group_for_output = + std::any_of(groups_.begin(), groups_.end(), [&](const auto &group) { + const bool group_on_output = group->has_output(output) || all_outputs_; + const bool workspace_on_group = group->has_workspace(workspace->handle()); + return group_on_output && workspace_on_group; + }); + const bool active_or_urgent = workspace->is_active() || workspace->is_urgent(); + const bool valid_workspace_state = (active_or_urgent && active_only_) || !active_only_; + + const bool show_button = workspace_on_any_group_for_output && valid_workspace_state; + const bool bar_contains_button = has_button(&workspace->button()); + + // add or remove buttons if needed, update button state + + if (show_button) { + if (!bar_contains_button) { + // add button to bar + box_.pack_start(workspace->button(), false, false); + workspace->button().show_all(); + } + workspace->update(); + } else { + if (bar_contains_button) { + // remove button from bar + box_.remove(workspace->button()); + } + } } } -auto WorkspaceGroup::remove_button(Gtk::Button &button) -> void { box_.remove(button); } +// WorkspaceGroup -Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group, - zext_workspace_handle_v1 *workspace, uint32_t id, std::string name) - : bar_(bar), - config_(config), - workspace_group_(workspace_group), - workspace_handle_(workspace), - id_(id), - name_(name) { - if (workspace) { - add_workspace_listener(workspace, this); - } else { - state_ = (uint32_t)State::EMPTY; +WorkspaceGroup::WorkspaceGroup(WorkspaceManager &manager, ext_workspace_group_handle_v1 *handle, + uint32_t id) + : workspaces_manager_(manager), ext_handle_(handle), id_(id) { + add_workspace_group_listener(ext_handle_, this); +} + +WorkspaceGroup::~WorkspaceGroup() { + if (ext_handle_ != nullptr) { + ext_workspace_group_handle_v1_destroy(ext_handle_); } + spdlog::debug("[wlr/workspaces]: Workspace group {} destroyed", id_); +} - auto config_format = config["format"]; +bool WorkspaceGroup::has_output(const wl_output *output) { + return std::find(outputs_.begin(), outputs_.end(), output) != outputs_.end(); +} +bool WorkspaceGroup::has_workspace(const ext_workspace_handle_v1 *workspace) { + return std::find(workspaces_.begin(), workspaces_.end(), workspace) != workspaces_.end(); +} + +void WorkspaceGroup::handle_capabilities(uint32_t capabilities) { + spdlog::debug("[wlr/workspaces]: Capabilities for workspace group {}:", id_); + if ((capabilities & EXT_WORKSPACE_GROUP_HANDLE_V1_GROUP_CAPABILITIES_CREATE_WORKSPACE) == + capabilities) { + spdlog::debug("[wlr/workspaces]: create-workspace"); + } +} + +void WorkspaceGroup::handle_output_enter(wl_output *output) { outputs_.push_back(output); } + +void WorkspaceGroup::handle_output_leave(wl_output *output) { + const auto it = std::find(outputs_.begin(), outputs_.end(), output); + if (it != outputs_.end()) { + outputs_.erase(it); + } +} + +void WorkspaceGroup::handle_workspace_enter(ext_workspace_handle_v1 *handle) { + workspaces_.push_back(handle); +} + +void WorkspaceGroup::handle_workspace_leave(ext_workspace_handle_v1 *handle) { + const auto it = std::find(workspaces_.begin(), workspaces_.end(), handle); + if (it != workspaces_.end()) { + workspaces_.erase(it); + } +} + +void WorkspaceGroup::handle_removed() { + spdlog::debug("[wlr/workspaces]: Removing workspace group {}", id_); + workspaces_manager_.remove_workspace_group(id_); +} + +// Workspace + +Workspace::Workspace(const Json::Value &config, WorkspaceManager &manager, + ext_workspace_handle_v1 *handle, uint32_t id, const std::string &name) + : workspace_manager_(manager), ext_handle_(handle), id_(id), workspace_id_(name), name_(name) { + add_workspace_listener(ext_handle_, this); + + // parse configuration + + const auto &config_format = config["format"]; format_ = config_format.isString() ? config_format.asString() : "{name}"; with_icon_ = format_.find("{icon}") != std::string::npos; - if (with_icon_ && icons_map_.empty()) { - auto format_icons = config["format-icons"]; - for (auto &name : format_icons.getMemberNames()) { - icons_map_.emplace(name, format_icons[name].asString()); + if (with_icon_ && icon_map_.empty()) { + const auto &format_icons = config["format-icons"]; + for (auto &n : format_icons.getMemberNames()) { + icon_map_.emplace(n, format_icons[n].asString()); } } - /* Handle click events if configured */ - if (config_["on-click"].isString() || config_["on-click-middle"].isString() || - config_["on-click-right"].isString()) { + const bool config_on_click = config["on-click"].isString(); + if (config_on_click) { + on_click_action_ = config["on-click"].asString(); + } + const bool config_on_click_middle = config["on-click-middle"].isString(); + if (config_on_click_middle) { + on_click_middle_action_ = config["on-click"].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(); + } + + // setup UI + + if (config_on_click || config_on_click_middle || config_on_click_right) { button_.add_events(Gdk::BUTTON_PRESS_MASK); button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked), false); @@ -409,177 +366,145 @@ Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup & button_.set_relief(Gtk::RELIEF_NONE); content_.set_center_widget(label_); button_.add(content_); - - if (!workspace_group.is_visible()) { - return; - } - - workspace_group.add_button(button_); - button_.show_all(); } Workspace::~Workspace() { - workspace_group_.remove_button(button_); - if (!workspace_handle_) { + if (ext_handle_ != nullptr) { + ext_workspace_handle_v1_destroy(ext_handle_); + } + spdlog::debug("[wlr/workspaces]: Workspace {} destroyed", id_); +} + +bool Workspace::is_active() const { + return (state_ & EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE) == EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE; +} + +bool Workspace::is_urgent() const { + return (state_ & EXT_WORKSPACE_HANDLE_V1_STATE_URGENT) == EXT_WORKSPACE_HANDLE_V1_STATE_URGENT; +} + +bool Workspace::is_hidden() const { + return (state_ & EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN) == EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN; +} + +void Workspace::update() { + if (!needs_updating_) { return; } - zext_workspace_handle_v1_destroy(workspace_handle_); - workspace_handle_ = nullptr; -} + // update style + const auto style_context = button_.get_style_context(); + style_context->remove_class("active"); + style_context->remove_class("urgent"); + style_context->remove_class("hidden"); -auto Workspace::update() -> void { - label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("name", name_), - fmt::arg("icon", with_icon_ ? get_icon() : ""))); -} - -auto Workspace::handle_state(const std::vector &state) -> void { - state_ = 0; - for (auto state_entry : state) { - switch (state_entry) { - case ZEXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE: - state_ |= (uint32_t)State::ACTIVE; - break; - case ZEXT_WORKSPACE_HANDLE_V1_STATE_URGENT: - state_ |= (uint32_t)State::URGENT; - break; - case ZEXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN: - state_ |= (uint32_t)State::HIDDEN; - break; - } - } -} - -auto Workspace::handle_remove() -> void { - if (workspace_handle_) { - zext_workspace_handle_v1_destroy(workspace_handle_); - workspace_handle_ = nullptr; - } - if (!persistent_) { - workspace_group_.remove_workspace(id_); - } else { - state_ = (uint32_t)State::EMPTY; - } -} - -auto add_or_remove_class(Glib::RefPtr context, bool condition, - const std::string &class_name) { - if (condition) { - context->add_class(class_name); - } else { - context->remove_class(class_name); - } -} - -auto Workspace::handle_done() -> void { - spdlog::debug("Workspace {} changed to state {}", id_, state_); - auto style_context = button_.get_style_context(); - add_or_remove_class(style_context, is_active(), "active"); - add_or_remove_class(style_context, is_urgent(), "urgent"); - add_or_remove_class(style_context, is_hidden(), "hidden"); - add_or_remove_class(style_context, is_empty(), "persistent"); - - if (workspace_group_.creation_delayed()) { - return; - } - - if (workspace_group_.active_only() && (is_active() || is_urgent())) { - button_.show_all(); - } else if (workspace_group_.active_only() && !(is_active() || is_urgent())) { - button_.hide(); - } -} - -auto Workspace::get_icon() -> std::string { if (is_active()) { - auto active_icon_it = icons_map_.find("active"); - if (active_icon_it != icons_map_.end()) { + style_context->add_class("active"); + } + if (is_urgent()) { + style_context->add_class("urgent"); + } + if (is_hidden()) { + style_context->add_class("hidden"); + } + + // update label + 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) { + workspace_id_ = id; + needs_updating_ = true; + workspace_manager_.set_needs_sorting(); +} + +void Workspace::handle_name(const std::string &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_capabilities(uint32_t capabilities) { + spdlog::debug("[wlr/workspaces]: Capabilities for workspace {}:", id_); + if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE) == capabilities) { + spdlog::debug("[wlr/workspaces]: activate"); + } + if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_DEACTIVATE) == capabilities) { + spdlog::debug("[wlr/workspaces]: deactivate"); + } + if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_REMOVE) == capabilities) { + spdlog::debug("[wlr/workspaces]: remove"); + } + if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ASSIGN) == capabilities) { + spdlog::debug("[wlr/workspaces]: assign"); + } + needs_updating_ = true; +} + +void Workspace::handle_removed() { + spdlog::debug("[wlr/workspaces]: Removing workspace {}", id_); + workspace_manager_.remove_workspace(id_); +} + +bool Workspace::handle_clicked(const GdkEventButton *button) const { + std::string action; + if (button->button == GDK_BUTTON_PRIMARY) { + action = on_click_action_; + } else if (button->button == GDK_BUTTON_MIDDLE) { + action = on_click_middle_action_; + } else if (button->button == GDK_BUTTON_SECONDARY) { + action = on_click_right_action_; + } + + if (action.empty()) { + return true; + } + + if (action == "activate") { + ext_workspace_handle_v1_activate(ext_handle_); + } else if (action == "close") { + ext_workspace_handle_v1_remove(ext_handle_); + } else { + spdlog::warn("[wlr/workspaces]: Unknown action {}", action); + } + workspace_manager_.commit(); + return true; +} + +std::string Workspace::icon() { + if (is_active()) { + const auto active_icon_it = icon_map_.find("active"); + if (active_icon_it != icon_map_.end()) { return active_icon_it->second; } } - auto named_icon_it = icons_map_.find(name_); - if (named_icon_it != icons_map_.end()) { + const auto named_icon_it = icon_map_.find(name_); + if (named_icon_it != icon_map_.end()) { return named_icon_it->second; } - if (is_empty()) { - auto persistent_icon_it = icons_map_.find("persistent"); - if (persistent_icon_it != icons_map_.end()) { - return persistent_icon_it->second; - } - } - - auto default_icon_it = icons_map_.find("default"); - if (default_icon_it != icons_map_.end()) { + const auto default_icon_it = icon_map_.find("default"); + if (default_icon_it != icon_map_.end()) { return default_icon_it->second; } return name_; } -auto Workspace::handle_clicked(GdkEventButton *bt) -> bool { - std::string action; - if (config_["on-click"].isString() && bt->button == 1) { - action = config_["on-click"].asString(); - } else if (config_["on-click-middle"].isString() && bt->button == 2) { - action = config_["on-click-middle"].asString(); - } else if (config_["on-click-right"].isString() && bt->button == 3) { - action = config_["on-click-right"].asString(); - } - - if (action.empty()) - return true; - else if (action == "activate") { - zext_workspace_handle_v1_activate(workspace_handle_); - } else if (action == "close") { - zext_workspace_handle_v1_remove(workspace_handle_); - } else { - spdlog::warn("Unknown action {}", action); - } - - workspace_group_.commit(); - - return true; -} - -auto Workspace::show() -> void { button_.show_all(); } -auto Workspace::hide() -> void { button_.hide(); } - -auto Workspace::handle_name(const std::string &name) -> void { - if (name_ != name) { - workspace_group_.set_need_to_sort(); - } - name_ = name; - spdlog::debug("Workspace {} added to group {}", name, workspace_group_.id()); - - make_persistent(); - handle_duplicate(); -} - -auto Workspace::make_persistent() -> void { - auto p_workspaces = workspace_group_.persistent_workspaces(); - - if (std::find(p_workspaces.begin(), p_workspaces.end(), name_) != p_workspaces.end()) { - persistent_ = true; - } -} - -auto Workspace::handle_duplicate() -> void { - auto duplicate = - std::find_if(workspace_group_.workspaces().begin(), workspace_group_.workspaces().end(), - [this](const std::unique_ptr &g) { - return g->get_name() == name_ && g->id() != id_; - }); - if (duplicate != workspace_group_.workspaces().end()) { - workspace_group_.remove_workspace(duplicate->get()->id()); - } -} - -auto Workspace::handle_coordinates(const std::vector &coordinates) -> void { - if (coordinates_ != coordinates) { - workspace_group_.set_need_to_sort(); - } - coordinates_ = coordinates; -} } // namespace waybar::modules::wlr diff --git a/src/modules/wlr/workspace_manager_binding.cpp b/src/modules/wlr/workspace_manager_binding.cpp index 22e68fbf..1333e607 100644 --- a/src/modules/wlr/workspace_manager_binding.cpp +++ b/src/modules/wlr/workspace_manager_binding.cpp @@ -11,7 +11,7 @@ namespace waybar::modules::wlr { static void handle_global(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { - if (std::strcmp(interface, zext_workspace_manager_v1_interface.name) == 0) { + if (std::strcmp(interface, ext_workspace_manager_v1_interface.name) == 0) { static_cast(data)->register_manager(registry, name, version); } } @@ -29,76 +29,97 @@ void add_registry_listener(void *data) { wl_registry_add_listener(registry, ®istry_listener_impl, data); wl_display_roundtrip(display); - wl_display_roundtrip(display); } static void workspace_manager_handle_workspace_group( - void *data, zext_workspace_manager_v1 *_, zext_workspace_group_handle_v1 *workspace_group) { - static_cast(data)->handle_workspace_group_create(workspace_group); + void *data, ext_workspace_manager_v1 *_, ext_workspace_group_handle_v1 *workspace_group) { + static_cast(data)->handle_workspace_group(workspace_group); } -static void workspace_manager_handle_done(void *data, zext_workspace_manager_v1 *_) { +static void workspace_manager_handle_workspace(void *data, ext_workspace_manager_v1 *_, + ext_workspace_handle_v1 *workspace) { + static_cast(data)->handle_workspace(workspace); +} + +static void workspace_manager_handle_done(void *data, ext_workspace_manager_v1 *_) { static_cast(data)->handle_done(); } -static void workspace_manager_handle_finished(void *data, zext_workspace_manager_v1 *_) { +static void workspace_manager_handle_finished(void *data, ext_workspace_manager_v1 *_) { static_cast(data)->handle_finished(); } -static const zext_workspace_manager_v1_listener workspace_manager_impl = { +static const ext_workspace_manager_v1_listener workspace_manager_impl = { .workspace_group = workspace_manager_handle_workspace_group, + .workspace = workspace_manager_handle_workspace, .done = workspace_manager_handle_done, .finished = workspace_manager_handle_finished, }; -zext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name, - uint32_t version, void *data) { - auto *workspace_manager = static_cast( - wl_registry_bind(registry, name, &zext_workspace_manager_v1_interface, version)); +ext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name, + uint32_t version, void *data) { + auto *workspace_manager = static_cast( + wl_registry_bind(registry, name, &ext_workspace_manager_v1_interface, version)); if (workspace_manager) - zext_workspace_manager_v1_add_listener(workspace_manager, &workspace_manager_impl, data); + ext_workspace_manager_v1_add_listener(workspace_manager, &workspace_manager_impl, data); else spdlog::error("Failed to register manager"); return workspace_manager; } -static void workspace_group_handle_output_enter(void *data, zext_workspace_group_handle_v1 *_, +static void workspace_group_handle_capabilities(void *data, ext_workspace_group_handle_v1 *_, + uint32_t capabilities) { + static_cast(data)->handle_capabilities(capabilities); +} + +static void workspace_group_handle_output_enter(void *data, ext_workspace_group_handle_v1 *_, wl_output *output) { static_cast(data)->handle_output_enter(output); } -static void workspace_group_handle_output_leave(void *data, zext_workspace_group_handle_v1 *_, +static void workspace_group_handle_output_leave(void *data, ext_workspace_group_handle_v1 *_, wl_output *output) { - static_cast(data)->handle_output_leave(); + static_cast(data)->handle_output_leave(output); } -static void workspace_group_handle_workspace(void *data, zext_workspace_group_handle_v1 *_, - zext_workspace_handle_v1 *workspace) { - static_cast(data)->handle_workspace_create(workspace); +static void workspace_group_handle_workspace_enter(void *data, ext_workspace_group_handle_v1 *_, + ext_workspace_handle_v1 *workspace) { + static_cast(data)->handle_workspace_enter(workspace); } -static void workspace_group_handle_remove(void *data, zext_workspace_group_handle_v1 *_) { - static_cast(data)->handle_remove(); +static void workspace_group_handle_workspace_leave(void *data, ext_workspace_group_handle_v1 *_, + ext_workspace_handle_v1 *workspace) { + static_cast(data)->handle_workspace_leave(workspace); } -static const zext_workspace_group_handle_v1_listener workspace_group_impl = { +static void workspace_group_handle_removed(void *data, ext_workspace_group_handle_v1 *_) { + static_cast(data)->handle_removed(); +} + +static const ext_workspace_group_handle_v1_listener workspace_group_impl = { + .capabilities = workspace_group_handle_capabilities, .output_enter = workspace_group_handle_output_enter, .output_leave = workspace_group_handle_output_leave, - .workspace = workspace_group_handle_workspace, - .remove = workspace_group_handle_remove}; + .workspace_enter = workspace_group_handle_workspace_enter, + .workspace_leave = workspace_group_handle_workspace_leave, + .removed = workspace_group_handle_removed}; -void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle, +void add_workspace_group_listener(ext_workspace_group_handle_v1 *workspace_group_handle, void *data) { - zext_workspace_group_handle_v1_add_listener(workspace_group_handle, &workspace_group_impl, data); + ext_workspace_group_handle_v1_add_listener(workspace_group_handle, &workspace_group_impl, data); } -void workspace_handle_name(void *data, struct zext_workspace_handle_v1 *_, const char *name) { +void workspace_handle_name(void *data, struct ext_workspace_handle_v1 *_, const char *name) { static_cast(data)->handle_name(name); } -void workspace_handle_coordinates(void *data, struct zext_workspace_handle_v1 *_, +void workspace_handle_id(void *data, struct ext_workspace_handle_v1 *_, const char *id) { + static_cast(data)->handle_id(id); +} + +void workspace_handle_coordinates(void *data, struct ext_workspace_handle_v1 *_, struct wl_array *coordinates) { std::vector coords_vec; auto coords = static_cast(coordinates->data); @@ -109,28 +130,30 @@ void workspace_handle_coordinates(void *data, struct zext_workspace_handle_v1 *_ static_cast(data)->handle_coordinates(coords_vec); } -void workspace_handle_state(void *data, struct zext_workspace_handle_v1 *workspace_handle, - struct wl_array *state) { - std::vector state_vec; - auto states = static_cast(state->data); - for (size_t i = 0; i < state->size / sizeof(uint32_t); ++i) { - state_vec.push_back(states[i]); - } - - static_cast(data)->handle_state(state_vec); +void workspace_handle_state(void *data, struct ext_workspace_handle_v1 *workspace_handle, + uint32_t state) { + static_cast(data)->handle_state(state); } -void workspace_handle_remove(void *data, struct zext_workspace_handle_v1 *_) { - static_cast(data)->handle_remove(); +static void workspace_handle_capabilities(void *data, + struct ext_workspace_handle_v1 *workspace_handle, + uint32_t capabilities) { + static_cast(data)->handle_capabilities(capabilities); } -static const zext_workspace_handle_v1_listener workspace_impl = { +void workspace_handle_removed(void *data, struct ext_workspace_handle_v1 *workspace_handle) { + static_cast(data)->handle_removed(); +} + +static const ext_workspace_handle_v1_listener workspace_impl = { + .id = workspace_handle_id, .name = workspace_handle_name, .coordinates = workspace_handle_coordinates, .state = workspace_handle_state, - .remove = workspace_handle_remove}; + .capabilities = workspace_handle_capabilities, + .removed = workspace_handle_removed}; -void add_workspace_listener(zext_workspace_handle_v1 *workspace_handle, void *data) { - zext_workspace_handle_v1_add_listener(workspace_handle, &workspace_impl, data); +void add_workspace_listener(ext_workspace_handle_v1 *workspace_handle, void *data) { + ext_workspace_handle_v1_add_listener(workspace_handle, &workspace_impl, data); } } // namespace waybar::modules::wlr From 29bf5d5da13fe1bcfbcf21e78aae36ba7abcfd2c Mon Sep 17 00:00:00 2001 From: Jens Peters Date: Fri, 20 Jun 2025 07:17:46 +0200 Subject: [PATCH 303/780] ext/workspaces: moved/renamed from wlr/workspaces The ext-workspace-v1 protocol is not wlr/wlroots specific. --- .../{wlr => ext}/workspace_manager.hpp | 12 ++-- .../workspace_manager_binding.hpp | 4 +- ...aces.5.scd => waybar-ext-workspaces.5.scd} | 4 +- meson.build | 8 +-- src/factory.cpp | 10 ++-- .../{wlr => ext}/workspace_manager.cpp | 56 +++++++++---------- .../workspace_manager_binding.cpp | 8 +-- 7 files changed, 51 insertions(+), 51 deletions(-) rename include/modules/{wlr => ext}/workspace_manager.hpp (96%) rename include/modules/{wlr => ext}/workspace_manager_binding.hpp (87%) rename man/{waybar-wlr-workspaces.5.scd => waybar-ext-workspaces.5.scd} (97%) rename src/modules/{wlr => ext}/workspace_manager.cpp (89%) rename src/modules/{wlr => ext}/workspace_manager_binding.cpp (97%) diff --git a/include/modules/wlr/workspace_manager.hpp b/include/modules/ext/workspace_manager.hpp similarity index 96% rename from include/modules/wlr/workspace_manager.hpp rename to include/modules/ext/workspace_manager.hpp index 7522134e..74718848 100644 --- a/include/modules/wlr/workspace_manager.hpp +++ b/include/modules/ext/workspace_manager.hpp @@ -13,7 +13,7 @@ #include "bar.hpp" #include "ext-workspace-v1-client-protocol.h" -namespace waybar::modules::wlr { +namespace waybar::modules::ext { class WorkspaceGroup; class Workspace; @@ -27,13 +27,13 @@ class WorkspaceManager final : public AModule { void remove_workspace(uint32_t id); void set_needs_sorting() { needs_sorting_ = true; } - // wlr events + // wl events void handle_workspace_group(ext_workspace_group_handle_v1 *handle); void handle_workspace(ext_workspace_handle_v1 *handle); void handle_done(); void handle_finished(); - // wlr requests + // wl requests void commit() const; private: @@ -72,7 +72,7 @@ class WorkspaceGroup { bool has_output(const wl_output *output); bool has_workspace(const ext_workspace_handle_v1 *workspace); - // wlr events + // wl events void handle_capabilities(uint32_t capabilities); void handle_output_enter(wl_output *output); void handle_output_leave(wl_output *output); @@ -105,7 +105,7 @@ class Workspace { bool is_hidden() const; void update(); - // wlr events + // wl events void handle_id(const std::string &id); void handle_name(const std::string &name); void handle_coordinates(const std::vector &coordinates); @@ -141,4 +141,4 @@ class Workspace { bool needs_updating_ = false; }; -} // namespace waybar::modules::wlr +} // namespace waybar::modules::ext diff --git a/include/modules/wlr/workspace_manager_binding.hpp b/include/modules/ext/workspace_manager_binding.hpp similarity index 87% rename from include/modules/wlr/workspace_manager_binding.hpp rename to include/modules/ext/workspace_manager_binding.hpp index a64d9b02..b41f207c 100644 --- a/include/modules/wlr/workspace_manager_binding.hpp +++ b/include/modules/ext/workspace_manager_binding.hpp @@ -1,10 +1,10 @@ #include "ext-workspace-v1-client-protocol.h" -namespace waybar::modules::wlr { +namespace waybar::modules::ext { void add_registry_listener(void *data); void add_workspace_listener(ext_workspace_handle_v1 *workspace_handle, void *data); void add_workspace_group_listener(ext_workspace_group_handle_v1 *workspace_group_handle, void *data); ext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name, uint32_t version, void *data); -} // namespace waybar::modules::wlr +} // namespace waybar::modules::ext diff --git a/man/waybar-wlr-workspaces.5.scd b/man/waybar-ext-workspaces.5.scd similarity index 97% rename from man/waybar-wlr-workspaces.5.scd rename to man/waybar-ext-workspaces.5.scd index 433ded15..9a697cef 100644 --- a/man/waybar-wlr-workspaces.5.scd +++ b/man/waybar-ext-workspaces.5.scd @@ -10,7 +10,7 @@ The *workspaces* module displays the currently used workspaces in wayland compos # CONFIGURATION -Addressed by *wlr/workspaces* +Addressed by *ext/workspaces* *format*: ++ typeof: string ++ @@ -71,7 +71,7 @@ In addition to workspace name matching, the following *format-icons* can be set. # EXAMPLES ``` -"wlr/workspaces": { +"ext/workspaces": { "format": "{name}: {icon}", "on-click": "activate", "format-icons": { diff --git a/meson.build b/meson.build index caf6a06b..8d44dd67 100644 --- a/meson.build +++ b/meson.build @@ -277,13 +277,13 @@ if true endif if wayland_protos.version().version_compare('>=1.39') - add_project_arguments('-DHAVE_WLR_WORKSPACES', language: 'cpp') + add_project_arguments('-DHAVE_EXT_WORKSPACES', language: 'cpp') src_files += files( - 'src/modules/wlr/workspace_manager.cpp', - 'src/modules/wlr/workspace_manager_binding.cpp', + 'src/modules/ext/workspace_manager.cpp', + 'src/modules/ext/workspace_manager_binding.cpp', ) man_files += files( - 'man/waybar-wlr-workspaces.5.scd', + 'man/waybar-ext-workspaces.5.scd', ) endif diff --git a/src/factory.cpp b/src/factory.cpp index f7aa2d30..20408106 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -17,8 +17,8 @@ #ifdef HAVE_WLR_TASKBAR #include "modules/wlr/taskbar.hpp" #endif -#ifdef HAVE_WLR_WORKSPACES -#include "modules/wlr/workspace_manager.hpp" +#ifdef HAVE_EXT_WORKSPACES +#include "modules/ext/workspace_manager.hpp" #endif #ifdef HAVE_RIVER #include "modules/river/layout.hpp" @@ -178,9 +178,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]); } #endif -#ifdef HAVE_WLR_WORKSPACES - if (ref == "wlr/workspaces") { - return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]); +#ifdef HAVE_EXT_WORKSPACES + if (ref == "ext/workspaces") { + return new waybar::modules::ext::WorkspaceManager(id, bar_, config_[name]); } #endif #ifdef HAVE_RIVER diff --git a/src/modules/wlr/workspace_manager.cpp b/src/modules/ext/workspace_manager.cpp similarity index 89% rename from src/modules/wlr/workspace_manager.cpp rename to src/modules/ext/workspace_manager.cpp index 9cc6d4ef..882aee71 100644 --- a/src/modules/wlr/workspace_manager.cpp +++ b/src/modules/ext/workspace_manager.cpp @@ -1,4 +1,4 @@ -#include "modules/wlr/workspace_manager.hpp" +#include "modules/ext/workspace_manager.hpp" #include #include @@ -10,9 +10,9 @@ #include "client.hpp" #include "gtkmm/widget.h" -#include "modules/wlr/workspace_manager_binding.hpp" +#include "modules/ext/workspace_manager_binding.hpp" -namespace waybar::modules::wlr { +namespace waybar::modules::ext { // WorkspaceManager @@ -29,7 +29,7 @@ WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar const auto config_sort_by_number = config_["sort-by-number"]; if (config_sort_by_number.isBool()) { - spdlog::warn("[wlr/workspaces]: Prefer sort-by-id instead of sort-by-number"); + spdlog::warn("[ext/workspaces]: Prefer sort-by-id instead of sort-by-number"); sort_by_id_ = config_sort_by_number.asBool(); } @@ -67,7 +67,7 @@ WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar box_.get_style_context()->add_class(MODULE_CLASS); event_box_.add(box_); - spdlog::debug("[wlr/workspaces]: Workspace manager created"); + spdlog::debug("[ext/workspaces]: Workspace manager created"); } WorkspaceManager::~WorkspaceManager() { @@ -82,20 +82,20 @@ WorkspaceManager::~WorkspaceManager() { } if (ext_manager_ != nullptr) { - spdlog::warn("[wlr/workspaces]: Destroying workspace manager before .finished event"); + spdlog::warn("[ext/workspaces]: Destroying workspace manager before .finished event"); ext_workspace_manager_v1_destroy(ext_manager_); } - spdlog::debug("[wlr/workspaces]: Workspace manager destroyed"); + spdlog::debug("[ext/workspaces]: Workspace manager destroyed"); } void WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version) { if (ext_manager_ != nullptr) { - spdlog::warn("[wlr/workspaces]: Register workspace manager again although already registered!"); + spdlog::warn("[ext/workspaces]: Register workspace manager again although already registered!"); return; } if (version != 1) { - spdlog::warn("[wlr/workspaces]: Using different workspace manager protocol version: {}", + spdlog::warn("[ext/workspaces]: Using different workspace manager protocol version: {}", version); } @@ -107,7 +107,7 @@ void WorkspaceManager::remove_workspace_group(uint32_t id) { std::find_if(groups_.begin(), groups_.end(), [id](const auto &g) { return g->id() == id; }); if (it == groups_.end()) { - spdlog::warn("[wlr/workspaces]: Can't find workspace group with id {}", id); + spdlog::warn("[ext/workspaces]: Can't find workspace group with id {}", id); return; } @@ -119,7 +119,7 @@ void WorkspaceManager::remove_workspace(uint32_t id) { [id](const auto &w) { return w->id() == id; }); if (it == workspaces_.end()) { - spdlog::warn("[wlr/workspaces]: Can't find workspace with id {}", id); + spdlog::warn("[ext/workspaces]: Can't find workspace with id {}", id); return; } @@ -129,7 +129,7 @@ void WorkspaceManager::remove_workspace(uint32_t id) { void WorkspaceManager::handle_workspace_group(ext_workspace_group_handle_v1 *handle) { const auto new_id = ++group_global_id; groups_.push_back(std::make_unique(*this, handle, new_id)); - spdlog::debug("[wlr/workspaces]: Workspace group {} created", new_id); + spdlog::debug("[ext/workspaces]: Workspace group {} created", new_id); } void WorkspaceManager::handle_workspace(ext_workspace_handle_v1 *handle) { @@ -137,13 +137,13 @@ void WorkspaceManager::handle_workspace(ext_workspace_handle_v1 *handle) { const auto new_name = std::to_string(++workspace_name); workspaces_.push_back(std::make_unique(config_, *this, handle, new_id, new_name)); set_needs_sorting(); - spdlog::debug("[wlr/workspaces]: Workspace {} created", new_id); + spdlog::debug("[ext/workspaces]: Workspace {} created", new_id); } void WorkspaceManager::handle_done() { dp.emit(); } void WorkspaceManager::handle_finished() { - spdlog::debug("[wlr/workspaces]: Finishing workspace manager"); + spdlog::debug("[ext/workspaces]: Finishing workspace manager"); ext_workspace_manager_v1_destroy(ext_manager_); ext_manager_ = nullptr; } @@ -151,7 +151,7 @@ void WorkspaceManager::handle_finished() { void WorkspaceManager::commit() const { ext_workspace_manager_v1_commit(ext_manager_); } void WorkspaceManager::update() { - spdlog::debug("[wlr/workspaces]: Updating state"); + spdlog::debug("[ext/workspaces]: Updating state"); if (needs_sorting_) { clear_buttons(); @@ -278,7 +278,7 @@ WorkspaceGroup::~WorkspaceGroup() { if (ext_handle_ != nullptr) { ext_workspace_group_handle_v1_destroy(ext_handle_); } - spdlog::debug("[wlr/workspaces]: Workspace group {} destroyed", id_); + spdlog::debug("[ext/workspaces]: Workspace group {} destroyed", id_); } bool WorkspaceGroup::has_output(const wl_output *output) { @@ -290,10 +290,10 @@ bool WorkspaceGroup::has_workspace(const ext_workspace_handle_v1 *workspace) { } void WorkspaceGroup::handle_capabilities(uint32_t capabilities) { - spdlog::debug("[wlr/workspaces]: Capabilities for workspace group {}:", id_); + spdlog::debug("[ext/workspaces]: Capabilities for workspace group {}:", id_); if ((capabilities & EXT_WORKSPACE_GROUP_HANDLE_V1_GROUP_CAPABILITIES_CREATE_WORKSPACE) == capabilities) { - spdlog::debug("[wlr/workspaces]: create-workspace"); + spdlog::debug("[ext/workspaces]: create-workspace"); } } @@ -318,7 +318,7 @@ void WorkspaceGroup::handle_workspace_leave(ext_workspace_handle_v1 *handle) { } void WorkspaceGroup::handle_removed() { - spdlog::debug("[wlr/workspaces]: Removing workspace group {}", id_); + spdlog::debug("[ext/workspaces]: Removing workspace group {}", id_); workspaces_manager_.remove_workspace_group(id_); } @@ -372,7 +372,7 @@ Workspace::~Workspace() { if (ext_handle_ != nullptr) { ext_workspace_handle_v1_destroy(ext_handle_); } - spdlog::debug("[wlr/workspaces]: Workspace {} destroyed", id_); + spdlog::debug("[ext/workspaces]: Workspace {} destroyed", id_); } bool Workspace::is_active() const { @@ -440,24 +440,24 @@ void Workspace::handle_state(uint32_t state) { } void Workspace::handle_capabilities(uint32_t capabilities) { - spdlog::debug("[wlr/workspaces]: Capabilities for workspace {}:", id_); + spdlog::debug("[ext/workspaces]: Capabilities for workspace {}:", id_); if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE) == capabilities) { - spdlog::debug("[wlr/workspaces]: activate"); + spdlog::debug("[ext/workspaces]: activate"); } if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_DEACTIVATE) == capabilities) { - spdlog::debug("[wlr/workspaces]: deactivate"); + spdlog::debug("[ext/workspaces]: deactivate"); } if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_REMOVE) == capabilities) { - spdlog::debug("[wlr/workspaces]: remove"); + spdlog::debug("[ext/workspaces]: remove"); } if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ASSIGN) == capabilities) { - spdlog::debug("[wlr/workspaces]: assign"); + spdlog::debug("[ext/workspaces]: assign"); } needs_updating_ = true; } void Workspace::handle_removed() { - spdlog::debug("[wlr/workspaces]: Removing workspace {}", id_); + spdlog::debug("[ext/workspaces]: Removing workspace {}", id_); workspace_manager_.remove_workspace(id_); } @@ -480,7 +480,7 @@ bool Workspace::handle_clicked(const GdkEventButton *button) const { } else if (action == "close") { ext_workspace_handle_v1_remove(ext_handle_); } else { - spdlog::warn("[wlr/workspaces]: Unknown action {}", action); + spdlog::warn("[ext/workspaces]: Unknown action {}", action); } workspace_manager_.commit(); return true; @@ -507,4 +507,4 @@ std::string Workspace::icon() { return name_; } -} // namespace waybar::modules::wlr +} // namespace waybar::modules::ext diff --git a/src/modules/wlr/workspace_manager_binding.cpp b/src/modules/ext/workspace_manager_binding.cpp similarity index 97% rename from src/modules/wlr/workspace_manager_binding.cpp rename to src/modules/ext/workspace_manager_binding.cpp index 1333e607..2d9c6b48 100644 --- a/src/modules/wlr/workspace_manager_binding.cpp +++ b/src/modules/ext/workspace_manager_binding.cpp @@ -1,13 +1,13 @@ -#include "modules/wlr/workspace_manager_binding.hpp" +#include "modules/ext/workspace_manager_binding.hpp" #include #include #include "client.hpp" -#include "modules/wlr/workspace_manager.hpp" +#include "modules/ext/workspace_manager.hpp" -namespace waybar::modules::wlr { +namespace waybar::modules::ext { static void handle_global(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { @@ -156,4 +156,4 @@ static const ext_workspace_handle_v1_listener workspace_impl = { void add_workspace_listener(ext_workspace_handle_v1 *workspace_handle, void *data) { ext_workspace_handle_v1_add_listener(workspace_handle, &workspace_impl, data); } -} // namespace waybar::modules::wlr +} // namespace waybar::modules::ext From 984d0de1fc7f92ddd498852be3d546f13cd704c0 Mon Sep 17 00:00:00 2001 From: Jens Peters Date: Fri, 20 Jun 2025 09:50:43 +0200 Subject: [PATCH 304/780] ext/workspaces: introduce ignore-hidden option Hide hidden workspaces by default, but add an option for overriding. While at it, move button visibility handling to Workspaces and only handle add/removal of buttons at WorkspaceManager. This makes it easier to keep track if sorting is needed. --- include/modules/ext/workspace_manager.hpp | 7 ++-- man/waybar-ext-workspaces.5.scd | 5 +++ src/modules/ext/workspace_manager.cpp | 50 +++++++++++------------ 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/include/modules/ext/workspace_manager.hpp b/include/modules/ext/workspace_manager.hpp index 74718848..0607b5ba 100644 --- a/include/modules/ext/workspace_manager.hpp +++ b/include/modules/ext/workspace_manager.hpp @@ -50,7 +50,6 @@ class WorkspaceManager final : public AModule { bool sort_by_id_ = false; bool sort_by_name_ = true; bool sort_by_coordinates_ = false; - bool active_only_ = false; bool all_outputs_ = false; const waybar::Bar &bar_; @@ -100,9 +99,6 @@ class Workspace { std::string &name() { return name_; } std::vector &coordinates() { return coordinates_; } Gtk::Button &button() { return button_; } - bool is_active() const; - bool is_urgent() const; - bool is_hidden() const; void update(); // wl events @@ -117,6 +113,7 @@ class Workspace { bool handle_clicked(const GdkEventButton *button) const; private: + bool has_state(uint32_t state) const { return (state_ & state) == state; } std::string icon(); WorkspaceManager &workspace_manager_; @@ -127,6 +124,8 @@ class Workspace { std::string name_; std::vector coordinates_; + bool active_only_ = false; + bool ignore_hidden_ = true; std::string format_; bool with_icon_ = false; static std::map icon_map_; diff --git a/man/waybar-ext-workspaces.5.scd b/man/waybar-ext-workspaces.5.scd index 9a697cef..54c67be2 100644 --- a/man/waybar-ext-workspaces.5.scd +++ b/man/waybar-ext-workspaces.5.scd @@ -47,6 +47,11 @@ Addressed by *ext/workspaces* default: false ++ If set to true only active or urgent workspaces will be shown. +*ignore-hidden*: ++ + typeof: bool ++ + default: true ++ + If set to false hidden workspaces will be shown. + # FORMAT REPLACEMENTS *{name}*: Name of workspace assigned by compositor. diff --git a/src/modules/ext/workspace_manager.cpp b/src/modules/ext/workspace_manager.cpp index 882aee71..54750bb2 100644 --- a/src/modules/ext/workspace_manager.cpp +++ b/src/modules/ext/workspace_manager.cpp @@ -53,11 +53,6 @@ WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar all_outputs_ = config_all_outputs.asBool(); } - const auto config_active_only = config_["active-only"]; - if (config_active_only.isBool()) { - active_only_ = config_active_only.asBool(); - } - // setup UI box_.set_name("workspaces"); @@ -242,15 +237,11 @@ void WorkspaceManager::update_buttons() { const bool workspace_on_group = group->has_workspace(workspace->handle()); return group_on_output && workspace_on_group; }); - const bool active_or_urgent = workspace->is_active() || workspace->is_urgent(); - const bool valid_workspace_state = (active_or_urgent && active_only_) || !active_only_; - - const bool show_button = workspace_on_any_group_for_output && valid_workspace_state; const bool bar_contains_button = has_button(&workspace->button()); // add or remove buttons if needed, update button state - if (show_button) { + if (workspace_on_any_group_for_output) { if (!bar_contains_button) { // add button to bar box_.pack_start(workspace->button(), false, false); @@ -331,6 +322,16 @@ Workspace::Workspace(const Json::Value &config, WorkspaceManager &manager, // parse configuration + const auto &config_active_only = config["active-only"]; + if (config_active_only.isBool()) { + active_only_ = config_active_only.asBool(); + } + + const auto &config_ignore_hidden = config["ignore-hidden"]; + if (config_ignore_hidden.isBool()) { + ignore_hidden_ = config_ignore_hidden.asBool(); + } + const auto &config_format = config["format"]; format_ = config_format.isString() ? config_format.asString() : "{name}"; with_icon_ = format_.find("{icon}") != std::string::npos; @@ -375,38 +376,33 @@ Workspace::~Workspace() { spdlog::debug("[ext/workspaces]: Workspace {} destroyed", id_); } -bool Workspace::is_active() const { - return (state_ & EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE) == EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE; -} - -bool Workspace::is_urgent() const { - return (state_ & EXT_WORKSPACE_HANDLE_V1_STATE_URGENT) == EXT_WORKSPACE_HANDLE_V1_STATE_URGENT; -} - -bool Workspace::is_hidden() const { - return (state_ & EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN) == EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN; -} - void Workspace::update() { if (!needs_updating_) { return; } - // update style + // 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 (is_active()) { + if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) { + button_.set_visible(true); style_context->add_class("active"); } - if (is_urgent()) { + if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_URGENT)) { + button_.set_visible(true); style_context->add_class("urgent"); } - if (is_hidden()) { + if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN)) { + button_.set_visible(!active_only_ && !ignore_hidden_); style_context->add_class("hidden"); } + if (state_ == 0) { + button_.set_visible(!active_only_); + } // update label label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("name", name_), @@ -487,7 +483,7 @@ bool Workspace::handle_clicked(const GdkEventButton *button) const { } std::string Workspace::icon() { - if (is_active()) { + if (has_state(EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)) { const auto active_icon_it = icon_map_.find("active"); if (active_icon_it != icon_map_.end()) { return active_icon_it->second; From 6c48db6cee75609938e0524fcd59dc1997c03efb Mon Sep 17 00:00:00 2001 From: Jens Peters Date: Mon, 23 Jun 2025 23:02:25 +0200 Subject: [PATCH 305/780] ext/workspaces: log workspace id/name This should help to distinguish between internal and external ID in the logs. --- src/modules/ext/workspace_manager.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/modules/ext/workspace_manager.cpp b/src/modules/ext/workspace_manager.cpp index 54750bb2..5fec3cdb 100644 --- a/src/modules/ext/workspace_manager.cpp +++ b/src/modules/ext/workspace_manager.cpp @@ -281,10 +281,10 @@ bool WorkspaceGroup::has_workspace(const ext_workspace_handle_v1 *workspace) { } void WorkspaceGroup::handle_capabilities(uint32_t capabilities) { - spdlog::debug("[ext/workspaces]: Capabilities for workspace group {}:", id_); + spdlog::debug("[ext/workspaces]: Capabilities for workspace group {}:", id_); if ((capabilities & EXT_WORKSPACE_GROUP_HANDLE_V1_GROUP_CAPABILITIES_CREATE_WORKSPACE) == capabilities) { - spdlog::debug("[ext/workspaces]: create-workspace"); + spdlog::debug("[ext/workspaces]: - create-workspace"); } } @@ -413,12 +413,14 @@ void Workspace::update() { } 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(); @@ -436,18 +438,18 @@ void Workspace::handle_state(uint32_t state) { } void Workspace::handle_capabilities(uint32_t capabilities) { - spdlog::debug("[ext/workspaces]: Capabilities for workspace {}:", id_); + spdlog::debug("[ext/workspaces]: Capabilities for workspace {}:", id_); if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ACTIVATE) == capabilities) { - spdlog::debug("[ext/workspaces]: activate"); + spdlog::debug("[ext/workspaces]: - activate"); } if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_DEACTIVATE) == capabilities) { - spdlog::debug("[ext/workspaces]: deactivate"); + spdlog::debug("[ext/workspaces]: - deactivate"); } if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_REMOVE) == capabilities) { - spdlog::debug("[ext/workspaces]: remove"); + spdlog::debug("[ext/workspaces]: - remove"); } if ((capabilities & EXT_WORKSPACE_HANDLE_V1_WORKSPACE_CAPABILITIES_ASSIGN) == capabilities) { - spdlog::debug("[ext/workspaces]: assign"); + spdlog::debug("[ext/workspaces]: - assign"); } needs_updating_ = true; } From 08a39dd9eb7661b5e2f2cea5970c483821dced1f Mon Sep 17 00:00:00 2001 From: Emiliano Deustua Date: Mon, 30 Jun 2025 10:44:46 -0500 Subject: [PATCH 306/780] 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 e7a4bafede17a8daeed6014b235226cc8558ae87 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 1 Jul 2025 00:14:02 +0000 Subject: [PATCH 307/780] 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/96ec055edbe5ee227f28cdbc3f1ddf1df5965102?narHash=sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg%3D' (2025-05-28) → 'github:NixOS/nixpkgs/30e2e2857ba47844aa71991daa6ed1fc678bcbb7?narHash=sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM%3D' (2025-06-27) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 34545ed4..5c08338e 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1748460289, - "narHash": "sha256-7doLyJBzCllvqX4gszYtmZUToxKvMUrg45EUWaUYmBg=", + "lastModified": 1751011381, + "narHash": "sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "96ec055edbe5ee227f28cdbc3f1ddf1df5965102", + "rev": "30e2e2857ba47844aa71991daa6ed1fc678bcbb7", "type": "github" }, "original": { From b6c13ba58be6eb68e446c4bda9cf981ab20a5b7d Mon Sep 17 00:00:00 2001 From: peelz Date: Tue, 1 Jul 2025 10:37:31 -0400 Subject: [PATCH 308/780] fix: 'ethernet' network state should have precedence over 'disabled' --- src/modules/network.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 955f9f1d..cc096f72 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -272,11 +272,9 @@ void waybar::modules::Network::worker() { const std::string waybar::modules::Network::getNetworkState() const { #ifdef WANT_RFKILL - if (rfkill_.getState()) return "disabled"; + if (rfkill_.getState() && ifid_ == -1) return "disabled"; #endif - if (ifid_ == -1) { - return "disconnected"; - } + if (ifid_ == -1) return "disconnected"; if (!carrier_) return "disconnected"; if (ipaddr_.empty() && ipaddr6_.empty()) return "linked"; if (essid_.empty()) return "ethernet"; From 9ef6dc7380f863573346c25524f8cc7884d381d1 Mon Sep 17 00:00:00 2001 From: Skylar Abruzese Date: Thu, 3 Jul 2025 17:44:37 -0400 Subject: [PATCH 309/780] fix: hyprland named persistent workspaces allowed persistent workspaces to be defined with names instead of just id's --- src/modules/hyprland/workspaces.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index bb03f707..8dcd9b0e 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -253,10 +253,8 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs // value is an array => create defined workspaces for this monitor if (canCreate) { for (const Json::Value &workspace : value) { - if (workspace.isInt()) { - spdlog::debug("Creating workspace {} on monitor {}", workspace, currentMonitor); - persistentWorkspacesToCreate.emplace_back(std::to_string(workspace.asInt())); - } + spdlog::debug("Creating workspace {} on monitor {}", workspace, currentMonitor); + persistentWorkspacesToCreate.emplace_back(workspace.asString()); } } else { // key is the workspace and value is array of monitors to create on From 6d3b93bbf7e6f069a3c3f8539745989938a4dfea Mon Sep 17 00:00:00 2001 From: Skylar Abruzese Date: Thu, 3 Jul 2025 18:48:04 -0400 Subject: [PATCH 310/780] fix: added active workspace matching by name as fallback fixes bug where persistent workspaces would not be marked as active because their id is based on creation time by hyprland and thus we can't consistently match the id's without constantly changing them (this would also cause issues with workspace sorting). --- src/modules/hyprland/workspaces.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 8dcd9b0e..1f61e267 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -942,9 +942,17 @@ bool Workspaces::updateWindowsToCreate() { void Workspaces::updateWorkspaceStates() { const std::vector visibleWorkspaces = getVisibleWorkspaces(); auto updatedWorkspaces = m_ipc.getSocket1JsonReply("workspaces"); + + auto currentWorkspace = m_ipc.getSocket1JsonReply("activeworkspace"); + std::string currentWorkspaceName = + currentWorkspace.isMember("name") ? currentWorkspace["name"].asString() : ""; + for (auto &workspace : m_workspaces) { + bool isActiveByName = + !currentWorkspaceName.empty() && workspace->name() == currentWorkspaceName; + workspace->setActive( - workspace->id() == m_activeWorkspaceId || + workspace->id() == m_activeWorkspaceId || isActiveByName || (workspace->isSpecial() && workspace->name() == m_activeSpecialWorkspaceName)); if (workspace->isActive() && workspace->isUrgent()) { workspace->setUrgent(false); From 10c116e54a3efa7f2956b84017d9eabecffceff2 Mon Sep 17 00:00:00 2001 From: tpaau-17DB Date: Fri, 4 Jul 2025 19:02:10 +0200 Subject: [PATCH 311/780] Change default tooltip for the battery module. --- src/modules/battery.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 44481448..32488d53 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -687,8 +687,11 @@ auto waybar::modules::Battery::update() -> void { std::string tooltip_text_default; std::string tooltip_format = "{timeTo}"; if (time_remaining != 0) { - std::string time_to = std::string("Time to ") + ((time_remaining > 0) ? "empty" : "full"); - tooltip_text_default = time_to + ": " + time_remaining_formatted; + if (time_remaining > 0) { + tooltip_text_default = std::string("Empty in ") + time_remaining_formatted; + } else { + tooltip_text_default = std::string("Full in ") + time_remaining_formatted; + } } else { tooltip_text_default = status_pretty; } From fd670026627dde73a5eab68dc053c7c9e671742e Mon Sep 17 00:00:00 2001 From: Andy Carlson <2yinyang2@gmail.com> Date: Sun, 6 Jul 2025 23:11:00 -0400 Subject: [PATCH 312/780] fix: prevent persistent workspaces from being duplicated if they exist under a different name --- src/modules/hyprland/workspaces.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index bb03f707..49ab885e 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -65,11 +65,15 @@ Json::Value Workspaces::createMonitorWorkspaceData(std::string const &name, void Workspaces::createWorkspace(Json::Value const &workspace_data, Json::Value const &clients_data) { auto workspaceName = workspace_data["name"].asString(); + auto workspaceId = workspace_data["id"].asInt(); spdlog::debug("Creating workspace {}", workspaceName); // avoid recreating existing workspaces auto workspace = - std::ranges::find_if(m_workspaces, [workspaceName](std::unique_ptr const &w) { + std::ranges::find_if(m_workspaces, [&](std::unique_ptr const &w) { + if (workspaceId > 0) { + return w->id() == workspaceId; + } return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) || workspaceName == w->name(); }); From 4d74bb3004b7bc2722ef3b9b1e505606cdb12577 Mon Sep 17 00:00:00 2001 From: xb-bx Date: Sat, 8 Feb 2025 19:16:59 +0200 Subject: [PATCH 313/780] sway/workspaces: implement reverse-scroll --- man/waybar-sway-workspaces.5.scd | 5 +++++ src/modules/sway/workspaces.cpp | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/man/waybar-sway-workspaces.5.scd b/man/waybar-sway-workspaces.5.scd index 1c4360e9..25b59eb9 100644 --- a/man/waybar-sway-workspaces.5.scd +++ b/man/waybar-sway-workspaces.5.scd @@ -31,6 +31,11 @@ Addressed by *sway/workspaces* default: false ++ If set to false, you can scroll to cycle through workspaces. If set to true this behaviour is disabled. +*reverse-scroll*: ++ + typeof: bool ++ + default: false ++ + If set to false, scrolling up will switch to the previous workspace and scrolling down will switch to the next workspace. If set to true, the behavior will be reversed. + *disable-click*: ++ typeof: bool ++ default: false ++ diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 7a8ce571..86e7349e 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -443,10 +443,11 @@ bool Workspaces::handleScroll(GdkEventScroll *e) { if (it == workspaces_.end()) { return true; } + bool reverse_scroll = config_["reverse-scroll"].isBool() && config_["reverse-scroll"].asBool(); if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) { - name = getCycleWorkspace(it, false); + name = getCycleWorkspace(it, reverse_scroll ? true : false); } else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) { - name = getCycleWorkspace(it, true); + name = getCycleWorkspace(it, reverse_scroll ? false : true); } else { return true; } From 04796988e99b1a1d6bb1fcd2b2f0143158f75f38 Mon Sep 17 00:00:00 2001 From: Maarten van Gompel Date: Sat, 26 Apr 2025 13:10:42 +0200 Subject: [PATCH 314/780] feat(river): set layout name as class This allows for custom styling for specific river layouts (such as monocle). --- include/modules/river/layout.hpp | 1 + man/waybar-river-layout.5.scd | 1 + src/modules/river/layout.cpp | 9 ++++++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/include/modules/river/layout.hpp b/include/modules/river/layout.hpp index dc78ee20..89f0c7d9 100644 --- a/include/modules/river/layout.hpp +++ b/include/modules/river/layout.hpp @@ -24,6 +24,7 @@ class Layout : public waybar::ALabel { private: const waybar::Bar &bar_; + std::string name_; struct wl_output *output_; // stores the output this module belongs to struct wl_output *focused_output_; // stores the currently focused output struct zriver_output_status_v1 *output_status_; diff --git a/man/waybar-river-layout.5.scd b/man/waybar-river-layout.5.scd index 78e03634..a259b9ca 100644 --- a/man/waybar-river-layout.5.scd +++ b/man/waybar-river-layout.5.scd @@ -83,6 +83,7 @@ Addressed by *river/layout* - *#layout* - *#layout.focused* Applied when the output this module's bar belongs to is focused. +- *#layout.* Applied when the output this module's bar belongs uses this layout. # SEE ALSO diff --git a/src/modules/river/layout.cpp b/src/modules/river/layout.cpp index e938400f..117b9483 100644 --- a/src/modules/river/layout.cpp +++ b/src/modules/river/layout.cpp @@ -145,9 +145,16 @@ void Layout::handle_name(const char *name) { if (std::strcmp(name, "") == 0 || format_.empty()) { label_.hide(); // hide empty labels or labels with empty format } else { - label_.show(); + if (!name_.empty()) { + label_.get_style_context()->remove_class(name_); + } + + label_.get_style_context()->add_class(name); label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(name).raw())); + label_.show(); + } + name_ = name; ALabel::update(); } From 0cb605b529ddbeb8022e1d953e0c9d185319dee2 Mon Sep 17 00:00:00 2001 From: bilaliscarioth Date: Tue, 8 Jul 2025 02:13:33 +0200 Subject: [PATCH 315/780] start openbsd ports --- src/config.cpp | 17 +++++++++++++++++ src/modules/cpu_frequency/bsd.cpp | 8 ++++++++ src/modules/user.cpp | 8 +++++++- src/modules/wayfire/workspaces.cpp | 12 ++++++------ src/util/css_reload_helper.cpp | 6 ++++++ 5 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/config.cpp b/src/config.cpp index 7096ba89..f7c84192 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -2,7 +2,11 @@ #include #include +#ifndef __OpenBSD__ #include +#else +#include +#endif #include #include @@ -34,6 +38,7 @@ std::vector Config::tryExpandPath(const std::string &base, spdlog::debug("Try expanding: {}", path.string()); std::vector results; +#ifndef __OpenBSD__ wordexp_t p; if (wordexp(path.c_str(), &p, 0) == 0) { for (size_t i = 0; i < p.we_wordc; i++) { @@ -44,6 +49,18 @@ std::vector Config::tryExpandPath(const std::string &base, } wordfree(&p); } +#else + glob_t p; + if (glob(path.c_str(), 0, NULL, &p) == 0) { + for (size_t i = 0; i < p.gl_pathc; i++) { + if (access(p.gl_pathv[i], F_OK) == 0) { + results.emplace_back(p.gl_pathv[i]); + spdlog::debug("Found config file: {}", p.gl_pathv[i]); + } + } + globfree(&p); + } +#endif return results; } diff --git a/src/modules/cpu_frequency/bsd.cpp b/src/modules/cpu_frequency/bsd.cpp index 743fb288..97074474 100644 --- a/src/modules/cpu_frequency/bsd.cpp +++ b/src/modules/cpu_frequency/bsd.cpp @@ -10,6 +10,7 @@ std::vector waybar::modules::CpuFrequency::parseCpuFrequencies() { int32_t freq; uint32_t i = 0; +#ifndef __OpenBSD__ while (true) { len = 4; snprintf(buffer, 256, "dev.cpu.%u.freq", i); @@ -17,6 +18,13 @@ std::vector waybar::modules::CpuFrequency::parseCpuFrequencies() { frequencies.push_back(freq); ++i; } +#else + size_t sz; + int psize, cpuspeed, getMhz[] = {CTL_HW, HW_CPUSPEED}; + sz = sizeof(cpuspeed); + sysctl(getMhz, 2, &cpuspeed, &sz, NULL, 0); + frequencies.push_back((float)cpuspeed); +#endif if (frequencies.empty()) { spdlog::warn("cpu/bsd: parseCpuFrequencies failed, not found in sysctl"); diff --git a/src/modules/user.cpp b/src/modules/user.cpp index 418fc585..3ca5e431 100644 --- a/src/modules/user.cpp +++ b/src/modules/user.cpp @@ -62,7 +62,13 @@ long User::uptime_as_seconds() { #if HAVE_CPU_BSD struct timespec s_info; - if (0 == clock_gettime(CLOCK_UPTIME_PRECISE, &s_info)) { + int flags = 0; +#ifndef __OpenBSD__ + flags = CLOCK_UPTIME_PRECISE; +#else + flags = CLOCK_UPTIME; +#endif + if (0 == clock_gettime(flags, &s_info)) { uptime = s_info.tv_sec; } #endif diff --git a/src/modules/wayfire/workspaces.cpp b/src/modules/wayfire/workspaces.cpp index 6814004e..4c4cd6c1 100644 --- a/src/modules/wayfire/workspaces.cpp +++ b/src/modules/wayfire/workspaces.cpp @@ -74,9 +74,9 @@ auto Workspaces::handleScroll(GdkEventScroll* e) -> bool { const auto& wset = ipc->get_wsets().at(output.wset_idx); auto n = wset.ws_w * wset.ws_h; auto i = (wset.ws_idx() + delta + n) % n; - data["x"] = i % wset.ws_w; - data["y"] = i / wset.ws_h; - data["output-id"] = output.id; + data["x"] = Json::Value((uint64_t)i % wset.ws_w); + data["y"] = Json::Value((uint64_t)i / wset.ws_h); + data["output-id"] = Json::Value((uint64_t)output.id); } ipc->send("vswitch/set-workspace", std::move(data)); @@ -108,9 +108,9 @@ auto Workspaces::update_box() -> void { if (!config_["disable-click"].asBool()) { btn.signal_pressed().connect([=, this] { Json::Value data; - data["x"] = i % ws_w; - data["y"] = i / ws_h; - data["output-id"] = output.id; + data["x"] = Json::Value((uint64_t)i % ws_w); + data["y"] = Json::Value((uint64_t)i / ws_h); + data["output-id"] = Json::Value((uint64_t)output.id); ipc->send("vswitch/set-workspace", std::move(data)); }); } diff --git a/src/util/css_reload_helper.cpp b/src/util/css_reload_helper.cpp index 274bdeed..3a530f61 100644 --- a/src/util/css_reload_helper.cpp +++ b/src/util/css_reload_helper.cpp @@ -2,7 +2,13 @@ #include #include +#ifndef __OpenBSD__ #include +#else +#include +#include +#include +#endif #include #include From d808c00324618c35de30335ca6fd215f6d81860f Mon Sep 17 00:00:00 2001 From: bilaliscarioth Date: Tue, 8 Jul 2025 02:21:48 +0200 Subject: [PATCH 316/780] avoid useless vars on cpu_frequency, mimic sigrtmin/sigrtmax --- include/util/SafeSignal.hpp | 5 +++++ src/modules/cpu_frequency/bsd.cpp | 13 ++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/include/util/SafeSignal.hpp b/include/util/SafeSignal.hpp index 458bc57e..e19542f7 100644 --- a/include/util/SafeSignal.hpp +++ b/include/util/SafeSignal.hpp @@ -11,6 +11,11 @@ #include #include +#ifdef __OpenBSD__ +#define SIGRTMIN SIGUSR1-1 +#define SIGRTMAX SIGUSR1+1 +#endif + namespace waybar { /** diff --git a/src/modules/cpu_frequency/bsd.cpp b/src/modules/cpu_frequency/bsd.cpp index 97074474..eb5ae98a 100644 --- a/src/modules/cpu_frequency/bsd.cpp +++ b/src/modules/cpu_frequency/bsd.cpp @@ -5,12 +5,12 @@ std::vector waybar::modules::CpuFrequency::parseCpuFrequencies() { std::vector frequencies; - char buffer[256]; size_t len; int32_t freq; - uint32_t i = 0; #ifndef __OpenBSD__ + char buffer[256]; + uint32_t i = 0; while (true) { len = 4; snprintf(buffer, 256, "dev.cpu.%u.freq", i); @@ -19,11 +19,10 @@ std::vector waybar::modules::CpuFrequency::parseCpuFrequencies() { ++i; } #else - size_t sz; - int psize, cpuspeed, getMhz[] = {CTL_HW, HW_CPUSPEED}; - sz = sizeof(cpuspeed); - sysctl(getMhz, 2, &cpuspeed, &sz, NULL, 0); - frequencies.push_back((float)cpuspeed); + int getMhz[] = {CTL_HW, HW_CPUSPEED}; + len = sizeof(freq); + sysctl(getMhz, 2, &freq, &len, NULL, 0); + frequencies.push_back((float)freq); #endif if (frequencies.empty()) { From be819be8bd622db9ba6b76e19852c74b3479d131 Mon Sep 17 00:00:00 2001 From: Emiliano Deustua Date: Mon, 7 Jul 2025 22:10:09 -0500 Subject: [PATCH 317/780] 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 310a473e65c4a0e0dd380f1a6ece0e2cc5774d36 Mon Sep 17 00:00:00 2001 From: hritix Date: Wed, 9 Jul 2025 22:50:03 +0530 Subject: [PATCH 318/780] enabled markup support for tooltip of battery and pulseaudio modules --- src/modules/battery.cpp | 2 +- src/modules/pulseaudio.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 32488d53..3d0da330 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -704,7 +704,7 @@ auto waybar::modules::Battery::update() -> void { } else if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text( + label_.set_tooltip_markup( fmt::format(fmt::runtime(tooltip_format), fmt::arg("timeTo", tooltip_text_default), fmt::arg("power", power), fmt::arg("capacity", capacity), fmt::arg("time", time_remaining_formatted), fmt::arg("cycles", cycles), diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index 255ca571..ceed20dd 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -132,7 +132,7 @@ auto waybar::modules::Pulseaudio::update() -> void { tooltip_format = config_["tooltip-format"].asString(); } if (!tooltip_format.empty()) { - label_.set_tooltip_text(fmt::format( + label_.set_tooltip_markup(fmt::format( fmt::runtime(tooltip_format), fmt::arg("desc", sink_desc), fmt::arg("volume", sink_volume), fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume), fmt::arg("source_desc", source_desc), From 46a152abc8fe006be2d7a48e85d8c42d8aabffb8 Mon Sep 17 00:00:00 2001 From: peelz Date: Tue, 1 Jul 2025 23:40:42 -0400 Subject: [PATCH 319/780] fix(network): display as disabled if rfkill and no carrier We want the "disabled" state even when an interface is selected explicitly. --- src/modules/network.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index cc096f72..6548e420 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -271,11 +271,12 @@ void waybar::modules::Network::worker() { } const std::string waybar::modules::Network::getNetworkState() const { + if (ifid_ == -1 || !carrier_) { #ifdef WANT_RFKILL - if (rfkill_.getState() && ifid_ == -1) return "disabled"; + if (rfkill_.getState()) return "disabled"; #endif - if (ifid_ == -1) return "disconnected"; - if (!carrier_) return "disconnected"; + return "disconnected"; + } if (ipaddr_.empty() && ipaddr6_.empty()) return "linked"; if (essid_.empty()) return "ethernet"; return "wifi"; From 0e07c7ac5c61abcf842106a4258ef823b9cefcc6 Mon Sep 17 00:00:00 2001 From: peelz Date: Tue, 1 Jul 2025 23:40:42 -0400 Subject: [PATCH 320/780] feat(network): add rfkill setting This setting makes it possible to have a configuration with two network modules where one of them displays the ethernet state (disconnected, linked, ethernet), and the other, the wifi state (disabled, disconnected, linked, wifi). Otherwise the ethernet state would show up as "disabled" (instead of "disconnected") when rfkill is active. --- man/waybar-network.5.scd | 9 +++++++-- src/modules/network.cpp | 6 +++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/man/waybar-network.5.scd b/man/waybar-network.5.scd index 3b63e3ee..11cf18f0 100644 --- a/man/waybar-network.5.scd +++ b/man/waybar-network.5.scd @@ -16,6 +16,11 @@ Addressed by *network* typeof: string ++ Use the defined interface instead of auto-detection. Accepts wildcard. +*rfkill*: ++ + typeof: bool ++ + default: true ++ + If enabled, the *disabled* format will be used when rfkill is blocking wlan interfaces. + *interval*: ++ typeof: integer ++ default: 60 ++ @@ -49,7 +54,7 @@ Addressed by *network* *format-disabled*: ++ typeof: string ++ - This format is used when the displayed interface is disabled. + This format is used when rfkill is blocking wlan interfaces. *format-icons*: ++ typeof: array/object ++ @@ -127,7 +132,7 @@ Addressed by *network* *tooltip-format-disabled*: ++ typeof: string ++ - This format is used when the displayed interface is disabled. + This format is used when rfkill is blocking wlan interfaces. *menu*: ++ typeof: string ++ diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 6548e420..ca441bd5 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -273,7 +273,11 @@ void waybar::modules::Network::worker() { const std::string waybar::modules::Network::getNetworkState() const { if (ifid_ == -1 || !carrier_) { #ifdef WANT_RFKILL - if (rfkill_.getState()) return "disabled"; + bool display_rfkill = true; + if (config_["rfkill"].isBool()) { + display_rfkill = config_["rfkill"].asBool(); + } + if (rfkill_.getState() && display_rfkill) return "disabled"; #endif return "disconnected"; } From f991af2893aabe1fb71495698cbc582048805c6b Mon Sep 17 00:00:00 2001 From: peelz Date: Tue, 1 Jul 2025 23:41:22 -0400 Subject: [PATCH 321/780] style(network): fix trailing white space --- man/waybar-network.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/waybar-network.5.scd b/man/waybar-network.5.scd index 11cf18f0..9e146ac0 100644 --- a/man/waybar-network.5.scd +++ b/man/waybar-network.5.scd @@ -162,7 +162,7 @@ Addressed by *network* *{netmask}*: The subnetmask corresponding to the IP(V4). -*{netmask6}*: The subnetmask corresponding to the IP(V6). +*{netmask6}*: The subnetmask corresponding to the IP(V6). *{cidr}*: The subnetmask corresponding to the IP(V4) in CIDR notation. From 2dfbaabf31eeb8637380f2870db18017347bf8e6 Mon Sep 17 00:00:00 2001 From: peelz Date: Thu, 3 Jul 2025 15:13:37 -0400 Subject: [PATCH 322/780] fix(network): use atomic_bool to store the rfkill state --- include/util/rfkill.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/util/rfkill.hpp b/include/util/rfkill.hpp index f620db53..47ab729b 100644 --- a/include/util/rfkill.hpp +++ b/include/util/rfkill.hpp @@ -5,6 +5,8 @@ #include #include +#include + namespace waybar::util { class Rfkill : public sigc::trackable { @@ -17,7 +19,7 @@ class Rfkill : public sigc::trackable { private: enum rfkill_type rfkill_type_; - bool state_ = false; + std::atomic_bool state_ = false; int fd_ = -1; bool on_event(Glib::IOCondition cond); From b02694caef5bdb4509eaf5d5db0593114ce739fa Mon Sep 17 00:00:00 2001 From: peelz Date: Thu, 3 Jul 2025 16:35:18 -0400 Subject: [PATCH 323/780] fix(network): initialize all fields Some fields were previously uninitialized (e.g. carrier), which could lead to UB. --- include/modules/network.hpp | 44 ++++++++++++++++++------------------- src/modules/network.cpp | 20 +---------------- 2 files changed, 23 insertions(+), 41 deletions(-) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index 5fd0c180..8e80d5aa 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -27,8 +27,8 @@ class Network : public ALabel { auto update() -> void override; private: - static const uint8_t MAX_RETRY = 5; - static const uint8_t EPOLL_MAX = 200; + static const uint8_t MAX_RETRY{5}; + static const uint8_t EPOLL_MAX{200}; static int handleEvents(struct nl_msg*, void*); static int handleEventsDone(struct nl_msg*, void*); @@ -51,37 +51,37 @@ class Network : public ALabel { bool wildcardMatch(const std::string& pattern, const std::string& text) const; std::optional> readBandwidthUsage(); - int ifid_; - ip_addr_pref addr_pref_; - struct sockaddr_nl nladdr_ = {0}; - struct nl_sock* sock_ = nullptr; - struct nl_sock* ev_sock_ = nullptr; - int efd_; - int ev_fd_; - int nl80211_id_; + int ifid_{-1}; + ip_addr_pref addr_pref_{ip_addr_pref::IPV4}; + struct sockaddr_nl nladdr_{0}; + struct nl_sock* sock_{nullptr}; + struct nl_sock* ev_sock_{nullptr}; + int efd_{-1}; + int ev_fd_{-1}; + int nl80211_id_{-1}; std::mutex mutex_; - bool want_route_dump_; - bool want_link_dump_; - bool want_addr_dump_; - bool dump_in_progress_; - bool is_p2p_; + bool want_route_dump_{false}; + bool want_link_dump_{false}; + bool want_addr_dump_{false}; + bool dump_in_progress_{false}; + bool is_p2p_{false}; - unsigned long long bandwidth_down_total_; - unsigned long long bandwidth_up_total_; + unsigned long long bandwidth_down_total_{0}; + unsigned long long bandwidth_up_total_{0}; std::string state_; std::string essid_; std::string bssid_; - bool carrier_; + bool carrier_{false}; std::string ifname_; std::string ipaddr_; std::string ipaddr6_; std::string gwaddr_; std::string netmask_; std::string netmask6_; - int cidr_; - int cidr6_; + int cidr_{0}; + int cidr6_{0}; int32_t signal_strength_dbm_; uint8_t signal_strength_; std::string signal_strength_app_; @@ -90,9 +90,9 @@ class Network : public ALabel { util::SleeperThread thread_; util::SleeperThread thread_timer_; #ifdef WANT_RFKILL - util::Rfkill rfkill_; + util::Rfkill rfkill_{RFKILL_TYPE_WLAN}; #endif - float frequency_; + float frequency_{0}; }; } // namespace waybar::modules diff --git a/src/modules/network.cpp b/src/modules/network.cpp index ca441bd5..35ba18fd 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -78,25 +78,7 @@ waybar::modules::Network::readBandwidthUsage() { } waybar::modules::Network::Network(const std::string &id, const Json::Value &config) - : ALabel(config, "network", id, DEFAULT_FORMAT, 60), - ifid_(-1), - addr_pref_(IPV4), - efd_(-1), - ev_fd_(-1), - want_route_dump_(false), - want_link_dump_(false), - want_addr_dump_(false), - dump_in_progress_(false), - is_p2p_(false), - cidr_(0), - cidr6_(0), - signal_strength_dbm_(0), - signal_strength_(0), -#ifdef WANT_RFKILL - rfkill_{RFKILL_TYPE_WLAN}, -#endif - frequency_(0.0) { - + : ALabel(config, "network", id, DEFAULT_FORMAT, 60) { // Start with some "text" in the module's label_. update() will then // update it. Since the text should be different, update() will be able // to show or hide the event_box_. This is to work around the case where From b85f0c17c39eb5f294b862bfe0c9d5ad08e1fd42 Mon Sep 17 00:00:00 2001 From: peelz Date: Thu, 3 Jul 2025 16:36:25 -0400 Subject: [PATCH 324/780] fix(network): ignore carrier state when the interface is down I'm not sure why, but my network card (mt7921e) sometimes will report having a carrier when the interface is down. This usually happens when rfkill was active before rebooting. --- src/modules/network.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 35ba18fd..9271aecb 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -494,6 +494,11 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { net->ifname_ = new_ifname; net->ifid_ = ifi->ifi_index; if (ifi->ifi_flags & IFF_POINTOPOINT) net->is_p2p_ = true; + if ((ifi->ifi_flags & IFF_UP) == 0) { + // With some network drivers (e.g. mt7921e), the interface may + // report having a carrier even though interface is down. + carrier = false; + } if (carrier.has_value()) { net->carrier_ = carrier.value(); } From 73d9c5f560e2b8b710402848faa1e1f99abe8841 Mon Sep 17 00:00:00 2001 From: peelz Date: Thu, 3 Jul 2025 16:46:43 -0400 Subject: [PATCH 325/780] fix(network): reset is_p2p_ in clearIface --- src/modules/network.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 9271aecb..94c77a69 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -405,6 +405,7 @@ void waybar::modules::Network::clearIface() { netmask_.clear(); netmask6_.clear(); carrier_ = false; + is_p2p_ = false; cidr_ = 0; cidr6_ = 0; signal_strength_dbm_ = 0; From 19360462bac545d372c84c43ca01555a4cb34489 Mon Sep 17 00:00:00 2001 From: Nick Raffaele Date: Sat, 12 Jul 2025 09:45:14 -0700 Subject: [PATCH 326/780] respect gtk color schema variant for gtk css variable --- src/client.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/client.cpp b/src/client.cpp index e363f236..946780db 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -151,15 +151,19 @@ void waybar::Client::handleDeferredMonitorRemoval(Glib::RefPtr mon const std::string waybar::Client::getStyle(const std::string &style, std::optional appearance = std::nullopt) { + auto gtk_settings = Gtk::Settings::get_default(); std::optional css_file; + if (style.empty()) { std::vector search_files; switch (appearance.value_or(portal->getAppearance())) { case waybar::Appearance::LIGHT: search_files.emplace_back("style-light.css"); + gtk_settings->property_gtk_application_prefer_dark_theme() = false; break; case waybar::Appearance::DARK: search_files.emplace_back("style-dark.css"); + gtk_settings->property_gtk_application_prefer_dark_theme() = true; break; case waybar::Appearance::UNKNOWN: break; @@ -169,9 +173,11 @@ const std::string waybar::Client::getStyle(const std::string &style, } else { css_file = style; } + if (!css_file) { throw std::runtime_error("Missing required resource files"); } + spdlog::info("Using CSS file {}", css_file.value()); return css_file.value(); }; From f91889ce1cb0805e9474910754f81a183a49677d Mon Sep 17 00:00:00 2001 From: bilaliscarioth <98892516+bilaliscarioth@users.noreply.github.com> Date: Sun, 13 Jul 2025 14:01:59 +0200 Subject: [PATCH 327/780] format SafeSignal.hpp as the linters want --- include/util/SafeSignal.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/util/SafeSignal.hpp b/include/util/SafeSignal.hpp index e19542f7..340f74ee 100644 --- a/include/util/SafeSignal.hpp +++ b/include/util/SafeSignal.hpp @@ -12,8 +12,8 @@ #include #ifdef __OpenBSD__ -#define SIGRTMIN SIGUSR1-1 -#define SIGRTMAX SIGUSR1+1 +#define SIGRTMIN SIGUSR1 - 1 +#define SIGRTMAX SIGUSR1 + 1 #endif namespace waybar { From 682fc34c1d59113b30a54c9d937da2b79a699d63 Mon Sep 17 00:00:00 2001 From: bilaliscarioth <98892516+bilaliscarioth@users.noreply.github.com> Date: Sun, 13 Jul 2025 14:03:09 +0200 Subject: [PATCH 328/780] fix linters for css_reload_helper.cpp --- src/util/css_reload_helper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/css_reload_helper.cpp b/src/util/css_reload_helper.cpp index 3a530f61..06381d60 100644 --- a/src/util/css_reload_helper.cpp +++ b/src/util/css_reload_helper.cpp @@ -5,9 +5,9 @@ #ifndef __OpenBSD__ #include #else -#include #include #include +#include #endif #include From 061c5617628e14be4199add47880f7330e61c294 Mon Sep 17 00:00:00 2001 From: csskevin Date: Tue, 15 Jul 2025 00:19:55 +0200 Subject: [PATCH 329/780] adding dependency command for arch --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 5266e916..4edf2253 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,36 @@ sudo apt install \ libxkbregistry-dev ``` +On Arch, you can use this command: + +``` +pacman -S \ + gtkmm3 \ + jsoncpp \ + libsigc++ \ + fmt \ + wayland \ + chrono-date \ + spdlog \ + gtk3 \ + gobject-introspection \ + libgirepository \ + libpulse \ + libnl \ + libappindicator-gtk3 \ + libdbusmenu-gtk3 \ + libmpdclient \ + sndio \ + libevdev \ + libxkbcommon \ + upower \ + meson \ + cmake \ + scdoc \ + wayland-protocols \ + glib2-devel +``` + Contributions welcome!
Have fun :)
From e45883088d5d75515632729157231fdd8ac0e4a5 Mon Sep 17 00:00:00 2001 From: arnaud-ma Date: Sat, 19 Jul 2025 03:18:48 +0200 Subject: [PATCH 330/780] hyprland: Remove redundant workspace rules loading --- include/modules/hyprland/workspaces.hpp | 1 - src/modules/hyprland/workspaces.cpp | 48 +++++-------------------- 2 files changed, 9 insertions(+), 40 deletions(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 9a44e594..4f9eda47 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -118,7 +118,6 @@ class Workspaces : public AModule, public EventHandler { void initializeWorkspaces(); void setCurrentMonitorId(); void loadPersistentWorkspacesFromConfig(Json::Value const& clientsJson); - void loadPersistentWorkspacesFromWorkspaceRules(const Json::Value& clientsJson); bool m_allOutputs = false; bool m_showSpecial = false; diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 06a6ed3c..739f0a9d 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -71,12 +71,12 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data, // avoid recreating existing workspaces auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr const &w) { - if (workspaceId > 0) { - return w->id() == workspaceId; - } - return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) || - workspaceName == w->name(); - }); + if (workspaceId > 0) { + return w->id() == workspaceId; + } + return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) || + workspaceName == w->name(); + }); if (workspace != m_workspaces.end()) { // don't recreate workspace, but update persistency if necessary @@ -206,8 +206,9 @@ void Workspaces::initializeWorkspaces() { // a persistent workspace config is defined, so use that instead of workspace rules loadPersistentWorkspacesFromConfig(clientsJson); } - // load Hyprland's workspace rules - loadPersistentWorkspacesFromWorkspaceRules(clientsJson); + // persistent workspaces configured with hyprland's workspace rules + // are already listed in 'workspacesJson', so we don't need to + // load them again. } bool isDoubleSpecial(std::string const &workspace_name) { @@ -282,37 +283,6 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs } } -void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &clientsJson) { - spdlog::info("Loading persistent workspaces from Hyprland workspace rules"); - - auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules"); - for (Json::Value const &rule : workspaceRules) { - if (!rule["workspaceString"].isString()) { - spdlog::warn("Workspace rules: invalid workspaceString, skipping: {}", rule); - continue; - } - if (!rule["persistent"].asBool()) { - continue; - } - auto const &workspace = rule.isMember("defaultName") ? rule["defaultName"].asString() - : rule["workspaceString"].asString(); - auto const &monitor = rule["monitor"].asString(); - // create this workspace persistently if: - // 1. the allOutputs config option is enabled - // 2. the rule's monitor is the current monitor - // 3. no monitor is specified in the rule => assume it needs to be persistent on every monitor - if (allOutputs() || m_bar.output->name == monitor || monitor.empty()) { - // => persistent workspace should be shown on this monitor - auto workspaceData = createMonitorWorkspaceData(workspace, m_bar.output->name); - workspaceData["persistent-rule"] = true; - m_workspacesToCreate.emplace_back(workspaceData, clientsJson); - } else { - // This can be any workspace selector. - m_workspacesToRemove.emplace_back(workspace); - } - } -} - void Workspaces::onEvent(const std::string &ev) { std::lock_guard lock(m_mutex); std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>')); From 64ed2cd97015f7c8ea6aad39838e36fe35acfcfd Mon Sep 17 00:00:00 2001 From: arnaud-ma Date: Sat, 19 Jul 2025 03:23:32 +0200 Subject: [PATCH 331/780] fix indentation --- src/modules/hyprland/workspaces.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 739f0a9d..19768c59 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -71,12 +71,12 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data, // avoid recreating existing workspaces auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr const &w) { - if (workspaceId > 0) { - return w->id() == workspaceId; - } - return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) || - workspaceName == w->name(); - }); + if (workspaceId > 0) { + return w->id() == workspaceId; + } + return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) || + workspaceName == w->name(); + }); if (workspace != m_workspaces.end()) { // don't recreate workspace, but update persistency if necessary From a4f200cdb5e470ef789124fa40461fb5b42cc260 Mon Sep 17 00:00:00 2001 From: arnaud-ma Date: Sat, 19 Jul 2025 04:47:31 +0200 Subject: [PATCH 332/780] revert --- include/modules/hyprland/workspaces.hpp | 2 +- src/modules/hyprland/workspaces.cpp | 53 +++++++++++++++++++------ 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 4f9eda47..cd3ccca5 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -118,7 +118,7 @@ class Workspaces : public AModule, public EventHandler { void initializeWorkspaces(); void setCurrentMonitorId(); void loadPersistentWorkspacesFromConfig(Json::Value const& clientsJson); - + void loadPersistentWorkspacesFromWorkspaceRules(const Json::Value& clientsJson); bool m_allOutputs = false; bool m_showSpecial = false; bool m_activeOnly = false; diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 19768c59..cb422d35 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -69,14 +69,13 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data, spdlog::debug("Creating workspace {}", workspaceName); // avoid recreating existing workspaces - auto workspace = - std::ranges::find_if(m_workspaces, [&](std::unique_ptr const &w) { - if (workspaceId > 0) { - return w->id() == workspaceId; - } - return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) || - workspaceName == w->name(); - }); + auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr const &w) { + if (workspaceId > 0) { + return w->id() == workspaceId; + } + return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) || + workspaceName == w->name(); + }); if (workspace != m_workspaces.end()) { // don't recreate workspace, but update persistency if necessary @@ -206,9 +205,8 @@ void Workspaces::initializeWorkspaces() { // a persistent workspace config is defined, so use that instead of workspace rules loadPersistentWorkspacesFromConfig(clientsJson); } - // persistent workspaces configured with hyprland's workspace rules - // are already listed in 'workspacesJson', so we don't need to - // load them again. + // load Hyprland's workspace rules + loadPersistentWorkspacesFromWorkspaceRules(clientsJson); } bool isDoubleSpecial(std::string const &workspace_name) { @@ -283,6 +281,37 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs } } +void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &clientsJson) { + spdlog::info("Loading persistent workspaces from Hyprland workspace rules"); + + auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules"); + for (Json::Value const &rule : workspaceRules) { + if (!rule["workspaceString"].isString()) { + spdlog::warn("Workspace rules: invalid workspaceString, skipping: {}", rule); + continue; + } + if (!rule["persistent"].asBool()) { + continue; + } + auto const &workspace = rule.isMember("defaultName") ? rule["defaultName"].asString() + : rule["workspaceString"].asString(); + auto const &monitor = rule["monitor"].asString(); + // create this workspace persistently if: + // 1. the allOutputs config option is enabled + // 2. the rule's monitor is the current monitor + // 3. no monitor is specified in the rule => assume it needs to be persistent on every monitor + if (allOutputs() || m_bar.output->name == monitor || monitor.empty()) { + // => persistent workspace should be shown on this monitor + auto workspaceData = createMonitorWorkspaceData(workspace, m_bar.output->name); + workspaceData["persistent-rule"] = true; + m_workspacesToCreate.emplace_back(workspaceData, clientsJson); + } else { + // This can be any workspace selector. + m_workspacesToRemove.emplace_back(workspace); + } + } +} + void Workspaces::onEvent(const std::string &ev) { std::lock_guard lock(m_mutex); std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>')); @@ -1004,4 +1033,4 @@ std::optional Workspaces::parseWorkspaceId(std::string const &workspaceIdSt } } -} // namespace waybar::modules::hyprland +} // namespace waybar::modules::hyprland \ No newline at end of file From 2dc2b5ccfd368a3cd2fb27d2d1ece05bf0582441 Mon Sep 17 00:00:00 2001 From: arnaud-ma Date: Sat, 19 Jul 2025 04:50:59 +0200 Subject: [PATCH 333/780] fix #4307 --- src/modules/hyprland/workspaces.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index cb422d35..79838ffc 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -293,8 +293,11 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c if (!rule["persistent"].asBool()) { continue; } - auto const &workspace = rule.isMember("defaultName") ? rule["defaultName"].asString() + auto workspace = rule.isMember("defaultName") ? rule["defaultName"].asString() : rule["workspaceString"].asString(); + if (workspace.starts_with("name:")) { + workspace = workspace.substr(5); + } auto const &monitor = rule["monitor"].asString(); // create this workspace persistently if: // 1. the allOutputs config option is enabled From 8dcdd97879f4fe84c97d0ac5a12c4f89c4d98606 Mon Sep 17 00:00:00 2001 From: arnaud-ma Date: Sat, 19 Jul 2025 05:01:29 +0200 Subject: [PATCH 334/780] small fixes --- include/modules/hyprland/workspaces.hpp | 1 + src/modules/hyprland/workspaces.cpp | 17 ++++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index cd3ccca5..9a44e594 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -119,6 +119,7 @@ class Workspaces : public AModule, public EventHandler { void setCurrentMonitorId(); void loadPersistentWorkspacesFromConfig(Json::Value const& clientsJson); void loadPersistentWorkspacesFromWorkspaceRules(const Json::Value& clientsJson); + bool m_allOutputs = false; bool m_showSpecial = false; bool m_activeOnly = false; diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 79838ffc..b6f1fbc0 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -69,13 +69,14 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data, spdlog::debug("Creating workspace {}", workspaceName); // avoid recreating existing workspaces - auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr const &w) { - if (workspaceId > 0) { - return w->id() == workspaceId; - } - return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) || - workspaceName == w->name(); - }); + auto workspace = + std::ranges::find_if(m_workspaces, [&](std::unique_ptr const &w) { + if (workspaceId > 0) { + return w->id() == workspaceId; + } + return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) || + workspaceName == w->name(); + }); if (workspace != m_workspaces.end()) { // don't recreate workspace, but update persistency if necessary @@ -295,6 +296,8 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c } auto workspace = rule.isMember("defaultName") ? rule["defaultName"].asString() : rule["workspaceString"].asString(); + + // The prefix "name:" cause mismatches with workspace names taken anywhere else. if (workspace.starts_with("name:")) { workspace = workspace.substr(5); } From b4d95b405cba2700aa3c9d1c4e4110c51555c5c9 Mon Sep 17 00:00:00 2001 From: peelz Date: Sun, 20 Jul 2025 22:40:29 -0400 Subject: [PATCH 335/780] style: clang-format --- src/modules/hyprland/workspaces.cpp | 17 ++++++++--------- src/modules/sni/item.cpp | 1 - 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index b6f1fbc0..cfc62025 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -69,14 +69,13 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data, spdlog::debug("Creating workspace {}", workspaceName); // avoid recreating existing workspaces - auto workspace = - std::ranges::find_if(m_workspaces, [&](std::unique_ptr const &w) { - if (workspaceId > 0) { - return w->id() == workspaceId; - } - return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) || - workspaceName == w->name(); - }); + auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr const &w) { + if (workspaceId > 0) { + return w->id() == workspaceId; + } + return (workspaceName.starts_with("special:") && workspaceName.substr(8) == w->name()) || + workspaceName == w->name(); + }); if (workspace != m_workspaces.end()) { // don't recreate workspace, but update persistency if necessary @@ -295,7 +294,7 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c continue; } auto workspace = rule.isMember("defaultName") ? rule["defaultName"].asString() - : rule["workspaceString"].asString(); + : rule["workspaceString"].asString(); // The prefix "name:" cause mismatches with workspace names taken anywhere else. if (workspace.starts_with("name:")) { diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 69417f62..4e80eba7 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -1,4 +1,3 @@ -#include #include "modules/sni/item.hpp" #include From bb190409deeef4a5885c8d190ff6059867c623cc Mon Sep 17 00:00:00 2001 From: Erik Alonso Date: Mon, 21 Jul 2025 19:04:37 +0100 Subject: [PATCH 336/780] fix: unable to disable upower's tooltip --- src/modules/upower.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/modules/upower.cpp b/src/modules/upower.cpp index 4b832b7e..4844582f 100644 --- a/src/modules/upower.cpp +++ b/src/modules/upower.cpp @@ -10,7 +10,6 @@ UPower::UPower(const std::string &id, const Json::Value &config) : AIconLabel(config, "upower", id, "{percentage}", 0, true, true, true), sleeping_{false} { box_.set_name(name_); box_.set_spacing(0); - box_.set_has_tooltip(AModule::tooltipEnabled()); // Tooltip box contentBox_.set_orientation((box_.get_orientation() == Gtk::ORIENTATION_HORIZONTAL) ? Gtk::ORIENTATION_VERTICAL @@ -70,8 +69,10 @@ UPower::UPower(const std::string &id, const Json::Value &config) g_signal_connect(upClient_, "device-removed", G_CALLBACK(deviceRemoved_cb), this); // Subscribe tooltip query events - box_.set_has_tooltip(); - box_.signal_query_tooltip().connect(sigc::mem_fun(*this, &UPower::queryTooltipCb), false); + box_.set_has_tooltip(AModule::tooltipEnabled()); + if (AModule::tooltipEnabled()) { + box_.signal_query_tooltip().connect(sigc::mem_fun(*this, &UPower::queryTooltipCb), false); + } resetDevices(); setDisplayDevice(); From 91e57376ef8b8c6da245ef71a03af3cd59dd2d85 Mon Sep 17 00:00:00 2001 From: Henrique Oliveira Date: Wed, 23 Jul 2025 12:18:22 +0200 Subject: [PATCH 337/780] Fix: WirePlumber module - states not working --- src/modules/wireplumber.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index 106ca403..ba37a51b 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -340,12 +340,21 @@ auto waybar::modules::Wireplumber::update() -> void { } int vol = round(volume_ * 100.0); + + // Get the state and apply state-specific format if available + auto state = getState(vol); + if (!state.empty()) { + std::string format_name = muted_ ? "format-muted" : "format"; + std::string state_format_name = format_name + "-" + state; + if (config_[state_format_name].isString()) { + format = config_[state_format_name].asString(); + } + } + std::string markup = fmt::format(fmt::runtime(format), fmt::arg("node_name", node_name_), fmt::arg("volume", vol), fmt::arg("icon", getIcon(vol))); label_.set_markup(markup); - getState(vol); - if (tooltipEnabled()) { if (tooltipFormat.empty() && config_["tooltip-format"].isString()) { tooltipFormat = config_["tooltip-format"].asString(); From 7cb1a0e3d6517ccadf677551be2ef41e60262f60 Mon Sep 17 00:00:00 2001 From: Henrique Oliveira Date: Wed, 23 Jul 2025 13:19:53 +0200 Subject: [PATCH 338/780] Add: Wireplumber - support for microphone --- include/modules/wireplumber.hpp | 5 ++ src/modules/wireplumber.cpp | 153 ++++++++++++++++++++++---------- 2 files changed, 110 insertions(+), 48 deletions(-) diff --git a/include/modules/wireplumber.hpp b/include/modules/wireplumber.hpp index e0033e8a..047c9545 100644 --- a/include/modules/wireplumber.hpp +++ b/include/modules/wireplumber.hpp @@ -22,6 +22,7 @@ class Wireplumber : public ALabel { void activatePlugins(); static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id); static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id); + static void updateSourceVolume(waybar::modules::Wireplumber* self, uint32_t id); static void onPluginActivated(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self); static void onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self); @@ -47,6 +48,10 @@ class Wireplumber : public ALabel { uint32_t node_id_{0}; std::string node_name_; gchar* type_; + uint32_t source_node_id_; + bool source_muted_; + double source_volume_; + gchar* default_source_name_; }; } // namespace waybar::modules diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index ba37a51b..6260088a 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -14,11 +14,15 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val mixer_api_(nullptr), def_nodes_api_(nullptr), default_node_name_(nullptr), + default_source_name_(nullptr), pending_plugins_(0), muted_(false), + source_muted_(false), volume_(0.0), + source_volume_(0.0), min_step_(0.0), node_id_(0), + source_node_id_(0), type_(nullptr) { waybar::modules::Wireplumber::modules.push_back(this); @@ -55,6 +59,7 @@ waybar::modules::Wireplumber::~Wireplumber() { g_clear_object(&mixer_api_); g_clear_object(&def_nodes_api_); g_free(default_node_name_); + g_free(default_source_name_); g_free(type_); } @@ -120,6 +125,29 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se self->dp.emit(); } +void waybar::modules::Wireplumber::updateSourceVolume(waybar::modules::Wireplumber* self, uint32_t id) { + spdlog::debug("[{}]: updating source volume", self->name_); + GVariant* variant = nullptr; + + if (!isValidNodeId(id)) { + spdlog::error("[{}]: '{}' is not a valid source node ID. Ignoring source volume update.", self->name_, id); + return; + } + + g_signal_emit_by_name(self->mixer_api_, "get-volume", id, &variant); + + if (variant == nullptr) { + spdlog::debug("[{}]: Source node {} does not support volume", self->name_, id); + return; + } + + g_variant_lookup(variant, "volume", "d", &self->source_volume_); + g_variant_lookup(variant, "mute", "b", &self->source_muted_); + g_clear_pointer(&variant, g_variant_unref); + + self->dp.emit(); +} + void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id) { g_autoptr(WpNode) node = static_cast(wp_object_manager_lookup( self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, nullptr)); @@ -127,9 +155,9 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* if (node == nullptr) { // log a warning only if no other widget is targeting the id. // this reduces log spam when multiple instances of the module are used on different node types. - if (id != self->node_id_) { + if (id != self->node_id_ && id != self->source_node_id_) { for (auto const& module : waybar::modules::Wireplumber::modules) { - if (module->node_id_ == id) { + if (module->node_id_ == id || module->source_node_id_ == id) { return; } } @@ -142,68 +170,72 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* const gchar* name = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name"); - if (self->node_id_ != id) { - spdlog::debug( - "[{}]: (onMixerChanged: {}) - ignoring mixer update for node: id: {}, name: {} as it is " - "not the default node: {} with id: {}", - self->name_, self->type_, id, name, self->default_node_name_, self->node_id_); - return; + if (self->node_id_ == id) { + spdlog::debug("[{}]: (onMixerChanged: {}) - updating sink volume for node: {}", + self->name_, self->type_, name); + updateVolume(self, id); + } else if (self->source_node_id_ == id) { + spdlog::debug("[{}]: (onMixerChanged: {}) - updating source volume for node: {}", + self->name_, self->type_, name); + updateSourceVolume(self, id); } - - spdlog::debug( - "[{}]: (onMixerChanged: {}) - Need to update volume for node with id {} and name {}", - self->name_, self->type_, id, name); - updateVolume(self, id); } void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wireplumber* self) { spdlog::debug("[{}]: (onDefaultNodesApiChanged: {})", self->name_, self->type_); + // Handle sink uint32_t defaultNodeId; g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &defaultNodeId); - if (!isValidNodeId(defaultNodeId)) { - spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring '{}' node change.", self->name_, - defaultNodeId, self->type_); - return; - } - - g_autoptr(WpNode) node = static_cast( + if (isValidNodeId(defaultNodeId)) { + g_autoptr(WpNode) node = static_cast( wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", - "=u", defaultNodeId, nullptr)); + "=u", defaultNodeId, nullptr)); - if (node == nullptr) { - spdlog::warn("[{}]: (onDefaultNodesApiChanged: {}) - Object with id {} not found", self->name_, - self->type_, defaultNodeId); - return; + if (node != nullptr) { + const gchar* defaultNodeName = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name"); + + if (g_strcmp0(self->default_node_name_, defaultNodeName) != 0 || + self->node_id_ != defaultNodeId) { + spdlog::debug("[{}]: Default sink changed to -> Node(name: {}, id: {})", + self->name_, defaultNodeName, defaultNodeId); + + g_free(self->default_node_name_); + self->default_node_name_ = g_strdup(defaultNodeName); + self->node_id_ = defaultNodeId; + updateVolume(self, defaultNodeId); + updateNodeName(self, defaultNodeId); + } + } } - const gchar* defaultNodeName = - wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name"); + // Handle source + uint32_t defaultSourceId; + g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Source", &defaultSourceId); - spdlog::debug( - "[{}]: (onDefaultNodesApiChanged: {}) - got the following default node: Node(name: {}, id: " - "{})", - self->name_, self->type_, defaultNodeName, defaultNodeId); + if (isValidNodeId(defaultSourceId)) { + g_autoptr(WpNode) sourceNode = static_cast( + wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", + "=u", defaultSourceId, nullptr)); - if (g_strcmp0(self->default_node_name_, defaultNodeName) == 0 && - self->node_id_ == defaultNodeId) { - spdlog::debug( - "[{}]: (onDefaultNodesApiChanged: {}) - Default node has not changed. Node(name: {}, id: " - "{}). Ignoring.", - self->name_, self->type_, self->default_node_name_, defaultNodeId); - return; + if (sourceNode != nullptr) { + const gchar* defaultSourceName = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(sourceNode), "node.name"); + + if (g_strcmp0(self->default_source_name_, defaultSourceName) != 0 || + self->source_node_id_ != defaultSourceId) { + spdlog::debug("[{}]: Default source changed to -> Node(name: {}, id: {})", + self->name_, defaultSourceName, defaultSourceId); + + g_free(self->default_source_name_); + self->default_source_name_ = g_strdup(defaultSourceName); + self->source_node_id_ = defaultSourceId; + updateSourceVolume(self, defaultSourceId); + } + } } - - spdlog::debug( - "[{}]: (onDefaultNodesApiChanged: {}) - Default node changed to -> Node(name: {}, id: {})", - self->name_, self->type_, defaultNodeName, defaultNodeId); - - g_free(self->default_node_name_); - self->default_node_name_ = g_strdup(defaultNodeName); - self->node_id_ = defaultNodeId; - updateVolume(self, defaultNodeId); - updateNodeName(self, defaultNodeId); } void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wireplumber* self) { @@ -223,18 +255,31 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir throw std::runtime_error("Mixer api is not loaded\n"); } + // Get default sink g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", self->type_, &self->default_node_name_); g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", self->type_, &self->node_id_); + // Get default source + g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", "Audio/Source", + &self->default_source_name_); + g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Source", &self->source_node_id_); + + if (self->default_node_name_ != nullptr) { spdlog::debug( "[{}]: (onObjectManagerInstalled: {}) - default configured node name: {} and id: {}", self->name_, self->type_, self->default_node_name_, self->node_id_); } + if (self->default_source_name_ != nullptr) { + spdlog::debug( + "[{}]: default source: {} (id: {})", + self->name_, self->default_source_name_, self->source_node_id_); + } updateVolume(self, self->node_id_); updateNodeName(self, self->node_id_); + updateSourceVolume(self, self->source_node_id_); g_signal_connect_swapped(self->mixer_api_, "changed", (GCallback)onMixerChanged, self); g_signal_connect_swapped(self->def_nodes_api_, "changed", (GCallback)onDefaultNodesApiChanged, @@ -271,6 +316,8 @@ void waybar::modules::Wireplumber::prepare(waybar::modules::Wireplumber* self) { spdlog::debug("[{}]: preparing object manager: '{}'", name_, self->type_); wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class", "=s", self->type_, nullptr); + wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class", + "=s", "Audio/Source", nullptr); } void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res, @@ -332,11 +379,21 @@ auto waybar::modules::Wireplumber::update() -> void { auto format = format_; std::string tooltipFormat; + // Handle sink mute state if (muted_) { format = config_["format-muted"].isString() ? config_["format-muted"].asString() : format; label_.get_style_context()->add_class("muted"); + label_.get_style_context()->add_class("sink-muted"); } else { label_.get_style_context()->remove_class("muted"); + label_.get_style_context()->remove_class("sink-muted"); + } + + // Handle source mute state + if (source_muted_) { + label_.get_style_context()->add_class("source-muted"); + } else { + label_.get_style_context()->remove_class("source-muted"); } int vol = round(volume_ * 100.0); From a6ac75983fffd847766ac90eab08b392a7bfec2b Mon Sep 17 00:00:00 2001 From: Henrique Oliveira Date: Wed, 23 Jul 2025 16:59:07 +0200 Subject: [PATCH 339/780] Add: WirePlumber - format-source support --- include/modules/wireplumber.hpp | 2 + src/modules/wireplumber.cpp | 69 +++++++++++++++++++++++++++++++-- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/include/modules/wireplumber.hpp b/include/modules/wireplumber.hpp index 047c9545..0565704f 100644 --- a/include/modules/wireplumber.hpp +++ b/include/modules/wireplumber.hpp @@ -23,6 +23,7 @@ class Wireplumber : public ALabel { static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id); static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id); static void updateSourceVolume(waybar::modules::Wireplumber* self, uint32_t id); + static void updateSourceName(waybar::modules::Wireplumber* self, uint32_t id); // NEW static void onPluginActivated(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self); static void onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self); @@ -47,6 +48,7 @@ class Wireplumber : public ALabel { double min_step_; uint32_t node_id_{0}; std::string node_name_; + std::string source_name_; gchar* type_; uint32_t source_node_id_; bool source_muted_; diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index 6260088a..383a5106 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -99,6 +99,41 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* spdlog::debug("[{}]: Updating '{}' node name to: {}", self->name_, self->type_, self->node_name_); } +void waybar::modules::Wireplumber::updateSourceName(waybar::modules::Wireplumber* self, uint32_t id) { + spdlog::debug("[{}]: updating source name with node.id {}", self->name_, id); + + if (!isValidNodeId(id)) { + spdlog::warn("[{}]: '{}' is not a valid source node ID. Ignoring source name update.", self->name_, id); + return; + } + + auto* proxy = static_cast(wp_object_manager_lookup(self->om_, WP_TYPE_GLOBAL_PROXY, + WP_CONSTRAINT_TYPE_G_PROPERTY, + "bound-id", "=u", id, nullptr)); + + if (proxy == nullptr) { + auto err = fmt::format("Source object '{}' not found\n", id); + spdlog::error("[{}]: {}", self->name_, err); + return; + } + + g_autoptr(WpProperties) properties = + WP_IS_PIPEWIRE_OBJECT(proxy) != 0 + ? wp_pipewire_object_get_properties(WP_PIPEWIRE_OBJECT(proxy)) + : wp_properties_new_empty(); + g_autoptr(WpProperties) globalP = wp_global_proxy_get_global_properties(WP_GLOBAL_PROXY(proxy)); + properties = wp_properties_ensure_unique_owner(properties); + wp_properties_add(properties, globalP); + wp_properties_set(properties, "object.id", nullptr); + const auto* nick = wp_properties_get(properties, "node.nick"); + const auto* description = wp_properties_get(properties, "node.description"); + + self->source_name_ = nick != nullptr ? nick + : description != nullptr ? description + : "Unknown source name"; + spdlog::debug("[{}]: Updating source name to: {}", self->name_, self->source_name_); +} + void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self, uint32_t id) { spdlog::debug("[{}]: updating volume", self->name_); GVariant* variant = nullptr; @@ -233,6 +268,7 @@ void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wir self->default_source_name_ = g_strdup(defaultSourceName); self->source_node_id_ = defaultSourceId; updateSourceVolume(self, defaultSourceId); + updateSourceName(self, defaultSourceId); } } } @@ -280,6 +316,7 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir updateVolume(self, self->node_id_); updateNodeName(self, self->node_id_); updateSourceVolume(self, self->source_node_id_); + updateSourceName(self, self->source_node_id_); g_signal_connect_swapped(self->mixer_api_, "changed", (GCallback)onMixerChanged, self); g_signal_connect_swapped(self->def_nodes_api_, "changed", (GCallback)onDefaultNodesApiChanged, @@ -397,6 +434,7 @@ auto waybar::modules::Wireplumber::update() -> void { } int vol = round(volume_ * 100.0); + int source_vol = round(source_volume_ * 100.0); // Get the state and apply state-specific format if available auto state = getState(vol); @@ -408,8 +446,29 @@ auto waybar::modules::Wireplumber::update() -> void { } } - std::string markup = fmt::format(fmt::runtime(format), fmt::arg("node_name", node_name_), - fmt::arg("volume", vol), fmt::arg("icon", getIcon(vol))); + // Prepare source format string (similar to PulseAudio) + std::string format_source = "{volume}%"; + if (source_muted_) { + if (config_["format-source-muted"].isString()) { + format_source = config_["format-source-muted"].asString(); + } + } else { + if (config_["format-source"].isString()) { + format_source = config_["format-source"].asString(); + } + } + + // Format the source string with actual volume + std::string formatted_source = fmt::format(fmt::runtime(format_source), + fmt::arg("volume", source_vol)); + + std::string markup = fmt::format(fmt::runtime(format), + fmt::arg("node_name", node_name_), + fmt::arg("volume", vol), + fmt::arg("icon", getIcon(vol)), + fmt::arg("format_source", formatted_source), + fmt::arg("source_volume", source_vol), + fmt::arg("source_desc", source_name_)); label_.set_markup(markup); if (tooltipEnabled()) { @@ -420,7 +479,11 @@ auto waybar::modules::Wireplumber::update() -> void { if (!tooltipFormat.empty()) { label_.set_tooltip_text(fmt::format(fmt::runtime(tooltipFormat), fmt::arg("node_name", node_name_), - fmt::arg("volume", vol), fmt::arg("icon", getIcon(vol)))); + fmt::arg("volume", vol), + fmt::arg("icon", getIcon(vol)), + fmt::arg("format_source", formatted_source), + fmt::arg("source_volume", source_vol), + fmt::arg("source_desc", source_name_))); } else { label_.set_tooltip_text(node_name_); } From 90f2490407d05edba18f570722f0f2b52ba4e215 Mon Sep 17 00:00:00 2001 From: Henrique Oliveira Date: Wed, 23 Jul 2025 20:26:17 +0200 Subject: [PATCH 340/780] Fix: Backlight same issue with states --- src/modules/backlight.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/modules/backlight.cpp b/src/modules/backlight.cpp index ff58951c..3c50f1d1 100644 --- a/src/modules/backlight.cpp +++ b/src/modules/backlight.cpp @@ -38,10 +38,20 @@ auto waybar::modules::Backlight::update() -> void { event_box_.show(); const uint8_t percent = best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max()); - std::string desc = fmt::format(fmt::runtime(format_), fmt::arg("percent", percent), + + // Get the state and apply state-specific format if available + auto state = getState(percent); + std::string current_format = format_; + if (!state.empty()) { + std::string state_format_name = "format-" + state; + if (config_[state_format_name].isString()) { + current_format = config_[state_format_name].asString(); + } + } + + std::string desc = fmt::format(fmt::runtime(current_format), fmt::arg("percent", percent), fmt::arg("icon", getIcon(percent))); label_.set_markup(desc); - getState(percent); if (tooltipEnabled()) { std::string tooltip_format; if (config_["tooltip-format"].isString()) { From f824ae9334f3991ecc82d5ffbcf6582b75a92747 Mon Sep 17 00:00:00 2001 From: Arkoniak Date: Tue, 29 Jul 2025 10:45:31 +0300 Subject: [PATCH 341/780] Relative paths in included configs --- include/config.hpp | 1 + src/bar.cpp | 6 +++++- src/config.cpp | 22 ++++++++++++++++++---- src/modules/custom.cpp | 3 +++ 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/include/config.hpp b/include/config.hpp index 5256bb46..3dd699ae 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -35,6 +35,7 @@ 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_); + std::optional findIncludePath(const std::string name); std::string config_file_; diff --git a/src/bar.cpp b/src/bar.cpp index 3c3ab690..0d4f632f 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -496,7 +496,11 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, auto vertical = (group != nullptr ? group->getBox().get_orientation() : box_.get_orientation()) == Gtk::ORIENTATION_VERTICAL; - auto* group_module = new waybar::Group(id_name, class_name, config[ref], vertical); + auto group_config = config[ref]; + if (group_config["modules"].isNull()) { + spdlog::warn("Group definition '{}' has not been found, group will be hidden", ref); + } + auto* group_module = new waybar::Group(id_name, class_name, group_config, vertical); getModules(factory, ref, group_module); module = group_module; } else { diff --git a/src/config.cpp b/src/config.cpp index 7096ba89..29a55e7c 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -89,19 +89,33 @@ 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) { + auto match1 = tryExpandPath(name, ""); + if (!match1.empty()) { + return match1.front(); + } + return findConfigPath({name}); +} + void Config::resolveConfigIncludes(Json::Value &config, int depth) { Json::Value includes = config["include"]; if (includes.isArray()) { for (const auto &include : includes) { spdlog::info("Including resource file: {}", include.asString()); - for (const auto &match : tryExpandPath(include.asString(), "")) { - setupConfig(config, match, depth + 1); + auto match = findIncludePath(include.asString()); + if (match.has_value()) { + setupConfig(config, match.value(), depth + 1); + } else { + spdlog::warn("Unable to find resource file: {}", include.asString()); } } } else if (includes.isString()) { spdlog::info("Including resource file: {}", includes.asString()); - for (const auto &match : tryExpandPath(includes.asString(), "")) { - setupConfig(config, match, depth + 1); + auto match = findIncludePath(includes.asString()); + if (match.has_value()) { + setupConfig(config, match.value(), depth + 1); + } else { + spdlog::warn("Unable to find resource file: {}", includes.asString()); } } } diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index db5c6db3..f9fd621e 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -14,6 +14,9 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id, percentage_(0), fp_(nullptr), pid_(-1) { + if (config.isNull()) { + spdlog::warn("There is no configuration for 'custom/{}', element will be hidden", name); + } dp.emit(); if (!config_["signal"].empty() && config_["interval"].empty() && config_["restart-interval"].empty()) { From b5093f3e583ea551efdf38e0c3ccfdba4a297f45 Mon Sep 17 00:00:00 2001 From: peelz Date: Fri, 25 Jul 2025 18:19:14 -0400 Subject: [PATCH 342/780] feat: match network interfaces by their altnames --- include/modules/network.hpp | 5 +- src/modules/network.cpp | 170 ++++++++++++++++++++++-------------- 2 files changed, 106 insertions(+), 69 deletions(-) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index 8e80d5aa..4abe26bd 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -9,6 +9,7 @@ #include #include +#include #include "ALabel.hpp" #include "util/sleeper_thread.hpp" @@ -44,11 +45,11 @@ class Network : public ALabel { void parseFreq(struct nlattr**); void parseBssid(struct nlattr**); bool associatedOrJoined(struct nlattr**); - bool checkInterface(std::string name); + bool matchInterface(const std::string& ifname, const std::vector& altnames, + std::string& matched) const; auto getInfo() -> void; const std::string getNetworkState() const; void clearIface(); - bool wildcardMatch(const std::string& pattern, const std::string& text) const; std::optional> readBandwidthUsage(); int ifid_{-1}; diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 94c77a69..2a3254c0 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -1,6 +1,8 @@ #include "modules/network.hpp" #include +#include +#include #include #include @@ -8,6 +10,8 @@ #include #include #include +#include +#include #include "util/format.hpp" #ifdef WANT_RFKILL @@ -386,11 +390,65 @@ auto waybar::modules::Network::update() -> void { ALabel::update(); } -bool waybar::modules::Network::checkInterface(std::string name) { - if (config_["interface"].isString()) { - return config_["interface"].asString() == name || - wildcardMatch(config_["interface"].asString(), name); +// https://gist.github.com/rressi/92af77630faf055934c723ce93ae2495 +static bool wildcardMatch(const std::string &pattern, const std::string &text) { + auto P = int(pattern.size()); + auto T = int(text.size()); + + auto p = 0, fallback_p = -1; + auto t = 0, fallback_t = -1; + + while (t < T) { + // Wildcard match: + if (p < P && pattern[p] == '*') { + fallback_p = p++; // starting point after failures + fallback_t = t; // starting point after failures + } + + // Simple match: + else if (p < P && (pattern[p] == '?' || pattern[p] == text[t])) { + p++; + t++; + } + + // Failure, fall back just after last matched '*': + else if (fallback_p >= 0) { + p = fallback_p + 1; // position just after last matched '*" + t = ++fallback_t; // re-try to match text from here + } + + // There were no '*' before, so we fail here: + else { + return false; + } } + + // Consume all '*' at the end of pattern: + while (p < P && pattern[p] == '*') p++; + + return p == P; +} + +bool waybar::modules::Network::matchInterface(const std::string &ifname, + const std::vector &altnames, + std::string &matched) const { + if (!config_["interface"].isString()) { + return false; + } + + auto config_ifname = config_["interface"].asString(); + if (config_ifname == ifname || wildcardMatch(config_ifname, ifname)) { + matched = ifname; + return true; + } + + for (const auto &altname : altnames) { + if (config_ifname == altname || wildcardMatch(config_ifname, altname)) { + matched = altname; + return true; + } + } + return false; } @@ -425,12 +483,16 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { is_del_event = true; case RTM_NEWLINK: { struct ifinfomsg *ifi = static_cast(NLMSG_DATA(nh)); - ssize_t attrlen = IFLA_PAYLOAD(nh); - struct rtattr *ifla = IFLA_RTA(ifi); - const char *ifname = NULL; - size_t ifname_len = 0; + struct nlattr *attrs[IFLA_MAX + 1]; + std::string ifname; + std::vector altnames; std::optional carrier; + if (nlmsg_parse(nh, sizeof(*ifi), attrs, IFLA_MAX, nullptr) < 0) { + spdlog::error("network: failed to parse netlink attributes"); + return NL_SKIP; + } + if (net->ifid_ != -1 && ifi->ifi_index != net->ifid_) { return NL_OK; } @@ -448,26 +510,33 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { return NL_OK; } - for (; RTA_OK(ifla, attrlen); ifla = RTA_NEXT(ifla, attrlen)) { - switch (ifla->rta_type) { - case IFLA_IFNAME: - ifname = static_cast(RTA_DATA(ifla)); - ifname_len = RTA_PAYLOAD(ifla) - 1; // minus \0 - if (ifi->ifi_flags & IFF_POINTOPOINT && net->checkInterface(ifname)) - net->is_p2p_ = true; - break; - case IFLA_CARRIER: { - carrier = *(char *)RTA_DATA(ifla) == 1; - break; + if (attrs[IFLA_IFNAME] != nullptr) { + const char *ifname_ptr = nla_get_string(attrs[IFLA_IFNAME]); + size_t ifname_len = nla_len(attrs[IFLA_IFNAME]) - 1; // minus \0 + ifname = std::string(ifname_ptr, ifname_len); + } + + if (attrs[IFLA_CARRIER] != nullptr) { + carrier = nla_get_u8(attrs[IFLA_CARRIER]) == 1; + } + + if (attrs[IFLA_PROP_LIST] != nullptr) { + struct nlattr *prop; + int rem; + + nla_for_each_nested(prop, attrs[IFLA_PROP_LIST], rem) { + if (nla_type(prop) == IFLA_ALT_IFNAME) { + const char *altname_ptr = nla_get_string(prop); + size_t altname_len = nla_len(prop) - 1; // minus \0 + altnames.emplace_back(altname_ptr, altname_len); } } } if (!is_del_event && ifi->ifi_index == net->ifid_) { // Update interface information - if (net->ifname_.empty() && ifname != NULL) { - std::string new_ifname(ifname, ifname_len); - net->ifname_ = new_ifname; + if (net->ifname_.empty() && !ifname.empty()) { + net->ifname_ = ifname; } if (carrier.has_value()) { if (net->carrier_ != *carrier) { @@ -488,13 +557,20 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { } } else if (!is_del_event && net->ifid_ == -1) { // Checking if it's an interface we care about. - std::string new_ifname(ifname, ifname_len); - if (net->checkInterface(new_ifname)) { - spdlog::debug("network: selecting new interface {}/{}", new_ifname, ifi->ifi_index); + std::string matched; + if (net->matchInterface(ifname, altnames, matched)) { + if (ifname == matched) { + spdlog::debug("network: selecting new interface {}/{}", ifname, ifi->ifi_index); + } else { + spdlog::debug("network: selecting new interface {}/{} (matched altname {})", ifname, + ifi->ifi_index, matched); + } - net->ifname_ = new_ifname; + net->ifname_ = ifname; net->ifid_ = ifi->ifi_index; - if (ifi->ifi_flags & IFF_POINTOPOINT) net->is_p2p_ = true; + if ((ifi->ifi_flags & IFF_POINTOPOINT) != 0) { + net->is_p2p_ = true; + } if ((ifi->ifi_flags & IFF_UP) == 0) { // With some network drivers (e.g. mt7921e), the interface may // report having a carrier even though interface is down. @@ -888,43 +964,3 @@ auto waybar::modules::Network::getInfo() -> void { } nl_send_sync(sock_, nl_msg); } - -// https://gist.github.com/rressi/92af77630faf055934c723ce93ae2495 -bool waybar::modules::Network::wildcardMatch(const std::string &pattern, - const std::string &text) const { - auto P = int(pattern.size()); - auto T = int(text.size()); - - auto p = 0, fallback_p = -1; - auto t = 0, fallback_t = -1; - - while (t < T) { - // Wildcard match: - if (p < P && pattern[p] == '*') { - fallback_p = p++; // starting point after failures - fallback_t = t; // starting point after failures - } - - // Simple match: - else if (p < P && (pattern[p] == '?' || pattern[p] == text[t])) { - p++; - t++; - } - - // Failure, fall back just after last matched '*': - else if (fallback_p >= 0) { - p = fallback_p + 1; // position just after last matched '*" - t = ++fallback_t; // re-try to match text from here - } - - // There were no '*' before, so we fail here: - else { - return false; - } - } - - // Consume all '*' at the end of pattern: - while (p < P && pattern[p] == '*') p++; - - return p == P; -} From 4c720333091b2fda5b663956ad9da8196ace6252 Mon Sep 17 00:00:00 2001 From: peelz Date: Fri, 25 Jul 2025 18:19:14 -0400 Subject: [PATCH 343/780] refactor: why is that line there --- src/modules/network.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 2a3254c0..b0e01364 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -669,7 +670,6 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { break; } - char temp_gw_addr[INET6_ADDRSTRLEN]; case RTM_DELROUTE: is_del_event = true; case RTM_NEWROUTE: { @@ -680,6 +680,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { int family = rtm->rtm_family; ssize_t attrlen = RTM_PAYLOAD(nh); struct rtattr *attr = RTM_RTA(rtm); + char gateway_addr[INET6_ADDRSTRLEN]; bool has_gateway = false; bool has_destination = false; int temp_idx = -1; @@ -705,7 +706,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { * If someone ever needs to figure out the gateway address as well, * it's here as the attribute payload. */ - inet_ntop(family, RTA_DATA(attr), temp_gw_addr, sizeof(temp_gw_addr)); + inet_ntop(family, RTA_DATA(attr), gateway_addr, sizeof(gateway_addr)); has_gateway = true; break; case RTA_DST: { @@ -750,8 +751,8 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { net->clearIface(); net->ifid_ = temp_idx; net->route_priority = priority; - net->gwaddr_ = temp_gw_addr; - spdlog::debug("network: new default route via {} on if{} metric {}", temp_gw_addr, + net->gwaddr_ = gateway_addr; + spdlog::debug("network: new default route via {} on if{} metric {}", gateway_addr, temp_idx, priority); /* Ask ifname associated with temp_idx as well as carrier status */ From 03eba632158fabcc17cd2847ad10a08e67ad6487 Mon Sep 17 00:00:00 2001 From: Theo Ratkin Date: Tue, 29 Jul 2025 14:04:00 -0400 Subject: [PATCH 344/780] feat(mpd): add playing-interval option --- include/modules/mpd/mpd.hpp | 3 +++ include/modules/mpd/state.hpp | 1 + include/modules/mpd/state.inl.hpp | 1 + src/modules/mpd/mpd.cpp | 5 +++++ src/modules/mpd/state.cpp | 6 +++--- 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/include/modules/mpd/mpd.hpp b/include/modules/mpd/mpd.hpp index 32d526e9..699ff2cd 100644 --- a/include/modules/mpd/mpd.hpp +++ b/include/modules/mpd/mpd.hpp @@ -28,6 +28,8 @@ class MPD : public ALabel { unsigned timeout_; + unsigned playing_interval_; + detail::unique_connection connection_; detail::unique_status status_; @@ -59,6 +61,7 @@ class MPD : public ALabel { inline bool stopped() const { return connection_ && state_ == MPD_STATE_STOP; } inline bool playing() const { return connection_ && state_ == MPD_STATE_PLAY; } inline bool paused() const { return connection_ && state_ == MPD_STATE_PAUSE; } + inline unsigned playing_interval() const { return playing_interval_; } }; #if !defined(MPD_NOINLINE) diff --git a/include/modules/mpd/state.hpp b/include/modules/mpd/state.hpp index 2c9071b4..93246f9f 100644 --- a/include/modules/mpd/state.hpp +++ b/include/modules/mpd/state.hpp @@ -194,6 +194,7 @@ class Context { bool is_paused() const; bool is_stopped() const; constexpr std::size_t interval() const; + unsigned playing_interval() const; void tryConnect() const; void checkErrors(mpd_connection*) const; void do_update(); diff --git a/include/modules/mpd/state.inl.hpp b/include/modules/mpd/state.inl.hpp index 895970e6..76d48bb4 100644 --- a/include/modules/mpd/state.inl.hpp +++ b/include/modules/mpd/state.inl.hpp @@ -8,6 +8,7 @@ inline bool Context::is_paused() const { return mpd_module_->paused(); } inline bool Context::is_stopped() const { return mpd_module_->stopped(); } constexpr inline std::size_t Context::interval() const { return mpd_module_->interval_.count(); } +inline unsigned Context::playing_interval() const { return mpd_module_->playing_interval(); } inline void Context::tryConnect() const { mpd_module_->tryConnect(); } inline unique_connection& Context::connection() { return mpd_module_->connection_; } constexpr inline mpd_state Context::state() const { return mpd_module_->state_; } diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 192e6c1a..479eb09e 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -22,6 +22,7 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config) port_(config_["port"].isUInt() ? config["port"].asUInt() : 0), password_(config_["password"].empty() ? "" : config_["password"].asString()), timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000), + playing_interval_(config_["playing-interval"].isUInt() ? config_["playing-interval"].asUInt() : 1'000), connection_(nullptr, &mpd_connection_free), status_(nullptr, &mpd_status_free), song_(nullptr, &mpd_song_free) { @@ -33,6 +34,10 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config) spdlog::warn("{}: `timeout` configuration should be an unsigned int", module_name_); } + if (!config_["playing-interval"].isNull() && !config_["playing-interval"].isUInt()) { + spdlog::warn("{}: `playing-interval` configuration should be an unsigned int", module_name_); + } + if (!config["server"].isNull()) { if (!config_["server"].isString()) { spdlog::warn("{}:`server` configuration should be a string", module_name_); diff --git a/src/modules/mpd/state.cpp b/src/modules/mpd/state.cpp index 3d7c8561..4f10f7c3 100644 --- a/src/modules/mpd/state.cpp +++ b/src/modules/mpd/state.cpp @@ -119,14 +119,14 @@ bool Idle::on_io(Glib::IOCondition const&) { void Playing::entry() noexcept { sigc::slot timer_slot = sigc::mem_fun(*this, &Playing::on_timer); - timer_connection_ = Glib::signal_timeout().connect_seconds(timer_slot, 1); - spdlog::debug("mpd: Playing: enabled 1 second periodic timer."); + timer_connection_ = Glib::signal_timeout().connect(timer_slot, ctx_->playing_interval()); + spdlog::debug("mpd: Playing: enabled {}ms periodic timer.", ctx_->playing_interval()); } void Playing::exit() noexcept { if (timer_connection_.connected()) { timer_connection_.disconnect(); - spdlog::debug("mpd: Playing: disabled 1 second periodic timer."); + spdlog::debug("mpd: Playing: disabled {}ms periodic timer.", ctx_->playing_interval()); } } From 1e0037a873e5809ad91fea7f1881f415bca1ca56 Mon Sep 17 00:00:00 2001 From: Arkoniak Date: Thu, 31 Jul 2025 21:29:20 +0300 Subject: [PATCH 345/780] fixed extra copy --- include/config.hpp | 2 +- src/config.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/config.hpp b/include/config.hpp index 3dd699ae..3490c3f1 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -35,7 +35,7 @@ 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_); - std::optional findIncludePath(const std::string name); + static std::optional findIncludePath(const std::string &name); std::string config_file_; diff --git a/src/config.cpp b/src/config.cpp index 29a55e7c..5f14df5e 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -89,7 +89,7 @@ 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::optional Config::findIncludePath(const std::string &name) { auto match1 = tryExpandPath(name, ""); if (!match1.empty()) { return match1.front(); From ff69525e29b517291716fde630ecf1a0cc79feee Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 1 Aug 2025 00:14:36 +0000 Subject: [PATCH 346/780] 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/30e2e2857ba47844aa71991daa6ed1fc678bcbb7?narHash=sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM%3D' (2025-06-27) → 'github:NixOS/nixpkgs/dc9637876d0dcc8c9e5e22986b857632effeb727?narHash=sha256-cKgvtz6fKuK1Xr5LQW/zOUiAC0oSQoA9nOISB0pJZqM%3D' (2025-07-28) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 5c08338e..0818b622 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1751011381, - "narHash": "sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM=", + "lastModified": 1753694789, + "narHash": "sha256-cKgvtz6fKuK1Xr5LQW/zOUiAC0oSQoA9nOISB0pJZqM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "30e2e2857ba47844aa71991daa6ed1fc678bcbb7", + "rev": "dc9637876d0dcc8c9e5e22986b857632effeb727", "type": "github" }, "original": { From 79c30e77a774372cf2c906ee6d7439c992ed300d Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 8 Aug 2025 08:42:17 +0200 Subject: [PATCH 347/780] fix: lint --- src/modules/mpd/mpd.cpp | 22 ++++++++++------------ src/modules/river/layout.cpp | 1 - src/modules/sni/tray.cpp | 1 + 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 396ce3e1..8e6bbb25 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -185,8 +185,7 @@ void waybar::modules::MPD::setLabel() { fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon), - fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename), - fmt::arg("uri", uri)); + fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename), fmt::arg("uri", uri)); if (text.empty()) { label_.hide(); } else { @@ -202,16 +201,15 @@ void waybar::modules::MPD::setLabel() { tooltip_format = config_["tooltip-format"].isString() ? config_["tooltip-format"].asString() : "MPD (connected)"; try { - auto tooltip_text = - fmt::format(fmt::runtime(tooltip_format), fmt::arg("artist", artist.raw()), - fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()), - fmt::arg("title", title.raw()), fmt::arg("date", date), - fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime), - fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos), - fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), - fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), - fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon), - fmt::arg("filename", filename), fmt::arg("uri", uri)); + auto tooltip_text = fmt::format( + fmt::runtime(tooltip_format), fmt::arg("artist", artist.raw()), + fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()), + fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume), + fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime), + fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length), + fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), + fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon), + fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename), fmt::arg("uri", uri)); label_.set_tooltip_text(tooltip_text); } catch (fmt::format_error const& e) { spdlog::warn("mpd: format error (tooltip): {}", e.what()); diff --git a/src/modules/river/layout.cpp b/src/modules/river/layout.cpp index 117b9483..308a0901 100644 --- a/src/modules/river/layout.cpp +++ b/src/modules/river/layout.cpp @@ -152,7 +152,6 @@ void Layout::handle_name(const char *name) { label_.get_style_context()->add_class(name); label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(name).raw())); label_.show(); - } name_ = name; ALabel::update(); diff --git a/src/modules/sni/tray.cpp b/src/modules/sni/tray.cpp index bc940c35..34a3c05f 100644 --- a/src/modules/sni/tray.cpp +++ b/src/modules/sni/tray.cpp @@ -3,6 +3,7 @@ #include #include + #include "modules/sni/icon_manager.hpp" namespace waybar::modules::SNI { From 7e0cbf4a1fc18648f7f01b63dce3ef816b7d9004 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 8 Aug 2025 08:59:10 +0200 Subject: [PATCH 348/780] fix: lint --- src/modules/wireplumber.cpp | 77 ++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index 383a5106..a43ad29b 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -99,11 +99,13 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* spdlog::debug("[{}]: Updating '{}' node name to: {}", self->name_, self->type_, self->node_name_); } -void waybar::modules::Wireplumber::updateSourceName(waybar::modules::Wireplumber* self, uint32_t id) { +void waybar::modules::Wireplumber::updateSourceName(waybar::modules::Wireplumber* self, + uint32_t id) { spdlog::debug("[{}]: updating source name with node.id {}", self->name_, id); if (!isValidNodeId(id)) { - spdlog::warn("[{}]: '{}' is not a valid source node ID. Ignoring source name update.", self->name_, id); + spdlog::warn("[{}]: '{}' is not a valid source node ID. Ignoring source name update.", + self->name_, id); return; } @@ -160,12 +162,14 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se self->dp.emit(); } -void waybar::modules::Wireplumber::updateSourceVolume(waybar::modules::Wireplumber* self, uint32_t id) { +void waybar::modules::Wireplumber::updateSourceVolume(waybar::modules::Wireplumber* self, + uint32_t id) { spdlog::debug("[{}]: updating source volume", self->name_); GVariant* variant = nullptr; if (!isValidNodeId(id)) { - spdlog::error("[{}]: '{}' is not a valid source node ID. Ignoring source volume update.", self->name_, id); + spdlog::error("[{}]: '{}' is not a valid source node ID. Ignoring source volume update.", + self->name_, id); return; } @@ -206,12 +210,12 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* const gchar* name = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name"); if (self->node_id_ == id) { - spdlog::debug("[{}]: (onMixerChanged: {}) - updating sink volume for node: {}", - self->name_, self->type_, name); + spdlog::debug("[{}]: (onMixerChanged: {}) - updating sink volume for node: {}", self->name_, + self->type_, name); updateVolume(self, id); } else if (self->source_node_id_ == id) { - spdlog::debug("[{}]: (onMixerChanged: {}) - updating source volume for node: {}", - self->name_, self->type_, name); + spdlog::debug("[{}]: (onMixerChanged: {}) - updating source volume for node: {}", self->name_, + self->type_, name); updateSourceVolume(self, id); } } @@ -225,17 +229,17 @@ void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wir if (isValidNodeId(defaultNodeId)) { g_autoptr(WpNode) node = static_cast( - wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", - "=u", defaultNodeId, nullptr)); + wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", + "=u", defaultNodeId, nullptr)); if (node != nullptr) { const gchar* defaultNodeName = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name"); if (g_strcmp0(self->default_node_name_, defaultNodeName) != 0 || - self->node_id_ != defaultNodeId) { - spdlog::debug("[{}]: Default sink changed to -> Node(name: {}, id: {})", - self->name_, defaultNodeName, defaultNodeId); + self->node_id_ != defaultNodeId) { + spdlog::debug("[{}]: Default sink changed to -> Node(name: {}, id: {})", self->name_, + defaultNodeName, defaultNodeId); g_free(self->default_node_name_); self->default_node_name_ = g_strdup(defaultNodeName); @@ -252,17 +256,17 @@ void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wir if (isValidNodeId(defaultSourceId)) { g_autoptr(WpNode) sourceNode = static_cast( - wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", - "=u", defaultSourceId, nullptr)); + wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", + "=u", defaultSourceId, nullptr)); if (sourceNode != nullptr) { const gchar* defaultSourceName = - wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(sourceNode), "node.name"); + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(sourceNode), "node.name"); if (g_strcmp0(self->default_source_name_, defaultSourceName) != 0 || - self->source_node_id_ != defaultSourceId) { - spdlog::debug("[{}]: Default source changed to -> Node(name: {}, id: {})", - self->name_, defaultSourceName, defaultSourceId); + self->source_node_id_ != defaultSourceId) { + spdlog::debug("[{}]: Default source changed to -> Node(name: {}, id: {})", self->name_, + defaultSourceName, defaultSourceId); g_free(self->default_source_name_); self->default_source_name_ = g_strdup(defaultSourceName); @@ -299,8 +303,8 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir // Get default source g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", "Audio/Source", &self->default_source_name_); - g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Source", &self->source_node_id_); - + g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Source", + &self->source_node_id_); if (self->default_node_name_ != nullptr) { spdlog::debug( @@ -308,9 +312,8 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir self->name_, self->type_, self->default_node_name_, self->node_id_); } if (self->default_source_name_ != nullptr) { - spdlog::debug( - "[{}]: default source: {} (id: {})", - self->name_, self->default_source_name_, self->source_node_id_); + spdlog::debug("[{}]: default source: {} (id: {})", self->name_, self->default_source_name_, + self->source_node_id_); } updateVolume(self, self->node_id_); @@ -459,16 +462,13 @@ auto waybar::modules::Wireplumber::update() -> void { } // Format the source string with actual volume - std::string formatted_source = fmt::format(fmt::runtime(format_source), - fmt::arg("volume", source_vol)); + std::string formatted_source = + fmt::format(fmt::runtime(format_source), fmt::arg("volume", source_vol)); - std::string markup = fmt::format(fmt::runtime(format), - fmt::arg("node_name", node_name_), - fmt::arg("volume", vol), - fmt::arg("icon", getIcon(vol)), - fmt::arg("format_source", formatted_source), - fmt::arg("source_volume", source_vol), - fmt::arg("source_desc", source_name_)); + std::string markup = + fmt::format(fmt::runtime(format), fmt::arg("node_name", node_name_), fmt::arg("volume", vol), + fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source), + fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_)); label_.set_markup(markup); if (tooltipEnabled()) { @@ -477,13 +477,10 @@ auto waybar::modules::Wireplumber::update() -> void { } if (!tooltipFormat.empty()) { - label_.set_tooltip_text(fmt::format(fmt::runtime(tooltipFormat), - fmt::arg("node_name", node_name_), - fmt::arg("volume", vol), - fmt::arg("icon", getIcon(vol)), - fmt::arg("format_source", formatted_source), - fmt::arg("source_volume", source_vol), - fmt::arg("source_desc", source_name_))); + label_.set_tooltip_text(fmt::format( + fmt::runtime(tooltipFormat), fmt::arg("node_name", node_name_), fmt::arg("volume", vol), + fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source), + fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_))); } else { label_.set_tooltip_text(node_name_); } From 41de8964f1e3278edf07902ad68ca5e01e7abeeb Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 8 Aug 2025 09:15:22 +0200 Subject: [PATCH 349/780] chore: 0.14.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 6f404b29..b3e6ca0f 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'waybar', 'cpp', 'c', - version: '0.13.0', + version: '0.14.0', license: 'MIT', meson_version: '>= 0.59.0', default_options : [ From 929c817eb40f48bff74ce83fbef92e7d6627cf6f Mon Sep 17 00:00:00 2001 From: Theo Ratkin Date: Fri, 8 Aug 2025 07:55:28 -0400 Subject: [PATCH 350/780] chore(man): add playing-interval option to mpd man page --- man/waybar-mpd.5.scd | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/man/waybar-mpd.5.scd b/man/waybar-mpd.5.scd index 84abc2e8..9ce01c4d 100644 --- a/man/waybar-mpd.5.scd +++ b/man/waybar-mpd.5.scd @@ -29,6 +29,11 @@ Addressed by *mpd* default: 5 ++ The interval in which the connection to the MPD server is retried +*playing-interval*: ++ + typeof: integer++ + default: 1000 ++ + The interval (in milliseconds) in which the playing state is updated. + *timeout*: ++ typeof: integer++ default: 30 ++ From ca8f51a39f67a0a5c90be2d12fd24b1110899b76 Mon Sep 17 00:00:00 2001 From: shibjyoti555 Date: Sat, 9 Aug 2025 03:11:21 +0530 Subject: [PATCH 351/780] added a "fallback" option to hyprland/window when no window in focus --- src/modules/hyprland/window.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index 815fbad8..13c4a924 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -13,6 +13,8 @@ #include "util/rewrite_string.hpp" #include "util/sanitize_str.hpp" +#include + namespace waybar::modules::hyprland { std::shared_mutex windowIpcSmtx; @@ -54,8 +56,15 @@ auto Window::update() -> void { std::string label_text; if (!format_.empty()) { label_.show(); + + // If the focused window name is empty and fallback is configured, use fallback text + std::string displayTitle = windowName; + if (displayTitle.empty() && config_["fallback"].isString()) { + displayTitle = config_["fallback"].asString(); + } + label_text = waybar::util::rewriteString( - fmt::format(fmt::runtime(format_), fmt::arg("title", windowName), + fmt::format(fmt::runtime(format_), fmt::arg("title", displayTitle), fmt::arg("initialTitle", windowData_.initial_title), fmt::arg("class", windowData_.class_name), fmt::arg("initialClass", windowData_.initial_class_name)), From 64b64d03160a4552fcc4555802924c154d744302 Mon Sep 17 00:00:00 2001 From: Skylar Abruzese Date: Sat, 9 Aug 2025 18:33:35 -0400 Subject: [PATCH 352/780] 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 e355b40c66279bd62411c388f26ffde26ebdee3c Mon Sep 17 00:00:00 2001 From: winkelnp <68015877+winkelnp@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:34:50 +0100 Subject: [PATCH 353/780] Add `format-bluetooth` support to wireplumber module --- src/modules/wireplumber.cpp | 44 +++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index a43ad29b..4b25f817 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -14,16 +14,16 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val mixer_api_(nullptr), def_nodes_api_(nullptr), default_node_name_(nullptr), - default_source_name_(nullptr), pending_plugins_(0), muted_(false), - source_muted_(false), volume_(0.0), - source_volume_(0.0), min_step_(0.0), node_id_(0), + type_(nullptr), source_node_id_(0), - type_(nullptr) { + source_muted_(false), + source_volume_(0.0), + default_source_name_(nullptr) { waybar::modules::Wireplumber::modules.push_back(this); wp_init(WP_INIT_PIPEWIRE); @@ -418,10 +418,33 @@ void waybar::modules::Wireplumber::asyncLoadRequiredApiModules() { auto waybar::modules::Wireplumber::update() -> void { auto format = format_; std::string tooltipFormat; + std::string format_name = "format"; + + // Handle sink bluetooth state + const std::string name = default_node_name_ != nullptr ? default_node_name_ : ""; + + auto bt = name.find("bluez") != std::string::npos || name.find("a2dp-sink") != std::string::npos; + if (bt) { + // format = + // config_["format-bluetooth"].isString() ? config_["format-bluetooth"].asString() : format; + format_name += "-bluetooth"; + label_.get_style_context()->add_class("bluetooth"); + } else { + label_.get_style_context()->remove_class("bluetooth"); + } // Handle sink mute state if (muted_) { - format = config_["format-muted"].isString() ? config_["format-muted"].asString() : format; + // if (bt) + // format = config_["format-bluetooth-muted"].isString() + // ? config_["format-bluetooth-muted"].asString() + // : format; + // else + // format = config_["format-muted"].isString() ? config_["format-muted"].asString() : format; + // Check muted bluetooth format exists, otherwise fall back to default muted format. + if (format_name != "format" && !config_[format_name + "-muted"].isString()) + format_name = "format"; + format_name += "-muted"; label_.get_style_context()->add_class("muted"); label_.get_style_context()->add_class("sink-muted"); } else { @@ -441,13 +464,10 @@ auto waybar::modules::Wireplumber::update() -> void { // Get the state and apply state-specific format if available auto state = getState(vol); - if (!state.empty()) { - std::string format_name = muted_ ? "format-muted" : "format"; - std::string state_format_name = format_name + "-" + state; - if (config_[state_format_name].isString()) { - format = config_[state_format_name].asString(); - } - } + if (!state.empty() && config_[format_name + "-" + state].isString()) + format = config_[format_name + "-" + state].asString(); + else if (config_[format_name].isString()) + format = config_[format_name].asString(); // Prepare source format string (similar to PulseAudio) std::string format_source = "{volume}%"; 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 354/780] 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 5079884b789a0c18acb6b75e7d77ebf9827d4c8b Mon Sep 17 00:00:00 2001 From: zjeffer <4633209+zjeffer@users.noreply.github.com> Date: Tue, 12 Aug 2025 19:39:36 +0200 Subject: [PATCH 355/780] Hyprland IPC improvements, fix tsan warning, WindowCount shouldn't create a separate IPC --- include/modules/hyprland/backend.hpp | 13 +++--- include/modules/hyprland/windowcount.hpp | 6 +-- src/modules/hyprland/backend.cpp | 2 - src/modules/hyprland/language.cpp | 2 - src/modules/hyprland/submap.cpp | 4 -- src/modules/hyprland/window.cpp | 1 - src/modules/hyprland/windowcount.cpp | 55 ++++++++++++------------ src/modules/hyprland/workspaces.cpp | 1 - 8 files changed, 38 insertions(+), 46 deletions(-) diff --git a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp index d9f16526..9ee97f98 100644 --- a/include/modules/hyprland/backend.hpp +++ b/include/modules/hyprland/backend.hpp @@ -17,9 +17,13 @@ class EventHandler { virtual ~EventHandler() = default; }; +/// If you want to use the Hyprland IPC, simply use IPC::inst() to get the singleton instance. +/// Do not create multiple instances. class IPC { + protected: + IPC(); // use IPC::inst() instead. + public: - IPC(); ~IPC(); static IPC& inst(); @@ -41,10 +45,7 @@ class IPC { std::mutex callbackMutex_; util::JsonParser parser_; std::list> callbacks_; - int socketfd_; // the hyprland socket file descriptor - bool running_ = true; + int socketfd_; // the hyprland socket file descriptor + bool running_ = true; // the ipcThread will stop running when this is false }; - -inline bool modulesReady = false; -inline std::unique_ptr gIPC; }; // namespace waybar::modules::hyprland diff --git a/include/modules/hyprland/windowcount.hpp b/include/modules/hyprland/windowcount.hpp index 195e6a34..7c86fb3e 100644 --- a/include/modules/hyprland/windowcount.hpp +++ b/include/modules/hyprland/windowcount.hpp @@ -7,7 +7,6 @@ #include "AAppIconLabel.hpp" #include "bar.hpp" #include "modules/hyprland/backend.hpp" -#include "util/json.hpp" namespace waybar::modules::hyprland { @@ -26,8 +25,8 @@ class WindowCount : public waybar::AAppIconLabel, public EventHandler { static auto parse(const Json::Value& value) -> Workspace; }; - static auto getActiveWorkspace(const std::string&) -> Workspace; - static auto getActiveWorkspace() -> Workspace; + auto getActiveWorkspace(const std::string&) -> Workspace; + auto getActiveWorkspace() -> Workspace; void onEvent(const std::string& ev) override; void queryActiveWorkspace(); void setClass(const std::string&, bool enable); @@ -36,6 +35,7 @@ class WindowCount : public waybar::AAppIconLabel, public EventHandler { std::mutex mutex_; const Bar& bar_; Workspace workspace_; + IPC& m_ipc; }; } // namespace waybar::modules::hyprland diff --git a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp index 2bd3b509..cac59f9c 100644 --- a/src/modules/hyprland/backend.cpp +++ b/src/modules/hyprland/backend.cpp @@ -78,8 +78,6 @@ void IPC::socketListener() { return; } - if (!modulesReady) return; - spdlog::info("Hyprland IPC starting"); struct sockaddr_un addr; diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index 3f141bbd..25f6789d 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -11,8 +11,6 @@ namespace waybar::modules::hyprland { Language::Language(const std::string& id, const Bar& bar, const Json::Value& config) : ALabel(config, "language", id, "{}", 0, true), bar_(bar), m_ipc(IPC::inst()) { - modulesReady = true; - // get the active layout when open initLanguage(); diff --git a/src/modules/hyprland/submap.cpp b/src/modules/hyprland/submap.cpp index a2b3f460..eb9b1de8 100644 --- a/src/modules/hyprland/submap.cpp +++ b/src/modules/hyprland/submap.cpp @@ -2,14 +2,10 @@ #include -#include "util/sanitize_str.hpp" - namespace waybar::modules::hyprland { Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config) : ALabel(config, "submap", id, "{}", 0, true), bar_(bar), m_ipc(IPC::inst()) { - modulesReady = true; - parseConfig(config); label_.hide(); diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index 815fbad8..23437c5f 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -21,7 +21,6 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), m_ipc(IPC::inst()) { std::unique_lock windowIpcUniqueLock(windowIpcSmtx); - modulesReady = true; separateOutputs_ = config["separate-outputs"].asBool(); // register for hyprland ipc diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index 68f7c3b4..b2721aea 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -9,35 +9,29 @@ #include #include "modules/hyprland/backend.hpp" -#include "util/sanitize_str.hpp" namespace waybar::modules::hyprland { WindowCount::WindowCount(const std::string& id, const Bar& bar, const Json::Value& config) - : AAppIconLabel(config, "windowcount", id, "{count}", 0, true), bar_(bar) { - modulesReady = true; + : AAppIconLabel(config, "windowcount", id, "{count}", 0, true), bar_(bar), m_ipc(IPC::inst()) { separateOutputs_ = config.isMember("separate-outputs") ? config["separate-outputs"].asBool() : true; - if (!gIPC) { - gIPC = std::make_unique(); - } - queryActiveWorkspace(); update(); dp.emit(); // register for hyprland ipc - gIPC->registerForIPC("fullscreen", this); - gIPC->registerForIPC("workspace", this); - gIPC->registerForIPC("focusedmon", this); - gIPC->registerForIPC("openwindow", this); - gIPC->registerForIPC("closewindow", this); - gIPC->registerForIPC("movewindow", this); + m_ipc.registerForIPC("fullscreen", this); + m_ipc.registerForIPC("workspace", this); + m_ipc.registerForIPC("focusedmon", this); + m_ipc.registerForIPC("openwindow", this); + m_ipc.registerForIPC("closewindow", this); + m_ipc.registerForIPC("movewindow", this); } WindowCount::~WindowCount() { - gIPC->unregisterForIPC(this); + m_ipc.unregisterForIPC(this); // wait for possible event handler to finish std::lock_guard lg(mutex_); } @@ -70,7 +64,7 @@ auto WindowCount::update() -> void { } auto WindowCount::getActiveWorkspace() -> Workspace { - const auto workspace = gIPC->getSocket1JsonReply("activeworkspace"); + const auto workspace = m_ipc.getSocket1JsonReply("activeworkspace"); if (workspace.isObject()) { return Workspace::parse(workspace); @@ -80,24 +74,31 @@ auto WindowCount::getActiveWorkspace() -> Workspace { } auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspace { - const auto monitors = gIPC->getSocket1JsonReply("monitors"); + const auto monitors = m_ipc.getSocket1JsonReply("monitors"); if (monitors.isArray()) { - auto monitor = std::find_if(monitors.begin(), monitors.end(), [&](Json::Value monitor) { - return monitor["name"] == monitorName; - }); + auto monitor = std::ranges::find_if( + monitors, [&](Json::Value monitor) { return monitor["name"] == monitorName; }); if (monitor == std::end(monitors)) { spdlog::warn("Monitor not found: {}", monitorName); - return Workspace{-1, 0, false}; + return Workspace{ + .id = -1, + .windows = 0, + .hasfullscreen = false, + }; } const int id = (*monitor)["activeWorkspace"]["id"].asInt(); - const auto workspaces = gIPC->getSocket1JsonReply("workspaces"); + const auto workspaces = m_ipc.getSocket1JsonReply("workspaces"); if (workspaces.isArray()) { - auto workspace = std::find_if(workspaces.begin(), workspaces.end(), - [&](Json::Value workspace) { return workspace["id"] == id; }); + auto workspace = std::ranges::find_if( + workspaces, [&](Json::Value workspace) { return workspace["id"] == id; }); if (workspace == std::end(workspaces)) { spdlog::warn("No workspace with id {}", id); - return Workspace{-1, 0, false}; + return Workspace{ + .id = -1, + .windows = 0, + .hasfullscreen = false, + }; } return Workspace::parse(*workspace); }; @@ -108,9 +109,9 @@ auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspac auto WindowCount::Workspace::parse(const Json::Value& value) -> WindowCount::Workspace { return Workspace{ - value["id"].asInt(), - value["windows"].asInt(), - value["hasfullscreen"].asBool(), + .id = value["id"].asInt(), + .windows = value["windows"].asInt(), + .hasfullscreen = value["hasfullscreen"].asBool(), }; } diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 5d2903dc..4634a999 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -19,7 +19,6 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value m_bar(bar), m_box(bar.orientation, 0), m_ipc(IPC::inst()) { - modulesReady = true; parseConfig(config); m_box.set_name("workspaces"); From be48f6bff202a5589a3f169c91b1c394cfff12d3 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Tue, 12 Aug 2025 13:25:50 -0500 Subject: [PATCH 356/780] 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 357/780] 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 358/780] 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 359/780] 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 360/780] 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 361/780] 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 362/780] 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 363/780] 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 2e231472159e2b2a5dfc2e4bed69855b88e8fab3 Mon Sep 17 00:00:00 2001 From: ErrorNoInternet Date: Thu, 17 Apr 2025 16:45:47 -0400 Subject: [PATCH 364/780] feat(group): add reveal-by-default option --- man/waybar.5.scd.in | 5 +++++ src/group.cpp | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 5bb62724..e817e26a 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -322,6 +322,11 @@ A group may hide all but one element, showing them only on mouse hover. In order Defines the direction of the transition animation. If true, the hidden elements will slide from left to right. If false, they will slide from right to left. When the bar is vertical, it reads as top-to-bottom. +*reveal-by-default*: ++ + typeof: bool ++ + default: false ++ + Whether the child should be revealed when Waybar starts up. This has to be used with click-to-reveal to take effect. + ``` "group/power": { "orientation": "inherit", diff --git a/src/group.cpp b/src/group.cpp index 50841efd..31d60285 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -62,13 +62,22 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value& const bool left_to_right = (drawer_config["transition-left-to-right"].isBool() ? drawer_config["transition-left-to-right"].asBool() : true); + const bool reveal_by_default = + (drawer_config["reveal-by-default"].isBool() ? drawer_config["reveal-by-default"].asBool() + : false); + click_to_reveal = drawer_config["click-to-reveal"].asBool(); auto transition_type = getPreferredTransitionType(vertical); revealer.set_transition_type(transition_type); revealer.set_transition_duration(transition_duration); - revealer.set_reveal_child(false); + if (click_to_reveal && reveal_by_default) { + box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT); + revealer.set_reveal_child(true); + } else { + revealer.set_reveal_child(false); + } revealer.get_style_context()->add_class("drawer"); From 4a7da0cab2747981bdab15acb0fc3af07c48a6e3 Mon Sep 17 00:00:00 2001 From: Steffen Kothe Date: Fri, 15 Aug 2025 10:55:21 +0000 Subject: [PATCH 365/780] fix: Fail early when neither user nor system is requested The module can be configured to neither request the user nor the system session. In fact does this display non-valuable information to the user and can be considered as abuse case of the configuration. Therefore throw an exception when neither user nor system session are requested. Signed-off-by: Steffen Kothe --- src/modules/systemd_failed_units.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index 90f33be7..ac389257 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -47,6 +47,9 @@ SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value& user_proxy->signal_signal().connect(sigc::mem_fun(*this, &SystemdFailedUnits::notify_cb)); } + if (!user_proxy && !system_proxy) + throw std::runtime_error("Neither system nor user status is requested."); + updateData(); /* Always update for the first time. */ dp.emit(); From 3060141a65fad5a79c7f8375c1e363d1e6e34f58 Mon Sep 17 00:00:00 2001 From: Steffen Kothe Date: Fri, 15 Aug 2025 10:58:23 +0000 Subject: [PATCH 366/780] fix: Fix hide_on_ok when user or system session is ignored In case of an user ignoring the failed system or user units, hiding of the waybar module did not work. Ensure that the ignore case of a system or user bus is reflected correctly by determining the configuration. Fixes [1] where regression was introduced in [2]. Links: [1] https://github.com/Alexays/Waybar/issues/4241 [2] https://github.com/Alexays/Waybar/issues/4240 Signed-off-by: Steffen Kothe --- src/modules/systemd_failed_units.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index ac389257..9e3f16f3 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -74,7 +74,7 @@ auto SystemdFailedUnits::notify_cb(const Glib::ustring& sender_name, void SystemdFailedUnits::RequestSystemState() { auto load = [](const char* kind, Glib::RefPtr& proxy) -> std::string { try { - if (!proxy) return "unknown"; + if (!proxy) return "ignored"; auto parameters = Glib::VariantContainerBase( g_variant_new("(ss)", "org.freedesktop.systemd1.Manager", "SystemState")); Glib::VariantContainerBase data = proxy->call_sync("Get", parameters); @@ -93,7 +93,9 @@ void SystemdFailedUnits::RequestSystemState() { system_state = load("systemwide", system_proxy); user_state = load("user", user_proxy); - if (system_state == "running" && user_state == "running") + + if ((system_state == "running" || system_state == "ignored") && + (user_state == "running" || user_state == "ignored")) overall_state = "ok"; else overall_state = "degraded"; From e8755b1a7f5c28180877e0a14793466d0654a8d2 Mon Sep 17 00:00:00 2001 From: Sebastian Schwarz Date: Sat, 16 Aug 2025 14:14:59 +0200 Subject: [PATCH 367/780] 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 368/780] 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 369/780] 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 370/780] 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 371/780] 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 372/780] 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 556c5f5a3027dbaaaf2ec47e8e70108460311e4b Mon Sep 17 00:00:00 2001 From: zjeffer <4633209+zjeffer@users.noreply.github.com> Date: Mon, 11 Aug 2025 19:54:52 +0200 Subject: [PATCH 373/780] Add tsan.supp file to easily ignore common tsan issues from external libraries --- tsan.supp | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 tsan.supp diff --git a/tsan.supp b/tsan.supp new file mode 100644 index 00000000..48a47611 --- /dev/null +++ b/tsan.supp @@ -0,0 +1,7 @@ +# Suppress common thread issues in dependencies, these are often non-fixable or not an issue. +# Use it like this (when in repo root): TSAN_OPTIONS="suppressions=./tsan.supp" ./build/waybar +race:libfontconfig.so +race:libglib-2.0.so +race:libpango-1.0.so +race:libc.so.6 +race:libgio-2.0.so From 3c3164eb8e7681822c6a480e1656350b29a14a82 Mon Sep 17 00:00:00 2001 From: zjeffer <4633209+zjeffer@users.noreply.github.com> Date: Mon, 11 Aug 2025 19:56:58 +0200 Subject: [PATCH 374/780] Fix warning if swap-icon-label is not defined in config --- src/AIconLabel.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/AIconLabel.cpp b/src/AIconLabel.cpp index 79cd5fe1..e73b6571 100644 --- a/src/AIconLabel.cpp +++ b/src/AIconLabel.cpp @@ -36,10 +36,12 @@ 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 - swap_icon_label = config_["swap-icon-label"].asBool(); + if (config_.isMember("swap-icon-label")) { + if (!config_["swap-icon-label"].isBool()) + spdlog::warn("'swap-icon-label' must be a bool."); + else + swap_icon_label = config_["swap-icon-label"].asBool(); + } if ((rot == 0 || rot == 3) ^ swap_icon_label) { box_.add(image_); From aaffebed4c0c7f3940e206319d7fc0ef8ca9f6b8 Mon Sep 17 00:00:00 2001 From: Marc Bornand Date: Fri, 24 Jan 2025 14:46:25 +0100 Subject: [PATCH 375/780] wireplumber: add more volume formats - Add the possibility to show the volume in linear, cubic, db, and cubic percent (default for backward compatibility). - Add the ability to change the scale for the scrolling action. - Make the minimal step flexible to fit any scale. --- man/waybar-wireplumber.5.scd | 25 ++++++++++- src/modules/wireplumber.cpp | 81 +++++++++++++++++++++++++++++------- 2 files changed, 88 insertions(+), 18 deletions(-) diff --git a/man/waybar-wireplumber.5.scd b/man/waybar-wireplumber.5.scd index ae78f184..45f1b92a 100644 --- a/man/waybar-wireplumber.5.scd +++ b/man/waybar-wireplumber.5.scd @@ -63,6 +63,11 @@ The *wireplumber* module displays the current volume reported by WirePlumber. default: 1.0 ++ The speed at which to change the volume when scrolling. +*scroll-scale*: ++ + typeof: string ++ + default: cubic_percent + The scale to use for the scrolling volume change. Options are 'linear', 'db', 'cubic', and 'cubic_percent'. + *on-click*: ++ typeof: string ++ Command to execute when clicked on the module. @@ -107,7 +112,13 @@ The *wireplumber* module displays the current volume reported by WirePlumber. # FORMAT REPLACEMENTS -*{volume}*: Volume in percentage. +*{volume}*: Volume in percentage, cubic scale as integer. + +*{volume_linear}*: Volume in linear scale as float. + +*{volume_cubic}*: Volume in cubic scale as float. + +*{volume_db}*: Volume in decibel scale as float. *{node_name}*: The node's nickname as reported by WirePlumber (*node.nick* property) @@ -123,7 +134,7 @@ The *wireplumber* module displays the current volume reported by WirePlumber. } ``` -## Separate Sink and Source Widgets +## Separate Sink and Source Widgets ``` "wireplumber#sink": { @@ -143,6 +154,16 @@ The *wireplumber* module displays the current volume reported by WirePlumber. } ``` +## Use a Different Scale + +``` +"wireplumber": { + "format": "{volume_db:.2f}dB", + "scroll-scale": "db", + "scroll-step": 2.5 +} +``` + # STYLE - *#wireplumber* diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index a43ad29b..f37445c7 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -1,6 +1,8 @@ #include "modules/wireplumber.hpp" #include +#include +#include bool isValidNodeId(uint32_t id) { return id > 0 && id < G_MAXUINT32; } @@ -396,7 +398,7 @@ void waybar::modules::Wireplumber::onMixerApiLoaded(WpObject* p, GAsyncResult* r spdlog::debug("[{}]: loaded mixer API", self->name_); g_ptr_array_add(self->apis_, ({ WpPlugin* p = wp_plugin_find(self->wp_core_, "mixer-api"); - g_object_set(G_OBJECT(p), "scale", 1 /* cubic */, nullptr); + g_object_set(G_OBJECT(p), "scale", 0 /* linear */, nullptr); p; })); @@ -436,8 +438,16 @@ auto waybar::modules::Wireplumber::update() -> void { label_.get_style_context()->remove_class("source-muted"); } - int vol = round(volume_ * 100.0); - int source_vol = round(source_volume_ * 100.0); + + double vol_cube = pow(volume_, 3); + double source_vol_cube = pow(source_volume_, 3); + + int vol = round(vol_cube * 100.0); + int source_vol = round(source_vol_cube * 100.0); + + double vol_db = 20.0 * log10(volume_); + double source_vol_db = 20.0 * log10(source_volume_); + // Get the state and apply state-specific format if available auto state = getState(vol); @@ -468,7 +478,10 @@ auto waybar::modules::Wireplumber::update() -> void { std::string markup = fmt::format(fmt::runtime(format), fmt::arg("node_name", node_name_), fmt::arg("volume", vol), fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source), - fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_)); + fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_), + fmt::arg("volume_linear", volume_), fmt::arg("volume_cubic", vol_cube), + fmt::arg("volume_db", vol_db),fmt::arg("source_volume_linear", source_volume_), + fmt::arg("source_volume_cubic", source_vol_cube), fmt::arg("source_volume_db", source_vol_db)); label_.set_markup(markup); if (tooltipEnabled()) { @@ -480,7 +493,10 @@ auto waybar::modules::Wireplumber::update() -> void { label_.set_tooltip_text(fmt::format( fmt::runtime(tooltipFormat), fmt::arg("node_name", node_name_), fmt::arg("volume", vol), fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source), - fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_))); + fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_), + fmt::arg("volume_linear", volume_), fmt::arg("volume_cubic", vol_cube), + fmt::arg("volume_db", vol_db), fmt::arg("source_volume_linear", source_volume_), + fmt::arg("source_volume_cubic", source_vol_cube), fmt::arg("source_volume_db", source_vol_db))); } else { label_.set_tooltip_text(node_name_); } @@ -499,28 +515,61 @@ bool waybar::modules::Wireplumber::handleScroll(GdkEventScroll* e) { return true; } double maxVolume = 1; - double step = 1.0 / 100.0; + double step = 1.0; if (config_["scroll-step"].isDouble()) { - step = config_["scroll-step"].asDouble() / 100.0; + step = config_["scroll-step"].asDouble(); } if (config_["max-volume"].isDouble()) { - maxVolume = config_["max-volume"].asDouble() / 100.0; + maxVolume = config_["max-volume"].asDouble(); } - if (step < min_step_) step = min_step_; + double vol = volume_; + std::string scale = "cubic_percent"; + if (config_["scroll-scale"].isString()) { + scale = config_["scroll-scale"].asString(); + } - double newVol = volume_; + if (scale == "cubic") { + vol = pow(vol, 3); + } + else if (scale == "db") { + vol = log10(vol) * 20.0; + } + else if (scale == "cubic_percent") { + vol = pow(vol, 3) * 100.0; + } + + + double newVol = vol; if (dir == SCROLL_DIR::UP) { - if (volume_ < maxVolume) { - newVol = volume_ + step; - if (newVol > maxVolume) newVol = maxVolume; + newVol = vol + step; + } else if (dir == SCROLL_DIR::DOWN) { + newVol = vol - step; + } + + if (scale == "cubic") { + newVol = cbrt(newVol); + } + else if (scale == "db") { + newVol = exp10(newVol / 20.0); + } + else if (scale == "cubic_percent") { + newVol = cbrt(newVol / 100.0); + } + + if (dir == SCROLL_DIR::UP) { + if (volume_ + min_step_ > newVol) { + newVol = volume_ + min_step_; } } else if (dir == SCROLL_DIR::DOWN) { - if (volume_ > 0) { - newVol = volume_ - step; - if (newVol < 0) newVol = 0; + if (volume_ - min_step_ < newVol) { + newVol = volume_ - min_step_; } } + + if (newVol < 0) newVol = 0; + else if (newVol > maxVolume) newVol = maxVolume; + if (newVol != volume_) { GVariant* variant = g_variant_new_double(newVol); gboolean ret; From 5a29473080cb63b372204d94e70cc9bd28f94fe4 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Fri, 22 Aug 2025 18:44:20 -0400 Subject: [PATCH 376/780] 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 377/780] 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 9720d80524208c38953c0abaec693fdb859e9340 Mon Sep 17 00:00:00 2001 From: zjeffer <4633209+zjeffer@users.noreply.github.com> Date: Sat, 23 Aug 2025 18:25:45 +0200 Subject: [PATCH 378/780] add asan.supp --- asan.supp | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 asan.supp diff --git a/asan.supp b/asan.supp new file mode 100644 index 00000000..3633b361 --- /dev/null +++ b/asan.supp @@ -0,0 +1,5 @@ +# Suppress common address sanitizer issues in dependencies, these are often non-fixable or not an issue. +# Use it like this (when in repo root): ASAN_OPTIONS="suppressions=./asan.supp" ./build/waybar +leak:libpangoft2-1.0.so.0 +leak:libgtk-3.so.0 +leak:libfontconfig.so.1 \ No newline at end of file From 19468d4365bd8578b8d2c0925c89085ca6db6212 Mon Sep 17 00:00:00 2001 From: Manse Date: Mon, 25 Aug 2025 10:50:51 -0300 Subject: [PATCH 379/780] 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 380/780] 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 381/780] 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 382/780] 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 383/780] 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 384/780] 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 385/780] 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 946f175aa287f978163341e6df2ddd85bb53dacb Mon Sep 17 00:00:00 2001 From: Diego Velez Date: Sat, 13 Sep 2025 15:17:01 -0400 Subject: [PATCH 386/780] feat: Show Niri/window component when workspace is empty --- src/modules/niri/window.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/modules/niri/window.cpp b/src/modules/niri/window.cpp index 6e6fd36f..18f5d8a4 100644 --- a/src/modules/niri/window.cpp +++ b/src/modules/niri/window.cpp @@ -82,7 +82,12 @@ void Window::doUpdate() { oldAppId_ = appId; } } else { - label_.hide(); + label_.show(); + label_.set_markup(waybar::util::rewriteString( + fmt::format(fmt::runtime(format_), fmt::arg("title", ""), + fmt::arg("app_id", "")), + config_["rewrite"])); + updateAppIconName("", ""); setClass("solo", false); if (!oldAppId_.empty()) setClass(oldAppId_, false); 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 387/780] 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 388/780] 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 389/780] 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 eee0362367be8a574b2e9fcd9faa7d594248527e Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Wed, 17 Sep 2025 22:09:49 +0000 Subject: [PATCH 390/780] Fix clang build error --- src/modules/clock.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index a7d57437..e4c48207 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -375,7 +375,7 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea } } os << Glib::ustring::format( - (cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right, std::setfill(L' '), + (cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right, std::setfill(' '), std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ - wideCharCount : 0)), calendarLine); From 4a5358e8c58dd608c892514da5f7b51a63789411 Mon Sep 17 00:00:00 2001 From: Cole Leavitt Date: Wed, 17 Sep 2025 21:17:26 -0700 Subject: [PATCH 391/780] 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 392/780] 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 393/780] 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 394/780] 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 395/780] 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 396/780] 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 397/780] 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 398/780] 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 399/780] 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 400/780] 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 401/780] 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 402/780] 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 41c34b15d6f4bdd77d650c9adc5a59b368eb9b13 Mon Sep 17 00:00:00 2001 From: Aleksi Hannula Date: Fri, 3 Oct 2025 09:15:47 +0300 Subject: [PATCH 403/780] Hide niri/language widget if formatted string is empty --- src/modules/niri/language.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/niri/language.cpp b/src/modules/niri/language.cpp index 496e5404..64b8c99d 100644 --- a/src/modules/niri/language.cpp +++ b/src/modules/niri/language.cpp @@ -84,7 +84,7 @@ void Language::doUpdate() { spdlog::debug("niri language formatted layout name {}", layoutName); - if (!format_.empty()) { + if (!layoutName.empty()) { label_.show(); label_.set_markup(layoutName); } else { From d8e23924106c790c11a6b8d64367fa759a20c0c1 Mon Sep 17 00:00:00 2001 From: Pierre Lairez Date: Fri, 3 Oct 2025 11:24:18 +0200 Subject: [PATCH 404/780] 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 405/780] 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; From 559079e9a6afda77754afaf7c8d3f588c1d6206d Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 5 Oct 2025 10:56:24 +0200 Subject: [PATCH 406/780] fix: lint --- include/modules/hyprland/backend.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp index 7caa321d..2e0ef657 100644 --- a/include/modules/hyprland/backend.hpp +++ b/include/modules/hyprland/backend.hpp @@ -45,7 +45,7 @@ class IPC { std::mutex callbackMutex_; util::JsonParser parser_; std::list> callbacks_; - int socketfd_; // the hyprland socket file descriptor + int socketfd_; // the hyprland socket file descriptor pid_t socketOwnerPid_; bool running_ = true; // the ipcThread will stop running when this is false }; From 192baa4556b681f68ccc698000be62c37be671ab Mon Sep 17 00:00:00 2001 From: dimti Date: Tue, 7 Oct 2025 10:41:19 +0300 Subject: [PATCH 407/780] Add css class to hyprland language module of short_name language code --- src/modules/hyprland/language.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index 25f6789d..b1e88521 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -103,7 +103,9 @@ void Language::initLanguage() { searcher = waybar::util::sanitize_string(searcher); + label_.get_style_context()->remove_class(layout_.short_name); layout_ = getLayout(searcher); + label_.get_style_context()->add_class(layout_.short_name); spdlog::debug("hyprland language initLanguage found {}", layout_.full_name); From 632e30eb9ff2e26b491c0ec7c672befa18cc1a15 Mon Sep 17 00:00:00 2001 From: Dominique Martinet Date: Tue, 7 Oct 2025 22:46:22 +0900 Subject: [PATCH 408/780] niri/window: add col/max_col parameters niri v25.08 added window positions to IPC, so waybar actually has the information stored in json around. This handles the associated WindowLayoutsChanged IPC message to keep the position up to date in memory, and adds a couple of format messages to show the current column, so one can dislpay where we are in the workspace (in the hope of improving this feeling of being lost...) Ideally a visual representation of the workspace like the one illustrated in the PR[1] would be even better but this will probably do for now. Link: https://github.com/YaLTeR/niri/pull/1265#issuecomment-2724621758 [1] --- man/waybar-niri-window.5.scd | 4 ++++ src/modules/niri/backend.cpp | 12 ++++++++++++ src/modules/niri/window.cpp | 13 ++++++++++++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/man/waybar-niri-window.5.scd b/man/waybar-niri-window.5.scd index 8e886c29..c836f057 100644 --- a/man/waybar-niri-window.5.scd +++ b/man/waybar-niri-window.5.scd @@ -48,6 +48,10 @@ See the output of "niri msg windows" for examples *{app_id}*: The current app ID of the focused window. +*{col}*: The current column of the focused window in the workspace. + +*{max_col}*: The maximum column inside the workspace of the focused window. + # REWRITE RULES *rewrite* is an object where keys are regular expressions and values are diff --git a/src/modules/niri/backend.cpp b/src/modules/niri/backend.cpp index fa4dc287..528b7b5c 100644 --- a/src/modules/niri/backend.cpp +++ b/src/modules/niri/backend.cpp @@ -202,6 +202,18 @@ void IPC::parseIPC(const std::string &line) { for (auto &win : windows_) { win["is_focused"] = focused && win["id"].asUInt64() == id; } + } else if (const auto &payload = ev["WindowLayoutsChanged"]) { + const auto &values = payload["changes"]; + for (const auto &changed : values) { + const auto id = changed[0].asUInt64(); + const auto &change = changed[1]; + for (auto &win : windows_) { + if (win["id"].asUInt64() == id) { + win["layout"] = change; + break; + } + } + } } } diff --git a/src/modules/niri/window.cpp b/src/modules/niri/window.cpp index 6e6fd36f..e9b11908 100644 --- a/src/modules/niri/window.cpp +++ b/src/modules/niri/window.cpp @@ -17,6 +17,7 @@ Window::Window(const std::string &id, const Bar &bar, const Json::Value &config) gIPC->registerForIPC("WindowOpenedOrChanged", this); gIPC->registerForIPC("WindowClosed", this); gIPC->registerForIPC("WindowFocusChanged", this); + gIPC->registerForIPC("WindowLayoutsChanged", this); dp.emit(); } @@ -54,15 +55,25 @@ void Window::doUpdate() { if (it != windows.cend()) { const auto &window = *it; + auto max_col = -1; + for (const auto &win : windows) { + if (win["workspace_id"].asUInt64() != window["workspace_id"].asUInt64()) { + continue; + } + const auto col = win["layout"]["pos_in_scrolling_layout"][0].asInt64(); + if (col > max_col) max_col = col; + } const auto title = window["title"].asString(); const auto appId = window["app_id"].asString(); + const auto col = window["layout"]["pos_in_scrolling_layout"][0].asInt64(); const auto sanitizedTitle = waybar::util::sanitize_string(title); const auto sanitizedAppId = waybar::util::sanitize_string(appId); label_.show(); label_.set_markup(waybar::util::rewriteString( fmt::format(fmt::runtime(format_), fmt::arg("title", sanitizedTitle), - fmt::arg("app_id", sanitizedAppId)), + fmt::arg("app_id", sanitizedAppId), fmt::arg("col", col), + fmt::arg("max_col", max_col)), config_["rewrite"])); updateAppIconName(appId, ""); From 730b502c769603415611da1315f27f000f7d57c4 Mon Sep 17 00:00:00 2001 From: Maik Broemme Date: Wed, 8 Oct 2025 08:52:19 +0200 Subject: [PATCH 409/780] wlr/taskbar: add `justify` option for Gtk::Box horizontal alignment The `wlr/taskbar` module had no way to control its container's horizontal alignment. This change adds a `justify` config key (left|center|right) that maps to Gtk::Align::START/CENTER/END and is applied via set_halign() on the module's Gtk::Box. Default behavior remains unchanged (left). --- man/waybar-wlr-taskbar.5.scd | 4 ++++ src/modules/wlr/taskbar.cpp | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/man/waybar-wlr-taskbar.5.scd b/man/waybar-wlr-taskbar.5.scd index af1ba97f..ef24a1aa 100644 --- a/man/waybar-wlr-taskbar.5.scd +++ b/man/waybar-wlr-taskbar.5.scd @@ -57,6 +57,10 @@ Addressed by *wlr/taskbar* default: false ++ If set to true, group tasks by their app_id. Cannot be used with 'active-first'. +*justify*: ++ + typeof: string ++ + The alignment of the text within the module's box, allowing options 'left', 'right', or 'center' to define the positioning. + *on-click*: ++ typeof: string ++ The action which should be triggered when clicking on the application button with the left mouse button. diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index c5c522d8..925cec09 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -98,6 +98,17 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, content_.add(icon_); content_.add(text_after_); + if (config_["justify"].isString()) { + auto justify_str = config_["justify"].asString(); + if (justify_str == "left") { + content_.set_halign(Gtk::ALIGN_START); + } else if (justify_str == "right") { + content_.set_halign(Gtk::ALIGN_END); + } else if (justify_str == "center") { + content_.set_halign(Gtk::ALIGN_CENTER); + } + } + content_.show(); button.add(content_); From bef35e48fe8b38aa1cfb67bc25bf7ae42c2ffd4b Mon Sep 17 00:00:00 2001 From: Aaron Andersen Date: Thu, 9 Oct 2025 20:52:51 -0400 Subject: [PATCH 410/780] backlight: gracefully handle a null epoll device --- src/util/backlight_backend.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/util/backlight_backend.cpp b/src/util/backlight_backend.cpp index 863896d5..cc513cce 100644 --- a/src/util/backlight_backend.cpp +++ b/src/util/backlight_backend.cpp @@ -201,7 +201,9 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval, const auto &event = events[i]; check_eq(event.data.fd, udev_fd, "unexpected udev fd"); std::unique_ptr dev{udev_monitor_receive_device(mon.get())}; - check_nn(dev.get(), "epoll dev was null"); + if (!dev) { + continue; + } upsert_device(devices, dev.get()); } From cd820a100dfbbf6908f33ac4495d2e982f5ee85a Mon Sep 17 00:00:00 2001 From: dimti Date: Fri, 10 Oct 2025 11:14:30 +0300 Subject: [PATCH 411/780] also remove/add css class in onEvent method --- src/modules/hyprland/language.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index b1e88521..dd966b17 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -82,7 +82,9 @@ void Language::onEvent(const std::string& ev) { layoutName = waybar::util::sanitize_string(layoutName); + label_.get_style_context()->remove_class(layout_.short_name); layout_ = getLayout(layoutName); + label_.get_style_context()->add_class(layout_.short_name); spdlog::debug("hyprland language onevent with {}", layoutName); From bdf501a8a5d7377491f00acabe2df85449697cbd Mon Sep 17 00:00:00 2001 From: dimti Date: Fri, 10 Oct 2025 11:35:47 +0300 Subject: [PATCH 412/780] make getLayout is not static and set css classes for short name keyboard layout in getLayout method --- include/modules/hyprland/language.hpp | 2 +- src/modules/hyprland/language.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/modules/hyprland/language.hpp b/include/modules/hyprland/language.hpp index ec59e5c3..d1469f60 100644 --- a/include/modules/hyprland/language.hpp +++ b/include/modules/hyprland/language.hpp @@ -30,7 +30,7 @@ class Language : public waybar::ALabel, public EventHandler { std::string short_description; }; - static auto getLayout(const std::string&) -> Layout; + auto getLayout(const std::string&) -> Layout; std::mutex mutex_; const Bar& bar_; diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index dd966b17..9aa97e81 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -82,9 +82,7 @@ void Language::onEvent(const std::string& ev) { layoutName = waybar::util::sanitize_string(layoutName); - label_.get_style_context()->remove_class(layout_.short_name); layout_ = getLayout(layoutName); - label_.get_style_context()->add_class(layout_.short_name); spdlog::debug("hyprland language onevent with {}", layoutName); @@ -105,9 +103,7 @@ void Language::initLanguage() { searcher = waybar::util::sanitize_string(searcher); - label_.get_style_context()->remove_class(layout_.short_name); layout_ = getLayout(searcher); - label_.get_style_context()->add_class(layout_.short_name); spdlog::debug("hyprland language initLanguage found {}", layout_.full_name); @@ -122,6 +118,8 @@ auto Language::getLayout(const std::string& fullName) -> Layout { rxkb_context_parse_default_ruleset(context); rxkb_layout* layout = rxkb_layout_first(context); + label_.get_style_context()->remove_class(layout_.short_name); + spdlog::debug("hyprland language try to remove currently short_name css class {}", layout_.short_name); while (layout != nullptr) { std::string nameOfLayout = rxkb_layout_get_description(layout); @@ -141,6 +139,8 @@ auto Language::getLayout(const std::string& fullName) -> Layout { rxkb_context_unref(context); + label_.get_style_context()->add_class(info.short_name); + spdlog::debug("hyprland language add new short_name css class {}", info.short_name); return info; } From 2e6bf549a05d1f4c72aed4b473626ccf9cd83cd0 Mon Sep 17 00:00:00 2001 From: dimti Date: Fri, 10 Oct 2025 11:52:59 +0300 Subject: [PATCH 413/780] Change label css classes in initial and on event language class method. Used in getLabel - make a strange behaviour --- src/modules/hyprland/language.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index 9aa97e81..3dd5aef7 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -82,7 +82,11 @@ void Language::onEvent(const std::string& ev) { layoutName = waybar::util::sanitize_string(layoutName); + label_.get_style_context()->remove_class(layout_.short_name); + spdlog::debug("hyprland language try to remove currently short_name css class {}", layout_.short_name); layout_ = getLayout(layoutName); + label_.get_style_context()->add_class(layout_.short_name); + spdlog::debug("hyprland language add new short_name css class {}", layout_.short_name); spdlog::debug("hyprland language onevent with {}", layoutName); @@ -103,7 +107,11 @@ void Language::initLanguage() { searcher = waybar::util::sanitize_string(searcher); + label_.get_style_context()->remove_class(layout_.short_name); + spdlog::debug("hyprland language try to remove currently short_name css class {}", layout_.short_name); layout_ = getLayout(searcher); + label_.get_style_context()->add_class(layout_.short_name); + spdlog::debug("hyprland language add new short_name css class {}", layout_.short_name); spdlog::debug("hyprland language initLanguage found {}", layout_.full_name); @@ -118,8 +126,6 @@ auto Language::getLayout(const std::string& fullName) -> Layout { rxkb_context_parse_default_ruleset(context); rxkb_layout* layout = rxkb_layout_first(context); - label_.get_style_context()->remove_class(layout_.short_name); - spdlog::debug("hyprland language try to remove currently short_name css class {}", layout_.short_name); while (layout != nullptr) { std::string nameOfLayout = rxkb_layout_get_description(layout); @@ -139,8 +145,6 @@ auto Language::getLayout(const std::string& fullName) -> Layout { rxkb_context_unref(context); - label_.get_style_context()->add_class(info.short_name); - spdlog::debug("hyprland language add new short_name css class {}", info.short_name); return info; } From a36a5e89a9871773b24dba9e51ac056877a7902a Mon Sep 17 00:00:00 2001 From: dimti Date: Fri, 10 Oct 2025 11:57:44 +0300 Subject: [PATCH 414/780] Approach to dry pattern - separate remove/add css-classes logic to methods --- include/modules/hyprland/language.hpp | 4 +++- src/modules/hyprland/language.cpp | 21 +++++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/include/modules/hyprland/language.hpp b/include/modules/hyprland/language.hpp index d1469f60..199356c1 100644 --- a/include/modules/hyprland/language.hpp +++ b/include/modules/hyprland/language.hpp @@ -30,7 +30,9 @@ class Language : public waybar::ALabel, public EventHandler { std::string short_description; }; - auto getLayout(const std::string&) -> Layout; + auto removeXkbLayoutCssClass() -> void; + auto addXkbLayoutCssClass() -> void; + static auto getLayout(const std::string&) -> Layout; std::mutex mutex_; const Bar& bar_; diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index 3dd5aef7..33e56720 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -82,11 +82,9 @@ void Language::onEvent(const std::string& ev) { layoutName = waybar::util::sanitize_string(layoutName); - label_.get_style_context()->remove_class(layout_.short_name); - spdlog::debug("hyprland language try to remove currently short_name css class {}", layout_.short_name); + removeXkbLayoutCssClass(); layout_ = getLayout(layoutName); - label_.get_style_context()->add_class(layout_.short_name); - spdlog::debug("hyprland language add new short_name css class {}", layout_.short_name); + addXkbLayoutCssClass(); spdlog::debug("hyprland language onevent with {}", layoutName); @@ -107,11 +105,9 @@ void Language::initLanguage() { searcher = waybar::util::sanitize_string(searcher); - label_.get_style_context()->remove_class(layout_.short_name); - spdlog::debug("hyprland language try to remove currently short_name css class {}", layout_.short_name); + removeXkbLayoutCssClass(); layout_ = getLayout(searcher); - label_.get_style_context()->add_class(layout_.short_name); - spdlog::debug("hyprland language add new short_name css class {}", layout_.short_name); + addXkbLayoutCssClass(); spdlog::debug("hyprland language initLanguage found {}", layout_.full_name); @@ -121,6 +117,15 @@ void Language::initLanguage() { } } +auto Language::removeXkbLayoutCssClass() -> void { + label_.get_style_context()->remove_class(layout_.short_name); + spdlog::debug("hyprland language try to remove currently short_name css class {}", layout_.short_name); +} +auto Language::addXkbLayoutCssClass() -> void { + label_.get_style_context()->add_class(layout_.short_name); + spdlog::debug("hyprland language add new short_name css class {}", layout_.short_name); +} + auto Language::getLayout(const std::string& fullName) -> Layout { auto* const context = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES); rxkb_context_parse_default_ruleset(context); From 4c12001569f54cb11596324cff68c52aeaa72a67 Mon Sep 17 00:00:00 2001 From: dimti Date: Fri, 10 Oct 2025 11:58:56 +0300 Subject: [PATCH 415/780] do not need to remove css class from gtk label on init phase --- src/modules/hyprland/language.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index 33e56720..1ece0713 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -105,7 +105,6 @@ void Language::initLanguage() { searcher = waybar::util::sanitize_string(searcher); - removeXkbLayoutCssClass(); layout_ = getLayout(searcher); addXkbLayoutCssClass(); From 7add8b2726d239c825be2eb0106e403a74821e89 Mon Sep 17 00:00:00 2001 From: Kosa Matyas Date: Fri, 10 Oct 2025 19:40:43 +0300 Subject: [PATCH 416/780] fix: FMT version update from 11.0.2 to 12.0.0 --- subprojects/fmt.wrap | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/subprojects/fmt.wrap b/subprojects/fmt.wrap index fd508477..b5262719 100644 --- a/subprojects/fmt.wrap +++ b/subprojects/fmt.wrap @@ -1,13 +1,13 @@ [wrap-file] -directory = fmt-11.0.2 -source_url = https://github.com/fmtlib/fmt/archive/11.0.2.tar.gz -source_filename = fmt-11.0.2.tar.gz +directory = fmt-12.0.0 +source_url = https://github.com/fmtlib/fmt/archive/12.0.0.tar.gz +source_filename = fmt-12.0.0.tar.gz source_hash = 6cb1e6d37bdcb756dbbe59be438790db409cdb4868c66e888d5df9f13f7c027f -patch_filename = fmt_11.0.2-1_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/fmt_11.0.2-1/get_patch +patch_filename = fmt_12.0.0-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/fmt_12.0.0-1/get_patch patch_hash = 90c9e3b8e8f29713d40ca949f6f93ad115d78d7fb921064112bc6179e6427c5e -source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_11.0.2-1/fmt-11.0.2.tar.gz -wrapdb_version = 11.0.2-1 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_12.0.0-1/fmt-12.0.0.tar.gz +wrapdb_version = 12.0.0-1 [provide] fmt = fmt_dep From a96f1caa197f5f83e6e98c47669885bf6fbfe0c1 Mon Sep 17 00:00:00 2001 From: lilly-lizard Date: Sat, 11 Oct 2025 10:25:50 +1300 Subject: [PATCH 417/780] fixed bug after merge with master duplicating code in handle_show() --- src/modules/river/tags.cpp | 57 ++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index c50fcdeb..5712e9a0 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -27,15 +27,11 @@ static void listen_urgent_tags(void *data, struct zriver_output_status_v1 *zrive static_cast(data)->handle_urgent_tags(tags); } -static void listen_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, - const char *title) { - // This module doesn't care -} - -static void listen_mode(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, - const char *mode) { - // This module doesn't care -} +static const zriver_output_status_v1_listener output_status_listener_impl{ + .focused_tags = listen_focused_tags, + .view_tags = listen_view_tags, + .urgent_tags = listen_urgent_tags, +}; static void listen_focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, struct wl_output *output) { @@ -47,11 +43,15 @@ static void listen_unfocused_output(void *data, struct zriver_seat_status_v1 *zr static_cast(data)->handle_unfocused_output(output); } -static const zriver_output_status_v1_listener output_status_listener_impl{ - .focused_tags = listen_focused_tags, - .view_tags = listen_view_tags, - .urgent_tags = listen_urgent_tags, -}; +static void listen_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, + const char *title) { + // This module doesn't care +} + +static void listen_mode(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, + const char *mode) { + // This module doesn't care +} static const zriver_seat_status_v1_listener seat_status_listener_impl{ .focused_output = listen_focused_output, @@ -115,6 +115,7 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con seat_{nullptr}, bar_(bar), box_{bar.orientation, 0}, + output_{nullptr}, output_status_{nullptr}, seat_status_{nullptr} { struct wl_display *display = Client::inst()->wl_display; @@ -122,8 +123,6 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con wl_registry_add_listener(registry, ®istry_listener_impl, this); wl_display_roundtrip(display); - output_ = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); - if (!status_manager_) { spdlog::error("river_status_manager_v1 not advertised"); return; @@ -137,6 +136,10 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con spdlog::error("wl_seat not advertised"); } + output_ = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output_); + seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_); + box_.set_name("tags"); if (!id.empty()) { box_.get_style_context()->add_class(id); @@ -180,14 +183,6 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con button.show(); } - 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); - - seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_); - zriver_seat_status_v1_add_listener(seat_status_, &seat_status_listener_impl, this); - - zriver_status_manager_v1_destroy(status_manager_); - box_.signal_show().connect(sigc::mem_fun(*this, &Tags::handle_show)); } @@ -196,10 +191,6 @@ Tags::~Tags() { zriver_output_status_v1_destroy(output_status_); } - if (seat_status_) { - zriver_seat_status_v1_destroy(seat_status_); - } - if (control_) { zriver_control_v1_destroy(control_); } @@ -210,12 +201,18 @@ Tags::~Tags() { } 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); + spdlog::info("bruh1"); + + 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); + seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_); + zriver_seat_status_v1_add_listener(seat_status_, &seat_status_listener_impl, this); + zriver_status_manager_v1_destroy(status_manager_); status_manager_ = nullptr; + + spdlog::info("bruh2"); } void Tags::handle_primary_clicked(uint32_t tag) { From 966da11f3b0ee7157e6410d54c2f670b2209bae6 Mon Sep 17 00:00:00 2001 From: Viktar Lukashonak Date: Sun, 12 Oct 2025 14:53:49 +0300 Subject: [PATCH 418/780] cava bump & fixing memory leaks --- include/modules/cava/cava_backend.hpp | 4 +-- meson.build | 2 +- src/modules/cava/cava_backend.cpp | 35 +++++++++++++++------------ subprojects/cava.wrap | 14 +++++++---- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/include/modules/cava/cava_backend.hpp b/include/modules/cava/cava_backend.hpp index d8a2ce06..c88182b3 100644 --- a/include/modules/cava/cava_backend.hpp +++ b/include/modules/cava/cava_backend.hpp @@ -43,13 +43,13 @@ class CavaBackend final { util::SleeperThread thread_; util::SleeperThread read_thread_; // Cava API to read audio source - ::cava::ptr input_source_; + ::cava::ptr input_source_{NULL}; 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{}}; + struct ::cava::cava_plan* plan_{NULL}; //{new cava_plan{}}; std::chrono::seconds fetch_input_delay_{4}; // Delay to handle audio source diff --git a/meson.build b/meson.build index 822c566b..0675a963 100644 --- a/meson.build +++ b/meson.build @@ -498,7 +498,7 @@ else endif cava = dependency('cava', - version : '>=0.10.4', + version : '>=0.10.6', required: get_option('cava'), fallback : ['cava', 'cava_dep'], not_found_message: 'cava is not found. Building waybar without cava') diff --git a/src/modules/cava/cava_backend.cpp b/src/modules/cava/cava_backend.cpp index ec32261c..c9dba67f 100644 --- a/src/modules/cava/cava_backend.cpp +++ b/src/modules/cava/cava_backend.cpp @@ -18,7 +18,7 @@ waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) { // Load cava config error_.length = 0; - if (!load_config(cfgPath, &prm_, false, &error_)) { + if (!load_config(cfgPath, &prm_, false, &error_, 0)) { spdlog::error("cava backend. Error loading config. {0}", error_.message); exit(EXIT_FAILURE); } @@ -26,8 +26,10 @@ waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) { // 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"); + if (prm_.data_format) free(prm_.data_format); + prm_.data_format = strdup("ascii"); + if (prm_.raw_target) free(prm_.raw_target); + prm_.raw_target = strdup("/dev/stdout"); prm_.ascii_range = config["format-icons"].size() - 1; prm_.bar_width = 2; @@ -54,7 +56,10 @@ waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) { 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["source"].isString()) { + if (prm_.audio_source) free(prm_.audio_source); + 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(); @@ -67,25 +72,14 @@ waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) { 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_); @@ -95,8 +89,9 @@ waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) { exit(EXIT_FAILURE); } + // Make cava parameters configuration // Init cava plan, audio_raw structure - audio_raw_init(&audio_data_, &audio_raw_, &prm_, plan_); + 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 @@ -118,8 +113,16 @@ waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) { waybar::modules::cava::CavaBackend::~CavaBackend() { thread_.stop(); read_thread_.stop(); + cava_destroy(plan_); delete plan_; plan_ = nullptr; + audio_raw_clean(&audio_raw_); + pthread_mutex_lock(&audio_data_.lock); + audio_data_.terminate = 1; + pthread_mutex_unlock(&audio_data_.lock); + config_clean(&prm_); + free(audio_data_.source); + free(audio_data_.cava_in); } static void upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { diff --git a/subprojects/cava.wrap b/subprojects/cava.wrap index f220207c..e32bb0da 100644 --- a/subprojects/cava.wrap +++ b/subprojects/cava.wrap @@ -1,7 +1,11 @@ -[wrap-file] -directory = cava-0.10.4 -source_url = https://github.com/LukashonakV/cava/archive/0.10.4.tar.gz -source_filename = cava-0.10.4.tar.gz -source_hash =7bc1c1f9535f2bcc5cd2ae8a2434a2e3a05f5670b1c96316df304137ffc65756 +[wrap-git] +url = https://github.com/LukashonakV/cava.git +revision = 23efcced43b5a395747b18a2e5f2171fc0925d18 +depth = 1 + +#directory = cava-0.10.6 +#source_url = https://github.com/LukashonakV/cava/archive/0.10.6.tar.gz +#source_filename = cava-0.10.6.tar.gz +#source_hash = e715c4c6a625b8dc063e57e8e81c80e4d1015ec1b98db69a283b2c6770f839f4 [provide] cava = cava_dep From 644a3575416e457286ffb691d0ee941f52d5a175 Mon Sep 17 00:00:00 2001 From: winkelnp <68015877+winkelnp@users.noreply.github.com> Date: Mon, 13 Oct 2025 23:27:45 +0200 Subject: [PATCH 419/780] [wireplumber] Add support for device.form-factor --- include/modules/wireplumber.hpp | 2 + src/modules/wireplumber.cpp | 72 ++++++++++++++++++++++++++++++--- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/include/modules/wireplumber.hpp b/include/modules/wireplumber.hpp index 0565704f..9b7d5a7b 100644 --- a/include/modules/wireplumber.hpp +++ b/include/modules/wireplumber.hpp @@ -33,6 +33,7 @@ class Wireplumber : public ALabel { static void onDefaultNodesApiChanged(waybar::modules::Wireplumber* self); bool handleScroll(GdkEventScroll* e) override; + std::vector getWPIcon(); static std::list modules; @@ -54,6 +55,7 @@ class Wireplumber : public ALabel { bool source_muted_; double source_volume_; gchar* default_source_name_; + std::string form_factor_; }; } // namespace waybar::modules diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index 4b25f817..d4962e68 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -23,7 +23,8 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val source_node_id_(0), source_muted_(false), source_volume_(0.0), - default_source_name_(nullptr) { + default_source_name_(nullptr), + form_factor_("") { waybar::modules::Wireplumber::modules.push_back(this); wp_init(WP_INIT_PIPEWIRE); @@ -97,6 +98,36 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* : description != nullptr ? description : "Unknown node name"; spdlog::debug("[{}]: Updating '{}' node name to: {}", self->name_, self->type_, self->node_name_); + + // find form-factor only if sink + if (g_strcmp0(self->type_, "Audio/Sink") == 0) { + const auto* devid = wp_properties_get(properties, "device.id"); + spdlog::debug("[{}]: '{}' device.id is {}", self->name_, self->type_, devid); + + auto* devproxy = static_cast( + wp_object_manager_lookup(self->om_, WP_TYPE_GLOBAL_PROXY, WP_CONSTRAINT_TYPE_G_PROPERTY, + "bound-id", "=s", devid, nullptr)); + + if (devproxy == nullptr) { + auto err = fmt::format("Object '{}' not found\n", devid); + spdlog::error("[{}]: {}", self->name_, err); + throw std::runtime_error(err); + } + + g_autoptr(WpProperties) devprop = + WP_IS_PIPEWIRE_OBJECT(devproxy) != 0 + ? wp_pipewire_object_get_properties(WP_PIPEWIRE_OBJECT(devproxy)) + : wp_properties_new_empty(); + devprop = wp_properties_ensure_unique_owner(devprop); + + if (const auto* ff = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(devproxy), "device.form-factor")) { + self->form_factor_ = ff; + spdlog::debug("[{}]: Updating node form factor to: {}", self->name_, self->form_factor_); + } else { + self->form_factor_ = ""; + } + } } void waybar::modules::Wireplumber::updateSourceName(waybar::modules::Wireplumber* self, @@ -358,6 +389,8 @@ void waybar::modules::Wireplumber::prepare(waybar::modules::Wireplumber* self) { "=s", self->type_, nullptr); wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class", "=s", "Audio/Source", nullptr); + wp_object_manager_add_interest(om_, WP_TYPE_DEVICE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class", + "=s", "Audio/Device", nullptr); } void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncResult* res, @@ -415,6 +448,33 @@ void waybar::modules::Wireplumber::asyncLoadRequiredApiModules() { this); } +static const std::array ports = { + "headphone", "speaker", "headset", "hands-free", "portable", "car", "hifi", +}; + +std::vector waybar::modules::Wireplumber::getWPIcon() { + std::vector res; + if (muted_) { + res.emplace_back(node_name_ + "-muted"); + } + res.push_back(node_name_); + res.push_back(source_name_); + std::transform(form_factor_.begin(), form_factor_.end(), form_factor_.begin(), ::tolower); + for (auto const& port : ports) { + if (form_factor_.find(port) != std::string::npos) { + if (muted_) { + res.emplace_back(port + "-muted"); + } + res.push_back(port); + break; + } + } + if (muted_) { + res.emplace_back("default-muted"); + } + return res; +} + auto waybar::modules::Wireplumber::update() -> void { auto format = format_; std::string tooltipFormat; @@ -485,10 +545,10 @@ auto waybar::modules::Wireplumber::update() -> void { std::string formatted_source = fmt::format(fmt::runtime(format_source), fmt::arg("volume", source_vol)); - std::string markup = - fmt::format(fmt::runtime(format), fmt::arg("node_name", node_name_), fmt::arg("volume", vol), - fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source), - fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_)); + std::string markup = fmt::format( + fmt::runtime(format), fmt::arg("node_name", node_name_), fmt::arg("volume", vol), + fmt::arg("icon", getIcon(vol, getWPIcon())), fmt::arg("format_source", formatted_source), + fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_)); label_.set_markup(markup); if (tooltipEnabled()) { @@ -499,7 +559,7 @@ auto waybar::modules::Wireplumber::update() -> void { if (!tooltipFormat.empty()) { label_.set_tooltip_text(fmt::format( fmt::runtime(tooltipFormat), fmt::arg("node_name", node_name_), fmt::arg("volume", vol), - fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source), + fmt::arg("icon", getIcon(vol, getWPIcon())), fmt::arg("format_source", formatted_source), fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_))); } else { label_.set_tooltip_text(node_name_); From deb8a56eb1cc284925c1f238b04c1e011293a6a7 Mon Sep 17 00:00:00 2001 From: winkelnp <68015877+winkelnp@users.noreply.github.com> Date: Tue, 14 Oct 2025 00:40:56 +0200 Subject: [PATCH 420/780] [wireplumber] remove old version (bluetooth) and unnecessary checks (form-factor) --- src/modules/wireplumber.cpp | 45 +++++++++---------------------------- 1 file changed, 11 insertions(+), 34 deletions(-) diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index d4962e68..cec7968d 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -99,34 +99,19 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* : "Unknown node name"; spdlog::debug("[{}]: Updating '{}' node name to: {}", self->name_, self->type_, self->node_name_); - // find form-factor only if sink - if (g_strcmp0(self->type_, "Audio/Sink") == 0) { - const auto* devid = wp_properties_get(properties, "device.id"); - spdlog::debug("[{}]: '{}' device.id is {}", self->name_, self->type_, devid); + // find form-factor + const auto* devid = wp_properties_get(properties, "device.id"); + spdlog::debug("[{}]: '{}' device.id is {}", self->name_, self->type_, devid); - auto* devproxy = static_cast( - wp_object_manager_lookup(self->om_, WP_TYPE_GLOBAL_PROXY, WP_CONSTRAINT_TYPE_G_PROPERTY, - "bound-id", "=s", devid, nullptr)); + auto* dev = static_cast(wp_object_manager_lookup( + self->om_, WP_TYPE_DEVICE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=s", devid, nullptr)); - if (devproxy == nullptr) { - auto err = fmt::format("Object '{}' not found\n", devid); - spdlog::error("[{}]: {}", self->name_, err); - throw std::runtime_error(err); - } - - g_autoptr(WpProperties) devprop = - WP_IS_PIPEWIRE_OBJECT(devproxy) != 0 - ? wp_pipewire_object_get_properties(WP_PIPEWIRE_OBJECT(devproxy)) - : wp_properties_new_empty(); - devprop = wp_properties_ensure_unique_owner(devprop); - - if (const auto* ff = - wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(devproxy), "device.form-factor")) { - self->form_factor_ = ff; - spdlog::debug("[{}]: Updating node form factor to: {}", self->name_, self->form_factor_); - } else { - self->form_factor_ = ""; - } + if (const auto* ff = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(dev), "device.form-factor")) { + self->form_factor_ = ff; + spdlog::debug("[{}]: Updating node form factor to: {}", self->name_, self->form_factor_); + } else { + self->form_factor_ = ""; } } @@ -485,8 +470,6 @@ auto waybar::modules::Wireplumber::update() -> void { auto bt = name.find("bluez") != std::string::npos || name.find("a2dp-sink") != std::string::npos; if (bt) { - // format = - // config_["format-bluetooth"].isString() ? config_["format-bluetooth"].asString() : format; format_name += "-bluetooth"; label_.get_style_context()->add_class("bluetooth"); } else { @@ -495,12 +478,6 @@ auto waybar::modules::Wireplumber::update() -> void { // Handle sink mute state if (muted_) { - // if (bt) - // format = config_["format-bluetooth-muted"].isString() - // ? config_["format-bluetooth-muted"].asString() - // : format; - // else - // format = config_["format-muted"].isString() ? config_["format-muted"].asString() : format; // Check muted bluetooth format exists, otherwise fall back to default muted format. if (format_name != "format" && !config_[format_name + "-muted"].isString()) format_name = "format"; From 6021261383a209ddc3220652ea2caf95eca4bbee Mon Sep 17 00:00:00 2001 From: carljustineoyales Date: Wed, 15 Oct 2025 20:45:42 +0800 Subject: [PATCH 421/780] fix(pulseaudio-slider): Use `background` in CSS example The `background-color` property does not work as expected for the slider. Using the `background` shorthand property correctly applies the color. --- man/waybar-pulseaudio-slider.5.scd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/man/waybar-pulseaudio-slider.5.scd b/man/waybar-pulseaudio-slider.5.scd index cb274826..0271e7c5 100644 --- a/man/waybar-pulseaudio-slider.5.scd +++ b/man/waybar-pulseaudio-slider.5.scd @@ -77,12 +77,12 @@ The slider is a component with multiple CSS Nodes, of which the following are ex min-height: 80px; min-width: 10px; border-radius: 5px; - background-color: black; + background: black; } #pulseaudio-slider highlight { min-width: 10px; border-radius: 5px; - background-color: green; + background: green; } ``` From 9d37dedb57922cb5cb68e06d9ae2ce6170d58b16 Mon Sep 17 00:00:00 2001 From: NicolasBuchin Date: Thu, 16 Oct 2025 10:04:12 +0200 Subject: [PATCH 422/780] fix: resolve memory leak by removing old CSS provider before reloading styles --- src/client.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index 946780db..5136cfd8 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -183,16 +183,24 @@ const std::string waybar::Client::getStyle(const std::string &style, }; auto waybar::Client::setupCss(const std::string &css_file) -> void { - css_provider_ = Gtk::CssProvider::create(); - style_context_ = Gtk::StyleContext::create(); + auto screen = Gdk::Screen::get_default(); + if (!screen) { + throw std::runtime_error("No default screen"); + } - // Load our css file, wherever that may be hiding + if (css_provider_) { + Gtk::StyleContext::remove_provider_for_screen(screen, css_provider_); + css_provider_.reset(); + } + + css_provider_ = Gtk::CssProvider::create(); if (!css_provider_->load_from_path(css_file)) { + css_provider_.reset(); throw std::runtime_error("Can't open style file"); } - // there's always only one screen - style_context_->add_provider_for_screen(Gdk::Screen::get_default(), css_provider_, - GTK_STYLE_PROVIDER_PRIORITY_USER); + + Gtk::StyleContext::add_provider_for_screen(screen, css_provider_, + GTK_STYLE_PROVIDER_PRIORITY_USER); } void waybar::Client::bindInterfaces() { From c831a352f2dce8feb4b606d136f06e8d29c03e7e Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 17 Oct 2025 18:11:50 -0500 Subject: [PATCH 423/780] Added signal support and wait-for-activity bool. --- include/modules/idle_inhibitor.hpp | 10 +++ man/waybar-idle-inhibitor.5.scd | 44 +++++++++- src/modules/idle_inhibitor.cpp | 130 +++++++++++++++++++++++++---- 3 files changed, 167 insertions(+), 17 deletions(-) diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index 22bd808f..4bc9124a 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -10,21 +10,31 @@ namespace waybar::modules { class IdleInhibitor : public ALabel { sigc::connection timeout_; + sigc::connection activity_timeout_; + sigc::connection motion_connection_; + sigc::connection key_connection_; public: IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&); virtual ~IdleInhibitor(); auto update() -> void override; + auto refresh(int) -> void override; static std::list modules; static bool status; private: bool handleToggle(GdkEventButton* const& e) override; + bool handleMotion(GdkEventMotion* const& e); + bool handleKey(GdkEventKey* const& e); void toggleStatus(); + void resetActivityTimeout(); + void setupActivityMonitoring(); + void teardownActivityMonitoring(); const Bar& bar_; struct zwp_idle_inhibitor_v1* idle_inhibitor_; int pid_; + bool wait_for_activity_; }; } // namespace waybar::modules diff --git a/man/waybar-idle-inhibitor.5.scd b/man/waybar-idle-inhibitor.5.scd index 405c8fc5..cd6980f2 100644 --- a/man/waybar-idle-inhibitor.5.scd +++ b/man/waybar-idle-inhibitor.5.scd @@ -76,6 +76,17 @@ screensaver, also known as "presentation mode". typeof: double ++ The number of minutes the inhibition should last. +*wait-for-activity*: ++ + typeof: bool ++ + default: *false* ++ + When enabled, the idle inhibitor remains active as long as there is keyboard or mouse activity on the bar. If there is no activity for the duration specified in *timeout*, the inhibitor will automatically toggle off. This option requires *timeout* to be set. + +*signal*: ++ + typeof: integer ++ + The signal number used to toggle the idle inhibitor externally. ++ + The number is valid between 1 and N, where *SIGRTMIN+N* = *SIGRTMAX*. ++ + Use `pkill -SIGRTMIN+N waybar` to toggle the idle inhibitor from scripts or keybindings. + *tooltip*: ++ typeof: bool ++ default: true ++ @@ -115,17 +126,46 @@ screensaver, also known as "presentation mode". # EXAMPLES +Basic usage with timeout: + ``` "idle_inhibitor": { "format": "{icon}", "format-icons": { - "activated": "", - "deactivated": "" + "activated": "", + "deactivated": "" }, "timeout": 30.5 } ``` +With external control via signals (can be toggled with `pkill -SIGRTMIN+8 waybar`): + +``` +"idle_inhibitor": { + "format": "{icon}", + "format-icons": { + "activated": "", + "deactivated": "" + }, + "signal": 8 +} +``` + +With wait-for-activity feature: + +``` +"idle_inhibitor": { + "format": "{icon}", + "format-icons": { + "activated": "", + "deactivated": "" + }, + "timeout": 5.0, + "wait-for-activity": true +} +``` + # STYLE - *#idle_inhibitor* diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index a5fc9ac7..6012bb4c 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -11,11 +11,17 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& : ALabel(config, "idle_inhibitor", id, "{status}", 0, false, true), bar_(bar), idle_inhibitor_(nullptr), - pid_(-1) { + pid_(-1), + wait_for_activity_(false) { if (waybar::Client::inst()->idle_inhibit_manager == nullptr) { throw std::runtime_error("idle-inhibit not available"); } + // Read the wait-for-activity config option + if (config_["wait-for-activity"].isBool()) { + wait_for_activity_ = config_["wait-for-activity"].asBool(); + } + if (waybar::modules::IdleInhibitor::modules.empty() && config_["start-activated"].isBool() && config_["start-activated"].asBool() != status) { toggleStatus(); @@ -32,6 +38,8 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& } waybar::modules::IdleInhibitor::~IdleInhibitor() { + teardownActivityMonitoring(); + if (idle_inhibitor_ != nullptr) { zwp_idle_inhibitor_v1_destroy(idle_inhibitor_); idle_inhibitor_ = nullptr; @@ -77,6 +85,17 @@ auto waybar::modules::IdleInhibitor::update() -> void { ALabel::update(); } +auto waybar::modules::IdleInhibitor::refresh(int sig) -> void { + if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) { + toggleStatus(); + + // Make all other idle inhibitor modules update + for (auto const& module : waybar::modules::IdleInhibitor::modules) { + module->update(); + } + } +} + void waybar::modules::IdleInhibitor::toggleStatus() { status = !status; @@ -89,20 +108,30 @@ void waybar::modules::IdleInhibitor::toggleStatus() { auto timeoutMins = config_["timeout"].asDouble(); int timeoutSecs = timeoutMins * 60; - timeout_ = Glib::signal_timeout().connect_seconds( - []() { - /* intentionally not tied to a module instance lifetime - * as the output with `this` can be disconnected - */ - spdlog::info("deactivating idle_inhibitor by timeout"); - status = false; - for (auto const& module : waybar::modules::IdleInhibitor::modules) { - module->update(); - } - /* disconnect */ - return false; - }, - timeoutSecs); + // If wait-for-activity is enabled, set up activity monitoring + if (wait_for_activity_) { + setupActivityMonitoring(); + resetActivityTimeout(); + } else { + // Original behavior: simple timeout + timeout_ = Glib::signal_timeout().connect_seconds( + []() { + /* intentionally not tied to a module instance lifetime + * as the output with `this` can be disconnected + */ + spdlog::info("deactivating idle_inhibitor by timeout"); + status = false; + for (auto const& module : waybar::modules::IdleInhibitor::modules) { + module->update(); + } + /* disconnect */ + return false; + }, + timeoutSecs); + } + } else { + // When deactivated, tear down activity monitoring + teardownActivityMonitoring(); } } @@ -121,3 +150,74 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { ALabel::handleToggle(e); return true; } + +bool waybar::modules::IdleInhibitor::handleMotion(GdkEventMotion* const& e) { + if (wait_for_activity_ && status) { + resetActivityTimeout(); + } + return false; +} + +bool waybar::modules::IdleInhibitor::handleKey(GdkEventKey* const& e) { + if (wait_for_activity_ && status) { + resetActivityTimeout(); + } + return false; +} + +void waybar::modules::IdleInhibitor::resetActivityTimeout() { + if (!config_["timeout"].isNumeric()) { + return; + } + + if (activity_timeout_.connected()) { + activity_timeout_.disconnect(); + } + + auto timeoutMins = config_["timeout"].asDouble(); + int timeoutSecs = timeoutMins * 60; + + activity_timeout_ = Glib::signal_timeout().connect_seconds( + []() { + spdlog::info("deactivating idle_inhibitor due to inactivity"); + status = false; + for (auto const& module : waybar::modules::IdleInhibitor::modules) { + module->update(); + } + return false; + }, + timeoutSecs); +} + +void waybar::modules::IdleInhibitor::setupActivityMonitoring() { + // Don't set up if already connected + if (motion_connection_.connected() || key_connection_.connected()) { + return; + } + + // Enable motion and key event monitoring on the bar window + auto window = bar_.window.get_window(); + if (window) { + window->set_events(window->get_events() | Gdk::POINTER_MOTION_MASK | Gdk::KEY_PRESS_MASK); + } + + // Connect to the bar window's event signals + motion_connection_ = bar_.window.signal_motion_notify_event().connect( + sigc::mem_fun(*this, &IdleInhibitor::handleMotion)); + key_connection_ = bar_.window.signal_key_press_event().connect( + sigc::mem_fun(*this, &IdleInhibitor::handleKey)); +} + +void waybar::modules::IdleInhibitor::teardownActivityMonitoring() { + if (activity_timeout_.connected()) { + activity_timeout_.disconnect(); + } + + if (motion_connection_.connected()) { + motion_connection_.disconnect(); + } + + if (key_connection_.connected()) { + key_connection_.disconnect(); + } +} From 262f8d96f31d1ac608beadee680e5f7b63d76353 Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 17 Oct 2025 18:26:06 -0500 Subject: [PATCH 424/780] fixed the compilation errors in the `idle_inhibitor.cpp` file --- src/modules/idle_inhibitor.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 6012bb4c..619c52f1 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -195,16 +195,20 @@ void waybar::modules::IdleInhibitor::setupActivityMonitoring() { return; } + // Get non-const reference to the window to set up event monitoring + // This is safe because we're only setting up signal handlers, not modifying the Bar itself + auto& window = const_cast(bar_.window); + // Enable motion and key event monitoring on the bar window - auto window = bar_.window.get_window(); - if (window) { - window->set_events(window->get_events() | Gdk::POINTER_MOTION_MASK | Gdk::KEY_PRESS_MASK); + auto gdk_window = window.get_window(); + if (gdk_window) { + gdk_window->set_events(gdk_window->get_events() | Gdk::POINTER_MOTION_MASK | Gdk::KEY_PRESS_MASK); } // Connect to the bar window's event signals - motion_connection_ = bar_.window.signal_motion_notify_event().connect( + motion_connection_ = window.signal_motion_notify_event().connect( sigc::mem_fun(*this, &IdleInhibitor::handleMotion)); - key_connection_ = bar_.window.signal_key_press_event().connect( + key_connection_ = window.signal_key_press_event().connect( sigc::mem_fun(*this, &IdleInhibitor::handleKey)); } From 173e7306d0a968debedd934d24a9d9513df4c721 Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 17 Oct 2025 19:42:53 -0500 Subject: [PATCH 425/780] fix using the `ext-idle-notify-v1` protocol --- include/client.hpp | 2 + include/modules/idle_inhibitor.hpp | 14 ++- protocol/ext-idle-notify-v1.xml | 131 +++++++++++++++++++++++++++++ protocol/meson.build | 1 + src/client.cpp | 4 + src/modules/idle_inhibitor.cpp | 116 ++++++++++++------------- 6 files changed, 197 insertions(+), 71 deletions(-) create mode 100644 protocol/ext-idle-notify-v1.xml diff --git a/include/client.hpp b/include/client.hpp index 0e68f002..a4bc5332 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -12,6 +12,7 @@ struct zwp_idle_inhibitor_v1; struct zwp_idle_inhibit_manager_v1; +struct ext_idle_notifier_v1; namespace waybar { @@ -27,6 +28,7 @@ class Client { struct wl_registry *registry = nullptr; struct zxdg_output_manager_v1 *xdg_output_manager = nullptr; struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; + struct ext_idle_notifier_v1 *idle_notifier = nullptr; std::vector> bars; Config config; std::string bar_id; diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index 4bc9124a..523426c6 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -10,9 +10,8 @@ namespace waybar::modules { class IdleInhibitor : public ALabel { sigc::connection timeout_; - sigc::connection activity_timeout_; - sigc::connection motion_connection_; - sigc::connection key_connection_; + struct ext_idle_notification_v1* idle_notification_; + uint32_t idle_timeout_ms_; public: IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&); @@ -24,12 +23,11 @@ class IdleInhibitor : public ALabel { private: bool handleToggle(GdkEventButton* const& e) override; - bool handleMotion(GdkEventMotion* const& e); - bool handleKey(GdkEventKey* const& e); void toggleStatus(); - void resetActivityTimeout(); - void setupActivityMonitoring(); - void teardownActivityMonitoring(); + void setupIdleNotification(); + void teardownIdleNotification(); + static void handleIdled(void* data, struct ext_idle_notification_v1* notification); + static void handleResumed(void* data, struct ext_idle_notification_v1* notification); const Bar& bar_; struct zwp_idle_inhibitor_v1* idle_inhibitor_; diff --git a/protocol/ext-idle-notify-v1.xml b/protocol/ext-idle-notify-v1.xml new file mode 100644 index 00000000..db7d9c16 --- /dev/null +++ b/protocol/ext-idle-notify-v1.xml @@ -0,0 +1,131 @@ + + + + Copyright © 2015 Martin Gräßlin + Copyright © 2022 Simon Ser + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + This interface allows clients to monitor user idle status. + + After binding to this global, clients can create ext_idle_notification_v1 + objects to get notified when the user is idle for a given amount of time. + + + + + Destroy the manager object. All objects created via this interface + remain valid. + + + + + + Create a new idle notification object. + + The notification object has a minimum timeout duration and is tied to a + seat. The client will be notified if the seat is inactive for at least + the provided timeout. See ext_idle_notification_v1 for more details. + + A zero timeout is valid and means the client wants to be notified as + soon as possible when the seat is inactive. + + + + + + + + + + + Create a new idle notification object to track input from the + user, such as keyboard and mouse movement. Because this object is + meant to track user input alone, it ignores idle inhibitors. + + The notification object has a minimum timeout duration and is tied to a + seat. The client will be notified if the seat is inactive for at least + the provided timeout. See ext_idle_notification_v1 for more details. + + A zero timeout is valid and means the client wants to be notified as + soon as possible when the seat is inactive. + + + + + + + + + + + This interface is used by the compositor to send idle notification events + to clients. + + Initially the notification object is not idle. The notification object + becomes idle when no user activity has happened for at least the timeout + duration, starting from the creation of the notification object. User + activity may include input events or a presence sensor, but is + compositor-specific. + + How this notification responds to idle inhibitors depends on how + it was constructed. If constructed from the + get_idle_notification request, then if an idle inhibitor is + active (e.g. another client has created a zwp_idle_inhibitor_v1 + on a visible surface), the compositor must not make the + notification object idle. However, if constructed from the + get_input_idle_notification request, then idle inhibitors are + ignored, and only input from the user, e.g. from a keyboard or + mouse, counts as activity. + + When the notification object becomes idle, an idled event is sent. When + user activity starts again, the notification object stops being idle, + a resumed event is sent and the timeout is restarted. + + + + + Destroy the notification object. + + + + + + This event is sent when the notification object becomes idle. + + It's a compositor protocol error to send this event twice without a + resumed event in-between. + + + + + + This event is sent when the notification object stops being idle. + + It's a compositor protocol error to send this event twice without an + idled event in-between. It's a compositor protocol error to send this + event prior to any idled event. + + + + diff --git a/protocol/meson.build b/protocol/meson.build index b16113b2..3da6ee47 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -29,6 +29,7 @@ client_protocols = [ ['river-status-unstable-v1.xml'], ['river-control-unstable-v1.xml'], ['dwl-ipc-unstable-v2.xml'], + ['ext-idle-notify-v1.xml'], ] if wayland_protos.version().version_compare('>=1.39') diff --git a/src/client.cpp b/src/client.cpp index 946780db..a71d1c7a 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -7,6 +7,7 @@ #include #include "gtkmm/icontheme.h" +#include "ext-idle-notify-v1-client-protocol.h" #include "idle-inhibit-unstable-v1-client-protocol.h" #include "util/clara.hpp" #include "util/format.hpp" @@ -26,6 +27,9 @@ void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint } else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) { client->idle_inhibit_manager = static_cast( wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1)); + } else if (strcmp(interface, ext_idle_notifier_v1_interface.name) == 0) { + client->idle_notifier = static_cast( + wl_registry_bind(registry, name, &ext_idle_notifier_v1_interface, 1)); } } diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 619c52f1..7d5fdd01 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -1,5 +1,6 @@ #include "modules/idle_inhibitor.hpp" +#include "ext-idle-notify-v1-client-protocol.h" #include "idle-inhibit-unstable-v1-client-protocol.h" #include "util/command.hpp" @@ -11,6 +12,8 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& : ALabel(config, "idle_inhibitor", id, "{status}", 0, false, true), bar_(bar), idle_inhibitor_(nullptr), + idle_notification_(nullptr), + idle_timeout_ms_(0), pid_(-1), wait_for_activity_(false) { if (waybar::Client::inst()->idle_inhibit_manager == nullptr) { @@ -20,6 +23,11 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& // Read the wait-for-activity config option if (config_["wait-for-activity"].isBool()) { wait_for_activity_ = config_["wait-for-activity"].asBool(); + + // Check if ext-idle-notify protocol is available when wait-for-activity is enabled + if (wait_for_activity_ && waybar::Client::inst()->idle_notifier == nullptr) { + throw std::runtime_error("wait-for-activity requires ext-idle-notify-v1 protocol support"); + } } if (waybar::modules::IdleInhibitor::modules.empty() && config_["start-activated"].isBool() && @@ -38,7 +46,7 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& } waybar::modules::IdleInhibitor::~IdleInhibitor() { - teardownActivityMonitoring(); + teardownIdleNotification(); if (idle_inhibitor_ != nullptr) { zwp_idle_inhibitor_v1_destroy(idle_inhibitor_); @@ -107,11 +115,11 @@ void waybar::modules::IdleInhibitor::toggleStatus() { if (status && config_["timeout"].isNumeric()) { auto timeoutMins = config_["timeout"].asDouble(); int timeoutSecs = timeoutMins * 60; + idle_timeout_ms_ = timeoutSecs * 1000; - // If wait-for-activity is enabled, set up activity monitoring + // If wait-for-activity is enabled, set up idle notification if (wait_for_activity_) { - setupActivityMonitoring(); - resetActivityTimeout(); + setupIdleNotification(); } else { // Original behavior: simple timeout timeout_ = Glib::signal_timeout().connect_seconds( @@ -130,8 +138,8 @@ void waybar::modules::IdleInhibitor::toggleStatus() { timeoutSecs); } } else { - // When deactivated, tear down activity monitoring - teardownActivityMonitoring(); + // When deactivated, tear down idle notification + teardownIdleNotification(); } } @@ -151,77 +159,59 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { return true; } -bool waybar::modules::IdleInhibitor::handleMotion(GdkEventMotion* const& e) { - if (wait_for_activity_ && status) { - resetActivityTimeout(); +void waybar::modules::IdleInhibitor::handleIdled(void* data, + struct ext_idle_notification_v1* /*notification*/) { + spdlog::info("deactivating idle_inhibitor due to user inactivity"); + status = false; + for (auto const& module : waybar::modules::IdleInhibitor::modules) { + module->update(); } - return false; } -bool waybar::modules::IdleInhibitor::handleKey(GdkEventKey* const& e) { - if (wait_for_activity_ && status) { - resetActivityTimeout(); - } - return false; +void waybar::modules::IdleInhibitor::handleResumed(void* data, + struct ext_idle_notification_v1* /*notification*/) { + // User became active again - notification will continue monitoring + spdlog::debug("user activity detected, idle_inhibitor still active"); } -void waybar::modules::IdleInhibitor::resetActivityTimeout() { - if (!config_["timeout"].isNumeric()) { +void waybar::modules::IdleInhibitor::setupIdleNotification() { + // Don't set up if already exists + if (idle_notification_ != nullptr) { return; } - if (activity_timeout_.connected()) { - activity_timeout_.disconnect(); - } - - auto timeoutMins = config_["timeout"].asDouble(); - int timeoutSecs = timeoutMins * 60; - - activity_timeout_ = Glib::signal_timeout().connect_seconds( - []() { - spdlog::info("deactivating idle_inhibitor due to inactivity"); - status = false; - for (auto const& module : waybar::modules::IdleInhibitor::modules) { - module->update(); - } - return false; - }, - timeoutSecs); -} - -void waybar::modules::IdleInhibitor::setupActivityMonitoring() { - // Don't set up if already connected - if (motion_connection_.connected() || key_connection_.connected()) { + auto* client = waybar::Client::inst(); + if (client->idle_notifier == nullptr) { + spdlog::error("ext-idle-notify protocol not available"); return; } - // Get non-const reference to the window to set up event monitoring - // This is safe because we're only setting up signal handlers, not modifying the Bar itself - auto& window = const_cast(bar_.window); - - // Enable motion and key event monitoring on the bar window - auto gdk_window = window.get_window(); - if (gdk_window) { - gdk_window->set_events(gdk_window->get_events() | Gdk::POINTER_MOTION_MASK | Gdk::KEY_PRESS_MASK); + // Get the wayland seat from the display + auto* gdk_seat = gdk_display_get_default_seat(client->gdk_display->gobj()); + if (gdk_seat == nullptr) { + spdlog::error("failed to get default seat"); + return; } + auto* wl_seat = gdk_wayland_seat_get_wl_seat(gdk_seat); - // Connect to the bar window's event signals - motion_connection_ = window.signal_motion_notify_event().connect( - sigc::mem_fun(*this, &IdleInhibitor::handleMotion)); - key_connection_ = window.signal_key_press_event().connect( - sigc::mem_fun(*this, &IdleInhibitor::handleKey)); + // Create idle notification that monitors all input (not just when inhibitor is active) + // We use get_idle_notification instead of get_input_idle_notification to respect + // idle inhibitors from other applications + idle_notification_ = ext_idle_notifier_v1_get_idle_notification( + client->idle_notifier, idle_timeout_ms_, wl_seat); + + static const struct ext_idle_notification_v1_listener idle_notification_listener = { + .idled = &IdleInhibitor::handleIdled, + .resumed = &IdleInhibitor::handleResumed, + }; + + ext_idle_notification_v1_add_listener(idle_notification_, &idle_notification_listener, this); + wl_display_roundtrip(client->wl_display); } -void waybar::modules::IdleInhibitor::teardownActivityMonitoring() { - if (activity_timeout_.connected()) { - activity_timeout_.disconnect(); - } - - if (motion_connection_.connected()) { - motion_connection_.disconnect(); - } - - if (key_connection_.connected()) { - key_connection_.disconnect(); +void waybar::modules::IdleInhibitor::teardownIdleNotification() { + if (idle_notification_ != nullptr) { + ext_idle_notification_v1_destroy(idle_notification_); + idle_notification_ = nullptr; } } From d68f168e09cfbaf782bd5ef73ac737649a1014e6 Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 17 Oct 2025 19:50:38 -0500 Subject: [PATCH 426/780] fixed the compilation error --- include/modules/idle_inhibitor.hpp | 6 +++--- src/modules/idle_inhibitor.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index 523426c6..8ccb0d33 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -10,7 +10,7 @@ namespace waybar::modules { class IdleInhibitor : public ALabel { sigc::connection timeout_; - struct ext_idle_notification_v1* idle_notification_; + struct ::ext_idle_notification_v1* idle_notification_; uint32_t idle_timeout_ms_; public: @@ -26,8 +26,8 @@ class IdleInhibitor : public ALabel { void toggleStatus(); void setupIdleNotification(); void teardownIdleNotification(); - static void handleIdled(void* data, struct ext_idle_notification_v1* notification); - static void handleResumed(void* data, struct ext_idle_notification_v1* notification); + static void handleIdled(void* data, struct ::ext_idle_notification_v1* notification); + static void handleResumed(void* data, struct ::ext_idle_notification_v1* notification); const Bar& bar_; struct zwp_idle_inhibitor_v1* idle_inhibitor_; diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 7d5fdd01..78cc5ab8 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -160,7 +160,7 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { } void waybar::modules::IdleInhibitor::handleIdled(void* data, - struct ext_idle_notification_v1* /*notification*/) { + struct ::ext_idle_notification_v1* /*notification*/) { spdlog::info("deactivating idle_inhibitor due to user inactivity"); status = false; for (auto const& module : waybar::modules::IdleInhibitor::modules) { @@ -169,7 +169,7 @@ void waybar::modules::IdleInhibitor::handleIdled(void* data, } void waybar::modules::IdleInhibitor::handleResumed(void* data, - struct ext_idle_notification_v1* /*notification*/) { + struct ::ext_idle_notification_v1* /*notification*/) { // User became active again - notification will continue monitoring spdlog::debug("user activity detected, idle_inhibitor still active"); } From 3e2ea1a8705b47e3967b5ae54bf5cd134ce24bfc Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 17 Oct 2025 19:58:43 -0500 Subject: [PATCH 427/780] fixed the type declarations --- include/modules/idle_inhibitor.hpp | 6 +++--- src/modules/idle_inhibitor.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index 8ccb0d33..515d7f5a 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -10,7 +10,7 @@ namespace waybar::modules { class IdleInhibitor : public ALabel { sigc::connection timeout_; - struct ::ext_idle_notification_v1* idle_notification_; + ext_idle_notification_v1* idle_notification_; uint32_t idle_timeout_ms_; public: @@ -26,8 +26,8 @@ class IdleInhibitor : public ALabel { void toggleStatus(); void setupIdleNotification(); void teardownIdleNotification(); - static void handleIdled(void* data, struct ::ext_idle_notification_v1* notification); - static void handleResumed(void* data, struct ::ext_idle_notification_v1* notification); + static void handleIdled(void* data, ext_idle_notification_v1* notification); + static void handleResumed(void* data, ext_idle_notification_v1* notification); const Bar& bar_; struct zwp_idle_inhibitor_v1* idle_inhibitor_; diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 78cc5ab8..95ef94bd 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -160,7 +160,7 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { } void waybar::modules::IdleInhibitor::handleIdled(void* data, - struct ::ext_idle_notification_v1* /*notification*/) { + ext_idle_notification_v1* /*notification*/) { spdlog::info("deactivating idle_inhibitor due to user inactivity"); status = false; for (auto const& module : waybar::modules::IdleInhibitor::modules) { @@ -169,7 +169,7 @@ void waybar::modules::IdleInhibitor::handleIdled(void* data, } void waybar::modules::IdleInhibitor::handleResumed(void* data, - struct ::ext_idle_notification_v1* /*notification*/) { + ext_idle_notification_v1* /*notification*/) { // User became active again - notification will continue monitoring spdlog::debug("user activity detected, idle_inhibitor still active"); } From 26922c7fbc4296c27ae92384f0cfd5fd8283641c Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 17 Oct 2025 20:07:28 -0500 Subject: [PATCH 428/780] Update idle_inhibitor.hpp --- include/modules/idle_inhibitor.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp index 515d7f5a..40c5227c 100644 --- a/include/modules/idle_inhibitor.hpp +++ b/include/modules/idle_inhibitor.hpp @@ -6,6 +6,8 @@ #include "bar.hpp" #include "client.hpp" +struct ext_idle_notification_v1; + namespace waybar::modules { class IdleInhibitor : public ALabel { From e1e99716802d40814964f3e3a4351cad18a9062f Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 17 Oct 2025 20:49:24 -0500 Subject: [PATCH 429/780] fixed stale notification object issue --- src/modules/idle_inhibitor.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 95ef94bd..7ce192af 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -119,6 +119,9 @@ void waybar::modules::IdleInhibitor::toggleStatus() { // If wait-for-activity is enabled, set up idle notification if (wait_for_activity_) { + spdlog::debug("idle_inhibitor: wait-for-activity enabled, timeout: {} ms", idle_timeout_ms_); + // Tear down any existing notification first to ensure fresh setup + teardownIdleNotification(); setupIdleNotification(); } else { // Original behavior: simple timeout @@ -163,6 +166,13 @@ void waybar::modules::IdleInhibitor::handleIdled(void* data, ext_idle_notification_v1* /*notification*/) { spdlog::info("deactivating idle_inhibitor due to user inactivity"); status = false; + + // Clean up the notification since we're deactivating + auto* self = static_cast(data); + if (self != nullptr) { + self->teardownIdleNotification(); + } + for (auto const& module : waybar::modules::IdleInhibitor::modules) { module->update(); } @@ -175,9 +185,12 @@ void waybar::modules::IdleInhibitor::handleResumed(void* data, } void waybar::modules::IdleInhibitor::setupIdleNotification() { - // Don't set up if already exists + spdlog::debug("idle_inhibitor: setting up idle notification"); + + // Clean up any existing notification first if (idle_notification_ != nullptr) { - return; + spdlog::debug("idle_inhibitor: cleaning up existing notification before setup"); + teardownIdleNotification(); } auto* client = waybar::Client::inst(); @@ -197,9 +210,15 @@ void waybar::modules::IdleInhibitor::setupIdleNotification() { // Create idle notification that monitors all input (not just when inhibitor is active) // We use get_idle_notification instead of get_input_idle_notification to respect // idle inhibitors from other applications + spdlog::debug("idle_inhibitor: creating notification with timeout {} ms", idle_timeout_ms_); idle_notification_ = ext_idle_notifier_v1_get_idle_notification( client->idle_notifier, idle_timeout_ms_, wl_seat); + if (idle_notification_ == nullptr) { + spdlog::error("idle_inhibitor: failed to create idle notification"); + return; + } + static const struct ext_idle_notification_v1_listener idle_notification_listener = { .idled = &IdleInhibitor::handleIdled, .resumed = &IdleInhibitor::handleResumed, @@ -207,10 +226,12 @@ void waybar::modules::IdleInhibitor::setupIdleNotification() { ext_idle_notification_v1_add_listener(idle_notification_, &idle_notification_listener, this); wl_display_roundtrip(client->wl_display); + spdlog::debug("idle_inhibitor: idle notification setup complete"); } void waybar::modules::IdleInhibitor::teardownIdleNotification() { if (idle_notification_ != nullptr) { + spdlog::debug("idle_inhibitor: tearing down idle notification"); ext_idle_notification_v1_destroy(idle_notification_); idle_notification_ = nullptr; } From 19afad86751c022cc1a6690aea6be11b7da1ee21 Mon Sep 17 00:00:00 2001 From: guttermonk Date: Fri, 17 Oct 2025 21:21:48 -0500 Subject: [PATCH 430/780] updated client to bind version 2 of the ext-idle-notifier protocol --- src/client.cpp | 6 +++++- src/modules/idle_inhibitor.cpp | 26 ++++++++++++++++++++------ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index a71d1c7a..d4c7aca2 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -28,8 +29,11 @@ void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint client->idle_inhibit_manager = static_cast( wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1)); } else if (strcmp(interface, ext_idle_notifier_v1_interface.name) == 0) { + // Bind version 2 if available (for get_input_idle_notification), otherwise version 1 + auto bind_version = std::min(version, 2u); client->idle_notifier = static_cast( - wl_registry_bind(registry, name, &ext_idle_notifier_v1_interface, 1)); + wl_registry_bind(registry, name, &ext_idle_notifier_v1_interface, bind_version)); + spdlog::debug("Bound ext-idle-notifier-v1 at version {}", bind_version); } } diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 7ce192af..8dfe0b85 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -207,12 +207,26 @@ void waybar::modules::IdleInhibitor::setupIdleNotification() { } auto* wl_seat = gdk_wayland_seat_get_wl_seat(gdk_seat); - // Create idle notification that monitors all input (not just when inhibitor is active) - // We use get_idle_notification instead of get_input_idle_notification to respect - // idle inhibitors from other applications - spdlog::debug("idle_inhibitor: creating notification with timeout {} ms", idle_timeout_ms_); - idle_notification_ = ext_idle_notifier_v1_get_idle_notification( - client->idle_notifier, idle_timeout_ms_, wl_seat); + // Check protocol version to determine which function to use + uint32_t version = wl_proxy_get_version(reinterpret_cast(client->idle_notifier)); + + spdlog::debug("idle_inhibitor: creating notification with timeout {} ms (protocol version {})", + idle_timeout_ms_, version); + + if (version >= 2) { + // Version 2+: Use get_input_idle_notification which ignores idle inhibitors + // This allows us to detect actual user inactivity even while the inhibitor is active + spdlog::debug("idle_inhibitor: using get_input_idle_notification (ignores inhibitors)"); + idle_notification_ = ext_idle_notifier_v1_get_input_idle_notification( + client->idle_notifier, idle_timeout_ms_, wl_seat); + } else { + // Version 1: Fall back to get_idle_notification + // WARNING: This respects idle inhibitors, so it won't fire while inhibitor is active + spdlog::warn("idle_inhibitor: ext-idle-notifier-v1 version {} doesn't support get_input_idle_notification, " + "wait-for-activity may not work correctly", version); + idle_notification_ = ext_idle_notifier_v1_get_idle_notification( + client->idle_notifier, idle_timeout_ms_, wl_seat); + } if (idle_notification_ == nullptr) { spdlog::error("idle_inhibitor: failed to create idle notification"); From 4beb7ddac7f3eaaaeb1364496c201f9304553637 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Sun, 19 Oct 2025 17:35:07 -0500 Subject: [PATCH 431/780] nix: remove useless parens Signed-off-by: Austin Horstman --- nix/default.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/default.nix b/nix/default.nix index a96d0b3f..1f2f3d0f 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -15,7 +15,7 @@ let }; }; in -(waybar.overrideAttrs (oldAttrs: { +waybar.overrideAttrs (oldAttrs: { inherit version; src = lib.cleanSourceWith { @@ -43,4 +43,4 @@ in patchShebangs . popd ''; -})) +}) From 97682a13328dba6f38f0373c1cffd101efdedcaf Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Sun, 19 Oct 2025 17:43:52 -0500 Subject: [PATCH 432/780] nix: bump cava Signed-off-by: Austin Horstman --- nix/default.nix | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nix/default.nix b/nix/default.nix index 1f2f3d0f..ee7a981f 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -5,13 +5,13 @@ version, }: let - libcava = rec { - version = "0.10.4"; + libcava = { src = pkgs.fetchFromGitHub { owner = "LukashonakV"; repo = "cava"; - tag = version; - hash = "sha256-9eTDqM+O1tA/3bEfd1apm8LbEcR9CVgELTIspSVPMKM="; + # NOTE: Needs to match the cava.wrap + rev = "23efcced43b5a395747b18a2e5f2171fc0925d18"; + hash = "sha256-CNspaoK5KuME0GfaNijpC24BfALngzNi04/VNwPqMvo="; }; }; in @@ -39,7 +39,7 @@ waybar.overrideAttrs (oldAttrs: { postUnpack = '' pushd "$sourceRoot" - cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava-${libcava.version} + cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava patchShebangs . popd ''; From 37ac2daac8c5f98f7d013df0e5c793a0f1d9971c Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Tue, 21 Oct 2025 20:58:07 -0500 Subject: [PATCH 433/780] flake.lock: update Signed-off-by: Austin Horstman --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 9b8db656..d689d35b 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1759036355, - "narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=", + "lastModified": 1760878510, + "narHash": "sha256-K5Osef2qexezUfs0alLvZ7nQFTGS9DL2oTVsIXsqLgs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127", + "rev": "5e2a59a5b1a82f89f2c7e598302a9cacebb72a67", "type": "github" }, "original": { From 8b0a82ad34094b16ba27e900e475e6a0c358261d Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Tue, 21 Oct 2025 22:52:55 -0500 Subject: [PATCH 434/780] fix(subprojects): fmt hash mismatch Failing ci for multiple runners Signed-off-by: Austin Horstman --- subprojects/fmt.wrap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/subprojects/fmt.wrap b/subprojects/fmt.wrap index b5262719..15a716ac 100644 --- a/subprojects/fmt.wrap +++ b/subprojects/fmt.wrap @@ -2,10 +2,10 @@ directory = fmt-12.0.0 source_url = https://github.com/fmtlib/fmt/archive/12.0.0.tar.gz source_filename = fmt-12.0.0.tar.gz -source_hash = 6cb1e6d37bdcb756dbbe59be438790db409cdb4868c66e888d5df9f13f7c027f +source_hash = aa3e8fbb6a0066c03454434add1f1fc23299e85758ceec0d7d2d974431481e40 patch_filename = fmt_12.0.0-1_patch.zip patch_url = https://wrapdb.mesonbuild.com/v2/fmt_12.0.0-1/get_patch -patch_hash = 90c9e3b8e8f29713d40ca949f6f93ad115d78d7fb921064112bc6179e6427c5e +patch_hash = 307f288ebf3850abf2f0c50ac1fb07de97df9538d39146d802f3c0d6cada8998 source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_12.0.0-1/fmt-12.0.0.tar.gz wrapdb_version = 12.0.0-1 From bf0ccfd90c1e0e2462ea6737ec619e20ed16a4cf Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Tue, 21 Oct 2025 23:07:04 -0500 Subject: [PATCH 435/780] fix(clock): fix freebsd compatibility Recently introduced for ISO 8601 calendar compatibility. But, lib differences causing the explicit type to break freebsd. Signed-off-by: Austin Horstman --- src/modules/clock.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 62706944..5fe5407b 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -230,7 +230,7 @@ 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 { const unsigned idx = line - 2; - const std::chrono::weekday_indexed indexed_first_day_of_week = + const auto indexed_first_day_of_week = weekday{ym / 1} == firstdow ? firstdow[idx + 1] : firstdow[idx]; return ym / indexed_first_day_of_week; From 68cbb507d95c37c67fc68efe223381e6359d88fc Mon Sep 17 00:00:00 2001 From: cebem1nt Date: Fri, 24 Oct 2025 21:57:58 -0300 Subject: [PATCH 436/780] fixing crash on group scrolling --- src/group.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/group.cpp b/src/group.cpp index 50841efd..294743d7 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -26,7 +26,7 @@ Gtk::RevealerTransitionType getPreferredTransitionType(bool is_vertical) { Group::Group(const std::string& name, const std::string& id, const Json::Value& config, bool vertical) - : AModule(config, name, id, true, true), + : AModule(config, name, id, true, false), box{vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, revealer_box{vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} { box.set_name(name_); From b701d7f70b0486b2645f2c5a364ec141e7bf5c8b Mon Sep 17 00:00:00 2001 From: arne Date: Sun, 26 Oct 2025 06:34:04 +0100 Subject: [PATCH 437/780] docs: add information about weighted-average config option --- man/waybar-battery.5.scd | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/man/waybar-battery.5.scd b/man/waybar-battery.5.scd index 2819aa68..5da7d91d 100644 --- a/man/waybar-battery.5.scd +++ b/man/waybar-battery.5.scd @@ -91,6 +91,11 @@ The *battery* module displays the current capacity and state (eg. charging) of y typeof: string ++ Command to execute when scrolling up on the module. +*weighted-average*: ++ + typeof: bool ++ + default: false ++ + Option to combine multiple batteries with different capacities. + *on-scroll-down*: ++ typeof: string ++ Command to execute when scrolling down on the module. From ca62481dc9bc5567b9febb290c38def5e34ba91c Mon Sep 17 00:00:00 2001 From: arne Date: Sun, 26 Oct 2025 06:43:28 +0100 Subject: [PATCH 438/780] fix: default weighted average to true --- include/modules/battery.hpp | 1 + man/waybar-battery.5.scd | 4 ++-- src/modules/battery.cpp | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/include/modules/battery.hpp b/include/modules/battery.hpp index c593e59b..264edc6e 100644 --- a/include/modules/battery.hpp +++ b/include/modules/battery.hpp @@ -46,6 +46,7 @@ class Battery : public ALabel { std::string old_status_; std::string last_event_; bool warnFirstTime_{true}; + bool weightedAverage_{true}; const Bar& bar_; util::SleeperThread thread_; diff --git a/man/waybar-battery.5.scd b/man/waybar-battery.5.scd index 5da7d91d..6d98fd4e 100644 --- a/man/waybar-battery.5.scd +++ b/man/waybar-battery.5.scd @@ -93,7 +93,7 @@ The *battery* module displays the current capacity and state (eg. charging) of y *weighted-average*: ++ typeof: bool ++ - default: false ++ + default: true ++ Option to combine multiple batteries with different capacities. *on-scroll-down*: ++ @@ -180,7 +180,7 @@ The *battery* module allows one to define custom formats based on up to two fact Every entry in the *events* object consists of a ** (typeof: *string*) and a ** (typeof: *string*). ++ ** can be in one of the following formats: -- *on--* +- *on--* - *on--* Where: diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 138432d0..dcfce6f0 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -27,6 +27,8 @@ waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const J if (global_watch < 0) { throw std::runtime_error("Could not watch for battery plug/unplug"); } + + if (config_["weighted-average"].isBool()) weightedAverage_ = config_["weighted-average"].asBool(); #endif spdlog::debug("battery: worker interval is {}", interval_.count()); worker(); @@ -585,8 +587,7 @@ waybar::modules::Battery::getInfos() { } // Handle weighted-average - if ((config_["weighted-average"].isBool() ? config_["weighted-average"].asBool() : false) && - total_energy_exists && total_energy_full_exists) { + if (weightedAverage_ && total_energy_exists && total_energy_full_exists) { if (total_energy_full > 0.0f) calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full); } From 16d7c9d8497465a360e214f40944a2624852ea30 Mon Sep 17 00:00:00 2001 From: zspher <66728045+zspher@users.noreply.github.com> Date: Tue, 28 Oct 2025 13:35:48 -0600 Subject: [PATCH 439/780] feat(image): expand `~` & `$HOME` in path --- src/modules/image.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/modules/image.cpp b/src/modules/image.cpp index 173aabd3..8b9888bc 100644 --- a/src/modules/image.cpp +++ b/src/modules/image.cpp @@ -1,5 +1,7 @@ #include "modules/image.hpp" +#include + waybar::modules::Image::Image(const std::string& id, const Json::Value& config) : AModule(config, "image", id), box_(Gtk::ORIENTATION_HORIZONTAL, 0) { box_.pack_start(image_); @@ -56,6 +58,10 @@ auto waybar::modules::Image::update() -> void { path_ = ""; } + // expand path if "~" or "$HOME" is present in original path + auto result = Config::tryExpandPath(path_, ""); + path_ = result.empty() ? "" : result.front(); + if (Glib::file_test(path_, Glib::FILE_TEST_EXISTS)) { Glib::RefPtr pixbuf; From d61bc859ae7a086abed0f9b93cf69ce7f4e8b3bf Mon Sep 17 00:00:00 2001 From: zspher <66728045+zspher@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:07:36 -0600 Subject: [PATCH 440/780] perf(image): set image from `path` only once --- src/modules/image.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/modules/image.cpp b/src/modules/image.cpp index 8b9888bc..02b6d336 100644 --- a/src/modules/image.cpp +++ b/src/modules/image.cpp @@ -28,6 +28,13 @@ waybar::modules::Image::Image(const std::string& id, const Json::Value& config) size_ = 16; } + if (config_["path"].isString()) { + auto result = Config::tryExpandPath(config_["path"].asString(), ""); + path_ = result.empty() ? "" : result.front(); + } else { + path_.clear(); + } + if (interval_.count() == 0) { interval_ = std::chrono::milliseconds::max(); } @@ -49,19 +56,14 @@ void waybar::modules::Image::refresh(int sig) { } auto waybar::modules::Image::update() -> void { - if (config_["path"].isString()) { - path_ = config_["path"].asString(); - } else if (config_["exec"].isString()) { + if (config_["exec"].isString()) { output_ = util::command::exec(config_["exec"].asString(), ""); parseOutputRaw(); - } else { - path_ = ""; + // expand path if "~" or "$HOME" is present in original path + auto result = Config::tryExpandPath(path_, ""); + path_ = result.empty() ? "" : result.front(); } - // expand path if "~" or "$HOME" is present in original path - auto result = Config::tryExpandPath(path_, ""); - path_ = result.empty() ? "" : result.front(); - if (Glib::file_test(path_, Glib::FILE_TEST_EXISTS)) { Glib::RefPtr pixbuf; From 4e258716559d90bec9439355b550d782d6b19b17 Mon Sep 17 00:00:00 2001 From: schmop Date: Fri, 31 Oct 2025 13:26:27 +0100 Subject: [PATCH 441/780] Reapply "Make battery module update on plugging/unplugging again (refs #2519)" This reverts commit bb2c67ebadb0c7c48a4e12a8bf16c1a225e57ed4. --- include/modules/battery.hpp | 8 +++++-- include/util/udev_deleter.hpp | 21 ++++++++++++++++++ src/modules/battery.cpp | 40 +++++++++++++++++++--------------- src/util/backlight_backend.cpp | 17 +-------------- 4 files changed, 51 insertions(+), 35 deletions(-) create mode 100644 include/util/udev_deleter.hpp diff --git a/include/modules/battery.hpp b/include/modules/battery.hpp index c593e59b..435b01b4 100644 --- a/include/modules/battery.hpp +++ b/include/modules/battery.hpp @@ -5,8 +5,11 @@ #include #if defined(__linux__) #include +#include "util/udev_deleter.hpp" #endif +#include + #include #include #include @@ -37,11 +40,12 @@ class Battery : public ALabel { void setBarClass(std::string&); void processEvents(std::string& state, std::string& status, uint8_t capacity); - int global_watch; std::map batteries_; + std::unique_ptr udev_; + std::array poll_fds_; + std::unique_ptr mon_; fs::path adapter_; int battery_watch_fd_; - int global_watch_fd_; std::mutex battery_list_mutex_; std::string old_status_; std::string last_event_; diff --git a/include/util/udev_deleter.hpp b/include/util/udev_deleter.hpp new file mode 100644 index 00000000..b2f1e538 --- /dev/null +++ b/include/util/udev_deleter.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace waybar::util { +struct UdevDeleter { + void operator()(udev *ptr) const { udev_unref(ptr); } +}; + +struct UdevDeviceDeleter { + void operator()(udev_device *ptr) const { udev_device_unref(ptr); } +}; + +struct UdevEnumerateDeleter { + void operator()(udev_enumerate *ptr) const { udev_enumerate_unref(ptr); } +}; + +struct UdevMonitorDeleter { + void operator()(udev_monitor *ptr) const { udev_monitor_unref(ptr); } +}; +} // namespace waybar::util \ No newline at end of file diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 138432d0..d4594e2a 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -8,6 +8,9 @@ #include #endif #include +#include +#include +#include waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const Json::Value& config) : ALabel(config, "battery", id, "{capacity}%", 60), last_event_(""), bar_(bar) { @@ -16,17 +19,18 @@ waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const J if (battery_watch_fd_ == -1) { throw std::runtime_error("Unable to listen batteries."); } - - global_watch_fd_ = inotify_init1(IN_CLOEXEC); - if (global_watch_fd_ == -1) { - throw std::runtime_error("Unable to listen batteries."); + udev_ = std::unique_ptr(udev_new()); + if (udev_ == nullptr) { + throw std::runtime_error("udev_new failed"); } - - // Watch the directory for any added or removed batteries - global_watch = inotify_add_watch(global_watch_fd_, data_dir_.c_str(), IN_CREATE | IN_DELETE); - if (global_watch < 0) { - throw std::runtime_error("Could not watch for battery plug/unplug"); + mon_ = std::unique_ptr(udev_monitor_new_from_netlink(udev_.get(), "kernel")); + if (mon_ == nullptr) { + throw std::runtime_error("udev monitor new failed"); } + if (udev_monitor_filter_add_match_subsystem_devtype(mon_.get(), "power_supply", nullptr) < 0) { + throw std::runtime_error("udev failed to add monitor filter"); + } + udev_monitor_enable_receiving(mon_.get()); #endif spdlog::debug("battery: worker interval is {}", interval_.count()); worker(); @@ -36,11 +40,6 @@ waybar::modules::Battery::~Battery() { #if defined(__linux__) std::lock_guard guard(battery_list_mutex_); - if (global_watch >= 0) { - inotify_rm_watch(global_watch_fd_, global_watch); - } - close(global_watch_fd_); - for (auto it = batteries_.cbegin(), next_it = it; it != batteries_.cend(); it = next_it) { ++next_it; auto watch_id = (*it).second; @@ -77,12 +76,18 @@ void waybar::modules::Battery::worker() { dp.emit(); }; thread_battery_update_ = [this] { - struct inotify_event event = {0}; - int nbytes = read(global_watch_fd_, &event, sizeof(event)); - if (nbytes != sizeof(event) || event.mask & IN_IGNORED) { + poll_fds_[0].revents = 0; + poll_fds_[0].events = POLLIN; + poll_fds_[0].fd = udev_monitor_get_fd(mon_.get()); + int ret = poll(poll_fds_.data(), poll_fds_.size(), -1); + if (ret < 0) { thread_.stop(); return; } + if ((poll_fds_[0].revents & POLLIN) != 0) { + signalfd_siginfo signal_info; + read(poll_fds_[0].fd, &signal_info, sizeof(signal_info)); + } refreshBatteries(); dp.emit(); }; @@ -679,6 +684,7 @@ auto waybar::modules::Battery::update() -> void { status = getAdapterStatus(capacity); } auto status_pretty = status; + puts(status.c_str()); // Transform to lowercase and replace space with dash std::ranges::transform(status.begin(), status.end(), status.begin(), [](char ch) { return ch == ' ' ? '-' : std::tolower(ch); }); diff --git a/src/util/backlight_backend.cpp b/src/util/backlight_backend.cpp index cc513cce..204009ab 100644 --- a/src/util/backlight_backend.cpp +++ b/src/util/backlight_backend.cpp @@ -1,4 +1,5 @@ #include "util/backlight_backend.hpp" +#include "util/udev_deleter.hpp" #include #include @@ -29,22 +30,6 @@ class FileDescriptor { int fd_; }; -struct UdevDeleter { - void operator()(udev *ptr) { udev_unref(ptr); } -}; - -struct UdevDeviceDeleter { - void operator()(udev_device *ptr) { udev_device_unref(ptr); } -}; - -struct UdevEnumerateDeleter { - void operator()(udev_enumerate *ptr) { udev_enumerate_unref(ptr); } -}; - -struct UdevMonitorDeleter { - void operator()(udev_monitor *ptr) { udev_monitor_unref(ptr); } -}; - void check_eq(int rc, int expected, const char *message = "eq, rc was: ") { if (rc != expected) { throw std::runtime_error(fmt::format(fmt::runtime(message), rc)); From ce1da5a1781b493b0d358e81cdce46d59cd43b0f Mon Sep 17 00:00:00 2001 From: schmop Date: Fri, 31 Oct 2025 13:54:41 +0100 Subject: [PATCH 442/780] Make udev_deleter not linux-only and fix freebsd build --- include/modules/battery.hpp | 3 +-- src/modules/battery.cpp | 5 +++-- src/util/backlight_backend.cpp | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/modules/battery.hpp b/include/modules/battery.hpp index 435b01b4..af82e638 100644 --- a/include/modules/battery.hpp +++ b/include/modules/battery.hpp @@ -5,9 +5,7 @@ #include #if defined(__linux__) #include -#include "util/udev_deleter.hpp" #endif - #include #include @@ -18,6 +16,7 @@ #include "ALabel.hpp" #include "bar.hpp" #include "util/sleeper_thread.hpp" +#include "util/udev_deleter.hpp" namespace waybar::modules { diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index d4594e2a..ade71003 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -7,9 +7,9 @@ #if defined(__FreeBSD__) #include #endif -#include #include #include +#include #include waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const Json::Value& config) @@ -23,7 +23,8 @@ waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const J if (udev_ == nullptr) { throw std::runtime_error("udev_new failed"); } - mon_ = std::unique_ptr(udev_monitor_new_from_netlink(udev_.get(), "kernel")); + mon_ = std::unique_ptr( + udev_monitor_new_from_netlink(udev_.get(), "kernel")); if (mon_ == nullptr) { throw std::runtime_error("udev monitor new failed"); } diff --git a/src/util/backlight_backend.cpp b/src/util/backlight_backend.cpp index 204009ab..8ae52fb9 100644 --- a/src/util/backlight_backend.cpp +++ b/src/util/backlight_backend.cpp @@ -1,5 +1,4 @@ #include "util/backlight_backend.hpp" -#include "util/udev_deleter.hpp" #include #include @@ -9,6 +8,8 @@ #include #include +#include "util/udev_deleter.hpp" + namespace { class FileDescriptor { public: From 73808dfacccfcd1926a05a92924dfa6f2864aba7 Mon Sep 17 00:00:00 2001 From: Grzegorz Szymaszek Date: Tue, 18 Nov 2025 11:50:04 +0100 Subject: [PATCH 443/780] modules: memory: fix fully utilized swap percentage calculation Do calculate used swap percentage even if swapfree is 0, otherwise reported used_swap_percentage would be 0, even though it should be 100. Signed-off-by: Grzegorz Szymaszek --- src/modules/memory/common.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp index 18600cd2..a83f6526 100644 --- a/src/modules/memory/common.cpp +++ b/src/modules/memory/common.cpp @@ -36,7 +36,7 @@ auto waybar::modules::Memory::update() -> void { float total_swap_gigabytes = 0.01 * round(swaptotal / 10485.76); int used_ram_percentage = 100 * (memtotal - memfree) / memtotal; int used_swap_percentage = 0; - if (swaptotal && swapfree) { + if (swaptotal) { used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal; } float used_ram_gigabytes = 0.01 * round((memtotal - memfree) / 10485.76); From 5b5b80a03146751174572aaa5a2023ae731fcce3 Mon Sep 17 00:00:00 2001 From: OlivierNeyret Date: Sat, 22 Nov 2025 17:47:03 +0100 Subject: [PATCH 444/780] Fix indentation formatting in power_menu.xml --- resources/custom_modules/power_menu.xml | 30 ++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/resources/custom_modules/power_menu.xml b/resources/custom_modules/power_menu.xml index aa2a42ca..972499d7 100644 --- a/resources/custom_modules/power_menu.xml +++ b/resources/custom_modules/power_menu.xml @@ -2,27 +2,27 @@ - - Suspend - - - - - Hibernate - - + + Suspend + + - - Shutdown - + + Hibernate + + + + + Shutdown + - - Reboot - + + Reboot + From 097c00c7bd8febccde2f80e1ecf893007c48586a Mon Sep 17 00:00:00 2001 From: BartiX259 Date: Sat, 22 Nov 2025 20:09:34 +0100 Subject: [PATCH 445/780] fix(docs): pulseaudio man page example The example configuration in the man page used 'headphones' and 'handsfree' as keys for format-icons. The correct keys are 'headphone' and 'hands-free'. --- man/waybar-pulseaudio.5.scd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/man/waybar-pulseaudio.5.scd b/man/waybar-pulseaudio.5.scd index d47fc744..d1cf5884 100644 --- a/man/waybar-pulseaudio.5.scd +++ b/man/waybar-pulseaudio.5.scd @@ -173,8 +173,8 @@ to be selected when the corresponding audio device is muted. This applies to *de "format-icons": { "alsa_output.pci-0000_00_1f.3.analog-stereo": "", "alsa_output.pci-0000_00_1f.3.analog-stereo-muted": "", - "headphones": "", - "handsfree": "", + "headphone": "", + "hands-free": "", "headset": "", "phone": "", "phone-muted": "", From 13519ca5bfa6d24e638ace7b82fe0fe7bad9bcb8 Mon Sep 17 00:00:00 2001 From: Viktar Lukashonak Date: Wed, 26 Nov 2025 13:26:32 +0300 Subject: [PATCH 446/780] cava. nonsafe thread.= & cava bump --- include/modules/cava/cava.hpp | 2 +- include/modules/cava/cava_backend.hpp | 6 +- meson.build | 6 +- src/modules/cava/cava.cpp | 6 +- src/modules/cava/cava_backend.cpp | 239 ++++++++++++++------------ subprojects/cava.wrap | 11 -- subprojects/libcava.wrap | 12 ++ 7 files changed, 157 insertions(+), 125 deletions(-) delete mode 100644 subprojects/cava.wrap create mode 100644 subprojects/libcava.wrap diff --git a/include/modules/cava/cava.hpp b/include/modules/cava/cava.hpp index 6b13c4bd..7344f15d 100644 --- a/include/modules/cava/cava.hpp +++ b/include/modules/cava/cava.hpp @@ -16,7 +16,7 @@ class Cava final : public ALabel, public sigc::trackable { private: std::shared_ptr backend_; // Text to display - std::string label_text_{""}; + Glib::ustring label_text_{""}; bool hide_on_silence_{false}; std::string format_silent_{""}; int ascii_range_{0}; diff --git a/include/modules/cava/cava_backend.hpp b/include/modules/cava/cava_backend.hpp index c88182b3..17952c4a 100644 --- a/include/modules/cava/cava_backend.hpp +++ b/include/modules/cava/cava_backend.hpp @@ -40,8 +40,8 @@ class CavaBackend final { private: CavaBackend(const Json::Value& config); - util::SleeperThread thread_; util::SleeperThread read_thread_; + sigc::connection out_thread_; // Cava API to read audio source ::cava::ptr input_source_{NULL}; @@ -55,6 +55,7 @@ class CavaBackend final { // Delay to handle audio source std::chrono::milliseconds frame_time_milsec_{1s}; + const Json::Value& config_; int re_paint_{0}; bool silence_{false}; bool silence_prev_{false}; @@ -66,6 +67,9 @@ class CavaBackend final { void execute(); bool isSilence(); void doUpdate(bool force = false); + void loadConfig(); + void freeBackend(); + void doOutReadConnect(); // Signal type_signal_update m_signal_update_; diff --git a/meson.build b/meson.build index 0675a963..6b9805e1 100644 --- a/meson.build +++ b/meson.build @@ -497,10 +497,10 @@ else man_files += files('man/waybar-clock.5.scd') endif -cava = dependency('cava', - version : '>=0.10.6', +cava = dependency('libcava', + version : '>=0.10.7', required: get_option('cava'), - fallback : ['cava', 'cava_dep'], + fallback : ['libcava', 'cava_dep'], not_found_message: 'cava is not found. Building waybar without cava') if cava.found() diff --git a/src/modules/cava/cava.cpp b/src/modules/cava/cava.cpp index a2a74606..906b7021 100644 --- a/src/modules/cava/cava.cpp +++ b/src/modules/cava/cava.cpp @@ -26,7 +26,8 @@ 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"); + if (!label_.get_style_context()->has_class("updated")) + label_.get_style_context()->add_class("updated"); } label_text_.clear(); for (auto& ch : input) @@ -39,7 +40,8 @@ auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void { } auto waybar::modules::cava::Cava::onSilence() -> void { if (!silence_) { - label_.get_style_context()->remove_class("updated"); + if (label_.get_style_context()->has_class("updated")) + label_.get_style_context()->remove_class("updated"); if (hide_on_silence_) label_.hide(); diff --git a/src/modules/cava/cava_backend.cpp b/src/modules/cava/cava_backend.cpp index c9dba67f..1ff7c086 100644 --- a/src/modules/cava/cava_backend.cpp +++ b/src/modules/cava/cava_backend.cpp @@ -9,134 +9,42 @@ std::shared_ptr waybar::modules::cava::CavaB return backend_ptr; } -waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) { +waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) : config_(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_, 0)) { - 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; - if (prm_.data_format) free(prm_.data_format); - prm_.data_format = strdup("ascii"); - if (prm_.raw_target) free(prm_.raw_target); - prm_.raw_target = strdup("/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()) { - if (prm_.audio_source) free(prm_.audio_source); - 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()); - - audio_raw_.height = prm_.ascii_range; - audio_data_.format = -1; - 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_.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); - } - - // Make cava parameters configuration - // 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 + loadConfig(); // Read audio source trough cava API. Cava orginizes this process via infinity loop read_thread_ = [this] { + // Thread safe reading incoming source and do callbacks + doOutReadConnect(); + 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_); + loadConfig(); }; } -waybar::modules::cava::CavaBackend::~CavaBackend() { - thread_.stop(); - read_thread_.stop(); - cava_destroy(plan_); - delete plan_; - plan_ = nullptr; - audio_raw_clean(&audio_raw_); - pthread_mutex_lock(&audio_data_.lock); - audio_data_.terminate = 1; - pthread_mutex_unlock(&audio_data_.lock); - config_clean(&prm_); - free(audio_data_.source); - free(audio_data_.cava_in); -} +waybar::modules::cava::CavaBackend::~CavaBackend() { freeBackend(); } -static void upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { +static bool upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { if (delta == std::chrono::seconds{0}) { delta += std::chrono::seconds{1}; delay += delta; + return true; } + return false; } -static void downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { +static bool downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { if (delta > std::chrono::seconds{0}) { delay -= delta; delta -= std::chrono::seconds{1}; + return true; } + return false; } bool waybar::modules::cava::CavaBackend::isSilence() { @@ -186,6 +94,7 @@ void waybar::modules::cava::CavaBackend::doPauseResume() { upThreadDelay(frame_time_milsec_, suspend_silence_delay_); } pthread_mutex_unlock(&audio_data_.lock); + doOutReadConnect(); } waybar::modules::cava::CavaBackend::type_signal_update @@ -215,12 +124,128 @@ void waybar::modules::cava::CavaBackend::doUpdate(bool force) { } if (!silence_ || prm_.sleep_timer == 0) { - downThreadDelay(frame_time_milsec_, suspend_silence_delay_); + if (downThreadDelay(frame_time_milsec_, suspend_silence_delay_)) doOutReadConnect(); execute(); if (re_paint_ == 1 || force) m_signal_update_.emit(output_); } else { - upThreadDelay(frame_time_milsec_, suspend_silence_delay_); + if (upThreadDelay(frame_time_milsec_, suspend_silence_delay_)) doOutReadConnect(); if (silence_ != silence_prev_ || force) m_signal_silence_.emit(); } silence_prev_ = silence_; } + +void waybar::modules::cava::CavaBackend::freeBackend() { + out_thread_.disconnect(); + + if (plan_ != NULL) { + cava_destroy(plan_); + plan_ = NULL; + } + + audio_raw_clean(&audio_raw_); + pthread_mutex_lock(&audio_data_.lock); + audio_data_.terminate = 1; + pthread_mutex_unlock(&audio_data_.lock); + free_config(&prm_); + free(audio_data_.source); + free(audio_data_.cava_in); +} + +void waybar::modules::cava::CavaBackend::loadConfig() { + freeBackend(); + // 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_, &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; + if (prm_.data_format) free(prm_.data_format); + prm_.data_format = strdup("ascii"); + if (prm_.raw_target) free(prm_.raw_target); + prm_.raw_target = strdup("/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()) { + if (prm_.audio_source) free(prm_.audio_source); + 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()); + + audio_raw_.height = prm_.ascii_range; + audio_data_.format = -1; + 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_.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); + } + + // Make cava parameters configuration + // 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 +} + +void waybar::modules::cava::CavaBackend::doOutReadConnect() { + out_thread_.disconnect(); + // Thread safe reading incoming source and do callbacks + out_thread_ = Glib::signal_timeout().connect( + [&]() { + Update(); + return true; + }, + frame_time_milsec_.count()); +} diff --git a/subprojects/cava.wrap b/subprojects/cava.wrap deleted file mode 100644 index e32bb0da..00000000 --- a/subprojects/cava.wrap +++ /dev/null @@ -1,11 +0,0 @@ -[wrap-git] -url = https://github.com/LukashonakV/cava.git -revision = 23efcced43b5a395747b18a2e5f2171fc0925d18 -depth = 1 - -#directory = cava-0.10.6 -#source_url = https://github.com/LukashonakV/cava/archive/0.10.6.tar.gz -#source_filename = cava-0.10.6.tar.gz -#source_hash = e715c4c6a625b8dc063e57e8e81c80e4d1015ec1b98db69a283b2c6770f839f4 -[provide] -cava = cava_dep diff --git a/subprojects/libcava.wrap b/subprojects/libcava.wrap new file mode 100644 index 00000000..466e2262 --- /dev/null +++ b/subprojects/libcava.wrap @@ -0,0 +1,12 @@ +#[wrap-git] +#url = https://github.com/LukashonakV/cava.git +#revision = 866cfec40b7b9d38e97148d004d3134c1385b52f +#depth = 1 + +[wrap-file] +directory = cava-0.10.7-beta +source_url = https://github.com/LukashonakV/cava/archive/v0.10.7-beta.tar.gz +source_filename = cava-0.10.7.tar.gz +source_hash = 8915d7214f2046554c158fe6f2ae518881dfb573e421ea848727be11a5dfa8c4 +[provide] +libcava = cava_dep From e189649c336dc9dd7673ddbb965f1800987b9581 Mon Sep 17 00:00:00 2001 From: touero Date: Sat, 29 Nov 2025 17:29:52 +0800 Subject: [PATCH 447/780] docs(custom): fix example error about update moudle by signal --- man/waybar-custom.5.scd | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/man/waybar-custom.5.scd b/man/waybar-custom.5.scd index 37b4c42c..88b79151 100644 --- a/man/waybar-custom.5.scd +++ b/man/waybar-custom.5.scd @@ -234,7 +234,6 @@ $text\\n$tooltip\\n$class* ``` "custom/pacman": { "format": "{text} ", - "interval": 3600, // every hour "exec": "checkupdates | wc -l", // # of updates "exec-if": "exit 0", // always run; consider advanced run conditions "on-click": "termite -e 'sudo pacman -Syu'; pkill -SIGRTMIN+8 waybar", // update system @@ -242,7 +241,7 @@ $text\\n$tooltip\\n$class* } ``` -You can use the signal and update the number of available packages with *pkill -RTMIN+8 waybar*. +Under the premise that interval is not defined, you can use the signal and update the number of available packages with *pkill -RTMIN+8 waybar*. # STYLE From b277b639991318e1b99d146fa5c9059ae1664bb0 Mon Sep 17 00:00:00 2001 From: Mental-Vortex <206187961+Mental-Vortex@users.noreply.github.com> Date: Sun, 30 Nov 2025 15:31:51 +0800 Subject: [PATCH 448/780] fix: Return when ID doesn't exist, we only care about the interest list. --- src/modules/wireplumber.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index a43ad29b..b30961cb 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -200,6 +200,7 @@ void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* return; } } + return; } spdlog::warn("[{}]: (onMixerChanged: {}) - Object with id {} not found", self->name_, From cb0ee665a32ab72f1e8fc0a621613217bf12bab3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 1 Dec 2025 00:15:02 +0000 Subject: [PATCH 449/780] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'flake-compat': 'github:edolstra/flake-compat/9100a0f413b0c601e0533d1d94ffd501ce2e7885?narHash=sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX%2BfjA8Xf8PUmqCY%3D' (2025-05-12) → 'github:edolstra/flake-compat/f387cd2afec9419c8ee37694406ca490c3f34ee5?narHash=sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4%3D' (2025-10-27) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/5e2a59a5b1a82f89f2c7e598302a9cacebb72a67?narHash=sha256-K5Osef2qexezUfs0alLvZ7nQFTGS9DL2oTVsIXsqLgs%3D' (2025-10-19) → 'github:NixOS/nixpkgs/2fad6eac6077f03fe109c4d4eb171cf96791faa4?narHash=sha256-sKoIWfnijJ0%2B9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI%3D' (2025-11-27) --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index d689d35b..0a8c48c1 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1747046372, - "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", + "lastModified": 1761588595, + "narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=", "owner": "edolstra", "repo": "flake-compat", - "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5", "type": "github" }, "original": { @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1760878510, - "narHash": "sha256-K5Osef2qexezUfs0alLvZ7nQFTGS9DL2oTVsIXsqLgs=", + "lastModified": 1764242076, + "narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5e2a59a5b1a82f89f2c7e598302a9cacebb72a67", + "rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4", "type": "github" }, "original": { From e362550e270180644d3d7a7a5eb617b7c74e115f Mon Sep 17 00:00:00 2001 From: mliszcz Date: Tue, 2 Dec 2025 17:13:39 +0100 Subject: [PATCH 450/780] fix(sway/workspaces): window-rewrite: do not check for window-format Remove check for unused window-format option. Fixes #3797. --- man/waybar-sway-workspaces.5.scd | 1 - src/modules/sway/workspaces.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/man/waybar-sway-workspaces.5.scd b/man/waybar-sway-workspaces.5.scd index 25b59eb9..3d4e3ad0 100644 --- a/man/waybar-sway-workspaces.5.scd +++ b/man/waybar-sway-workspaces.5.scd @@ -179,7 +179,6 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge "format": "{name} {windows}", "format-window-separator": " | ", "window-rewrite-default": "{name}", - "window-format": "{name}", "window-rewrite": { "class": "", "class": "k", diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 86e7349e..182bf631 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -331,7 +331,7 @@ auto Workspaces::update() -> void { } std::string output = (*it)["name"].asString(); std::string windows = ""; - if (config_["window-format"].isString()) { + if (config_["window-rewrite"].isObject()) { updateWindows((*it), windows); } if (config_["format"].isString()) { From 52f4db115466c2848c24822e2b8fe54508a57f26 Mon Sep 17 00:00:00 2001 From: Viktar Lukashonak Date: Fri, 5 Dec 2025 10:04:52 +0300 Subject: [PATCH 451/780] Moving GTK dispatcher into frontend --- include/modules/cava/cava_backend.hpp | 4 +-- src/modules/cava/cava.cpp | 49 +++++++++++++++------------ src/modules/cava/cava_backend.cpp | 34 ++++++++----------- 3 files changed, 43 insertions(+), 44 deletions(-) diff --git a/include/modules/cava/cava_backend.hpp b/include/modules/cava/cava_backend.hpp index 17952c4a..c9d6b92b 100644 --- a/include/modules/cava/cava_backend.hpp +++ b/include/modules/cava/cava_backend.hpp @@ -41,7 +41,8 @@ class CavaBackend final { private: CavaBackend(const Json::Value& config); util::SleeperThread read_thread_; - sigc::connection out_thread_; + util::SleeperThread out_thread_; + // Cava API to read audio source ::cava::ptr input_source_{NULL}; @@ -69,7 +70,6 @@ class CavaBackend final { void doUpdate(bool force = false); void loadConfig(); void freeBackend(); - void doOutReadConnect(); // Signal type_signal_update m_signal_update_; diff --git a/src/modules/cava/cava.cpp b/src/modules/cava/cava.cpp index 906b7021..2ea4e694 100644 --- a/src/modules/cava/cava.cpp +++ b/src/modules/cava/cava.cpp @@ -24,30 +24,35 @@ auto waybar::modules::cava::Cava::doAction(const std::string& name) -> void { // 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"); - if (!label_.get_style_context()->has_class("updated")) - 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)); + Glib::signal_idle().connect_once([this, input]() { + if (silence_) { + label_.get_style_context()->remove_class("silent"); + if (!label_.get_style_context()->has_class("updated")) + 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(); + label_.set_markup(label_text_); + label_.show(); + ALabel::update(); + }); silence_ = false; } -auto waybar::modules::cava::Cava::onSilence() -> void { - if (!silence_) { - if (label_.get_style_context()->has_class("updated")) - 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"); - } +auto waybar::modules::cava::Cava::onSilence() -> void { + Glib::signal_idle().connect_once([this]() { + if (!silence_) { + if (label_.get_style_context()->has_class("updated")) + 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 index 1ff7c086..aec945dc 100644 --- a/src/modules/cava/cava_backend.cpp +++ b/src/modules/cava/cava_backend.cpp @@ -14,9 +14,6 @@ waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) : con loadConfig(); // Read audio source trough cava API. Cava orginizes this process via infinity loop read_thread_ = [this] { - // Thread safe reading incoming source and do callbacks - doOutReadConnect(); - try { input_source_(&audio_data_); } catch (const std::runtime_error& e) { @@ -25,9 +22,19 @@ waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) : con read_thread_.sleep_for(fetch_input_delay_); loadConfig(); }; + // Write outcoming data. Emit signals + out_thread_ = [this] { + Update(); + out_thread_.sleep_for(frame_time_milsec_); + }; } -waybar::modules::cava::CavaBackend::~CavaBackend() { freeBackend(); } +waybar::modules::cava::CavaBackend::~CavaBackend() { + out_thread_.stop(); + read_thread_.stop(); + + freeBackend(); +} static bool upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) { if (delta == std::chrono::seconds{0}) { @@ -94,7 +101,7 @@ void waybar::modules::cava::CavaBackend::doPauseResume() { upThreadDelay(frame_time_milsec_, suspend_silence_delay_); } pthread_mutex_unlock(&audio_data_.lock); - doOutReadConnect(); + Update(); } waybar::modules::cava::CavaBackend::type_signal_update @@ -124,19 +131,17 @@ void waybar::modules::cava::CavaBackend::doUpdate(bool force) { } if (!silence_ || prm_.sleep_timer == 0) { - if (downThreadDelay(frame_time_milsec_, suspend_silence_delay_)) doOutReadConnect(); + if (downThreadDelay(frame_time_milsec_, suspend_silence_delay_)) Update(); execute(); if (re_paint_ == 1 || force) m_signal_update_.emit(output_); } else { - if (upThreadDelay(frame_time_milsec_, suspend_silence_delay_)) doOutReadConnect(); + if (upThreadDelay(frame_time_milsec_, suspend_silence_delay_)) Update(); if (silence_ != silence_prev_ || force) m_signal_silence_.emit(); } silence_prev_ = silence_; } void waybar::modules::cava::CavaBackend::freeBackend() { - out_thread_.disconnect(); - if (plan_ != NULL) { cava_destroy(plan_); plan_ = NULL; @@ -238,14 +243,3 @@ void waybar::modules::cava::CavaBackend::loadConfig() { 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 } - -void waybar::modules::cava::CavaBackend::doOutReadConnect() { - out_thread_.disconnect(); - // Thread safe reading incoming source and do callbacks - out_thread_ = Glib::signal_timeout().connect( - [&]() { - Update(); - return true; - }, - frame_time_milsec_.count()); -} From 85a8fca0de0b2488ed81b4f3b88eca300da051e7 Mon Sep 17 00:00:00 2001 From: Raphael Kimmig Date: Sun, 7 Dec 2025 12:18:24 +0100 Subject: [PATCH 452/780] feat: allow sorting niri workspaces --- include/modules/niri/workspaces.hpp | 6 +++ man/waybar-niri-workspaces.5.scd | 17 ++++++ src/modules/niri/workspaces.cpp | 84 ++++++++++++++++++++++++++++- 3 files changed, 105 insertions(+), 2 deletions(-) diff --git a/include/modules/niri/workspaces.hpp b/include/modules/niri/workspaces.hpp index a6850ed1..09956267 100644 --- a/include/modules/niri/workspaces.hpp +++ b/include/modules/niri/workspaces.hpp @@ -2,6 +2,7 @@ #include #include +#include #include "AModule.hpp" #include "bar.hpp" @@ -18,6 +19,7 @@ class Workspaces : public AModule, public EventHandler { private: void onEvent(const Json::Value &ev) override; void doUpdate(); + void sortWorkspaces(std::vector &workspaces) const; Gtk::Button &addButton(const Json::Value &ws); std::string getIcon(const std::string &value, const Json::Value &ws); @@ -25,6 +27,10 @@ class Workspaces : public AModule, public EventHandler { Gtk::Box box_; // Map from niri workspace id to button. std::unordered_map buttons_; + + bool sort_by_id_ = false; + bool sort_by_name_ = false; + bool sort_by_coordinates_ = false; }; } // namespace waybar::modules::niri diff --git a/man/waybar-niri-workspaces.5.scd b/man/waybar-niri-workspaces.5.scd index 4be85eb3..b8fd15ec 100644 --- a/man/waybar-niri-workspaces.5.scd +++ b/man/waybar-niri-workspaces.5.scd @@ -17,6 +17,23 @@ Addressed by *niri/workspaces* default: false ++ If set to false, workspaces will only be shown on the output they are on. If set to true all workspaces will be shown on every output. +*sort-by-name*: ++ + typeof: bool ++ + default: false ++ + Sort workspaces by name (numeric sort when all names are numbers). Unnamed workspaces fall back to their index on the output. + +*sort-by-coordinates*: ++ + typeof: bool ++ + default: false ++ + Sort workspaces by output and index. If both *sort-by-name* and *sort-by-coordinates* are true, sorting by name is applied. + +*sort-by-id*: ++ + typeof: bool ++ + default: false ++ + Sort workspaces by id, taking precedence over other sort options. + +If none of the sorting options are enabled, workspaces keep their output/index order. + *format*: ++ typeof: string ++ default: {value} ++ diff --git a/src/modules/niri/workspaces.cpp b/src/modules/niri/workspaces.cpp index 7dfc0b35..ee9963b4 100644 --- a/src/modules/niri/workspaces.cpp +++ b/src/modules/niri/workspaces.cpp @@ -4,10 +4,34 @@ #include #include +#include +#include + namespace waybar::modules::niri { Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) : AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) { + const auto config_sort_by_number = config_["sort-by-number"]; + if (config_sort_by_number.isBool()) { + spdlog::warn("[niri/workspaces]: Prefer sort-by-id instead of sort-by-number"); + sort_by_id_ = config_sort_by_number.asBool(); + } + + const auto config_sort_by_id = config_["sort-by-id"]; + if (config_sort_by_id.isBool()) { + sort_by_id_ = config_sort_by_id.asBool(); + } + + const auto config_sort_by_name = config_["sort-by-name"]; + if (config_sort_by_name.isBool()) { + sort_by_name_ = config_sort_by_name.asBool(); + } + + const auto config_sort_by_coordinates = config_["sort-by-coordinates"]; + if (config_sort_by_coordinates.isBool()) { + sort_by_coordinates_ = config_sort_by_coordinates.asBool(); + } + box_.set_name("workspaces"); if (!id.empty()) { box_.get_style_context()->add_class(id); @@ -41,6 +65,8 @@ void Workspaces::doUpdate() { return ws["output"].asString() == bar_.output->name; }); + sortWorkspaces(my_workspaces); + // Remove buttons for removed workspaces. for (auto it = buttons_.begin(); it != buttons_.end();) { auto ws = std::find_if(my_workspaces.begin(), my_workspaces.end(), @@ -123,8 +149,7 @@ void Workspaces::doUpdate() { for (auto it = my_workspaces.cbegin(); it != my_workspaces.cend(); ++it) { const auto &ws = *it; - auto pos = ws["idx"].asUInt() - 1; - if (alloutputs) pos = it - my_workspaces.cbegin(); + const auto pos = static_cast(std::distance(my_workspaces.cbegin(), it)); auto &button = buttons_[ws["id"].asUInt64()]; box_.reorder_child(button, pos); @@ -193,4 +218,59 @@ std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws) return value; } +void Workspaces::sortWorkspaces(std::vector &workspaces) const { + auto get_name = [](const Json::Value &ws) -> std::string { + if (ws["name"]) return ws["name"].asString(); + return std::to_string(ws["idx"].asUInt()); + }; + + auto is_numeric = [](const std::string &value) { + return !value.empty() && + std::all_of(value.begin(), value.end(), [](unsigned char c) { return std::isdigit(c); }); + }; + + const bool names_are_numeric = + std::all_of(workspaces.begin(), workspaces.end(), + [&](const auto &ws) { return is_numeric(get_name(ws)); }); + + auto compare_numeric_strings = [](const std::string &a, const std::string &b) { + if (a.size() != b.size()) return a.size() < b.size(); + return a < b; + }; + + std::sort(workspaces.begin(), workspaces.end(), [&](const auto &a, const auto &b) { + if (sort_by_id_) { + return a["id"].asUInt64() < b["id"].asUInt64(); + } + + if (sort_by_name_) { + const auto a_name = get_name(a); + const auto b_name = get_name(b); + if (a_name == b_name) return a["id"].asUInt64() < b["id"].asUInt64(); + if (names_are_numeric) return compare_numeric_strings(a_name, b_name); + return a_name < b_name; + } + + if (sort_by_coordinates_) { + const auto &a_output = a["output"].asString(); + const auto &b_output = b["output"].asString(); + if (a_output == b_output) { + const auto a_idx = a["idx"].asUInt(); + const auto b_idx = b["idx"].asUInt(); + if (a_idx == b_idx) return a["id"].asUInt64() < b["id"].asUInt64(); + return a_idx < b_idx; + } + return a_output < b_output; + } + + // Default to sorting by workspace index on each output. + const auto &a_output = a["output"].asString(); + const auto &b_output = b["output"].asString(); + const auto a_idx = a["idx"].asUInt(); + const auto b_idx = b["idx"].asUInt(); + if (a_output == b_output) return a_idx < b_idx; + return a_output < b_output; + }); +} + } // namespace waybar::modules::niri From 3773021546d359b516a9a8d30e20c4821ef61eca Mon Sep 17 00:00:00 2001 From: Viktar Lukashonak Date: Sun, 7 Dec 2025 23:08:55 +0300 Subject: [PATCH 453/780] cavaGLSL --- include/modules/cava/cava.hpp | 6 +- include/modules/cava/cavaGLSL.hpp | 43 ++++ include/modules/cava/cava_backend.hpp | 6 + meson.build | 11 +- src/factory.cpp | 7 + src/modules/cava/cavaGLSL.cpp | 271 ++++++++++++++++++++++++++ src/modules/cava/cava_backend.cpp | 35 +++- 7 files changed, 367 insertions(+), 12 deletions(-) create mode 100644 include/modules/cava/cavaGLSL.hpp create mode 100644 src/modules/cava/cavaGLSL.cpp diff --git a/include/modules/cava/cava.hpp b/include/modules/cava/cava.hpp index 7344f15d..f4ceb603 100644 --- a/include/modules/cava/cava.hpp +++ b/include/modules/cava/cava.hpp @@ -9,20 +9,20 @@ 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 Glib::ustring label_text_{""}; + bool silence_{false}; bool hide_on_silence_{false}; std::string format_silent_{""}; int ascii_range_{0}; - bool silence_{false}; // Cava method void pause_resume(); + auto onUpdate(const std::string& input) -> void; + auto onSilence() -> void; // ModuleActionMap static inline std::map actionMap_{{"mode", &waybar::modules::cava::Cava::pause_resume}}; diff --git a/include/modules/cava/cavaGLSL.hpp b/include/modules/cava/cavaGLSL.hpp new file mode 100644 index 00000000..d2632aff --- /dev/null +++ b/include/modules/cava/cavaGLSL.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include "AModule.hpp" +#include "cava_backend.hpp" + +namespace waybar::modules::cava { + +class CavaGLSL final : public AModule, public Gtk::GLArea { + public: + CavaGLSL(const std::string&, const Json::Value&); + ~CavaGLSL() = default; + + private: + std::shared_ptr backend_; + struct ::cava::config_params prm_; + int frame_counter{0}; + bool silence_{false}; + bool hide_on_silence_{false}; + // Cava method + auto onUpdate(const ::cava::audio_raw& input) -> void; + auto onSilence() -> void; + // Member variable to store the shared pointer + std::shared_ptr<::cava::audio_raw> m_data_; + GLuint shaderProgram_; + // OpenGL variables + GLuint fbo_; + GLuint texture_; + GLint uniform_bars_; + GLint uniform_previous_bars_; + GLint uniform_bars_count_; + GLint uniform_time_; + // Methods + void onRealize(); + bool onRender(const Glib::RefPtr& context); + + void initShaders(); + void initSurface(); + void initGLSL(); + GLuint loadShader(const std::string& fileName, GLenum type); +}; +} // namespace waybar::modules::cava diff --git a/include/modules/cava/cava_backend.hpp b/include/modules/cava/cava_backend.hpp index c9d6b92b..4022a2ff 100644 --- a/include/modules/cava/cava_backend.hpp +++ b/include/modules/cava/cava_backend.hpp @@ -32,9 +32,14 @@ class CavaBackend final { int getAsciiRange(); void doPauseResume(); void Update(); + const struct ::cava::config_params* getPrm(); + std::chrono::milliseconds getFrameTimeMilsec(); + // Signal accessor using type_signal_update = sigc::signal; type_signal_update signal_update(); + using type_signal_audio_raw_update = sigc::signal; + type_signal_audio_raw_update signal_audio_raw_update(); using type_signal_silence = sigc::signal; type_signal_silence signal_silence(); @@ -73,6 +78,7 @@ class CavaBackend final { // Signal type_signal_update m_signal_update_; + type_signal_audio_raw_update m_signal_audio_raw_; type_signal_silence m_signal_silence_; }; } // namespace waybar::modules::cava diff --git a/meson.build b/meson.build index 6b9805e1..17818a09 100644 --- a/meson.build +++ b/meson.build @@ -503,10 +503,18 @@ cava = dependency('libcava', fallback : ['libcava', 'cava_dep'], not_found_message: 'cava is not found. Building waybar without cava') +eproxy = dependency('epoxy', required: false) + if cava.found() add_project_arguments('-DHAVE_LIBCAVA', language: 'cpp') - src_files += files('src/modules/cava/cava.cpp', 'src/modules/cava/cava_backend.cpp') + src_files += files('src/modules/cava/cava.cpp', + 'src/modules/cava/cava_backend.cpp') man_files += files('man/waybar-cava.5.scd') + + if eproxy.found() + add_project_arguments('-DHAVE_LIBCAVAGLSL', language: 'cpp') + src_files += files('src/modules/cava/cavaGLSL.cpp') + endif endif if libgps.found() @@ -554,6 +562,7 @@ executable( tz_dep, xkbregistry, cava, + eproxy, libgps ], include_directories: inc_dirs, diff --git a/src/factory.cpp b/src/factory.cpp index 7828ce75..94ca8e61 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -110,6 +110,9 @@ #endif #ifdef HAVE_LIBCAVA #include "modules/cava/cava.hpp" +#ifdef HAVE_LIBCAVAGLSL +#include "modules/cava/cavaGLSL.hpp" +#endif #endif #ifdef HAVE_SYSTEMD_MONITOR #include "modules/systemd_failed_units.hpp" @@ -344,6 +347,10 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, #ifdef HAVE_LIBCAVA if (ref == "cava") { return new waybar::modules::cava::Cava(id, config_[name]); +#ifdef HAVE_LIBCAVAGLSL + } else if (ref == "cavaGLSL") { + return new waybar::modules::cava::CavaGLSL(id, config_[name]); +#endif } #endif #ifdef HAVE_SYSTEMD_MONITOR diff --git a/src/modules/cava/cavaGLSL.cpp b/src/modules/cava/cavaGLSL.cpp new file mode 100644 index 00000000..4ab03bcd --- /dev/null +++ b/src/modules/cava/cavaGLSL.cpp @@ -0,0 +1,271 @@ +#include "modules/cava/cavaGLSL.hpp" + +#include + +#include + +waybar::modules::cava::CavaGLSL::CavaGLSL(const std::string& id, const Json::Value& config) + : AModule(config, "cavaGLSL", id, false, false), + backend_{waybar::modules::cava::CavaBackend::inst(config)} { + set_name(name_); + if (config_["hide_on_silence"].isBool()) hide_on_silence_ = config_["hide_on_silence"].asBool(); + if (!id.empty()) { + get_style_context()->add_class(id); + } + get_style_context()->add_class(MODULE_CLASS); + + set_use_es(true); + // set_auto_render(true); + signal_realize().connect(sigc::mem_fun(*this, &CavaGLSL::onRealize)); + signal_render().connect(sigc::mem_fun(*this, &CavaGLSL::onRender), false); + + // Get parameters_config struct from the backend + prm_ = *backend_->getPrm(); + + // Set widget length + int length{0}; + if (config_["min-length"].isUInt()) + length = config_["min-length"].asUInt(); + else if (config_["max-length"].isUInt()) + length = config_["max-length"].asUInt(); + else + length = prm_.sdl_width; + + set_size_request(length, prm_.sdl_height); + + // Subscribe for changes + backend_->signal_audio_raw_update().connect(sigc::mem_fun(*this, &CavaGLSL::onUpdate)); + // Subscribe for silence + backend_->signal_silence().connect(sigc::mem_fun(*this, &CavaGLSL::onSilence)); + event_box_.add(*this); +} + +auto waybar::modules::cava::CavaGLSL::onUpdate(const ::cava::audio_raw& input) -> void { + Glib::signal_idle().connect_once([this, input]() { + m_data_ = std::make_shared<::cava::audio_raw>(input); + if (silence_) { + get_style_context()->remove_class("silent"); + if (!get_style_context()->has_class("updated")) get_style_context()->add_class("updated"); + show(); + silence_ = false; + } + + queue_render(); + }); +} + +auto waybar::modules::cava::CavaGLSL::onSilence() -> void { + Glib::signal_idle().connect_once([this]() { + if (!silence_) { + if (get_style_context()->has_class("updated")) get_style_context()->remove_class("updated"); + + if (hide_on_silence_) hide(); + silence_ = true; + get_style_context()->add_class("silent"); + // Set clear color to black + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + queue_render(); + } + }); +} + +bool waybar::modules::cava::CavaGLSL::onRender(const Glib::RefPtr& context) { + if (!m_data_) return true; + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture_); + glUniform1i(glGetUniformLocation(shaderProgram_, "inputTexture"), 0); + + glUniform1fv(uniform_bars_, m_data_->number_of_bars, m_data_->bars_raw); + glUniform1fv(uniform_previous_bars_, m_data_->number_of_bars, m_data_->previous_bars_raw); + glUniform1i(uniform_bars_count_, m_data_->number_of_bars); + ++frame_counter; + glUniform1f(uniform_time_, (frame_counter / backend_->getFrameTimeMilsec().count()) / 1e3); + + // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr); + + glBindFramebuffer(GL_FRAMEBUFFER, fbo_); + glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + return true; +} + +void waybar::modules::cava::CavaGLSL::onRealize() { + make_current(); + initShaders(); + initGLSL(); + initSurface(); +} + +struct colors { + uint16_t R; + uint16_t G; + uint16_t B; +}; + +static void parse_color(char* color_string, struct colors* color) { + if (color_string[0] == '#') { + sscanf(++color_string, "%02hx%02hx%02hx", &color->R, &color->G, &color->B); + } +} + +void waybar::modules::cava::CavaGLSL::initGLSL() { + GLint gVertexPos2DLocation{glGetAttribLocation(shaderProgram_, "vertexPosition_modelspace")}; + if (gVertexPos2DLocation == -1) { + spdlog::error("{0}. Could not find vertex position shader variable", name_); + } + + glClearColor(0.f, 0.f, 0.f, 1.f); + + GLfloat vertexData[]{-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f}; + GLint indexData[]{0, 1, 2, 3}; + + GLuint gVBO{0}; + glGenBuffers(1, &gVBO); + glBindBuffer(GL_ARRAY_BUFFER, gVBO); + glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(GLfloat), vertexData, GL_STATIC_DRAW); + + GLuint gIBO{0}; + glGenBuffers(1, &gIBO); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gIBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, 4 * sizeof(GLuint), indexData, GL_STATIC_DRAW); + + GLuint gVAO{0}; + glGenVertexArrays(1, &gVAO); + glBindVertexArray(gVAO); + glEnableVertexAttribArray(gVertexPos2DLocation); + + glBindBuffer(GL_ARRAY_BUFFER, gVBO); + glVertexAttribPointer(gVertexPos2DLocation, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), nullptr); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gIBO); + + glGenFramebuffers(1, &fbo_); + glBindFramebuffer(GL_FRAMEBUFFER, fbo_); + + // Create a texture to attach the framebuffer + glGenTextures(1, &texture_); + glBindTexture(GL_TEXTURE_2D, texture_); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, prm_.sdl_width, prm_.sdl_height, 0, GL_RGBA, + GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_, 0); + + // Check is framebuffer is complete + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + spdlog::error("{0}. Framebuffer not complete", name_); + } + + // Unbind the framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, 0); + uniform_bars_ = glGetUniformLocation(shaderProgram_, "bars"); + uniform_previous_bars_ = glGetUniformLocation(shaderProgram_, "previous_bars"); + uniform_bars_count_ = glGetUniformLocation(shaderProgram_, "bars_count"); + uniform_time_ = glGetUniformLocation(shaderProgram_, "shader_time"); + + GLuint err{glGetError()}; + if (err != 0) { + spdlog::error("{0}. Error on initGLSL: {1}", name_, err); + } +} + +void waybar::modules::cava::CavaGLSL::initSurface() { + colors color = {0}; + GLint uniform_bg_col{glGetUniformLocation(shaderProgram_, "bg_color")}; + parse_color(prm_.bcolor, &color); + glUniform3f(uniform_bg_col, (float)color.R / 255.0, (float)color.G / 255.0, + (float)color.B / 255.0); + GLint uniform_fg_col{glGetUniformLocation(shaderProgram_, "fg_color")}; + parse_color(prm_.color, &color); + glUniform3f(uniform_fg_col, (float)color.R / 255.0, (float)color.G / 255.0, + (float)color.B / 255.0); + GLint uniform_res{glGetUniformLocation(shaderProgram_, "u_resolution")}; + glUniform3f(uniform_res, (float)prm_.sdl_width, (float)prm_.sdl_height, 0.0f); + GLint uniform_bar_width{glGetUniformLocation(shaderProgram_, "bar_width")}; + glUniform1i(uniform_bar_width, prm_.bar_width); + GLint uniform_bar_spacing{glGetUniformLocation(shaderProgram_, "bar_spacing")}; + glUniform1i(uniform_bar_spacing, prm_.bar_spacing); + GLint uniform_gradient_count{glGetUniformLocation(shaderProgram_, "gradient_count")}; + glUniform1i(uniform_gradient_count, prm_.gradient_count); + GLint uniform_gradient_colors{glGetUniformLocation(shaderProgram_, "gradient_colors")}; + GLfloat gradient_colors[8][3]; + for (int i{0}; i < prm_.gradient_count; ++i) { + parse_color(prm_.gradient_colors[i], &color); + gradient_colors[i][0] = (float)color.R / 255.0; + gradient_colors[i][1] = (float)color.G / 255.0; + gradient_colors[i][2] = (float)color.B / 255.0; + } + glUniform3fv(uniform_gradient_colors, 8, (const GLfloat*)gradient_colors); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, nullptr); +} + +void waybar::modules::cava::CavaGLSL::initShaders() { + shaderProgram_ = glCreateProgram(); + + GLuint vertexShader{loadShader(prm_.vertex_shader, GL_VERTEX_SHADER)}; + GLuint fragmentShader{loadShader(prm_.fragment_shader, GL_FRAGMENT_SHADER)}; + + glAttachShader(shaderProgram_, vertexShader); + glAttachShader(shaderProgram_, fragmentShader); + + glLinkProgram(shaderProgram_); + + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + + // Check for linking errors + GLint success, len; + glGetProgramiv(shaderProgram_, GL_LINK_STATUS, &success); + if (!success) { + glGetProgramiv(shaderProgram_, GL_INFO_LOG_LENGTH, &len); + GLchar* infoLog{(char*)'\0'}; + glGetProgramInfoLog(shaderProgram_, len, &len, infoLog); + spdlog::error("{0}. Shader linking error: {1}", name_, infoLog); + } + + glReleaseShaderCompiler(); + glUseProgram(shaderProgram_); +} + +GLuint waybar::modules::cava::CavaGLSL::loadShader(const std::string& fileName, GLenum type) { + spdlog::debug("{0}. loadShader: {1}", name_, fileName); + + // Read shader source code from the file + std::ifstream shaderFile{fileName}; + + if (!shaderFile.is_open()) { + spdlog::error("{0}. Could not open shader file: {1}", name_, fileName); + } + + std::ostringstream buffer; + buffer << shaderFile.rdbuf(); // read file content into stringstream + std::string str{buffer.str()}; + const char* shaderSource = str.c_str(); + shaderFile.close(); + + GLuint shaderID{glCreateShader(type)}; + if (shaderID == 0) spdlog::error("{0}. Error creating shader type: {0}", type); + glShaderSource(shaderID, 1, &shaderSource, nullptr); + glCompileShader(shaderID); + + // Check for compilation errors + GLint success, len; + + glGetShaderiv(shaderID, GL_COMPILE_STATUS, &success); + + if (!success) { + glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &len); + + GLchar* infoLog{(char*)'\0'}; + glGetShaderInfoLog(shaderID, len, nullptr, infoLog); + spdlog::error("{0}. Shader compilation error in {1}: {2}", name_, fileName, infoLog); + } + + return shaderID; +} diff --git a/src/modules/cava/cava_backend.cpp b/src/modules/cava/cava_backend.cpp index aec945dc..c2563272 100644 --- a/src/modules/cava/cava_backend.cpp +++ b/src/modules/cava/cava_backend.cpp @@ -24,7 +24,7 @@ waybar::modules::cava::CavaBackend::CavaBackend(const Json::Value& config) : con }; // Write outcoming data. Emit signals out_thread_ = [this] { - Update(); + doUpdate(false); out_thread_.sleep_for(frame_time_milsec_); }; } @@ -109,6 +109,11 @@ waybar::modules::cava::CavaBackend::signal_update() { return m_signal_update_; } +waybar::modules::cava::CavaBackend::type_signal_audio_raw_update +waybar::modules::cava::CavaBackend::signal_audio_raw_update() { + return m_signal_audio_raw_; +} + waybar::modules::cava::CavaBackend::type_signal_silence waybar::modules::cava::CavaBackend::signal_silence() { return m_signal_silence_; @@ -133,7 +138,10 @@ void waybar::modules::cava::CavaBackend::doUpdate(bool force) { if (!silence_ || prm_.sleep_timer == 0) { if (downThreadDelay(frame_time_milsec_, suspend_silence_delay_)) Update(); execute(); - if (re_paint_ == 1 || force) m_signal_update_.emit(output_); + if (re_paint_ == 1 || force || prm_.continuous_rendering) { + m_signal_update_.emit(output_); + m_signal_audio_raw_.emit(audio_raw_); + } } else { if (upThreadDelay(frame_time_milsec_, suspend_silence_delay_)) Update(); if (silence_ != silence_prev_ || force) m_signal_silence_.emit(); @@ -180,16 +188,15 @@ void waybar::modules::cava::CavaBackend::loadConfig() { prm_.raw_target = strdup("/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; + if (config_["bar_spacing"].isInt()) prm_.bar_spacing = config_["bar_spacing"].asInt(); + if (config_["bar_width"].isInt()) prm_.bar_width = config_["bar_width"].asInt(); + if (config_["bar_height"].isInt()) prm_.bar_height = config_["bar_height"].asInt(); 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_["gravity"].isInt()) prm_.gravity = config_["gravity"].asInt(); + if (config_["integral"].isInt()) prm_.integral = config_["integral"].asInt(); if (config_["framerate"].isInt()) prm_.framerate = config_["framerate"].asInt(); // Calculate delay for Update() thread @@ -219,6 +226,13 @@ void waybar::modules::cava::CavaBackend::loadConfig() { prm_.noise_reduction = config_["noise_reduction"].asDouble(); if (config_["input_delay"].isInt()) fetch_input_delay_ = std::chrono::seconds(config_["input_delay"].asInt()); + if (config_["gradient"].isInt()) prm_.gradient = config_["gradient"].asInt(); + if (prm_.gradient == 0) + prm_.gradient_count = 0; + else if (config_["gradient_count"].isInt()) + prm_.gradient_count = config_["gradient_count"].asInt(); + if (config_["sdl_width"].isInt()) prm_.sdl_width = config_["sdl_width"].asInt(); + if (config_["sdl_height"].isInt()) prm_.sdl_height = config_["sdl_height"].asInt(); audio_raw_.height = prm_.ascii_range; audio_data_.format = -1; @@ -243,3 +257,8 @@ void waybar::modules::cava::CavaBackend::loadConfig() { 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 } + +const struct ::cava::config_params* waybar::modules::cava::CavaBackend::getPrm() { return &prm_; } +std::chrono::milliseconds waybar::modules::cava::CavaBackend::getFrameTimeMilsec() { + return frame_time_milsec_; +}; From 5ee3bd53257a6318ea53f657c847ce6058aa8d96 Mon Sep 17 00:00:00 2001 From: marvelpokemaster Date: Mon, 8 Dec 2025 01:44:19 +0530 Subject: [PATCH 454/780] fix(wireplumber): reorder initializer list to match header and fix -Wreorder warnings --- src/modules/wireplumber.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index a43ad29b..bb3005bd 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -14,16 +14,18 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val mixer_api_(nullptr), def_nodes_api_(nullptr), default_node_name_(nullptr), - default_source_name_(nullptr), pending_plugins_(0), muted_(false), - source_muted_(false), volume_(0.0), - source_volume_(0.0), min_step_(0.0), node_id_(0), + node_name_(""), + source_name_(""), + type_(nullptr), source_node_id_(0), - type_(nullptr) { + source_muted_(false), + source_volume_(0.0), + default_source_name_(nullptr) { waybar::modules::Wireplumber::modules.push_back(this); wp_init(WP_INIT_PIPEWIRE); From e03119fe9414b8a83a4a87cadbbf6d0429b878c9 Mon Sep 17 00:00:00 2001 From: Viktar Lukashonak Date: Tue, 9 Dec 2025 18:13:45 +0300 Subject: [PATCH 455/780] Factory. cava_frontend --- .../modules/cava/{cava.hpp => cavaRaw.hpp} | 0 include/modules/cava/cava_frontend.hpp | 30 +++++++++++++++++++ meson.build | 2 +- src/factory.cpp | 15 ++-------- src/modules/cava/{cava.cpp => cavaRaw.cpp} | 2 +- src/modules/cava/cava_backend.cpp | 19 ++++++++---- 6 files changed, 48 insertions(+), 20 deletions(-) rename include/modules/cava/{cava.hpp => cavaRaw.hpp} (100%) create mode 100644 include/modules/cava/cava_frontend.hpp rename src/modules/cava/{cava.cpp => cavaRaw.cpp} (98%) diff --git a/include/modules/cava/cava.hpp b/include/modules/cava/cavaRaw.hpp similarity index 100% rename from include/modules/cava/cava.hpp rename to include/modules/cava/cavaRaw.hpp diff --git a/include/modules/cava/cava_frontend.hpp b/include/modules/cava/cava_frontend.hpp new file mode 100644 index 00000000..5a73f62f --- /dev/null +++ b/include/modules/cava/cava_frontend.hpp @@ -0,0 +1,30 @@ +#pragma once + +#ifdef HAVE_LIBCAVA +#include "cavaRaw.hpp" +#include "cava_backend.hpp" +#ifdef HAVE_LIBCAVAGLSL +#include "cavaGLSL.hpp" +#endif +#endif + +namespace waybar::modules::cava { +AModule* getModule(const std::string& id, const Json::Value& config) { +#ifdef HAVE_LIBCAVA + const std::shared_ptr backend_{waybar::modules::cava::CavaBackend::inst(config)}; + switch (backend_->getPrm()->output) { + case ::cava::output_method::OUTPUT_RAW: + return new waybar::modules::cava::Cava(id, config); + break; +#ifdef HAVE_LIBCAVAGLSL + case ::cava::output_method::OUTPUT_SDL_GLSL: + return new waybar::modules::cava::CavaGLSL(id, config); + break; +#endif + default: + break; + } +#endif + throw std::runtime_error("Unknown module"); +}; +} // namespace waybar::modules::cava diff --git a/meson.build b/meson.build index 17818a09..c6b7f90a 100644 --- a/meson.build +++ b/meson.build @@ -507,7 +507,7 @@ eproxy = dependency('epoxy', required: false) if cava.found() add_project_arguments('-DHAVE_LIBCAVA', language: 'cpp') - src_files += files('src/modules/cava/cava.cpp', + src_files += files('src/modules/cava/cavaRaw.cpp', 'src/modules/cava/cava_backend.cpp') man_files += files('man/waybar-cava.5.scd') diff --git a/src/factory.cpp b/src/factory.cpp index 94ca8e61..2fd3e3b8 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -108,18 +108,13 @@ #ifdef HAVE_LIBWIREPLUMBER #include "modules/wireplumber.hpp" #endif -#ifdef HAVE_LIBCAVA -#include "modules/cava/cava.hpp" -#ifdef HAVE_LIBCAVAGLSL -#include "modules/cava/cavaGLSL.hpp" -#endif -#endif #ifdef HAVE_SYSTEMD_MONITOR #include "modules/systemd_failed_units.hpp" #endif #ifdef HAVE_LIBGPS #include "modules/gps.hpp" #endif +#include "modules/cava/cava_frontend.hpp" #include "modules/cffi.hpp" #include "modules/custom.hpp" #include "modules/image.hpp" @@ -344,15 +339,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, return new waybar::modules::Wireplumber(id, config_[name]); } #endif -#ifdef HAVE_LIBCAVA if (ref == "cava") { - return new waybar::modules::cava::Cava(id, config_[name]); -#ifdef HAVE_LIBCAVAGLSL - } else if (ref == "cavaGLSL") { - return new waybar::modules::cava::CavaGLSL(id, config_[name]); -#endif + return waybar::modules::cava::getModule(id, config_[name]); } -#endif #ifdef HAVE_SYSTEMD_MONITOR if (ref == "systemd-failed-units") { return new waybar::modules::SystemdFailedUnits(id, config_[name]); diff --git a/src/modules/cava/cava.cpp b/src/modules/cava/cavaRaw.cpp similarity index 98% rename from src/modules/cava/cava.cpp rename to src/modules/cava/cavaRaw.cpp index 2ea4e694..edec902b 100644 --- a/src/modules/cava/cava.cpp +++ b/src/modules/cava/cavaRaw.cpp @@ -1,4 +1,4 @@ -#include "modules/cava/cava.hpp" +#include "modules/cava/cavaRaw.hpp" #include diff --git a/src/modules/cava/cava_backend.cpp b/src/modules/cava/cava_backend.cpp index c2563272..917e165f 100644 --- a/src/modules/cava/cava_backend.cpp +++ b/src/modules/cava/cava_backend.cpp @@ -181,11 +181,16 @@ void waybar::modules::cava::CavaBackend::loadConfig() { // Override cava parameters by the user config prm_.inAtty = 0; - prm_.output = ::cava::output_method::OUTPUT_RAW; - if (prm_.data_format) free(prm_.data_format); - prm_.data_format = strdup("ascii"); - if (prm_.raw_target) free(prm_.raw_target); - prm_.raw_target = strdup("/dev/stdout"); + auto const output{prm_.output}; + // prm_.output = ::cava::output_method::OUTPUT_RAW; + if (config_["data_format"].isString()) { + if (prm_.data_format) free(prm_.data_format); + prm_.data_format = strdup(config_["data_format"].asString().c_str()); + } + if (config_["raw_target"].isString()) { + if (prm_.raw_target) free(prm_.raw_target); + prm_.raw_target = strdup(config_["raw_target"].asString().c_str()); + } prm_.ascii_range = config_["format-icons"].size() - 1; if (config_["bar_spacing"].isInt()) prm_.bar_spacing = config_["bar_spacing"].asInt(); @@ -251,11 +256,15 @@ void waybar::modules::cava::CavaBackend::loadConfig() { exit(EXIT_FAILURE); } + prm_.output = ::cava::output_method::OUTPUT_RAW; + // Make cava parameters configuration // 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 + + prm_.output = output; } const struct ::cava::config_params* waybar::modules::cava::CavaBackend::getPrm() { return &prm_; } From 07cb2c02d25c679e40775b31df64997bff1d45a2 Mon Sep 17 00:00:00 2001 From: Owen Spafford Date: Tue, 9 Dec 2025 07:05:21 -0800 Subject: [PATCH 456/780] feat(dwl/tags): add empty tag option Added option in the `dwl/tags` to theme empty tags (i.e. tags without any clients) in `style.css` using `#tags button.empty`. Signed-off-by: ospafford --- man/waybar-dwl-tags.5.scd | 1 + src/modules/dwl/tags.cpp | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/man/waybar-dwl-tags.5.scd b/man/waybar-dwl-tags.5.scd index a2146dfd..39d15b7a 100644 --- a/man/waybar-dwl-tags.5.scd +++ b/man/waybar-dwl-tags.5.scd @@ -43,6 +43,7 @@ Addressed by *dwl/tags* - *#tags button* - *#tags button.occupied* +- *#tags button.empty* - *#tags button.focused* - *#tags button.urgent* diff --git a/src/modules/dwl/tags.cpp b/src/modules/dwl/tags.cpp index f8b250c8..c916eaf1 100644 --- a/src/modules/dwl/tags.cpp +++ b/src/modules/dwl/tags.cpp @@ -187,6 +187,12 @@ void Tags::handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint button.get_style_context()->remove_class("occupied"); } + if (clients & TAG_INACTIVE) { + button.get_style_context()->remove_class("empty"); + } else { + button.get_style_context()->add_class("empty"); + } + if (state & TAG_ACTIVE) { button.get_style_context()->add_class("focused"); } else { From 268e33e8927537498f37b6b4b34c82eab192ef72 Mon Sep 17 00:00:00 2001 From: Aaron Gerbert Date: Tue, 9 Dec 2025 19:13:24 -0500 Subject: [PATCH 457/780] Unique icons for active and named workspaces Allows you to set active icons for named workspaces: "active:" = "" --- src/modules/hyprland/workspace.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index ae66bc84..298ba590 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -161,6 +161,13 @@ std::string &Workspace::selectIcon(std::map &icons_map } } + if (isActive() && isSpecial()) { + auto activeIconIt = icons_map.find("active:" + name()); + if (activeIconIt != icons_map.end()) { + return activeIconIt->second; + } + } + if (isActive()) { auto activeIconIt = icons_map.find("active"); if (activeIconIt != icons_map.end()) { From 55e1c4c4a93061ce7ed5ad7b2922e91b89848676 Mon Sep 17 00:00:00 2001 From: Aaron Gerbert Date: Tue, 9 Dec 2025 20:47:04 -0500 Subject: [PATCH 458/780] Adds name of hyprland workspace to button class Allows you to target named hyprland special workspaces\n #workspaces button.special.\ {} --- src/modules/hyprland/workspace.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 298ba590..d84b973b 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -241,6 +241,7 @@ void Workspace::update(const std::string &workspace_icon) { auto styleContext = m_button.get_style_context(); addOrRemoveClass(styleContext, isActive(), "active"); addOrRemoveClass(styleContext, isSpecial(), "special"); + addOrRemoveClass(styleContext, isSpecial(), name()); addOrRemoveClass(styleContext, isEmpty(), "empty"); addOrRemoveClass(styleContext, isPersistent(), "persistent"); addOrRemoveClass(styleContext, isUrgent(), "urgent"); From a28fe0a9c437f1c591144e16d1a21b1849ca0da8 Mon Sep 17 00:00:00 2001 From: Aaron Gerbert Date: Tue, 9 Dec 2025 23:45:45 -0500 Subject: [PATCH 459/780] bugfix(hyprland/workspaces): Special workspaces being opened on different monitors now updates correctly Previously, if you were to open a special workspace on a different monitor than it was created, the display for workspaces wouldn't update properly\nI've implemented a somewhat hacky solution by just reloading hyprland everytime onSpecialWorkspaceActivated is called. --- src/modules/hyprland/workspaces.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 8360137f..670b1b11 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -366,6 +366,7 @@ void Workspaces::onWorkspaceActivated(std::string const &payload) { void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) { std::string name(begin(payload), begin(payload) + payload.find_first_of(',')); m_activeSpecialWorkspaceName = (!name.starts_with("special:") ? name : name.substr(8)); + m_ipc.getSocket1JsonReply("reload"); } void Workspaces::onWorkspaceDestroyed(std::string const &payload) { From a2f277a0b1efd94fc191cad3e06e60dc4e69ce55 Mon Sep 17 00:00:00 2001 From: Aaron Gerbert Date: Thu, 11 Dec 2025 12:04:10 -0500 Subject: [PATCH 460/780] Active special workspaces fallback on non-active icon before default --- src/modules/hyprland/workspace.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index d84b973b..82247020 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -166,6 +166,10 @@ std::string &Workspace::selectIcon(std::map &icons_map if (activeIconIt != icons_map.end()) { return activeIconIt->second; } + auto namedIconIt = icons_map.find(name()); + if (namedIconIt != icons_map.end()) { + return namedIconIt->second; + } } if (isActive()) { From 2c9e83441a01d5de926a4313a8d1198002369381 Mon Sep 17 00:00:00 2001 From: Max Gautier Date: Sat, 31 May 2025 23:05:55 +0200 Subject: [PATCH 461/780] log: use native journald protocol for when running as a service When running waybar as a systemd service, allows a slighty more readable and searchable (in particular, by log level) using journal metadata. --- meson.build | 5 +++++ src/main.cpp | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/meson.build b/meson.build index 0675a963..0f553d84 100644 --- a/meson.build +++ b/meson.build @@ -111,6 +111,10 @@ gtk_layer_shell = dependency('gtk-layer-shell-0', version: ['>=0.9.0'], default_options: ['introspection=false', 'vapi=false'], fallback: ['gtk-layer-shell', 'gtk_layer_shell']) systemd = dependency('systemd', required: get_option('systemd')) +libsystemd = dependency('libsystemd', required: get_option('systemd')) +if libsystemd.found() + add_project_arguments('-DHAVE_LIBSYSTEMD', language: 'cpp') +endif cpp_lib_chrono = compiler.compute_int('__cpp_lib_chrono', prefix : '#include ') have_chrono_timezones = cpp_lib_chrono >= 201611 @@ -541,6 +545,7 @@ executable( upower_glib, pipewire, playerctl, + libsystemd, libpulse, libjack, libwireplumber, diff --git a/src/main.cpp b/src/main.cpp index 021f8e1a..fa27ee98 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,17 @@ #include #include +#ifdef HAVE_LIBSYSTEMD +#include +#include + +#include +#include +#include +#include +#include +#endif + #include "bar.hpp" #include "client.hpp" #include "util/SafeSignal.hpp" @@ -154,7 +165,48 @@ static void handleSignalMainThread(int signum, bool& reload) { } } +static void logToJournalIfRunAsService() { +#ifdef HAVE_LIBSYSTEMD + /* Implementation of automatic protocol upgrading (from stderr to journal) + ** as described in https://systemd.io/JOURNAL_NATIVE_PROTOCOL */ + char const* journal_stream = std::getenv("JOURNAL_STREAM"); + + if (journal_stream != nullptr) { + dev_t device; + ino_t inode; + size_t len = std::strlen(journal_stream); + + auto result = std::from_chars(journal_stream, journal_stream + len, device); + if (result.ec == std::errc{}) + result = std::from_chars(result.ptr + 1, journal_stream + len, inode); + if (result.ec != std::errc{}) { + spdlog::warn("malformed JOURNAL_STREAM (\"{}\"): {}, logging to console", journal_stream, + std::make_error_condition(result.ec).message()); + } + + struct stat f_stderr; + + if (fstat(STDERR_FILENO, &f_stderr) != 0) { + spdlog::warn("unable to check stderr device and inode numbers: {}", strerror(errno)); + } else if (device == f_stderr.st_dev && inode == f_stderr.st_ino) { + auto journald = spdlog::systemd_logger_st("native_journal", "waybar", false); + /* systemd_logger_st is thread-safe with enable_formatter = false + ** thanks to underlying sd_journal_send being thread-safe + ** https://github.com/gabime/spdlog/issues/2320#issuecomment-1079766037 + */ + spdlog::set_default_logger(journald); + } else { + spdlog::info("JOURNAL_STREAM does not point to stderr, logging to console"); + } + } else { + spdlog::info("no JOURNAL_STREAM, logging to console"); + } +#endif +} + int main(int argc, char* argv[]) { + logToJournalIfRunAsService(); + try { auto* client = waybar::Client::inst(); From a9ef11a2b387593a50dde6ff1ce22f434a840bd8 Mon Sep 17 00:00:00 2001 From: Will Wernert Date: Wed, 17 Dec 2025 22:17:35 -0500 Subject: [PATCH 462/780] fix: prevent duplicate layer surfaces on monitor hotplug The monitor signal handlers (signal_monitor_added, signal_monitor_removed) were never disconnected during SIGUSR2 reload. Each reload accumulated additional handlers, causing multiple layer surfaces to be created when monitors were hotplugged. This fix: - Stores signal connections as class members - Disconnects them before reconnecting in bindInterfaces() - Clears stale outputs_ on reload --- include/client.hpp | 2 ++ src/client.cpp | 13 +++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/client.hpp b/include/client.hpp index 0e68f002..9dd09355 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -56,6 +56,8 @@ class Client { std::list outputs_; std::unique_ptr m_cssReloadHelper; std::string m_cssFile; + sigc::connection monitor_added_connection_; + sigc::connection monitor_removed_connection_; }; } // namespace waybar diff --git a/src/client.cpp b/src/client.cpp index 5136cfd8..f60e01a3 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -219,13 +219,22 @@ void waybar::Client::bindInterfaces() { if (xdg_output_manager == nullptr) { throw std::runtime_error("Failed to acquire required resources."); } + + // Disconnect previous signal handlers to prevent duplicate handlers on reload + monitor_added_connection_.disconnect(); + monitor_removed_connection_.disconnect(); + + // Clear stale outputs from previous run + outputs_.clear(); + // add existing outputs and subscribe to updates for (auto i = 0; i < gdk_display->get_n_monitors(); ++i) { auto monitor = gdk_display->get_monitor(i); handleMonitorAdded(monitor); } - gdk_display->signal_monitor_added().connect(sigc::mem_fun(*this, &Client::handleMonitorAdded)); - gdk_display->signal_monitor_removed().connect( + monitor_added_connection_ = gdk_display->signal_monitor_added().connect( + sigc::mem_fun(*this, &Client::handleMonitorAdded)); + monitor_removed_connection_ = gdk_display->signal_monitor_removed().connect( sigc::mem_fun(*this, &Client::handleMonitorRemoved)); } From 601b5f024168fe4e357403fbc1b2eeacec549ffb Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Thu, 18 Dec 2025 23:44:03 -0600 Subject: [PATCH 463/780] nix: bump cava again Signed-off-by: Austin Horstman --- nix/default.nix | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/nix/default.nix b/nix/default.nix index ee7a981f..43b6e097 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -5,13 +5,14 @@ version, }: let - libcava = { + libcava = rec { + version = "0.10.7-beta"; src = pkgs.fetchFromGitHub { owner = "LukashonakV"; repo = "cava"; # NOTE: Needs to match the cava.wrap - rev = "23efcced43b5a395747b18a2e5f2171fc0925d18"; - hash = "sha256-CNspaoK5KuME0GfaNijpC24BfALngzNi04/VNwPqMvo="; + tag = "v${version}"; + hash = "sha256-IX1B375gTwVDRjpRfwKGuzTAZOV2pgDWzUd4bW2cTDU="; }; }; in @@ -39,7 +40,7 @@ waybar.overrideAttrs (oldAttrs: { postUnpack = '' pushd "$sourceRoot" - cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava + cp -R --no-preserve=mode,ownership ${libcava.src} subprojects/cava-${libcava.version} patchShebangs . popd ''; From c8c3287be3a3c13701795c6dfb0f8fdfa40312d1 Mon Sep 17 00:00:00 2001 From: Viktar Lukashonak Date: Sat, 20 Dec 2025 15:35:20 +0300 Subject: [PATCH 464/780] cava man --- man/waybar-cava.5.scd | 523 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 512 insertions(+), 11 deletions(-) diff --git a/man/waybar-cava.5.scd b/man/waybar-cava.5.scd index 7825c38a..95336e24 100644 --- a/man/waybar-cava.5.scd +++ b/man/waybar-cava.5.scd @@ -8,6 +8,8 @@ waybar - cava module *cava* module for karlstav/cava project. See it on github: https://github.com/karlstav/cava. +Module supports two different frontends starting from the 0.15.0 release. The frontend that +will be used is managed by the method parameter in the [output] section of the cava configuration file. # FILES @@ -32,6 +34,10 @@ libcava lives in: :[ string :[ :< Path where cava configuration file is placed to +|[ *method* \[output\] +:[ string +:[ +:< Manages which frontend Waybar cava module should use. Values: raw, sdl_glsl |[ *framerate* :[ integer :[ 30 @@ -43,7 +49,7 @@ libcava lives in: |[ *sensitivity* :[ integer :[ 100 -:[ Manual sensitivity in %. It's recommended to be omitted when *autosens* = 1 +:[ Manual sensitivity in %. If autosens is enabled, this will only be the initial value. 200 means double height. Accepts only non-negative values |[ *bars* :[ integer :[ 12 @@ -68,7 +74,7 @@ libcava lives in: :[ string :[ :[ Widget's text after sleep_timer elapsed (hide_on_silence has to be false) -|[ *method* +|[ *method* \[input\] :[ string :[ pulse :[ Audio capturing method. Possible methods are: pipewire, pulse, alsa, fifo, sndio or shmem @@ -105,9 +111,9 @@ libcava lives in: :[ false :[ Disables or enables the so-called "Monstercat smoothing" with or without "waves" |[ *noise_reduction* -:[ double -:[ 0.77 -:[ Range between 0 - 1. The raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth. 1 - will be very slow and smooth, 0 - will be fast but noisy +:[ integer +:[ 77 +:[ Range between 0 - 100. The raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth. 100 will be very slow and smooth, 0 will be fast but noisy |[ *input_delay* :[ integer :[ 2 @@ -119,11 +125,11 @@ libcava lives in: |[ *data_format* :[ string :[ asci -:[ It's impossible to set it. Waybar sets it to = asci for internal needs +:[ Raw data format. Can be 'binary' or 'ascii' |[ *raw_target* :[ string :[ /dev/stdout -:[ It's impossible to set it. Waybar sets it to = /dev/stdout for internal needs +:[ Raw output target. A fifo will be created if target does not exist |[ *menu* :[ string :[ @@ -136,6 +142,50 @@ libcava lives in: :[ array :[ :[ The actions corresponding to the buttons of the menu. +|[ *bar_spacing* +:[ integer +:[ +:[ Bars' space between bars in number of characters +|[ *bar_width* +:[ integer +:[ +:[ Bars' width between bars in number of characters +|[ *bar_height* +:[ integer +:[ +:[ Useless. bar_height is only used for output in "noritake" format +|[ *background* +:[ string +:[ +:[ GLSL actual. Support hex code colors only. Must be within '' +|[ *foreground* +:[ string +:[ +:[ GLSL actual. Support hex code colors only. Must be within '' +|[ *gradient* +:[ integer +:[ 0 +:[ GLSL actual. Gradient mode(0/1 - on/off) +|[ *gradient_count* +:[ integer +:[ 0 +:[ GLSL actual. The count of colors for the gradient +|[ *gradient_color_N* +:[ string +:[ +:[ GLSL actual. N - the number of the gradient color between 1 and 8. Only hex defined colors are supported. Must be within '' +|[ *sdl_width* +:[ integer +:[ +:[ GLSL actual. Manages the width of the waybar cava GLSL frontend module +|[ *sdl_height* +:[ integer +:[ +:[ GLSL actual. Manages the height of the waybar cava GLSL frontend module +|[ *continuous_rendering* +:[ integer +:[ 0 +:[ GLSL actual. Keep rendering even if no audio. Recommended to set to 1 Configuration can be provided as: - The only cava configuration file which is provided through *cava_config*. The rest configuration can be skipped @@ -153,16 +203,17 @@ Configuration can be provided as: - iniparser - fftw3 +- epoxy (GLSL frontend only) # SOLVING ISSUES . On start Waybar throws an exception "error while loading shared libraries: libcava.so: cannot open shared object file: No such file or directory". It might happen when libcava for some reason hasn't been registered in the system. sudo ldconfig should help . Waybar is starting but cava module doesn't react to the music - 1. In such cases at first need to make sure usual cava application is working as well - 2. If so, need to comment all configuration options. Uncomment cava_config and provide the path to the working cava config - 3. You might set too huge or too small input_delay. Try to setup to 4 seconds, restart waybar, and check again 4 seconds past. Usual even on weak machines it should be enough - 4. You might accidentally switch action mode to pause mode + 1. In such cases at first need to make sure usual cava application is working as well + 2. If so, need to comment all configuration options. Uncomment cava_config and provide the path to the working cava config + 3. You might set too huge or too small input_delay. Try to setup to 4 seconds, restart waybar, and check again 4 seconds past. Usual even on weak machines it should be enough + 4. You might accidentally switch action mode to pause mode # RISING ISSUES @@ -205,3 +256,453 @@ In case when cava releases new version and you're wanna get it, it should be rai - *#cava* - *#cava.silent* Applied after no sound has been detected for sleep_timer seconds - *#cava.updated* Applied when a new frame is shown +# FRONTENDS + +## RAW +The cava raw frontend uses ASCII characters to visualize incoming audio data. Each ASCII symbol position corresponds to the value of the audio power pulse. + +Under the hood: +``` +. Incoming audio power pulse list is : 12684 +. Configured array of ASCII codes is: ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█" ]. See `format-icons` https://github.com/Alexays/Waybar/wiki/Module:-Cava#example +``` +As a result cava frontend will give ▁▂▆█▄ + +Examples: + +waybar config +``` +"cava": { + "cava_config": "$XDG_CONFIG_HOME/cava/waybar_raw.conf", + "input_delay": 2, + "format-icons" : ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█" ], + "actions": { + "on-click-right": "mode" + } + }, +``` + +waybar_raw.conf +``` +## Configuration file for CAVA. +# Remove the ; to change parameters. + + +[general] + +# Smoothing mode. Can be 'normal', 'scientific' or 'waves'. DEPRECATED as of 0.6.0 + +# Accepts only non-negative values. + +# 'autosens' will attempt to decrease sensitivity if the bars peak. 1 = on, 0 = off +# new as of 0.6.0 autosens of low values (dynamic range) +# 'overshoot' allows bars to overshoot (in % of terminal height) without initiating autosens. DEPRECATED as of 0.6.0 + +# Manual sensitivity in %. If autosens is enabled, this will only be the initial value. +# 200 means double height. Accepts only non-negative values. + +# The number of bars (0-512). 0 sets it to auto (fill up console). +# Bars' width and space between bars in number of characters. +bars = 12 +# bar_height is only used for output in "noritake" format + +# For SDL width and space between bars is in pixels, defaults are: + +# sdl_glsl have these default values, they are only used to calculate max number of bars. + + +# Lower and higher cutoff frequencies for lowest and highest bars +# the bandwidth of the visualizer. +# Note: there is a minimum total bandwidth of 43Mhz x number of bars. +# Cava will automatically increase the higher cutoff if a too low band is specified. + +# Seconds with no input before cava goes to sleep mode. Cava will not perform FFT or drawing and +# only check for input once per second. Cava will wake up once input is detected. 0 = disable. +sleep_timer = 5 + + +[input] + +# Audio capturing method. Possible methods are: 'fifo', 'portaudio', 'pipewire', 'alsa', 'pulse', 'sndio', 'oss', 'jack' or 'shmem' +# Defaults to 'oss', 'pipewire', 'sndio', 'jack', 'pulse', 'alsa', 'portaudio' or 'fifo', in that order, dependent on what support cava was built with. +# On Mac it defaults to 'portaudio' or 'fifo' +# On windows this is automatic and no input settings are needed. +# +# All input methods uses the same config variable 'source' +# to define where it should get the audio. +# +# For pulseaudio and pipewire 'source' will be the source. Default: 'auto', which uses the monitor source of the default sink +# (all pulseaudio sinks(outputs) have 'monitor' sources(inputs) associated with them). +# +# For pipewire 'source' will be the object name or object.serial of the device to capture from. +# Both input and output devices are supported. +# +# For alsa 'source' will be the capture device. +# For fifo 'source' will be the path to fifo-file. +# For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:EE:FF' will be squeezelite's MAC address +# +# For sndio 'source' will be a raw recording audio descriptor or a monitoring sub-device, e.g. 'rsnd/2' or 'snd/1'. Default: 'default'. +# README.md contains further information on how to setup CAVA for sndio. +# +# For oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. Default: '/dev/dsp', i.e. the default audio device. +# README.md contains further information on how to setup CAVA for OSS on FreeBSD. +# +# For jack 'source' will be the name of the JACK server to connect to, e.g. 'foobar'. Default: 'default'. +# README.md contains further information on how to setup CAVA for JACK. +# + +# The options 'sample_rate', 'sample_bits', 'channels' and 'autoconnect' can be configured for some input methods: +# sample_rate: fifo, pipewire, sndio, oss +# sample_bits: fifo, pipewire, sndio, oss +# channels: sndio, oss, jack +# autoconnect: jack +# Other methods ignore these settings. +# +# For 'sndio' and 'oss' they are only preferred values, i.e. if the values are not supported +# by the chosen audio device, the device will use other supported values instead. +# Example: 48000, 32 and 2, but the device only supports 44100, 16 and 1, then it +# will use 44100, 16 and 1. +# + + +[output] + +# Output method. Can be 'ncurses', 'noncurses', 'raw', 'noritake', 'sdl' +# or 'sdl_glsl'. +# 'noncurses' (default) uses a buffer and cursor movements to only print +# changes from frame to frame in the terminal. Uses less resources and is less +# prone to tearing (vsync issues) than 'ncurses'. +# +# 'raw' is an 8 or 16 bit (configurable via the 'bit_format' option) data +# stream of the bar heights that can be used to send to other applications. +# 'raw' defaults to 200 bars, which can be adjusted in the 'bars' option above. +# +# 'noritake' outputs a bitmap in the format expected by a Noritake VFD display +# in graphic mode. It only support the 3000 series graphical VFDs for now. +# +# 'sdl' uses the Simple DirectMedia Layer to render in a graphical context. +# 'sdl_glsl' uses SDL to create an OpenGL context. Write your own shaders or +# use one of the predefined ones. +method = raw + +# Orientation of the visualization. Can be 'bottom', 'top', 'left', 'right' or +# 'horizontal'. Default is 'bottom'. 'left and 'right' are only supported on sdl +# and ncruses output. 'horizontal' (bars go up and down from center) is only supported +# on noncurses output. +# Note: many fonts have weird or missing glyphs for characters used in orientations +# other than 'bottom', which can make output not look right. + +# Visual channels. Can be 'stereo' or 'mono'. +# 'stereo' mirrors both channels with low frequencies in center. +# 'mono' outputs left to right lowest to highest frequencies. +# 'mono_option' set mono to either take input from 'left', 'right' or 'average'. +# set 'reverse' to 1 to display frequencies the other way around. + +# Raw output target. A fifo will be created if target does not exist. +raw_target = /dev/stdout + +# Raw data format. Can be 'binary' or 'ascii'. +data_format = ascii + +# Binary bit format, can be '8bit' (0-255) or '16bit' (0-65530). + +# Ascii max value. In 'ascii' mode range will run from 0 to value specified here + +# Ascii delimiters. In ascii format each bar and frame is separated by a delimiters. +# Use decimal value in ascii table (i.e. 59 = ';' and 10 = '\n' (line feed)). +bar_delimiter = 0 + +# sdl window size and position. -1,-1 is centered. + +# set label on bars on the x-axis. Can be 'frequency' or 'none'. Default: 'none' +# 'frequency' displays the lower cut off frequency of the bar above. +# Only supported on ncurses and noncurses output. + +# enable synchronized sync. 1 = on, 0 = off +# removes flickering in alacritty terminal emulator. +# defaults to off since the behaviour in other terminal emulators is unknown + +# Shaders for sdl_glsl, located in $HOME/.config/cava/shaders + +; for glsl output mode, keep rendering even if no audio + +# disable console blank (screen saver) in tty +# (Not supported on FreeBSD) + +# show a flat bar at the bottom of the screen when idle, 1 = on, 0 = off + +# show waveform instead of frequency spectrum, 1 = on, 0 = off + +[color] + +# Colors can be one of seven predefined: black, blue, cyan, green, magenta, red, white, yellow. +# Or defined by hex code '#xxxxxx' (hex code must be within ''). User defined colors requires +# a terminal that can change color definitions such as Gnome-terminal or rxvt. +# default is to keep current terminal color + +# SDL and sdl_glsl only support hex code colors, these are the default: + +# Gradient mode, only hex defined colors are supported, +# background must also be defined in hex or remain commented out. 1 = on, 0 = off. +# You can define as many as 8 different colors. They range from bottom to top of screen + +[smoothing] + +# Disables or enables the so-called "Monstercat smoothing" with or without "waves". Set to 0 to disable. + +# Noise reduction, int 0 - 100. default 77 +# the raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth +# 100 will be very slow and smooth, 0 will be fast but noisy. + + +[eq] + +# This one is tricky. You can have as much keys as you want. +# Remember to uncomment more than one key! More keys = more precision. +# Look at readme.md on github for further explanations and examples. +``` +## GLSL +The Cava GLSL frontend delegates the visualization of incoming audio data to the GPU via OpenGL. + +There are some mandatory dependencies that need to be satisfied in order for Cava GLSL to be built and function properly: + +. epoxy library must be installed on the system +. Vertex and fragment shaders from the original project must be used. They should be downloaded, and the file paths must be configured correctly in the Waybar Cava configuration: + 1. cava shaders [cava shaders](https://github.com/karlstav/cava/tree/master/output/shaders) + 2. libcava shaders [libcava shaders](https://github.com/LukashonakV/cava/tree/master/output/shaders) +. It is highly recommended to have a separate cava configuration for the Waybar Cava GLSL module and to use this as the cava_config in the Waybar configuration. +. It is common for cava configurations to be placed in the XDG_CONFIG_HOME directory, including shaders as well. Consider keeping them in the $XDG_CONFIG_HOME/cava/shaders folder. + +Key configuration options: + +. bars. The more values the parameter has, the more interesting the visualization becomes. +. method in output section must be set to sdl_glsl +. sdl_width and sdl_height manage the size of the module. Adjust them according to your needs. +. Shaders for sdl_glsl, located in $HOME/.config/cava/shaders. Example: "vertex_shader" = "pass_through.vert" "fragment_shader" = "spectrogram.frag" +. Set continuous_rendering to 1 to enable smooth rendering; set it to 0 otherwise. It is recommended to keep it set to 1. +. background, foreground, and gradient_color_N (where N is a number between 1 and 8) must be defined using hex code + +Example: + +waybar config +``` +"cava": { + "cava_config": "$XDG_CONFIG_HOME/cava/waybar_cava#3.conf", + "input_delay": 2, + "actions": { + "on-click-right": "mode" + } + }, +``` + +waybar_raw.conf +``` +## Configuration file for CAVA. +# Remove the ; to change parameters. + + +[general] + +# Smoothing mode. Can be 'normal', 'scientific' or 'waves'. DEPRECATED as of 0.6.0 + +# Accepts only non-negative values. + +# 'autosens' will attempt to decrease sensitivity if the bars peak. 1 = on, 0 = off +# new as of 0.6.0 autosens of low values (dynamic range) +# 'overshoot' allows bars to overshoot (in % of terminal height) without initiating autosens. DEPRECATED as of 0.6.0 + +# Manual sensitivity in %. If autosens is enabled, this will only be the initial value. +# 200 means double height. Accepts only non-negative values. + +# The number of bars (0-512). 0 sets it to auto (fill up console). +# Bars' width and space between bars in number of characters. +bars = 50 + +# bar_height is only used for output in "noritake" format + +# For SDL width and space between bars is in pixels, defaults are: + +# sdl_glsl have these default values, they are only used to calculate max number of bars. + +# Lower and higher cutoff frequencies for lowest and highest bars +# the bandwidth of the visualizer. +# Note: there is a minimum total bandwidth of 43Mhz x number of bars. +# Cava will automatically increase the higher cutoff if a too low band is specified. + +# Seconds with no input before cava goes to sleep mode. Cava will not perform FFT or drawing and +# only check for input once per second. Cava will wake up once input is detected. 0 = disable. +sleep_timer = 5 + + +[input] + +# Audio capturing method. Possible methods are: 'fifo', 'portaudio', 'pipewire', 'alsa', 'pulse', 'sndio', 'oss', 'jack' or 'shmem' +# Defaults to 'oss', 'pipewire', 'sndio', 'jack', 'pulse', 'alsa', 'portaudio' or 'fifo', in that order, dependent on what support cava was built with. +# On Mac it defaults to 'portaudio' or 'fifo' +# On windows this is automatic and no input settings are needed. +# +# All input methods uses the same config variable 'source' +# to define where it should get the audio. +# +# For pulseaudio and pipewire 'source' will be the source. Default: 'auto', which uses the monitor source of the default sink +# (all pulseaudio sinks(outputs) have 'monitor' sources(inputs) associated with them). +# +# For pipewire 'source' will be the object name or object.serial of the device to capture from. +# Both input and output devices are supported. +# +# For alsa 'source' will be the capture device. +# For fifo 'source' will be the path to fifo-file. +# For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:EE:FF' will be squeezelite's MAC address +# +# For sndio 'source' will be a raw recording audio descriptor or a monitoring sub-device, e.g. 'rsnd/2' or 'snd/1'. Default: 'default'. +# README.md contains further information on how to setup CAVA for sndio. +# +# For oss 'source' will be the path to a audio device, e.g. '/dev/dsp2'. Default: '/dev/dsp', i.e. the default audio device. +# README.md contains further information on how to setup CAVA for OSS on FreeBSD. +# +# For jack 'source' will be the name of the JACK server to connect to, e.g. 'foobar'. Default: 'default'. +# README.md contains further information on how to setup CAVA for JACK. +# + + +# The options 'sample_rate', 'sample_bits', 'channels' and 'autoconnect' can be configured for some input methods: +# sample_rate: fifo, pipewire, sndio, oss +# sample_bits: fifo, pipewire, sndio, oss +# channels: sndio, oss, jack +# autoconnect: jack +# Other methods ignore these settings. +# +# For 'sndio' and 'oss' they are only preferred values, i.e. if the values are not supported +# by the chosen audio device, the device will use other supported values instead. +# Example: 48000, 32 and 2, but the device only supports 44100, 16 and 1, then it +# will use 44100, 16 and 1. +# + + +[output] + +# Output method. Can be 'ncurses', 'noncurses', 'raw', 'noritake', 'sdl' +# or 'sdl_glsl'. +# 'noncurses' (default) uses a buffer and cursor movements to only print +# changes from frame to frame in the terminal. Uses less resources and is less +# prone to tearing (vsync issues) than 'ncurses'. +# +# 'raw' is an 8 or 16 bit (configurable via the 'bit_format' option) data +# stream of the bar heights that can be used to send to other applications. +# 'raw' defaults to 200 bars, which can be adjusted in the 'bars' option above. +# +# 'noritake' outputs a bitmap in the format expected by a Noritake VFD display +# in graphic mode. It only support the 3000 series graphical VFDs for now. +# +# 'sdl' uses the Simple DirectMedia Layer to render in a graphical context. +# 'sdl_glsl' uses SDL to create an OpenGL context. Write your own shaders or +# use one of the predefined ones. +method = sdl_glsl + +# Orientation of the visualization. Can be 'bottom', 'top', 'left', 'right' or +# 'horizontal'. Default is 'bottom'. 'left and 'right' are only supported on sdl +# and ncruses output. 'horizontal' (bars go up and down from center) is only supported +# on noncurses output. +# Note: many fonts have weird or missing glyphs for characters used in orientations +# other than 'bottom', which can make output not look right. + +# Visual channels. Can be 'stereo' or 'mono'. +# 'stereo' mirrors both channels with low frequencies in center. +# 'mono' outputs left to right lowest to highest frequencies. +# 'mono_option' set mono to either take input from 'left', 'right' or 'average'. +# set 'reverse' to 1 to display frequencies the other way around. + +# Raw output target. A fifo will be created if target does not exist. + +# Raw data format. Can be 'binary' or 'ascii'. + +# Binary bit format, can be '8bit' (0-255) or '16bit' (0-65530). + +# Ascii max value. In 'ascii' mode range will run from 0 to value specified here + +# Ascii delimiters. In ascii format each bar and frame is separated by a delimiters. +# Use decimal value in ascii table (i.e. 59 = ';' and 10 = '\n' (line feed)). +bar_delimiter = 0 + +# sdl window size and position. -1,-1 is centered. +sdl_width = 150 +sdl_height = 39 + +# set label on bars on the x-axis. Can be 'frequency' or 'none'. Default: 'none' +# 'frequency' displays the lower cut off frequency of the bar above. +# Only supported on ncurses and noncurses output. + +# enable synchronized sync. 1 = on, 0 = off +# removes flickering in alacritty terminal emulator. +# defaults to off since the behaviour in other terminal emulators is unknown + +# Shaders for sdl_glsl, located in $HOME/.config/cava/shaders +vertex_shader = pass_through.vert +fragment_shader = bar_spectrum.frag + +; for glsl output mode, keep rendering even if no audio +continuous_rendering = 1; + +# disable console blank (screen saver) in tty +# (Not supported on FreeBSD) + +# show a flat bar at the bottom of the screen when idle, 1 = on, 0 = off + +# show waveform instead of frequency spectrum, 1 = on, 0 = off + +[color] + + +# Colors can be one of seven predefined: black, blue, cyan, green, magenta, red, white, yellow. +# Or defined by hex code '#xxxxxx' (hex code must be within ''). User defined colors requires +# a terminal that can change color definitions such as Gnome-terminal or rxvt. +# default is to keep current terminal color + +# SDL and sdl_glsl only support hex code colors, these are the default: +background = '#282C34' + +# Gradient mode, only hex defined colors are supported, +# background must also be defined in hex or remain commented out. 1 = on, 0 = off. +# You can define as many as 8 different colors. They range from bottom to top of screen +gradient = 1 +gradient_count = 2 +gradient_color_1 = '#282C34' +gradient_color_2 = '#45475A' + +; gradient_color_1 = '#59cc33' +; gradient_color_2 = '#80cc33' + gradient_color_3 = '#a6cc33' + gradient_color_4 = '#cccc33' + gradient_color_5 = '#cca633' + gradient_color_6 = '#cc8033' + gradient_color_7 = '#cc5933' + gradient_color_8 = '#cc3333' + +[smoothing] + +# Percentage value for integral smoothing. Takes values from 0 - 100. +# Higher values means smoother, but less precise. 0 to disable. +# DEPRECATED as of 0.8.0, use noise_reduction instead + +# Disables or enables the so-called "Monstercat smoothing" with or without "waves". Set to 0 to disable. + +# Set gravity percentage for "drop off". Higher values means bars will drop faster. +# Accepts only non-negative values. 50 means half gravity, 200 means double. Set to 0 to disable "drop off". +# DEPRECATED as of 0.8.0, use noise_reduction instead + + +# In bar height, bars that would have been lower that this will not be drawn. +# DEPRECATED as of 0.8.0 + +# Noise reduction, int 0 - 100. default 77 +# the raw visualization is very noisy, this factor adjusts the integral and gravity filters to keep the signal smooth +# 100 will be very slow and smooth, 0 will be fast but noisy. + +[eq] + +# This one is tricky. You can have as much keys as you want. +# Remember to uncomment more than one key! More keys = more precision. +# Look at readme.md on github for further explanations and examples. +``` + +Different waybar_cava#N.conf see at [cava GLSL](https://github.com/Alexays/Waybar/wiki/Module:-Cava:-GLSL) From c2c1a5d6148ad15a893ec2b1ecd2486d101e9eb1 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Tue, 23 Dec 2025 11:09:31 +0700 Subject: [PATCH 465/780] Add labelCOntainsIcon in AIconLabel --- include/AIconLabel.hpp | 2 ++ src/AIconLabel.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/AIconLabel.hpp b/include/AIconLabel.hpp index 054d031a..78a4fcf3 100644 --- a/include/AIconLabel.hpp +++ b/include/AIconLabel.hpp @@ -19,6 +19,8 @@ class AIconLabel : public ALabel { Gtk::Image image_; Gtk::Box box_; + bool labelContainsIcon; + bool iconEnabled() const; }; diff --git a/src/AIconLabel.cpp b/src/AIconLabel.cpp index a20c22e9..5ee12b1c 100644 --- a/src/AIconLabel.cpp +++ b/src/AIconLabel.cpp @@ -61,7 +61,7 @@ auto AIconLabel::update() -> void { } bool AIconLabel::iconEnabled() const { - return config_["icon"].isBool() ? config_["icon"].asBool() : false; + return labelContainsIcon || (config_["icon"].isBool() ? config_["icon"].asBool() : false); } } // namespace waybar From c0b9635e28a4d1286b0c936f1eb4e2065982c8d2 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Tue, 23 Dec 2025 11:10:34 +0700 Subject: [PATCH 466/780] Parse label_ to extract embedded icon markup --- src/AIconLabel.cpp | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/AIconLabel.cpp b/src/AIconLabel.cpp index 5ee12b1c..7c5dc2cb 100644 --- a/src/AIconLabel.cpp +++ b/src/AIconLabel.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include namespace waybar { @@ -56,6 +58,42 @@ AIconLabel::AIconLabel(const Json::Value &config, const std::string &name, const } auto AIconLabel::update() -> void { + std::string iconName = ""; + labelContainsIcon = false; + try { + std::string inputLabel = label_.get_label().c_str(); + + const std::regex iconSearch(R"((?=\\0icon\\1f).+?(?=\\n))"); + std::smatch iconMatch; + if (std::regex_search(inputLabel, iconMatch, iconSearch)) { + iconName = iconMatch[0].str().substr(9); + + const std::regex cleanLabelPattern(R"(\\0icon\\1f.+?\\n)"); + std::string labelResult = std::regex_replace(inputLabel, cleanLabelPattern, ""); + + label_.set_markup(labelResult); + labelContainsIcon = true; + } + } catch (const std::exception& e) { + spdlog::warn("Error while parsing icon from label. {}", e.what()); + } + + if (labelContainsIcon) { + if (iconName.front() == '/') { + auto pixbuf = Gdk::Pixbuf::create_from_file(iconName); + int scaled_icon_size = 24 * image_.get_scale_factor(); + pixbuf = Gdk::Pixbuf::create_from_file(iconName, scaled_icon_size, scaled_icon_size); + + auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), + image_.get_window()); + image_.set(surface); + image_.set_visible(true); + } else { + image_.set_from_icon_name(iconName, Gtk::ICON_SIZE_INVALID); + image_.set_visible(true); + } + } + image_.set_visible(image_.get_visible() && iconEnabled()); ALabel::update(); } From b8da9ee94c7fffc6c46acd0295bacfc58836e678 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Wed, 24 Dec 2025 07:45:06 +0700 Subject: [PATCH 467/780] Move extract icon into its own method --- include/AIconLabel.hpp | 1 + src/AIconLabel.cpp | 38 ++++++++++++++++++++++---------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/include/AIconLabel.hpp b/include/AIconLabel.hpp index 78a4fcf3..2e0956e5 100644 --- a/include/AIconLabel.hpp +++ b/include/AIconLabel.hpp @@ -14,6 +14,7 @@ class AIconLabel : public ALabel { bool enable_click = false, bool enable_scroll = false); virtual ~AIconLabel() = default; auto update() -> void override; + static std::tuple extractIcon(const std::string& input); protected: Gtk::Image image_; diff --git a/src/AIconLabel.cpp b/src/AIconLabel.cpp index 7c5dc2cb..532ed433 100644 --- a/src/AIconLabel.cpp +++ b/src/AIconLabel.cpp @@ -57,39 +57,45 @@ AIconLabel::AIconLabel(const Json::Value &config, const std::string &name, const event_box_.add(box_); } -auto AIconLabel::update() -> void { - std::string iconName = ""; - labelContainsIcon = false; +std::tuple AIconLabel::extractIcon(const std::string& input) { + std::string iconResult = ""; + std::string labelResult = input; try { - std::string inputLabel = label_.get_label().c_str(); - const std::regex iconSearch(R"((?=\\0icon\\1f).+?(?=\\n))"); std::smatch iconMatch; - if (std::regex_search(inputLabel, iconMatch, iconSearch)) { - iconName = iconMatch[0].str().substr(9); + if (std::regex_search(input, iconMatch, iconSearch)) { + iconResult = iconMatch[0].str().substr(9); const std::regex cleanLabelPattern(R"(\\0icon\\1f.+?\\n)"); - std::string labelResult = std::regex_replace(inputLabel, cleanLabelPattern, ""); - - label_.set_markup(labelResult); - labelContainsIcon = true; + labelResult = std::regex_replace(input, cleanLabelPattern, ""); } } catch (const std::exception& e) { spdlog::warn("Error while parsing icon from label. {}", e.what()); } + return std::make_tuple(iconResult, labelResult); +} + +auto AIconLabel::update() -> void { + labelContainsIcon = false; + + auto [iconLabel, cleanLabel] = extractIcon(label_.get_label().c_str()); + labelContainsIcon = iconLabel.length() > 0; + if (labelContainsIcon) { - if (iconName.front() == '/') { - auto pixbuf = Gdk::Pixbuf::create_from_file(iconName); - int scaled_icon_size = 24 * image_.get_scale_factor(); - pixbuf = Gdk::Pixbuf::create_from_file(iconName, scaled_icon_size, scaled_icon_size); + label_.set_markup(cleanLabel); + + if (iconLabel.front() == '/') { + auto pixbuf = Gdk::Pixbuf::create_from_file(iconLabel); + int scaled_icon_size = 6 * image_.get_scale_factor(); + pixbuf = Gdk::Pixbuf::create_from_file(iconLabel, scaled_icon_size, scaled_icon_size); auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), image_.get_window()); image_.set(surface); image_.set_visible(true); } else { - image_.set_from_icon_name(iconName, Gtk::ICON_SIZE_INVALID); + image_.set_from_icon_name(iconLabel, Gtk::ICON_SIZE_INVALID); image_.set_visible(true); } } From eb2090688ccbf66cfdecba63c2459a54090b1591 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Wed, 24 Dec 2025 08:25:51 +0700 Subject: [PATCH 468/780] AIconLabel: support icon-size config --- include/AIconLabel.hpp | 1 + src/AIconLabel.cpp | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/include/AIconLabel.hpp b/include/AIconLabel.hpp index 2e0956e5..91b022a4 100644 --- a/include/AIconLabel.hpp +++ b/include/AIconLabel.hpp @@ -19,6 +19,7 @@ class AIconLabel : public ALabel { protected: Gtk::Image image_; Gtk::Box box_; + unsigned app_icon_size_{24}; bool labelContainsIcon; diff --git a/src/AIconLabel.cpp b/src/AIconLabel.cpp index 532ed433..81953d7b 100644 --- a/src/AIconLabel.cpp +++ b/src/AIconLabel.cpp @@ -11,6 +11,10 @@ AIconLabel::AIconLabel(const Json::Value &config, const std::string &name, const const std::string &format, uint16_t interval, bool ellipsize, bool enable_click, bool enable_scroll) : ALabel(config, name, id, format, interval, ellipsize, enable_click, enable_scroll) { + if (config["icon-size"].isUInt()) { + app_icon_size_ = config["icon-size"].asUInt(); + } + image_.set_pixel_size(app_icon_size_); event_box_.remove(); label_.unset_name(); label_.get_style_context()->remove_class(MODULE_CLASS); @@ -86,9 +90,8 @@ auto AIconLabel::update() -> void { label_.set_markup(cleanLabel); if (iconLabel.front() == '/') { - auto pixbuf = Gdk::Pixbuf::create_from_file(iconLabel); - int scaled_icon_size = 6 * image_.get_scale_factor(); - pixbuf = Gdk::Pixbuf::create_from_file(iconLabel, scaled_icon_size, scaled_icon_size); + int scaled_icon_size = app_icon_size_ * image_.get_scale_factor(); + auto pixbuf = Gdk::Pixbuf::create_from_file(iconLabel, scaled_icon_size, scaled_icon_size); auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), image_.get_window()); From 11533ecf0a5b78171a95bce304cd9f905b4d7ff6 Mon Sep 17 00:00:00 2001 From: Adam Schwalm Date: Fri, 26 Dec 2025 17:16:50 -0600 Subject: [PATCH 469/780] Add '-' support to wifi config Most modules support config fields like "format--", so the user can, for example, set a different format for batter when unplugged _and_ under a given level. Wifi didn't support this. Change the module to support format state based on signal strength. --- src/modules/network.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index d0b7970c..951b099e 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -291,19 +291,27 @@ auto waybar::modules::Network::update() -> void { bandwidth_up_total_ = up_octets; } + auto threshold_state = getState(signal_strength_); + if (!alt_) { auto state = getNetworkState(); if (!state_.empty() && label_.get_style_context()->has_class(state_)) { label_.get_style_context()->remove_class(state_); } - if (config_["format-" + state].isString()) { + if (!threshold_state.empty() && + config_["format-" + state + "-" + threshold_state].isString()) { + default_format_ = config_["format-" + state + "-" + threshold_state].asString(); + } else if (config_["format-" + state].isString()) { default_format_ = config_["format-" + state].asString(); } else if (config_["format"].isString()) { default_format_ = config_["format"].asString(); } else { default_format_ = DEFAULT_FORMAT; } - if (config_["tooltip-format-" + state].isString()) { + if (!threshold_state.empty() && + config_["tooltip-format-" + state + "-" + threshold_state].isString()) { + tooltip_format = config_["tooltip-format-" + state + "-" + threshold_state].asString(); + } else if (config_["tooltip-format-" + state].isString()) { tooltip_format = config_["tooltip-format-" + state].asString(); } if (!label_.get_style_context()->has_class(state)) { @@ -312,7 +320,6 @@ auto waybar::modules::Network::update() -> void { format_ = default_format_; state_ = state; } - getState(signal_strength_); std::string final_ipaddr_; if (addr_pref_ == ip_addr_pref::IPV4) { From ce029c341939b88a91ec167ef1968bba2876158c Mon Sep 17 00:00:00 2001 From: Kaosu Date: Sat, 27 Dec 2025 08:16:03 +0100 Subject: [PATCH 470/780] temperature: add hwmon-name option for stable sensor selection Allow selecting a hwmon device for the temperature module by its name instead of a fixed hwmon path. This avoids fragile configurations that break when new hwmon devices are added and the kernel changes hwmon ordering. The hwmon-name option is mutually exclusive with hwmon-path and hwmon-path-abs to prevent ambiguous configurations. --- man/waybar-temperature.5.scd | 6 ++++++ src/modules/temperature.cpp | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/man/waybar-temperature.5.scd b/man/waybar-temperature.5.scd index 1e19ec82..672a2518 100644 --- a/man/waybar-temperature.5.scd +++ b/man/waybar-temperature.5.scd @@ -27,6 +27,12 @@ Addressed by *temperature* The path of the hwmon-directory of the device, e.g. */sys/devices/pci0000:00/0000:00:18.3/hwmon*. (Note that the subdirectory *hwmon/hwmon#*, where *#* is a number is not part of the path!) Has to be used together with *input-filename*. This can also be an array of strings, for which, it just works like *hwmon-path*. +*hwmon-name*: ++ + typeof: string ++ + Select a hwmon device by its name (from /sys/class/hwmon/*/name), e.g. *amdgpu* + Requires *input-filename* to be set. + Cannot be used together with *hwmon-path* or *hwmon-path-abs*. + *input-filename*: ++ typeof: string ++ The temperature filename of your *hwmon-path-abs*, e.g. *temp1_input* diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index fa23ef56..d8108577 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -20,6 +20,24 @@ waybar::modules::Temperature::Temperature(const std::string& id, const Json::Val if (check_set_path(item.asString())) break; }; + auto find_hwmon_by_name = [](const std::string& name) -> std::optional { + for (const auto& entry : std::filesystem::directory_iterator("/sys/class/hwmon")) { + std::ifstream f(entry.path() / "name"); + std::string hwname; + if (f >> hwname && hwname == name) { + return entry.path(); + } + } + return std::nullopt; + }; + + // ensure either hwmon-name OR old paths are used, not both + if (config_["hwmon-name"].isString() && + (!config_["hwmon-path"].isNull() || !config_["hwmon-path-abs"].isNull())) { + throw std::runtime_error( + "hwmon-name cannot be used together with hwmon-path or hwmon-path-abs"); + } + // if hwmon_path is an array, loop to find first valid item traverseAsArray(config_["hwmon-path"], [this](const std::string& path) { if (!std::filesystem::exists(path)) return false; @@ -40,6 +58,20 @@ waybar::modules::Temperature::Temperature(const std::string& id, const Json::Val }); } + if (file_path_.empty() && config_["hwmon-name"].isString()) { + if (!config_["input-filename"].isString()) { + throw std::runtime_error("hwmon-name requires input-filename to be set"); + } + + auto hwmon = find_hwmon_by_name(config_["hwmon-name"].asString()); + + if (!hwmon) { + throw std::runtime_error("hwmon-name '" + config_["hwmon-name"].asString() + "' not found"); + } + + file_path_ = hwmon->string() + "/" + config_["input-filename"].asString(); + } + if (file_path_.empty()) { auto zone = config_["thermal-zone"].isInt() ? config_["thermal-zone"].asInt() : 0; file_path_ = fmt::format("/sys/class/thermal/thermal_zone{}/temp", zone); From 61af3e95faa77afc699aa142846e9feee793a862 Mon Sep 17 00:00:00 2001 From: Kaosu Date: Sat, 27 Dec 2025 08:38:14 +0100 Subject: [PATCH 471/780] fix the doc --- man/waybar-temperature.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/waybar-temperature.5.scd b/man/waybar-temperature.5.scd index 672a2518..0783331e 100644 --- a/man/waybar-temperature.5.scd +++ b/man/waybar-temperature.5.scd @@ -29,7 +29,7 @@ Addressed by *temperature* *hwmon-name*: ++ typeof: string ++ - Select a hwmon device by its name (from /sys/class/hwmon/*/name), e.g. *amdgpu* + Select a hwmon device by its name (from /sys/class/hwmon/\*/name), e.g. *amdgpu*. Requires *input-filename* to be set. Cannot be used together with *hwmon-path* or *hwmon-path-abs*. From 8e9ebb8bb14c461b9b5175bc842d7f284c43bf30 Mon Sep 17 00:00:00 2001 From: Rio Sanjaya Date: Sat, 27 Dec 2025 17:57:20 +0700 Subject: [PATCH 472/780] use snake_case for variable name --- include/AIconLabel.hpp | 2 +- src/AIconLabel.cpp | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/include/AIconLabel.hpp b/include/AIconLabel.hpp index 91b022a4..34beb9b2 100644 --- a/include/AIconLabel.hpp +++ b/include/AIconLabel.hpp @@ -21,7 +21,7 @@ class AIconLabel : public ALabel { Gtk::Box box_; unsigned app_icon_size_{24}; - bool labelContainsIcon; + bool label_contains_icon; bool iconEnabled() const; }; diff --git a/src/AIconLabel.cpp b/src/AIconLabel.cpp index 81953d7b..c053aa97 100644 --- a/src/AIconLabel.cpp +++ b/src/AIconLabel.cpp @@ -62,31 +62,31 @@ AIconLabel::AIconLabel(const Json::Value &config, const std::string &name, const } std::tuple AIconLabel::extractIcon(const std::string& input) { - std::string iconResult = ""; - std::string labelResult = input; + std::string icon_result = ""; + std::string label_result = input; try { - const std::regex iconSearch(R"((?=\\0icon\\1f).+?(?=\\n))"); - std::smatch iconMatch; - if (std::regex_search(input, iconMatch, iconSearch)) { - iconResult = iconMatch[0].str().substr(9); + const std::regex icon_search(R"((?=\\0icon\\1f).+?(?=\\n))"); + std::smatch icon_match; + if (std::regex_search(input, icon_match, icon_search)) { + icon_result = icon_match[0].str().substr(9); - const std::regex cleanLabelPattern(R"(\\0icon\\1f.+?\\n)"); - labelResult = std::regex_replace(input, cleanLabelPattern, ""); + const std::regex clean_label_pattern(R"(\\0icon\\1f.+?\\n)"); + label_result = std::regex_replace(input, clean_label_pattern, ""); } } catch (const std::exception& e) { spdlog::warn("Error while parsing icon from label. {}", e.what()); } - return std::make_tuple(iconResult, labelResult); + return std::make_tuple(icon_result, label_result); } auto AIconLabel::update() -> void { - labelContainsIcon = false; + label_contains_icon = false; auto [iconLabel, cleanLabel] = extractIcon(label_.get_label().c_str()); - labelContainsIcon = iconLabel.length() > 0; + label_contains_icon = iconLabel.length() > 0; - if (labelContainsIcon) { + if (label_contains_icon) { label_.set_markup(cleanLabel); if (iconLabel.front() == '/') { @@ -108,7 +108,7 @@ auto AIconLabel::update() -> void { } bool AIconLabel::iconEnabled() const { - return labelContainsIcon || (config_["icon"].isBool() ? config_["icon"].asBool() : false); + return label_contains_icon || (config_["icon"].isBool() ? config_["icon"].asBool() : false); } } // namespace waybar From 9dfcd206d70c2e3be5785e262c39871aca430f39 Mon Sep 17 00:00:00 2001 From: Hallvard Lavik Date: Sat, 27 Dec 2025 12:11:43 +0100 Subject: [PATCH 473/780] Allow tooltip formatting the CPU module. --- src/modules/cpu.cpp | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/modules/cpu.cpp b/src/modules/cpu.cpp index 0703eaf7..b0519f8d 100644 --- a/src/modules/cpu.cpp +++ b/src/modules/cpu.cpp @@ -26,15 +26,17 @@ auto waybar::modules::Cpu::update() -> void { auto [load1, load5, load15] = Load::getLoad(); auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_); auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency(); - if (tooltipEnabled()) { - label_.set_tooltip_text(tooltip); - } + auto format = format_; + std::string tooltip_format; auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; auto state = getState(total_usage); if (!state.empty() && config_["format-" + state].isString()) { format = config_["format-" + state].asString(); } + if (config_["tooltip-format-" + state].isString()) { + tooltip_format = config_["tooltip-format-" + state].asString(); + } if (format.empty()) { event_box_.hide(); @@ -58,6 +60,38 @@ auto waybar::modules::Cpu::update() -> void { label_.set_markup(fmt::vformat(format, store)); } + if (tooltipEnabled()) { + if (tooltip_format.empty() && config_["tooltip-format"].isString()) { + tooltip_format = config_["tooltip-format"].asString(); + } + if (!tooltip_format.empty()) { + tooltip_format = config_["tooltip-format"].asString(); + + auto icons = std::vector{state}; + fmt::dynamic_format_arg_store store; + store.push_back(fmt::arg("load", load1)); + store.push_back(fmt::arg("usage", total_usage)); + store.push_back(fmt::arg("icon", getIcon(total_usage, icons))); + store.push_back(fmt::arg("max_frequency", max_frequency)); + store.push_back(fmt::arg("min_frequency", min_frequency)); + store.push_back(fmt::arg("avg_frequency", avg_frequency)); + for (size_t i = 1; i < cpu_usage.size(); ++i) { + auto core_i = i - 1; + auto core_format = fmt::format("usage{}", core_i); + store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i])); + auto icon_format = fmt::format("icon{}", core_i); + store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); + } + auto tooltip_text = fmt::vformat(tooltip_format, store); + + if (label_.get_tooltip_text() != tooltip_text) { + label_.set_tooltip_markup(tooltip_text); + } + } else { + label_.set_tooltip_text(tooltip); + } + } + // Call parent update ALabel::update(); } From e3186abdce3c17afc6a987f3ca1acc481df82e14 Mon Sep 17 00:00:00 2001 From: Xavier Ruiz Date: Sat, 27 Dec 2025 14:52:28 -0500 Subject: [PATCH 474/780] fix(cava): handle all output methods in frontend Default to raw Cava module for non-GLSL output methods. Fixes "Unknown module" error when no cava_config is specified. --- include/modules/cava/cava_frontend.hpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/include/modules/cava/cava_frontend.hpp b/include/modules/cava/cava_frontend.hpp index 5a73f62f..402a9bad 100644 --- a/include/modules/cava/cava_frontend.hpp +++ b/include/modules/cava/cava_frontend.hpp @@ -13,18 +13,15 @@ AModule* getModule(const std::string& id, const Json::Value& config) { #ifdef HAVE_LIBCAVA const std::shared_ptr backend_{waybar::modules::cava::CavaBackend::inst(config)}; switch (backend_->getPrm()->output) { - case ::cava::output_method::OUTPUT_RAW: - return new waybar::modules::cava::Cava(id, config); - break; #ifdef HAVE_LIBCAVAGLSL case ::cava::output_method::OUTPUT_SDL_GLSL: return new waybar::modules::cava::CavaGLSL(id, config); - break; #endif default: - break; + return new waybar::modules::cava::Cava(id, config); } -#endif +#else throw std::runtime_error("Unknown module"); +#endif }; } // namespace waybar::modules::cava From 19bb89874e915017fe40a7ad9490e9a56401719e Mon Sep 17 00:00:00 2001 From: Xavier Ruiz Date: Sat, 27 Dec 2025 18:00:38 -0500 Subject: [PATCH 475/780] fix(cava): default data_format to ascii for correct bar scaling data_format was changed from always "ascii" to user-configurable, but without a default. This caused cava to use "binary", calculating height as 65535 instead of ascii_range, making bars always peak. --- src/modules/cava/cava_backend.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/cava/cava_backend.cpp b/src/modules/cava/cava_backend.cpp index 917e165f..c576f0cf 100644 --- a/src/modules/cava/cava_backend.cpp +++ b/src/modules/cava/cava_backend.cpp @@ -183,10 +183,10 @@ void waybar::modules::cava::CavaBackend::loadConfig() { prm_.inAtty = 0; auto const output{prm_.output}; // prm_.output = ::cava::output_method::OUTPUT_RAW; - if (config_["data_format"].isString()) { - if (prm_.data_format) free(prm_.data_format); - prm_.data_format = strdup(config_["data_format"].asString().c_str()); - } + if (prm_.data_format) free(prm_.data_format); + // Default to ascii for format-icons output; allow user override + prm_.data_format = strdup( + config_["data_format"].isString() ? config_["data_format"].asString().c_str() : "ascii"); if (config_["raw_target"].isString()) { if (prm_.raw_target) free(prm_.raw_target); prm_.raw_target = strdup(config_["raw_target"].asString().c_str()); From 959f41ca9cff4635bb675f78cfcf8c705ef9a9d8 Mon Sep 17 00:00:00 2001 From: LorenzBischof <1837725+LorenzBischof@users.noreply.github.com> Date: Tue, 30 Dec 2025 12:30:30 +0100 Subject: [PATCH 476/780] docs: add missing reverse-mouse-scrolling to pulseaudio module man page --- man/waybar-pulseaudio.5.scd | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/man/waybar-pulseaudio.5.scd b/man/waybar-pulseaudio.5.scd index d1cf5884..f555fd4d 100644 --- a/man/waybar-pulseaudio.5.scd +++ b/man/waybar-pulseaudio.5.scd @@ -97,7 +97,11 @@ Additionally, you can control the volume by scrolling *up* or *down* while the c *reverse-scrolling*: ++ typeof: bool ++ - Option to reverse the scroll direction. + Option to reverse the scroll direction for touchpads. + +*reverse-mouse-scrolling*: ++ + typeof: bool ++ + Option to reverse the scroll direction for mice. *tooltip*: ++ typeof: bool ++ From 99867005a0edf8951ba97e30b273ca8502d9f4b5 Mon Sep 17 00:00:00 2001 From: Dennis Weiershaeuser Date: Wed, 31 Dec 2025 13:43:14 +0100 Subject: [PATCH 477/780] docs(arch): install build deps as deps to keep system clean --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4edf2253..607d88d2 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ sudo apt install \ On Arch, you can use this command: ``` -pacman -S \ +pacman -S --asdeps \ gtkmm3 \ jsoncpp \ libsigc++ \ From 0a5da13fbf5fa5e0c30de4d6d42d23c574b4450a Mon Sep 17 00:00:00 2001 From: Mathis Raguin Date: Wed, 31 Dec 2025 14:44:49 +0100 Subject: [PATCH 478/780] fix(workspaces/sway): handle workspaces with quotes --- include/modules/sway/workspaces.hpp | 2 +- src/modules/sway/workspaces.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/modules/sway/workspaces.hpp b/include/modules/sway/workspaces.hpp index d8a9e18a..fcc91ea1 100644 --- a/include/modules/sway/workspaces.hpp +++ b/include/modules/sway/workspaces.hpp @@ -23,7 +23,7 @@ class Workspaces : public AModule, public sigc::trackable { auto update() -> void override; private: - static constexpr std::string_view workspace_switch_cmd_ = "workspace {} \"{}\""; + static constexpr std::string_view workspace_switch_cmd_ = "workspace {} {}"; static constexpr std::string_view persistent_workspace_switch_cmd_ = R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")"; diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 182bf631..cb29b587 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -369,7 +369,7 @@ Gtk::Button &Workspaces::addButton(const Json::Value &node) { node["name"].asString(), node["target_output"].asString(), "--no-auto-back-and-forth", node["name"].asString())); } else { - ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace {} \"{}\"", + ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_cmd_, config_["disable-auto-back-and-forth"].asBool() ? "--no-auto-back-and-forth" : "", From da4367df2f37fd30eaee5f4bc57c7c9179a5917b Mon Sep 17 00:00:00 2001 From: Mathis Raguin Date: Wed, 31 Dec 2025 15:35:38 +0100 Subject: [PATCH 479/780] fix(sway/workspaces): use number when available to avoid parsing issues --- include/modules/sway/workspaces.hpp | 3 ++- src/modules/sway/workspaces.cpp | 13 ++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/include/modules/sway/workspaces.hpp b/include/modules/sway/workspaces.hpp index fcc91ea1..f68223be 100644 --- a/include/modules/sway/workspaces.hpp +++ b/include/modules/sway/workspaces.hpp @@ -23,7 +23,8 @@ class Workspaces : public AModule, public sigc::trackable { auto update() -> void override; private: - static constexpr std::string_view workspace_switch_cmd_ = "workspace {} {}"; + static constexpr std::string_view workspace_switch_cmd_ = "workspace {} \"{}\""; + static constexpr std::string_view workspace_switch_number_cmd_ = "workspace {} number {}"; static constexpr std::string_view persistent_workspace_switch_cmd_ = R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")"; diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index cb29b587..4efbeb74 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -369,11 +369,14 @@ Gtk::Button &Workspaces::addButton(const Json::Value &node) { node["name"].asString(), node["target_output"].asString(), "--no-auto-back-and-forth", node["name"].asString())); } else { - ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_cmd_, - config_["disable-auto-back-and-forth"].asBool() - ? "--no-auto-back-and-forth" - : "", - node["name"].asString())); + std::string flag = config_["disable-auto-back-and-forth"].asBool() + ? "--no-auto-back-and-forth" + : ""; + if (node["num"].asInt() >= 0) { + ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_number_cmd_, flag, node["num"].asInt())); + } else { + ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_cmd_, flag, node["name"].asString())); + } } } catch (const std::exception &e) { spdlog::error("Workspaces: {}", e.what()); From c2a038c222d4961b6f5f9ba1d6df9dc5682df45d Mon Sep 17 00:00:00 2001 From: Mathis Raguin Date: Wed, 31 Dec 2025 16:15:54 +0100 Subject: [PATCH 480/780] fix(sway/workspaces): escape markup in workspace if enabled --- src/modules/sway/workspaces.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 182bf631..450f08a9 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -329,7 +329,12 @@ auto Workspaces::update() -> void { } else { button.get_style_context()->remove_class("current_output"); } - std::string output = (*it)["name"].asString(); + std::string output; + if (!config_["disable-markup"].asBool()) { + output = g_markup_escape_text((*it)["name"].asString().c_str(), -1); + } else { + output = (*it)["name"].asString(); + } std::string windows = ""; if (config_["window-rewrite"].isObject()) { updateWindows((*it), windows); From a05e6c6f747587db3363c56a6b3f0aa6aa95ab80 Mon Sep 17 00:00:00 2001 From: Dennis Weiershaeuser Date: Wed, 31 Dec 2025 17:57:20 +0100 Subject: [PATCH 481/780] chore: update .gitingore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b486237e..8bf6481d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ vgcore.* *.swp packagecache /subprojects/**/ +/subprojects/.wraplock /build* /dist /meson.egg-info From d0f5fab52b538bd0fc41d37e4f34768f22310841 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 1 Jan 2026 00:14:44 +0000 Subject: [PATCH 482/780] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'flake-compat': 'github:edolstra/flake-compat/f387cd2afec9419c8ee37694406ca490c3f34ee5?narHash=sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4%3D' (2025-10-27) → 'github:edolstra/flake-compat/5edf11c44bc78a0d334f6334cdaf7d60d732daab?narHash=sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns%3D' (2025-12-29) • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/2fad6eac6077f03fe109c4d4eb171cf96791faa4?narHash=sha256-sKoIWfnijJ0%2B9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI%3D' (2025-11-27) → 'github:NixOS/nixpkgs/c0b0e0fddf73fd517c3471e546c0df87a42d53f4?narHash=sha256-coBu0ONtFzlwwVBzmjacUQwj3G%2BlybcZ1oeNSQkgC0M%3D' (2025-12-28) --- flake.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 0a8c48c1..ff953f44 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1761588595, - "narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=", + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", "owner": "edolstra", "repo": "flake-compat", - "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "type": "github" }, "original": { @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1764242076, - "narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=", + "lastModified": 1766902085, + "narHash": "sha256-coBu0ONtFzlwwVBzmjacUQwj3G+lybcZ1oeNSQkgC0M=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4", + "rev": "c0b0e0fddf73fd517c3471e546c0df87a42d53f4", "type": "github" }, "original": { From 9e53174ef59eee7d70bb0aee650818e344e6582f Mon Sep 17 00:00:00 2001 From: Dennis Weiershaeuser Date: Wed, 31 Dec 2025 18:05:43 +0100 Subject: [PATCH 483/780] feat(sway/workspaces): add format for negative indices --- man/waybar-sway-workspaces.5.scd | 12 ++++++++++ src/modules/sway/workspaces.cpp | 39 ++++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/man/waybar-sway-workspaces.5.scd b/man/waybar-sway-workspaces.5.scd index 3d4e3ad0..9bffda3d 100644 --- a/man/waybar-sway-workspaces.5.scd +++ b/man/waybar-sway-workspaces.5.scd @@ -22,6 +22,11 @@ Addressed by *sway/workspaces* default: {value} ++ The format, how information should be displayed. +*format-for-negative-index*: ++ + typeof: string ++ + default: *format* ++ + An alternative format, which will be used for workspaces with no explict or negative index ("num" in sway terms). Requires *format* to be set. + *format-icons*: ++ typeof: array ++ Based on the workspace name and state, the corresponding icon gets selected. See *icons*. @@ -187,6 +192,13 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge } ``` +``` +"sway/workspaces": { + "format": "{index} - {name}", + "format-for-negative-index": "{name}" +} +``` + # Style - *#workspaces button* diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 182bf631..88c090c5 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -329,24 +329,43 @@ auto Workspaces::update() -> void { } else { button.get_style_context()->remove_class("current_output"); } - std::string output = (*it)["name"].asString(); + std::string windows = ""; if (config_["window-rewrite"].isObject()) { updateWindows((*it), windows); } + + auto index = (*it)["num"].asInt(); + auto full_name = (*it)["name"].asString(); + if (config_["format"].isString()) { - auto format = config_["format"].asString(); - output = fmt::format( - fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)), fmt::arg("value", output), - fmt::arg("name", trimWorkspaceName(output)), fmt::arg("index", (*it)["num"].asString()), - fmt::arg("windows", - windows.substr(0, windows.length() - m_formatWindowSeparator.length())), - fmt::arg("output", (*it)["output"].asString())); + std::string format; + if(config_["format-for-negative-index"].isString() && index < 0) { + format = config_["format-for-negative-index"].asString(); + } else { + format = config_["format"].asString(); + } + + auto name = trimWorkspaceName(full_name); + auto output = (*it)["output"].asString(); + auto icon = getIcon(full_name, *it); + auto separated_windows = windows.substr(0, windows.length() - m_formatWindowSeparator.length()); + + full_name = fmt::format( + fmt::runtime(format), + fmt::arg("index", index), + fmt::arg("name", name), + fmt::arg("value", full_name), + fmt::arg("output", output), + fmt::arg("icon", icon), + fmt::arg("windows", separated_windows) + ); } + if (!config_["disable-markup"].asBool()) { - static_cast(button.get_children()[0])->set_markup(output); + static_cast(button.get_children()[0])->set_markup(full_name); } else { - button.set_label(output); + button.set_label(full_name); } onButtonReady(*it, button); } From 49083131d18fc0d11e315b7c046fa1cb167149a1 Mon Sep 17 00:00:00 2001 From: Ansari Date: Sat, 3 Jan 2026 11:16:03 +0530 Subject: [PATCH 484/780] fix(AModule): improve cursor configuration handling --- src/AModule.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/AModule.cpp b/src/AModule.cpp index c6fdff3e..f4a9470e 100644 --- a/src/AModule.cpp +++ b/src/AModule.cpp @@ -72,8 +72,12 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std:: // Respect user configuration of cursor if (config_.isMember("cursor")) { - if (config_["cursor"].isBool() && config_["cursor"].asBool()) { - setCursor(Gdk::HAND2); + if (config_["cursor"].isBool()) { + if (config_["cursor"].asBool()) { + setCursor(Gdk::HAND2); + } else { + setCursor(Gdk::ARROW); + } } else if (config_["cursor"].isInt()) { setCursor(Gdk::CursorType(config_["cursor"].asInt())); } else { From a8a41bd17cf68ff0ae83910147f7785f82da687d Mon Sep 17 00:00:00 2001 From: Ansari Date: Sat, 3 Jan 2026 11:20:03 +0530 Subject: [PATCH 485/780] fix(workspaces): skip ignored workspaces in persistent rules --- src/modules/hyprland/workspaces.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 8360137f..4a2c0e17 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -306,6 +306,10 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c // 2. the rule's monitor is the current monitor // 3. no monitor is specified in the rule => assume it needs to be persistent on every monitor if (allOutputs() || m_bar.output->name == monitor || monitor.empty()) { + // => skip ignore-workspaces even if its a persistent + if(isWorkspaceIgnored(workspace)) { + continue; + } // => persistent workspace should be shown on this monitor auto workspaceData = createMonitorWorkspaceData(workspace, m_bar.output->name); workspaceData["persistent-rule"] = true; From 5870c4ba0e85911160b04d99793989de2586eefc Mon Sep 17 00:00:00 2001 From: Aksel Lunde Aase Date: Fri, 9 Jan 2026 13:22:48 +0100 Subject: [PATCH 486/780] feat(clock): Add action to execute external command --- include/modules/clock.hpp | 3 +++ man/waybar-clock.5.scd | 2 ++ src/modules/clock.cpp | 8 ++++++++ 3 files changed, 13 insertions(+) diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index c3548063..4ac91d61 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -80,6 +80,7 @@ class Clock final : public ALabel { void cldShift_reset(); void tz_up(); void tz_down(); + void action_exec(const std::string& action); // Module Action Map static inline std::map actionMap_{ {"mode", &waybar::modules::Clock::cldModeSwitch}, @@ -88,6 +89,8 @@ class Clock final : public ALabel { {"shift_reset", &waybar::modules::Clock::cldShift_reset}, {"tz_up", &waybar::modules::Clock::tz_up}, {"tz_down", &waybar::modules::Clock::tz_down}}; + static inline std::map actionWithArgsMap_{ + {"exec", &waybar::modules::Clock::action_exec}}; }; } // namespace waybar::modules diff --git a/man/waybar-clock.5.scd b/man/waybar-clock.5.scd index b4b5d5b7..440c3447 100644 --- a/man/waybar-clock.5.scd +++ b/man/waybar-clock.5.scd @@ -180,6 +180,8 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe :[ Switch to the next calendar month/year |[ *shift_down* :[ Switch to the previous calendar month/year +|[ *exec * +:[ Execute the specified command # FORMAT REPLACEMENTS diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 5fe5407b..2bc99764 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -9,6 +9,7 @@ #include #include +#include "util/command.hpp" #include "util/ustring_clen.hpp" #ifdef HAVE_LANGINFO_1STDAY @@ -472,6 +473,8 @@ auto waybar::modules::Clock::local_zone() -> const time_zone* { auto waybar::modules::Clock::doAction(const std::string& name) -> void { if (actionMap_[name]) { (this->*actionMap_[name])(); + } else if (auto key = name.substr(0, name.find(" ")); actionWithArgsMap_[key]) { + (this->*actionWithArgsMap_[key])(name); } else spdlog::error("Clock. Unsupported action \"{0}\"", name); } @@ -498,6 +501,11 @@ void waybar::modules::Clock::tz_down() { if (tzSize == 1) return; tzCurrIdx_ = (tzCurrIdx_ == 0) ? tzSize - 1 : tzCurrIdx_ - 1; } +void waybar::modules::Clock::action_exec(const std::string& action) { + auto cmd = action.substr(strlen("exec ")); + pid_children_.push_back(util::command::forkExec(cmd)); +} + #ifdef HAVE_LANGINFO_1STDAY template From 7f63a905a13a8a2cfe911932216fbec529b6da1d Mon Sep 17 00:00:00 2001 From: Benji-clm Date: Fri, 9 Jan 2026 13:02:28 +0000 Subject: [PATCH 487/780] feat(battery): add option to smooth power for more stable time_remaining estimates --- include/modules/battery.hpp | 6 ++++++ src/modules/battery.cpp | 30 +++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/include/modules/battery.hpp b/include/modules/battery.hpp index ffc82aba..357cbfb3 100644 --- a/include/modules/battery.hpp +++ b/include/modules/battery.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -51,6 +52,11 @@ class Battery : public ALabel { bool warnFirstTime_{true}; bool weightedAverage_{true}; const Bar& bar_; + bool smoothPowerEnable_{false}; + double time_constant_s_{260.0}; + double smooth_power_{0.0}; // µW + std::chrono::steady_clock::time_point last_t_{std::chrono::steady_clock::now()}; + std::string old_status_raw_{""}; util::SleeperThread thread_; util::SleeperThread thread_battery_update_; diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index c706eb3c..0ff15ac6 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -2,6 +2,9 @@ #include #include +#include +#include +#include #include "util/command.hpp" #if defined(__FreeBSD__) @@ -33,6 +36,11 @@ waybar::modules::Battery::Battery(const std::string& id, const Bar& bar, const J } udev_monitor_enable_receiving(mon_.get()); + if (config_["smooth-power"].isBool()) { + smoothPowerEnable_ = config_["smooth-power"].asBool(); + if (smoothPowerEnable_ && config_["smooth-power-time-constant"].isNumeric()) + time_constant_s_ = std::max(1.0, config_["smooth-power-time-constant"].asDouble()); + } if (config_["weighted-average"].isBool()) weightedAverage_ = config_["weighted-average"].asBool(); #endif spdlog::debug("battery: worker interval is {}", interval_.count()); @@ -562,11 +570,31 @@ waybar::modules::Battery::getInfos() { if (online && current_status != "Discharging") status = "Plugged"; } + if (total_energy_exists && total_power_exists && total_power != 0) { + if (!smoothPowerEnable_) { + smooth_power_ = total_power; + } else { + if (status != old_status_raw_) { + smooth_power_ = total_power; + last_t_ = std::chrono::steady_clock::now(); + } else { + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + double dt_s = + std::chrono::duration_cast >(now - last_t_).count(); + smooth_power_ = smooth_power_ + ((1 - std::exp(-dt_s / time_constant_s_)) * + (total_power - smooth_power_)); + last_t_ = now; + } + + old_status_raw_ = status; + } + } + float time_remaining{0.0f}; if (status == "Discharging" && time_to_empty_now_exists) { if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 3600.0f; } else if (status == "Discharging" && total_power_exists && total_energy_exists) { - if (total_power != 0) time_remaining = (float)total_energy / total_power; + if (smooth_power_ != 0) time_remaining = (float)total_energy / smooth_power_; } else if (status == "Charging" && time_to_full_now_exists) { if (time_to_full_now_exists && (time_to_full_now != 0)) time_remaining = -(float)time_to_full_now / 3600.0f; From 8f5fc990a5ed862d4c728499c124db4ad70d0db5 Mon Sep 17 00:00:00 2001 From: zjeffer <4633209+zjeffer@users.noreply.github.com> Date: Sat, 10 Jan 2026 13:28:54 +0100 Subject: [PATCH 488/780] hyprland/workspaces: don't show persistent special workspaces if show-special is disabled --- src/modules/hyprland/workspaces.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 8360137f..fe38eabd 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -296,6 +296,11 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c auto workspace = rule.isMember("defaultName") ? rule["defaultName"].asString() : rule["workspaceString"].asString(); + // There could be persistent special workspaces, only show those when show-special is enabled. + if (workspace.starts_with("special:") && !showSpecial()) { + continue; + } + // The prefix "name:" cause mismatches with workspace names taken anywhere else. if (workspace.starts_with("name:")) { workspace = workspace.substr(5); From c38024bdecc818ecafecbdaf77456d8013dd4b4e Mon Sep 17 00:00:00 2001 From: szbmrk Date: Thu, 15 Jan 2026 00:29:49 +0100 Subject: [PATCH 489/780] feat(hyprland/submap): add config option to specify different icons for submaps --- include/modules/hyprland/submap.hpp | 2 ++ src/modules/hyprland/submap.cpp | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/include/modules/hyprland/submap.hpp b/include/modules/hyprland/submap.hpp index 7e3425ef..14faffd8 100644 --- a/include/modules/hyprland/submap.hpp +++ b/include/modules/hyprland/submap.hpp @@ -26,8 +26,10 @@ class Submap : public waybar::ALabel, public EventHandler { const Bar& bar_; util::JsonParser parser_; std::string submap_; + std::string icon_; bool always_on_ = false; std::string default_submap_ = "Default"; + std::unordered_map icons_; IPC& m_ipc; }; diff --git a/src/modules/hyprland/submap.cpp b/src/modules/hyprland/submap.cpp index eb9b1de8..a4591149 100644 --- a/src/modules/hyprland/submap.cpp +++ b/src/modules/hyprland/submap.cpp @@ -21,6 +21,18 @@ Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config) // register for hyprland ipc m_ipc.registerForIPC("submap", this); dp.emit(); + + if (config["icons"].isObject()) { + const Json::Value& icons = config["icons"]; + + for (const std::string& key : icons.getMemberNames()) { + const Json::Value& value = icons[key]; + + if (value.isString()) { + icons_[key] = value.asString(); + } + } + } } Submap::~Submap() { @@ -47,7 +59,8 @@ auto Submap::update() -> void { if (submap_.empty()) { event_box_.hide(); } else { - label_.set_markup(fmt::format(fmt::runtime(format_), submap_)); + label_.set_markup( + fmt::format(fmt::runtime(format_), fmt::arg("submap", submap_), fmt::arg("icon", icon_))); if (tooltipEnabled()) { label_.set_tooltip_text(submap_); } @@ -72,6 +85,12 @@ void Submap::onEvent(const std::string& ev) { submap_ = submapName; + if (!icons_[submap_].empty()) { + icon_ = icons_[submap_]; + } else { + icon_ = ""; + } + if (submap_.empty() && always_on_) { submap_ = default_submap_; } From f3c36282549a609061995810f32c0240732796d7 Mon Sep 17 00:00:00 2001 From: szbmrk Date: Thu, 15 Jan 2026 00:37:04 +0100 Subject: [PATCH 490/780] docs(hyprland/submap): add config option to specify different icons for submaps --- man/waybar-hyprland-submap.5.scd | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/man/waybar-hyprland-submap.5.scd b/man/waybar-hyprland-submap.5.scd index f6cdff94..0e540a93 100644 --- a/man/waybar-hyprland-submap.5.scd +++ b/man/waybar-hyprland-submap.5.scd @@ -15,7 +15,7 @@ Addressed by *hyprland/submap* *format*: ++ typeof: string ++ default: {} ++ - The format, how information should be displayed. On {} the currently active submap is displayed. + The format, how information should be displayed. This format supports two placeholders: *{submap}* for the currently active submap name and *{icon}* for the icon associated with the submap. *rotate*: ++ typeof: integer ++ @@ -80,6 +80,10 @@ Addressed by *hyprland/submap* default: Default ++ Option to set the submap name to display when not in an active submap. +*icons*: ++ + typeof: object ++ + Based on the submap name, the corresponding icon will be selected from this map and made available as the *{icon}* placeholder in the format string. The keys are submap names, and the values are the icons or strings to display for those submaps. + *menu*: ++ typeof: string ++ Action that popups the menu. @@ -103,9 +107,13 @@ Addressed by *hyprland/submap* ``` "hyprland/submap": { - "format": "✌️ {}", + "format": "{icon} {submap}", "max-length": 8, - "tooltip": false + "tooltip": false, + "icons": { + "resize": "", + "pause": "", + } } ``` From 762c4f2e2736b1992321c9be9f438eeaf69684df Mon Sep 17 00:00:00 2001 From: Holbormon <160845407+Holbormon@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:12:09 +0000 Subject: [PATCH 491/780] Use `name` config field to set GTK layer namespace Provides an easy way to disambiguate several waybars so that compositors like Hyprland may apply granular layer rules to different waybars. --- src/bar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bar.cpp b/src/bar.cpp index 70029a2a..a0090ccd 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -229,7 +229,7 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) gtk_layer_init_for_window(gtk_window); gtk_layer_set_keyboard_mode(gtk_window, GTK_LAYER_SHELL_KEYBOARD_MODE_NONE); gtk_layer_set_monitor(gtk_window, output->monitor->gobj()); - gtk_layer_set_namespace(gtk_window, "waybar"); + gtk_layer_set_namespace(gtk_window, config["name"].isString() ? config["name"].asString() : "waybar"); gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, margins_.left); gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, margins_.right); From b192244f50ab34885f011cadec327400c12b07cb Mon Sep 17 00:00:00 2001 From: Aaron Gerbert Date: Fri, 23 Jan 2026 11:27:38 -0500 Subject: [PATCH 492/780] Commit Squash --- src/modules/hyprland/workspace.cpp | 12 ++++++++++++ src/modules/hyprland/workspaces.cpp | 1 + 2 files changed, 13 insertions(+) diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index ae66bc84..82247020 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -161,6 +161,17 @@ std::string &Workspace::selectIcon(std::map &icons_map } } + if (isActive() && isSpecial()) { + auto activeIconIt = icons_map.find("active:" + name()); + if (activeIconIt != icons_map.end()) { + return activeIconIt->second; + } + auto namedIconIt = icons_map.find(name()); + if (namedIconIt != icons_map.end()) { + return namedIconIt->second; + } + } + if (isActive()) { auto activeIconIt = icons_map.find("active"); if (activeIconIt != icons_map.end()) { @@ -234,6 +245,7 @@ void Workspace::update(const std::string &workspace_icon) { auto styleContext = m_button.get_style_context(); addOrRemoveClass(styleContext, isActive(), "active"); addOrRemoveClass(styleContext, isSpecial(), "special"); + addOrRemoveClass(styleContext, isSpecial(), name()); addOrRemoveClass(styleContext, isEmpty(), "empty"); addOrRemoveClass(styleContext, isPersistent(), "persistent"); addOrRemoveClass(styleContext, isUrgent(), "urgent"); diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 8360137f..670b1b11 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -366,6 +366,7 @@ void Workspaces::onWorkspaceActivated(std::string const &payload) { void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) { std::string name(begin(payload), begin(payload) + payload.find_first_of(',')); m_activeSpecialWorkspaceName = (!name.starts_with("special:") ? name : name.substr(8)); + m_ipc.getSocket1JsonReply("reload"); } void Workspaces::onWorkspaceDestroyed(std::string const &payload) { From 1639dec7d87e1aefc15bcd1603d7634844ee3d26 Mon Sep 17 00:00:00 2001 From: xander1421 Date: Sat, 24 Jan 2026 18:45:08 +0200 Subject: [PATCH 493/780] fix(json): use local CharReaderBuilder for thread safety --- include/util/json.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/util/json.hpp b/include/util/json.hpp index f0736f9b..5f756f35 100644 --- a/include/util/json.hpp +++ b/include/util/json.hpp @@ -30,15 +30,16 @@ class JsonParser { std::istringstream jsonStream(modifiedJsonStr); std::string errs; - if (!Json::parseFromStream(m_readerBuilder, jsonStream, &root, &errs)) { + // Use local CharReaderBuilder for thread safety - the IPC singleton's + // parser can be called concurrently from multiple module threads + Json::CharReaderBuilder readerBuilder; + if (!Json::parseFromStream(readerBuilder, jsonStream, &root, &errs)) { throw std::runtime_error("Error parsing JSON: " + errs); } return root; } private: - Json::CharReaderBuilder m_readerBuilder; - static std::string replaceHexadecimalEscape(const std::string& str) { static std::regex re("\\\\x"); return std::regex_replace(str, re, "\\u00"); From c12658dea5459a12a0787e34f1cb00038503092c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 1 Feb 2026 00:18:11 +0000 Subject: [PATCH 494/780] 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/c0b0e0fddf73fd517c3471e546c0df87a42d53f4?narHash=sha256-coBu0ONtFzlwwVBzmjacUQwj3G%2BlybcZ1oeNSQkgC0M%3D' (2025-12-28) → 'github:NixOS/nixpkgs/bfc1b8a4574108ceef22f02bafcf6611380c100d?narHash=sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI%3D' (2026-01-26) --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index ff953f44..e3e70a02 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1766902085, - "narHash": "sha256-coBu0ONtFzlwwVBzmjacUQwj3G+lybcZ1oeNSQkgC0M=", + "lastModified": 1769461804, + "narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c0b0e0fddf73fd517c3471e546c0df87a42d53f4", + "rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d", "type": "github" }, "original": { From 5ab28b901c92dcf29f468dadcddaa77fa72a5f7b Mon Sep 17 00:00:00 2001 From: BBaoVanC Date: Sun, 1 Feb 2026 22:01:04 -0600 Subject: [PATCH 495/780] Fix return type of input to gtk_layer_set_namespace --- src/bar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bar.cpp b/src/bar.cpp index a0090ccd..4a202d7a 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -229,7 +229,7 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) gtk_layer_init_for_window(gtk_window); gtk_layer_set_keyboard_mode(gtk_window, GTK_LAYER_SHELL_KEYBOARD_MODE_NONE); gtk_layer_set_monitor(gtk_window, output->monitor->gobj()); - gtk_layer_set_namespace(gtk_window, config["name"].isString() ? config["name"].asString() : "waybar"); + gtk_layer_set_namespace(gtk_window, config["name"].isString() ? config["name"].asCString() : "waybar"); gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, margins_.left); gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, margins_.right); From 60e73e8d122dfb8616b712b056051c151a08d354 Mon Sep 17 00:00:00 2001 From: jackinfurs Date: Tue, 3 Feb 2026 23:24:10 +0000 Subject: [PATCH 496/780] docs: add fraudulent site notice on proj README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 607d88d2..9ca041d1 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,10 @@ Contributions welcome!
Have fun :)
The style guidelines are [Google's](https://google.github.io/styleguide/cppguide.html) +> [!CAUTION] +> Distributions of Waybar are only released on the [official GitHub page](https://github.com/Alexays/Waybar).
+> Waybar does **not** have an official website. Do not trust any sites that claim to be official. + ## License Waybar is licensed under the MIT license. [See LICENSE for more information](https://github.com/Alexays/Waybar/blob/master/LICENSE). From 47fb21a2c1bb1c786d65b77f2a9be0004c55b83b Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 4 Feb 2026 09:24:14 +0100 Subject: [PATCH 497/780] chore: upgrade to clang-format@21 --- .github/workflows/clang-format.yml | 10 +- include/AAppIconLabel.hpp | 8 +- include/AIconLabel.hpp | 4 +- include/ALabel.hpp | 12 +- include/AModule.hpp | 28 +- include/bar.hpp | 22 +- include/client.hpp | 34 +-- include/config.hpp | 22 +- include/group.hpp | 14 +- include/modules/backlight.hpp | 4 +- include/modules/dwl/tags.hpp | 12 +- include/modules/dwl/window.hpp | 14 +- include/modules/ext/workspace_manager.hpp | 62 ++--- .../modules/ext/workspace_manager_binding.hpp | 12 +- include/modules/gamemode.hpp | 22 +- include/modules/jack.hpp | 12 +- include/modules/niri/language.hpp | 8 +- include/modules/niri/window.hpp | 8 +- include/modules/niri/workspaces.hpp | 10 +- include/modules/power_profiles_daemon.hpp | 16 +- include/modules/privacy/privacy.hpp | 8 +- include/modules/privacy/privacy_item.hpp | 8 +- include/modules/river/layout.hpp | 22 +- include/modules/river/mode.hpp | 12 +- include/modules/river/tags.hpp | 16 +- include/modules/river/window.hpp | 20 +- include/modules/sndio.hpp | 10 +- include/modules/sni/watcher.hpp | 28 +- include/modules/sway/ipc/client.hpp | 14 +- include/modules/systemd_failed_units.hpp | 6 +- include/modules/upower.hpp | 44 +-- include/modules/wlr/taskbar.hpp | 70 ++--- include/util/backlight_backend.hpp | 16 +- include/util/clara.hpp | 256 +++++++++--------- include/util/icon_loader.hpp | 18 +- include/util/pipewire/privacy_node_info.hpp | 4 +- include/util/udev_deleter.hpp | 8 +- include/util/ustring_clen.hpp | 2 +- src/AIconLabel.cpp | 4 +- src/bar.cpp | 3 +- src/client.cpp | 72 ++--- src/config.cpp | 56 ++-- src/modules/backlight.cpp | 4 +- src/modules/cpu_usage/bsd.cpp | 2 +- src/modules/dwl/tags.cpp | 48 ++-- src/modules/dwl/window.cpp | 50 ++-- src/modules/ext/workspace_manager.cpp | 70 ++--- src/modules/ext/workspace_manager_binding.cpp | 104 +++---- .../hyprland/windowcreationpayload.cpp | 10 +- src/modules/hyprland/workspace.cpp | 46 ++-- src/modules/hyprland/workspaces.cpp | 194 ++++++------- src/modules/jack.cpp | 14 +- src/modules/network.cpp | 76 +++--- src/modules/niri/backend.cpp | 82 +++--- src/modules/niri/language.cpp | 18 +- src/modules/niri/window.cpp | 18 +- src/modules/niri/workspaces.cpp | 40 +-- src/modules/privacy/privacy_item.cpp | 18 +- src/modules/pulseaudio.cpp | 6 +- src/modules/river/layout.cpp | 64 ++--- src/modules/river/mode.cpp | 36 +-- src/modules/river/tags.cpp | 60 ++-- src/modules/river/window.cpp | 46 ++-- src/modules/sndio.cpp | 18 +- src/modules/sway/workspaces.cpp | 92 +++---- src/modules/upower.cpp | 84 +++--- src/modules/wlr/taskbar.cpp | 158 +++++------ src/util/audio_backend.cpp | 36 +-- src/util/backlight_backend.cpp | 60 ++-- src/util/icon_loader.cpp | 28 +- src/util/pipewire/pipewire_backend.cpp | 34 +-- src/util/pipewire/privacy_node_info.cpp | 14 +- src/util/prepare_for_sleep.cpp | 14 +- src/util/ustring_clen.cpp | 2 +- test/utils/date.cpp | 14 +- 75 files changed, 1294 insertions(+), 1297 deletions(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 0fad47c4..29b3e23b 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -11,12 +11,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - # TODO: bump to clang 19 release - # - uses: DoozyX/clang-format-lint-action@v0.18.2 - - uses: DoozyX/clang-format-lint-action@558090054b3f39e3d6af24f0cd73b319535da809 + - uses: RafikFarhad/clang-format-github-action@v6 name: clang-format with: - source: "." - extensions: "hpp,h,cpp,c" - style: "file:.clang-format" - clangFormatVersion: 19 + sources: "src/**/*.hpp,src/**/*.cpp" + style: "file" diff --git a/include/AAppIconLabel.hpp b/include/AAppIconLabel.hpp index d09ab14a..e8532e18 100644 --- a/include/AAppIconLabel.hpp +++ b/include/AAppIconLabel.hpp @@ -9,15 +9,15 @@ namespace waybar { class AAppIconLabel : public AIconLabel { public: - AAppIconLabel(const Json::Value &config, const std::string &name, const std::string &id, - const std::string &format, uint16_t interval = 0, bool ellipsize = false, + AAppIconLabel(const Json::Value& config, const std::string& name, const std::string& id, + const std::string& format, uint16_t interval = 0, bool ellipsize = false, bool enable_click = false, bool enable_scroll = false); virtual ~AAppIconLabel() = default; auto update() -> void override; protected: - void updateAppIconName(const std::string &app_identifier, - const std::string &alternative_app_identifier); + void updateAppIconName(const std::string& app_identifier, + const std::string& alternative_app_identifier); void updateAppIcon(); unsigned app_icon_size_{24}; bool update_app_icon_{true}; diff --git a/include/AIconLabel.hpp b/include/AIconLabel.hpp index 054d031a..f0fb8614 100644 --- a/include/AIconLabel.hpp +++ b/include/AIconLabel.hpp @@ -9,8 +9,8 @@ namespace waybar { class AIconLabel : public ALabel { public: - AIconLabel(const Json::Value &config, const std::string &name, const std::string &id, - const std::string &format, uint16_t interval = 0, bool ellipsize = false, + AIconLabel(const Json::Value& config, const std::string& name, const std::string& id, + const std::string& format, uint16_t interval = 0, bool ellipsize = false, bool enable_click = false, bool enable_scroll = false); virtual ~AIconLabel() = default; auto update() -> void override; diff --git a/include/ALabel.hpp b/include/ALabel.hpp index 92fc2e0f..b3852877 100644 --- a/include/ALabel.hpp +++ b/include/ALabel.hpp @@ -10,13 +10,13 @@ namespace waybar { class ALabel : public AModule { public: - ALabel(const Json::Value &, const std::string &, const std::string &, const std::string &format, + ALabel(const Json::Value&, const std::string&, const std::string&, const std::string& format, uint16_t interval = 0, bool ellipsize = false, bool enable_click = false, bool enable_scroll = false); virtual ~ALabel() = default; auto update() -> void override; - virtual std::string getIcon(uint16_t, const std::string &alt = "", uint16_t max = 0); - virtual std::string getIcon(uint16_t, const std::vector &alts, uint16_t max = 0); + virtual std::string getIcon(uint16_t, const std::string& alt = "", uint16_t max = 0); + virtual std::string getIcon(uint16_t, const std::vector& alts, uint16_t max = 0); protected: Gtk::Label label_; @@ -25,12 +25,12 @@ class ALabel : public AModule { bool alt_ = false; std::string default_format_; - bool handleToggle(GdkEventButton *const &e) override; + bool handleToggle(GdkEventButton* const& e) override; virtual std::string getState(uint8_t value, bool lesser = false); - std::map submenus_; + std::map submenus_; std::map menuActionsMap_; - static void handleGtkMenuEvent(GtkMenuItem *menuitem, gpointer data); + static void handleGtkMenuEvent(GtkMenuItem* menuitem, gpointer data); }; } // namespace waybar diff --git a/include/AModule.hpp b/include/AModule.hpp index 2fcbfc23..6b29045c 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -12,13 +12,13 @@ namespace waybar { class AModule : public IModule { public: - static constexpr const char *MODULE_CLASS = "module"; + static constexpr const char* MODULE_CLASS = "module"; ~AModule() override; auto update() -> void override; virtual auto refresh(int shouldRefresh) -> void {}; - operator Gtk::Widget &() override; - auto doAction(const std::string &name) -> void override; + operator Gtk::Widget&() override; + auto doAction(const std::string& name) -> void override; /// Emitting on this dispatcher triggers a update() call Glib::Dispatcher dp; @@ -28,30 +28,30 @@ class AModule : public IModule { protected: // Don't need to make an object directly // Derived classes are able to use it - AModule(const Json::Value &, const std::string &, const std::string &, bool enable_click = false, + AModule(const Json::Value&, const std::string&, const std::string&, bool enable_click = false, bool enable_scroll = false); enum SCROLL_DIR { NONE, UP, DOWN, LEFT, RIGHT }; - SCROLL_DIR getScrollDir(GdkEventScroll *e); + SCROLL_DIR getScrollDir(GdkEventScroll* e); bool tooltipEnabled() const; std::vector pid_children_; const std::string name_; - const Json::Value &config_; + const Json::Value& config_; Gtk::EventBox event_box_; - virtual void setCursor(Gdk::CursorType const &c); + virtual void setCursor(Gdk::CursorType const& c); - virtual bool handleToggle(GdkEventButton *const &ev); - virtual bool handleMouseEnter(GdkEventCrossing *const &ev); - virtual bool handleMouseLeave(GdkEventCrossing *const &ev); - virtual bool handleScroll(GdkEventScroll *); - virtual bool handleRelease(GdkEventButton *const &ev); - GObject *menu_; + virtual bool handleToggle(GdkEventButton* const& ev); + virtual bool handleMouseEnter(GdkEventCrossing* const& ev); + virtual bool handleMouseLeave(GdkEventCrossing* const& ev); + virtual bool handleScroll(GdkEventScroll*); + virtual bool handleRelease(GdkEventButton* const& ev); + GObject* menu_; private: - bool handleUserEvent(GdkEventButton *const &ev); + bool handleUserEvent(GdkEventButton* const& ev); const bool isTooltip; const bool isExpand; bool hasUserEvents_; diff --git a/include/bar.hpp b/include/bar.hpp index 9953b1f5..54b332bf 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -62,11 +62,11 @@ class Bar : public sigc::trackable { static const std::string MODE_DEFAULT; static const std::string MODE_INVISIBLE; - Bar(struct waybar_output *w_output, const Json::Value &); - Bar(const Bar &) = delete; + Bar(struct waybar_output* w_output, const Json::Value&); + Bar(const Bar&) = delete; ~Bar(); - void setMode(const std::string &mode); + void setMode(const std::string& mode); void setVisible(bool value); void toggle(); void show(); @@ -75,9 +75,9 @@ class Bar : public sigc::trackable { util::KillSignalAction getOnSigusr1Action(); util::KillSignalAction getOnSigusr2Action(); - struct waybar_output *output; + struct waybar_output* output; Json::Value config; - struct wl_surface *surface; + struct wl_surface* surface; bool visible = true; Gtk::Window window; Gtk::Orientation orientation = Gtk::ORIENTATION_HORIZONTAL; @@ -91,15 +91,15 @@ class Bar : public sigc::trackable { #endif private: - void onMap(GdkEventAny *); + void onMap(GdkEventAny*); auto setupWidgets() -> void; - void getModules(const Factory &, const std::string &, waybar::Group *); - void setupAltFormatKeyForModule(const std::string &module_name); - void setupAltFormatKeyForModuleList(const char *module_list_name); - void setMode(const bar_mode &); + void getModules(const Factory&, const std::string&, waybar::Group*); + void setupAltFormatKeyForModule(const std::string& module_name); + void setupAltFormatKeyForModuleList(const char* module_list_name); + void setMode(const bar_mode&); void setPassThrough(bool passthrough); void setPosition(Gtk::PositionType position); - void onConfigure(GdkEventConfigure *ev); + void onConfigure(GdkEventConfigure* ev); void configureGlobalOffset(int width, int height); void onOutputGeometryChanged(); diff --git a/include/client.hpp b/include/client.hpp index 9dd09355..c3df3f21 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -17,35 +17,35 @@ namespace waybar { class Client { public: - static Client *inst(); - int main(int argc, char *argv[]); + static Client* inst(); + int main(int argc, char* argv[]); void reset(); Glib::RefPtr gtk_app; Glib::RefPtr gdk_display; - struct wl_display *wl_display = nullptr; - struct wl_registry *registry = nullptr; - struct zxdg_output_manager_v1 *xdg_output_manager = nullptr; - struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; + struct wl_display* wl_display = nullptr; + struct wl_registry* registry = nullptr; + struct zxdg_output_manager_v1* xdg_output_manager = nullptr; + struct zwp_idle_inhibit_manager_v1* idle_inhibit_manager = nullptr; std::vector> bars; Config config; std::string bar_id; private: Client() = default; - const std::string getStyle(const std::string &style, std::optional appearance); + const std::string getStyle(const std::string& style, std::optional appearance); void bindInterfaces(); - void handleOutput(struct waybar_output &output); - auto setupCss(const std::string &css_file) -> void; - struct waybar_output &getOutput(void *); - std::vector getOutputConfigs(struct waybar_output &output); + void handleOutput(struct waybar_output& output); + auto setupCss(const std::string& css_file) -> void; + struct waybar_output& getOutput(void*); + std::vector getOutputConfigs(struct waybar_output& output); - static void handleGlobal(void *data, struct wl_registry *registry, uint32_t name, - const char *interface, uint32_t version); - static void handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name); - static void handleOutputDone(void *, struct zxdg_output_v1 *); - static void handleOutputName(void *, struct zxdg_output_v1 *, const char *); - static void handleOutputDescription(void *, struct zxdg_output_v1 *, const char *); + static void handleGlobal(void* data, struct wl_registry* registry, uint32_t name, + const char* interface, uint32_t version); + static void handleGlobalRemove(void* data, struct wl_registry* registry, uint32_t name); + static void handleOutputDone(void*, struct zxdg_output_v1*); + static void handleOutputName(void*, struct zxdg_output_v1*, const char*); + static void handleOutputDescription(void*, struct zxdg_output_v1*, const char*); void handleMonitorAdded(Glib::RefPtr monitor); void handleMonitorRemoved(Glib::RefPtr monitor); void handleDeferredMonitorRemoval(Glib::RefPtr monitor); diff --git a/include/config.hpp b/include/config.hpp index bf653c2a..1ef9ef5f 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -14,29 +14,29 @@ namespace waybar { class Config { public: static const std::vector CONFIG_DIRS; - static const char *CONFIG_PATH_ENV; + static const char* CONFIG_PATH_ENV; /* Try to find any of provided names in the supported set of config directories */ static std::optional findConfigPath( - const std::vector &names, const std::vector &dirs = CONFIG_DIRS); + const std::vector& names, const std::vector& dirs = CONFIG_DIRS); - static std::vector tryExpandPath(const std::string &base, - const std::string &filename); + static std::vector tryExpandPath(const std::string& base, + const std::string& filename); Config() = default; - void load(const std::string &config); + void load(const std::string& config); - Json::Value &getConfig() { return config_; } + Json::Value& getConfig() { return config_; } - std::vector getOutputConfigs(const std::string &name, const std::string &identifier); + std::vector getOutputConfigs(const std::string& name, const std::string& identifier); private: - 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_); + 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::vector findIncludePath( - const std::string &name, const std::vector &dirs = CONFIG_DIRS); + const std::string& name, const std::vector& dirs = CONFIG_DIRS); std::string config_file_; diff --git a/include/group.hpp b/include/group.hpp index 5ce331a8..41cb5ee0 100644 --- a/include/group.hpp +++ b/include/group.hpp @@ -11,13 +11,13 @@ namespace waybar { class Group : public AModule { public: - Group(const std::string &, const std::string &, const Json::Value &, bool); + Group(const std::string&, const std::string&, const Json::Value&, bool); ~Group() override = default; auto update() -> void override; - operator Gtk::Widget &() override; + operator Gtk::Widget&() override; - virtual Gtk::Box &getBox(); - void addWidget(Gtk::Widget &widget); + virtual Gtk::Box& getBox(); + void addWidget(Gtk::Widget& widget); protected: Gtk::Box box; @@ -27,9 +27,9 @@ class Group : public AModule { bool is_drawer = false; bool click_to_reveal = false; std::string add_class_to_drawer_children; - bool handleMouseEnter(GdkEventCrossing *const &ev) override; - bool handleMouseLeave(GdkEventCrossing *const &ev) override; - bool handleToggle(GdkEventButton *const &ev) override; + bool handleMouseEnter(GdkEventCrossing* const& ev) override; + bool handleMouseLeave(GdkEventCrossing* const& ev) override; + bool handleToggle(GdkEventButton* const& ev) override; void show_group(); void hide_group(); }; diff --git a/include/modules/backlight.hpp b/include/modules/backlight.hpp index 110cd434..46b38972 100644 --- a/include/modules/backlight.hpp +++ b/include/modules/backlight.hpp @@ -17,11 +17,11 @@ namespace waybar::modules { class Backlight : public ALabel { public: - Backlight(const std::string &, const Json::Value &); + Backlight(const std::string&, const Json::Value&); virtual ~Backlight() = default; auto update() -> void override; - bool handleScroll(GdkEventScroll *e) override; + bool handleScroll(GdkEventScroll* e) override; const std::string preferred_device_; diff --git a/include/modules/dwl/tags.hpp b/include/modules/dwl/tags.hpp index 53dff989..856a35dd 100644 --- a/include/modules/dwl/tags.hpp +++ b/include/modules/dwl/tags.hpp @@ -12,23 +12,23 @@ namespace waybar::modules::dwl { class Tags : public waybar::AModule { public: - Tags(const std::string &, const waybar::Bar &, const Json::Value &); + Tags(const std::string&, const waybar::Bar&, const Json::Value&); virtual ~Tags(); // Handlers for wayland events void handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint32_t focused); void handle_primary_clicked(uint32_t tag); - bool handle_button_press(GdkEventButton *event_button, uint32_t tag); + bool handle_button_press(GdkEventButton* event_button, uint32_t tag); - struct zdwl_ipc_manager_v2 *status_manager_; - struct wl_seat *seat_; + struct zdwl_ipc_manager_v2* status_manager_; + struct wl_seat* seat_; private: - const waybar::Bar &bar_; + const waybar::Bar& bar_; Gtk::Box box_; std::vector buttons_; - struct zdwl_ipc_output_v2 *output_status_; + struct zdwl_ipc_output_v2* output_status_; }; } /* namespace waybar::modules::dwl */ diff --git a/include/modules/dwl/window.hpp b/include/modules/dwl/window.hpp index 43586399..5efd0abb 100644 --- a/include/modules/dwl/window.hpp +++ b/include/modules/dwl/window.hpp @@ -13,26 +13,26 @@ namespace waybar::modules::dwl { class Window : public AAppIconLabel, public sigc::trackable { public: - Window(const std::string &, const waybar::Bar &, const Json::Value &); + Window(const std::string&, const waybar::Bar&, const Json::Value&); ~Window(); void handle_layout(const uint32_t layout); - void handle_title(const char *title); - void handle_appid(const char *ppid); - void handle_layout_symbol(const char *layout_symbol); + void handle_title(const char* title); + void handle_appid(const char* ppid); + void handle_layout_symbol(const char* layout_symbol); void handle_frame(); - struct zdwl_ipc_manager_v2 *status_manager_; + struct zdwl_ipc_manager_v2* status_manager_; private: - const Bar &bar_; + const Bar& bar_; std::string title_; std::string appid_; std::string layout_symbol_; uint32_t layout_; - struct zdwl_ipc_output_v2 *output_status_; + struct zdwl_ipc_output_v2* output_status_; }; } // namespace waybar::modules::dwl diff --git a/include/modules/ext/workspace_manager.hpp b/include/modules/ext/workspace_manager.hpp index 686c7fc7..f21ff26e 100644 --- a/include/modules/ext/workspace_manager.hpp +++ b/include/modules/ext/workspace_manager.hpp @@ -20,16 +20,16 @@ class Workspace; class WorkspaceManager final : public AModule { public: - WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config); + WorkspaceManager(const std::string& id, const waybar::Bar& bar, const Json::Value& config); ~WorkspaceManager() override; - void register_manager(wl_registry *registry, uint32_t name, uint32_t version); + void register_manager(wl_registry* registry, uint32_t name, uint32_t version); void remove_workspace_group(uint32_t id); void remove_workspace(uint32_t id); void set_needs_sorting() { needs_sorting_ = true; } // wl events - void handle_workspace_group(ext_workspace_group_handle_v1 *handle); - void handle_workspace(ext_workspace_handle_v1 *handle); + void handle_workspace_group(ext_workspace_group_handle_v1* handle); + void handle_workspace(ext_workspace_handle_v1* handle); void handle_done(); void handle_finished(); @@ -38,7 +38,7 @@ class WorkspaceManager final : public AModule { private: void update() override; - bool has_button(const Gtk::Button *button); + bool has_button(const Gtk::Button* button); void sort_workspaces(); void clear_buttons(); void update_buttons(); @@ -52,10 +52,10 @@ class WorkspaceManager final : public AModule { bool sort_by_coordinates_ = false; bool all_outputs_ = false; - const waybar::Bar &bar_; + const waybar::Bar& bar_; Gtk::Box box_; - ext_workspace_manager_v1 *ext_manager_ = nullptr; + ext_workspace_manager_v1* ext_manager_ = nullptr; std::vector> groups_; std::vector> workspaces_; @@ -64,60 +64,60 @@ class WorkspaceManager final : public AModule { class WorkspaceGroup { public: - WorkspaceGroup(WorkspaceManager &manager, ext_workspace_group_handle_v1 *handle, uint32_t id); + WorkspaceGroup(WorkspaceManager& manager, ext_workspace_group_handle_v1* handle, uint32_t id); ~WorkspaceGroup(); u_int32_t id() const { return id_; } - bool has_output(const wl_output *output); - bool has_workspace(const ext_workspace_handle_v1 *workspace); + bool has_output(const wl_output* output); + bool has_workspace(const ext_workspace_handle_v1* workspace); // wl events void handle_capabilities(uint32_t capabilities); - void handle_output_enter(wl_output *output); - void handle_output_leave(wl_output *output); - void handle_workspace_enter(ext_workspace_handle_v1 *handle); - void handle_workspace_leave(ext_workspace_handle_v1 *handle); + void handle_output_enter(wl_output* output); + void handle_output_leave(wl_output* output); + void handle_workspace_enter(ext_workspace_handle_v1* handle); + void handle_workspace_leave(ext_workspace_handle_v1* handle); void handle_removed(); private: - WorkspaceManager &workspaces_manager_; - ext_workspace_group_handle_v1 *ext_handle_; + WorkspaceManager& workspaces_manager_; + ext_workspace_group_handle_v1* ext_handle_; uint32_t id_; - std::vector outputs_; - std::vector workspaces_; + std::vector outputs_; + std::vector workspaces_; }; class Workspace { public: - Workspace(const Json::Value &config, WorkspaceManager &manager, ext_workspace_handle_v1 *handle, - uint32_t id, const std::string &name); + Workspace(const Json::Value& config, WorkspaceManager& manager, ext_workspace_handle_v1* handle, + uint32_t id, const std::string& name); ~Workspace(); - ext_workspace_handle_v1 *handle() const { return ext_handle_; } + ext_workspace_handle_v1* handle() const { return ext_handle_; } u_int32_t id() const { return id_; } - std::string &workspace_id() { return workspace_id_; } - std::string &name() { return name_; } - std::vector &coordinates() { return coordinates_; } - Gtk::Button &button() { return button_; } + std::string& workspace_id() { return workspace_id_; } + std::string& name() { return name_; } + std::vector& coordinates() { return coordinates_; } + Gtk::Button& button() { return button_; } void update(); // wl events - void handle_id(const std::string &id); - void handle_name(const std::string &name); - void handle_coordinates(const std::vector &coordinates); + void handle_id(const std::string& id); + void handle_name(const std::string& name); + void handle_coordinates(const std::vector& coordinates); void handle_state(uint32_t state); void handle_capabilities(uint32_t capabilities); void handle_removed(); // gdk events - bool handle_clicked(const GdkEventButton *button) const; + bool handle_clicked(const GdkEventButton* button) const; private: bool has_state(uint32_t state) const { return (state_ & state) == state; } std::string icon(); - WorkspaceManager &workspace_manager_; - ext_workspace_handle_v1 *ext_handle_ = nullptr; + WorkspaceManager& workspace_manager_; + ext_workspace_handle_v1* ext_handle_ = nullptr; uint32_t id_; uint32_t state_ = 0; std::string workspace_id_; diff --git a/include/modules/ext/workspace_manager_binding.hpp b/include/modules/ext/workspace_manager_binding.hpp index b41f207c..20232970 100644 --- a/include/modules/ext/workspace_manager_binding.hpp +++ b/include/modules/ext/workspace_manager_binding.hpp @@ -1,10 +1,10 @@ #include "ext-workspace-v1-client-protocol.h" namespace waybar::modules::ext { -void add_registry_listener(void *data); -void add_workspace_listener(ext_workspace_handle_v1 *workspace_handle, void *data); -void add_workspace_group_listener(ext_workspace_group_handle_v1 *workspace_group_handle, - void *data); -ext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name, - uint32_t version, void *data); +void add_registry_listener(void* data); +void add_workspace_listener(ext_workspace_handle_v1* workspace_handle, void* data); +void add_workspace_group_listener(ext_workspace_group_handle_v1* workspace_group_handle, + void* data); +ext_workspace_manager_v1* workspace_manager_bind(wl_registry* registry, uint32_t name, + uint32_t version, void* data); } // namespace waybar::modules::ext diff --git a/include/modules/gamemode.hpp b/include/modules/gamemode.hpp index 69c0c3ae..f63861a3 100644 --- a/include/modules/gamemode.hpp +++ b/include/modules/gamemode.hpp @@ -17,7 +17,7 @@ namespace waybar::modules { class Gamemode : public AModule { public: - Gamemode(const std::string &, const Json::Value &); + Gamemode(const std::string&, const Json::Value&); virtual ~Gamemode(); auto update() -> void override; @@ -28,18 +28,18 @@ class Gamemode : public AModule { const std::string DEFAULT_TOOLTIP_FORMAT = "Games running: {count}"; const std::string DEFAULT_GLYPH = ""; - void appear(const Glib::RefPtr &connection, const Glib::ustring &name, - const Glib::ustring &name_owner); - void disappear(const Glib::RefPtr &connection, const Glib::ustring &name); - void prepareForSleep_cb(const Glib::RefPtr &connection, - const Glib::ustring &sender_name, const Glib::ustring &object_path, - const Glib::ustring &interface_name, const Glib::ustring &signal_name, - const Glib::VariantContainerBase ¶meters); - void notify_cb(const Glib::ustring &sender_name, const Glib::ustring &signal_name, - const Glib::VariantContainerBase &arguments); + void appear(const Glib::RefPtr& connection, const Glib::ustring& name, + const Glib::ustring& name_owner); + void disappear(const Glib::RefPtr& connection, const Glib::ustring& name); + void prepareForSleep_cb(const Glib::RefPtr& connection, + const Glib::ustring& sender_name, const Glib::ustring& object_path, + const Glib::ustring& interface_name, const Glib::ustring& signal_name, + const Glib::VariantContainerBase& parameters); + void notify_cb(const Glib::ustring& sender_name, const Glib::ustring& signal_name, + const Glib::VariantContainerBase& arguments); void getData(); - bool handleToggle(GdkEventButton *const &) override; + bool handleToggle(GdkEventButton* const&) override; // Config std::string format = DEFAULT_FORMAT; diff --git a/include/modules/jack.hpp b/include/modules/jack.hpp index 452c43a3..5c30739e 100644 --- a/include/modules/jack.hpp +++ b/include/modules/jack.hpp @@ -13,7 +13,7 @@ namespace waybar::modules { class JACK : public ALabel { public: - JACK(const std::string &, const Json::Value &); + JACK(const std::string&, const Json::Value&); virtual ~JACK() = default; auto update() -> void override; @@ -25,7 +25,7 @@ class JACK : public ALabel { private: std::string JACKState(); - jack_client_t *client_; + jack_client_t* client_; jack_nframes_t bufsize_; jack_nframes_t samplerate_; unsigned int xruns_; @@ -38,7 +38,7 @@ class JACK : public ALabel { } // namespace waybar::modules -int bufSizeCallback(jack_nframes_t size, void *obj); -int sampleRateCallback(jack_nframes_t rate, void *obj); -int xrunCallback(void *obj); -void shutdownCallback(void *obj); +int bufSizeCallback(jack_nframes_t size, void* obj); +int sampleRateCallback(jack_nframes_t rate, void* obj); +int xrunCallback(void* obj); +void shutdownCallback(void* obj); diff --git a/include/modules/niri/language.hpp b/include/modules/niri/language.hpp index 77f0a624..cff2b352 100644 --- a/include/modules/niri/language.hpp +++ b/include/modules/niri/language.hpp @@ -10,13 +10,13 @@ namespace waybar::modules::niri { class Language : public ALabel, public EventHandler { public: - Language(const std::string &, const Bar &, const Json::Value &); + Language(const std::string&, const Bar&, const Json::Value&); ~Language() override; void update() override; private: void updateFromIPC(); - void onEvent(const Json::Value &ev) override; + void onEvent(const Json::Value& ev) override; void doUpdate(); struct Layout { @@ -26,10 +26,10 @@ class Language : public ALabel, public EventHandler { std::string short_description; }; - static Layout getLayout(const std::string &fullName); + static Layout getLayout(const std::string& fullName); std::mutex mutex_; - const Bar &bar_; + const Bar& bar_; std::vector layouts_; unsigned current_idx_; diff --git a/include/modules/niri/window.hpp b/include/modules/niri/window.hpp index 909ae6f0..6be4686e 100644 --- a/include/modules/niri/window.hpp +++ b/include/modules/niri/window.hpp @@ -11,16 +11,16 @@ namespace waybar::modules::niri { class Window : public AAppIconLabel, public EventHandler { public: - Window(const std::string &, const Bar &, const Json::Value &); + Window(const std::string&, const Bar&, const Json::Value&); ~Window() override; void update() override; private: - void onEvent(const Json::Value &ev) override; + void onEvent(const Json::Value& ev) override; void doUpdate(); - void setClass(const std::string &className, bool enable); + void setClass(const std::string& className, bool enable); - const Bar &bar_; + const Bar& bar_; std::string oldAppId_; }; diff --git a/include/modules/niri/workspaces.hpp b/include/modules/niri/workspaces.hpp index a6850ed1..08986412 100644 --- a/include/modules/niri/workspaces.hpp +++ b/include/modules/niri/workspaces.hpp @@ -11,17 +11,17 @@ namespace waybar::modules::niri { class Workspaces : public AModule, public EventHandler { public: - Workspaces(const std::string &, const Bar &, const Json::Value &); + Workspaces(const std::string&, const Bar&, const Json::Value&); ~Workspaces() override; void update() override; private: - void onEvent(const Json::Value &ev) override; + void onEvent(const Json::Value& ev) override; void doUpdate(); - Gtk::Button &addButton(const Json::Value &ws); - std::string getIcon(const std::string &value, const Json::Value &ws); + Gtk::Button& addButton(const Json::Value& ws); + std::string getIcon(const std::string& value, const Json::Value& ws); - const Bar &bar_; + const Bar& bar_; Gtk::Box box_; // Map from niri workspace id to button. std::unordered_map buttons_; diff --git a/include/modules/power_profiles_daemon.hpp b/include/modules/power_profiles_daemon.hpp index a2bd3858..b2436171 100644 --- a/include/modules/power_profiles_daemon.hpp +++ b/include/modules/power_profiles_daemon.hpp @@ -16,15 +16,15 @@ struct Profile { class PowerProfilesDaemon : public ALabel { public: - PowerProfilesDaemon(const std::string &, const Json::Value &); + PowerProfilesDaemon(const std::string&, const Json::Value&); auto update() -> void override; - void profileChangedCb(const Gio::DBus::Proxy::MapChangedProperties &, - const std::vector &); - void busConnectedCb(Glib::RefPtr &r); - void getAllPropsCb(Glib::RefPtr &r); - void setPropCb(Glib::RefPtr &r); + void profileChangedCb(const Gio::DBus::Proxy::MapChangedProperties&, + const std::vector&); + void busConnectedCb(Glib::RefPtr& r); + void getAllPropsCb(Glib::RefPtr& r); + void setPropCb(Glib::RefPtr& r); void populateInitState(); - bool handleToggle(GdkEventButton *const &e) override; + bool handleToggle(GdkEventButton* const& e) override; private: // True if we're connected to the dbug interface. False if we're @@ -32,7 +32,7 @@ class PowerProfilesDaemon : public ALabel { bool connected_; // Look for a profile name in the list of available profiles and // switch activeProfile_ to it. - void switchToProfile(std::string const &); + void switchToProfile(std::string const&); // Used to toggle/display the profiles std::vector availableProfiles_; // Points to the active profile in the profiles list diff --git a/include/modules/privacy/privacy.hpp b/include/modules/privacy/privacy.hpp index cb6a34da..68d2bf19 100644 --- a/include/modules/privacy/privacy.hpp +++ b/include/modules/privacy/privacy.hpp @@ -13,15 +13,15 @@ namespace waybar::modules::privacy { class Privacy : public AModule { public: - Privacy(const std::string &, const Json::Value &, Gtk::Orientation, const std::string &pos); + Privacy(const std::string&, const Json::Value&, Gtk::Orientation, const std::string& pos); auto update() -> void override; void onPrivacyNodesChanged(); private: - std::list nodes_screenshare; // Screen is being shared - std::list nodes_audio_in; // Application is using the microphone - std::list nodes_audio_out; // Application is outputting audio + std::list nodes_screenshare; // Screen is being shared + std::list nodes_audio_in; // Application is using the microphone + std::list nodes_audio_out; // Application is outputting audio std::mutex mutex_; sigc::connection visibility_conn; diff --git a/include/modules/privacy/privacy_item.hpp b/include/modules/privacy/privacy_item.hpp index f5f572c0..31e200e3 100644 --- a/include/modules/privacy/privacy_item.hpp +++ b/include/modules/privacy/privacy_item.hpp @@ -16,16 +16,16 @@ namespace waybar::modules::privacy { class PrivacyItem : public Gtk::Revealer { public: - PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_, - std::list *nodes, Gtk::Orientation orientation, - const std::string &pos, const uint icon_size, const uint transition_duration); + PrivacyItem(const Json::Value& config_, enum PrivacyNodeType privacy_type_, + std::list* nodes, Gtk::Orientation orientation, + const std::string& pos, const uint icon_size, const uint transition_duration); enum PrivacyNodeType privacy_type; void set_in_use(bool in_use); private: - std::list *nodes; + std::list* nodes; sigc::connection signal_conn; diff --git a/include/modules/river/layout.hpp b/include/modules/river/layout.hpp index 89f0c7d9..3486472e 100644 --- a/include/modules/river/layout.hpp +++ b/include/modules/river/layout.hpp @@ -10,25 +10,25 @@ namespace waybar::modules::river { class Layout : public waybar::ALabel { public: - Layout(const std::string &, const waybar::Bar &, const Json::Value &); + Layout(const std::string&, const waybar::Bar&, const Json::Value&); virtual ~Layout(); // Handlers for wayland events - void handle_name(const char *name); + void handle_name(const char* name); void handle_clear(); - void handle_focused_output(struct wl_output *output); - void handle_unfocused_output(struct wl_output *output); + void handle_focused_output(struct wl_output* output); + void handle_unfocused_output(struct wl_output* output); - struct zriver_status_manager_v1 *status_manager_; - struct wl_seat *seat_; + struct zriver_status_manager_v1* status_manager_; + struct wl_seat* seat_; private: - const waybar::Bar &bar_; + const waybar::Bar& bar_; std::string name_; - struct wl_output *output_; // stores the output this module belongs to - struct wl_output *focused_output_; // stores the currently focused output - struct zriver_output_status_v1 *output_status_; - struct zriver_seat_status_v1 *seat_status_; + struct wl_output* output_; // stores the output this module belongs to + struct wl_output* focused_output_; // stores the currently focused output + struct zriver_output_status_v1* output_status_; + struct zriver_seat_status_v1* seat_status_; }; } /* namespace waybar::modules::river */ diff --git a/include/modules/river/mode.hpp b/include/modules/river/mode.hpp index 246cecae..91585309 100644 --- a/include/modules/river/mode.hpp +++ b/include/modules/river/mode.hpp @@ -10,19 +10,19 @@ namespace waybar::modules::river { class Mode : public waybar::ALabel { public: - Mode(const std::string &, const waybar::Bar &, const Json::Value &); + Mode(const std::string&, const waybar::Bar&, const Json::Value&); virtual ~Mode(); // Handlers for wayland events - void handle_mode(const char *mode); + void handle_mode(const char* mode); - struct zriver_status_manager_v1 *status_manager_; - struct wl_seat *seat_; + struct zriver_status_manager_v1* status_manager_; + struct wl_seat* seat_; private: - const waybar::Bar &bar_; + const waybar::Bar& bar_; std::string mode_; - struct zriver_seat_status_v1 *seat_status_; + struct zriver_seat_status_v1* seat_status_; }; } /* namespace waybar::modules::river */ diff --git a/include/modules/river/tags.hpp b/include/modules/river/tags.hpp index fd867346..386ddecf 100644 --- a/include/modules/river/tags.hpp +++ b/include/modules/river/tags.hpp @@ -13,27 +13,27 @@ namespace waybar::modules::river { class Tags : public waybar::AModule { public: - Tags(const std::string &, const waybar::Bar &, const Json::Value &); + Tags(const std::string&, const waybar::Bar&, const Json::Value&); virtual ~Tags(); // Handlers for wayland events void handle_focused_tags(uint32_t tags); - void handle_view_tags(struct wl_array *tags); + 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); + bool handle_button_press(GdkEventButton* event_button, uint32_t tag); - struct zriver_status_manager_v1 *status_manager_; - struct zriver_control_v1 *control_; - struct wl_seat *seat_; + struct zriver_status_manager_v1* status_manager_; + struct zriver_control_v1* control_; + struct wl_seat* seat_; private: - const waybar::Bar &bar_; + const waybar::Bar& bar_; Gtk::Box box_; std::vector buttons_; - struct zriver_output_status_v1 *output_status_; + struct zriver_output_status_v1* output_status_; }; } /* namespace waybar::modules::river */ diff --git a/include/modules/river/window.hpp b/include/modules/river/window.hpp index bf29ebbf..0d759bb2 100644 --- a/include/modules/river/window.hpp +++ b/include/modules/river/window.hpp @@ -12,22 +12,22 @@ namespace waybar::modules::river { class Window : public waybar::ALabel { public: - Window(const std::string &, const waybar::Bar &, const Json::Value &); + Window(const std::string&, const waybar::Bar&, const Json::Value&); virtual ~Window(); // Handlers for wayland events - void handle_focused_view(const char *title); - void handle_focused_output(struct wl_output *output); - void handle_unfocused_output(struct wl_output *output); + void handle_focused_view(const char* title); + void handle_focused_output(struct wl_output* output); + void handle_unfocused_output(struct wl_output* output); - struct zriver_status_manager_v1 *status_manager_; - struct wl_seat *seat_; + struct zriver_status_manager_v1* status_manager_; + struct wl_seat* seat_; private: - const waybar::Bar &bar_; - struct wl_output *output_; // stores the output this module belongs to - struct wl_output *focused_output_; // stores the currently focused output - struct zriver_seat_status_v1 *seat_status_; + const waybar::Bar& bar_; + struct wl_output* output_; // stores the output this module belongs to + struct wl_output* focused_output_; // stores the currently focused output + struct zriver_seat_status_v1* seat_status_; }; } /* namespace waybar::modules::river */ diff --git a/include/modules/sndio.hpp b/include/modules/sndio.hpp index 3fe36fad..43c84f9e 100644 --- a/include/modules/sndio.hpp +++ b/include/modules/sndio.hpp @@ -11,18 +11,18 @@ namespace waybar::modules { class Sndio : public ALabel { public: - Sndio(const std::string &, const Json::Value &); + Sndio(const std::string&, const Json::Value&); virtual ~Sndio(); auto update() -> void override; - auto set_desc(struct sioctl_desc *, unsigned int) -> void; + auto set_desc(struct sioctl_desc*, unsigned int) -> void; auto put_val(unsigned int, unsigned int) -> void; - bool handleScroll(GdkEventScroll *) override; - bool handleToggle(GdkEventButton *const &) override; + bool handleScroll(GdkEventScroll*) override; + bool handleToggle(GdkEventButton* const&) override; private: auto connect_to_sndio() -> void; util::SleeperThread thread_; - struct sioctl_hdl *hdl_; + struct sioctl_hdl* hdl_; std::vector pfds_; unsigned int addr_; unsigned int volume_, old_volume_, maxval_; diff --git a/include/modules/sni/watcher.hpp b/include/modules/sni/watcher.hpp index 5cb0f19e..49d2dae5 100644 --- a/include/modules/sni/watcher.hpp +++ b/include/modules/sni/watcher.hpp @@ -30,27 +30,27 @@ class Watcher { typedef struct { GfWatchType type; - Watcher *watcher; - gchar *service; - gchar *bus_name; - gchar *object_path; + Watcher* watcher; + gchar* service; + gchar* bus_name; + gchar* object_path; guint watch_id; } GfWatch; - void busAcquired(const Glib::RefPtr &, Glib::ustring); - static gboolean handleRegisterHost(Watcher *, GDBusMethodInvocation *, const gchar *); - static gboolean handleRegisterItem(Watcher *, GDBusMethodInvocation *, const gchar *); - static GfWatch *gfWatchFind(GSList *list, const gchar *bus_name, const gchar *object_path); - static GfWatch *gfWatchNew(GfWatchType, const gchar *, const gchar *, const gchar *, Watcher *); - static void nameVanished(GDBusConnection *connection, const char *name, gpointer data); + void busAcquired(const Glib::RefPtr&, Glib::ustring); + static gboolean handleRegisterHost(Watcher*, GDBusMethodInvocation*, const gchar*); + static gboolean handleRegisterItem(Watcher*, GDBusMethodInvocation*, const gchar*); + static GfWatch* gfWatchFind(GSList* list, const gchar* bus_name, const gchar* object_path); + static GfWatch* gfWatchNew(GfWatchType, const gchar*, const gchar*, const gchar*, Watcher*); + static void nameVanished(GDBusConnection* connection, const char* name, gpointer data); static void gfWatchFree(gpointer data); - void updateRegisteredItems(SnWatcher *obj); + void updateRegisteredItems(SnWatcher* obj); uint32_t bus_name_id_; - GSList *hosts_ = nullptr; - GSList *items_ = nullptr; - SnWatcher *watcher_ = nullptr; + GSList* hosts_ = nullptr; + GSList* items_ = nullptr; + SnWatcher* watcher_ = nullptr; }; } // namespace waybar::modules::SNI diff --git a/include/modules/sway/ipc/client.hpp b/include/modules/sway/ipc/client.hpp index a9a3e4e9..9033a688 100644 --- a/include/modules/sway/ipc/client.hpp +++ b/include/modules/sway/ipc/client.hpp @@ -27,21 +27,21 @@ class Ipc { std::string payload; }; - sigc::signal signal_event; - sigc::signal signal_cmd; + sigc::signal signal_event; + sigc::signal signal_cmd; - void sendCmd(uint32_t type, const std::string &payload = ""); - void subscribe(const std::string &payload); + void sendCmd(uint32_t type, const std::string& payload = ""); + void subscribe(const std::string& payload); void handleEvent(); - void setWorker(std::function &&func); + void setWorker(std::function&& func); protected: static inline const std::string ipc_magic_ = "i3-ipc"; static inline const size_t ipc_header_size_ = ipc_magic_.size() + 8; const std::string getSocketPath() const; - int open(const std::string &) const; - struct ipc_response send(int fd, uint32_t type, const std::string &payload = ""); + int open(const std::string&) const; + struct ipc_response send(int fd, uint32_t type, const std::string& payload = ""); struct ipc_response recv(int fd); int fd_; diff --git a/include/modules/systemd_failed_units.hpp b/include/modules/systemd_failed_units.hpp index 48b0074e..ffb25082 100644 --- a/include/modules/systemd_failed_units.hpp +++ b/include/modules/systemd_failed_units.hpp @@ -10,7 +10,7 @@ namespace waybar::modules { class SystemdFailedUnits : public ALabel { public: - SystemdFailedUnits(const std::string &, const Json::Value &); + SystemdFailedUnits(const std::string&, const Json::Value&); virtual ~SystemdFailedUnits(); auto update() -> void override; @@ -24,8 +24,8 @@ class SystemdFailedUnits : public ALabel { std::string last_status; Glib::RefPtr system_proxy, user_proxy; - void notify_cb(const Glib::ustring &sender_name, const Glib::ustring &signal_name, - const Glib::VariantContainerBase &arguments); + void notify_cb(const Glib::ustring& sender_name, const Glib::ustring& signal_name, + const Glib::VariantContainerBase& arguments); void RequestFailedUnits(); void RequestSystemState(); void updateData(); diff --git a/include/modules/upower.hpp b/include/modules/upower.hpp index 60a276db..0d445047 100644 --- a/include/modules/upower.hpp +++ b/include/modules/upower.hpp @@ -12,7 +12,7 @@ namespace waybar::modules { class UPower final : public AIconLabel { public: - UPower(const std::string &, const Json::Value &); + UPower(const std::string&, const Json::Value&); virtual ~UPower(); auto update() -> void override; @@ -30,17 +30,17 @@ class UPower final : public AIconLabel { // UPower device info struct upDevice_output { - UpDevice *upDevice{NULL}; + UpDevice* upDevice{NULL}; double percentage{0.0}; double temperature{0.0}; guint64 time_full{0u}; guint64 time_empty{0u}; - gchar *icon_name{(char *)'\0'}; + gchar* icon_name{(char*)'\0'}; bool upDeviceValid{false}; UpDeviceState state; UpDeviceKind kind; - char *nativePath{(char *)'\0'}; - char *model{(char *)'\0'}; + char* nativePath{(char*)'\0'}; + char* model{(char*)'\0'}; }; // Technical variables @@ -53,13 +53,13 @@ class UPower final : public AIconLabel { bool sleeping_; // Technical functions - void addDevice(UpDevice *); - void removeDevice(const gchar *); + void addDevice(UpDevice*); + void removeDevice(const gchar*); void removeDevices(); void resetDevices(); void setDisplayDevice(); - const Glib::ustring getText(const upDevice_output &upDevice_, const std::string &format); - bool queryTooltipCb(int, int, bool, const Glib::RefPtr &); + const Glib::ustring getText(const upDevice_output& upDevice_, const std::string& format); + bool queryTooltipCb(int, int, bool, const Glib::RefPtr&); // DBUS variables guint watcherID_; @@ -67,28 +67,28 @@ class UPower final : public AIconLabel { guint subscrID_{0u}; // UPower variables - UpClient *upClient_; + UpClient* upClient_; upDevice_output upDevice_; // Device to display typedef std::unordered_map Devices; Devices devices_; bool upRunning_{true}; // DBus callbacks - void getConn_cb(Glib::RefPtr &result); - void onAppear(const Glib::RefPtr &, const Glib::ustring &, - const Glib::ustring &); - void onVanished(const Glib::RefPtr &, const Glib::ustring &); - void prepareForSleep_cb(const Glib::RefPtr &connection, - const Glib::ustring &sender_name, const Glib::ustring &object_path, - const Glib::ustring &interface_name, const Glib::ustring &signal_name, - const Glib::VariantContainerBase ¶meters); + void getConn_cb(Glib::RefPtr& result); + void onAppear(const Glib::RefPtr&, const Glib::ustring&, + const Glib::ustring&); + void onVanished(const Glib::RefPtr&, const Glib::ustring&); + void prepareForSleep_cb(const Glib::RefPtr& connection, + const Glib::ustring& sender_name, const Glib::ustring& object_path, + const Glib::ustring& interface_name, const Glib::ustring& signal_name, + const Glib::VariantContainerBase& parameters); // UPower callbacks - static void deviceAdded_cb(UpClient *client, UpDevice *device, gpointer data); - static void deviceRemoved_cb(UpClient *client, const gchar *objectPath, gpointer data); - static void deviceNotify_cb(UpDevice *device, GParamSpec *pspec, gpointer user_data); + static void deviceAdded_cb(UpClient* client, UpDevice* device, gpointer data); + static void deviceRemoved_cb(UpClient* client, const gchar* objectPath, gpointer data); + static void deviceNotify_cb(UpDevice* device, GParamSpec* pspec, gpointer user_data); // UPower secondary functions - void getUpDeviceInfo(upDevice_output &upDevice_); + void getUpDeviceInfo(upDevice_output& upDevice_); }; } // namespace waybar::modules diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp index 8dc4dadd..413eae85 100644 --- a/include/modules/wlr/taskbar.hpp +++ b/include/modules/wlr/taskbar.hpp @@ -33,8 +33,8 @@ class Taskbar; class Task { public: - Task(const waybar::Bar &, const Json::Value &, Taskbar *, - struct zwlr_foreign_toplevel_handle_v1 *, struct wl_seat *); + Task(const waybar::Bar&, const Json::Value&, Taskbar*, struct zwlr_foreign_toplevel_handle_v1*, + struct wl_seat*); ~Task(); public: @@ -53,11 +53,11 @@ class Task { static uint32_t global_id; private: - const waybar::Bar &bar_; - const Json::Value &config_; - Taskbar *tbar_; - struct zwlr_foreign_toplevel_handle_v1 *handle_; - struct wl_seat *seat_; + const waybar::Bar& bar_; + const Json::Value& config_; + Taskbar* tbar_; + struct zwlr_foreign_toplevel_handle_v1* handle_; + struct wl_seat* seat_; uint32_t id_; @@ -89,7 +89,7 @@ class Task { std::string repr() const; std::string state_string(bool = false) const; void set_minimize_hint(); - void on_button_size_allocated(Gtk::Allocation &alloc); + void on_button_size_allocated(Gtk::Allocation& alloc); void hide_if_ignored(); public: @@ -105,26 +105,26 @@ class Task { public: /* Callbacks for the wlr protocol */ - void handle_title(const char *); - void handle_app_id(const char *); - void handle_output_enter(struct wl_output *); - void handle_output_leave(struct wl_output *); - void handle_state(struct wl_array *); + void handle_title(const char*); + void handle_app_id(const char*); + void handle_output_enter(struct wl_output*); + void handle_output_leave(struct wl_output*); + void handle_state(struct wl_array*); void handle_done(); void handle_closed(); /* Callbacks for Gtk events */ - bool handle_clicked(GdkEventButton *); - bool handle_button_release(GdkEventButton *); - bool handle_motion_notify(GdkEventMotion *); - void handle_drag_data_get(const Glib::RefPtr &context, - Gtk::SelectionData &selection_data, guint info, guint time); - void handle_drag_data_received(const Glib::RefPtr &context, int x, int y, + bool handle_clicked(GdkEventButton*); + bool handle_button_release(GdkEventButton*); + bool handle_motion_notify(GdkEventMotion*); + void handle_drag_data_get(const Glib::RefPtr& context, + Gtk::SelectionData& selection_data, guint info, guint time); + void handle_drag_data_received(const Glib::RefPtr& context, int x, int y, Gtk::SelectionData selection_data, guint info, guint time); public: - bool operator==(const Task &) const; - bool operator!=(const Task &) const; + bool operator==(const Task&) const; + bool operator!=(const Task&) const; public: void update(); @@ -142,12 +142,12 @@ using TaskPtr = std::unique_ptr; class Taskbar : public waybar::AModule { public: - Taskbar(const std::string &, const waybar::Bar &, const Json::Value &); + Taskbar(const std::string&, const waybar::Bar&, const Json::Value&); ~Taskbar(); void update(); private: - const waybar::Bar &bar_; + const waybar::Bar& bar_; Gtk::Box box_; std::vector tasks_; @@ -155,30 +155,30 @@ class Taskbar : public waybar::AModule { std::unordered_set ignore_list_; std::map app_ids_replace_map_; - struct zwlr_foreign_toplevel_manager_v1 *manager_; - struct wl_seat *seat_; + struct zwlr_foreign_toplevel_manager_v1* manager_; + struct wl_seat* seat_; public: /* Callbacks for global registration */ - void register_manager(struct wl_registry *, uint32_t name, uint32_t version); - void register_seat(struct wl_registry *, uint32_t name, uint32_t version); + void register_manager(struct wl_registry*, uint32_t name, uint32_t version); + void register_seat(struct wl_registry*, uint32_t name, uint32_t version); /* Callbacks for the wlr protocol */ - void handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1 *); + void handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1*); void handle_finished(); public: - void add_button(Gtk::Button &); - void move_button(Gtk::Button &, int); - void remove_button(Gtk::Button &); + void add_button(Gtk::Button&); + void move_button(Gtk::Button&, int); + void remove_button(Gtk::Button&); void remove_task(uint32_t); - bool show_output(struct wl_output *) const; + bool show_output(struct wl_output*) const; bool all_outputs() const; - const IconLoader &icon_loader() const; - const std::unordered_set &ignore_list() const; - const std::map &app_ids_replace_map() const; + const IconLoader& icon_loader() const; + const std::unordered_set& ignore_list() const; + const std::map& app_ids_replace_map() const; }; } /* namespace waybar::modules::wlr */ diff --git a/include/util/backlight_backend.hpp b/include/util/backlight_backend.hpp index eb42d3cc..ba3ccca7 100644 --- a/include/util/backlight_backend.hpp +++ b/include/util/backlight_backend.hpp @@ -36,7 +36,7 @@ class BacklightDevice { void set_max(int max); bool get_powered() const; void set_powered(bool powered); - friend inline bool operator==(const BacklightDevice &lhs, const BacklightDevice &rhs) { + friend inline bool operator==(const BacklightDevice& lhs, const BacklightDevice& rhs) { return lhs.name_ == rhs.name_ && lhs.actual_ == rhs.actual_ && lhs.max_ == rhs.max_; } @@ -52,25 +52,25 @@ class BacklightBackend { BacklightBackend(std::chrono::milliseconds interval, std::function on_updated_cb = NOOP); // const inline BacklightDevice *get_best_device(std::string_view preferred_device); - const BacklightDevice *get_previous_best_device(); + const BacklightDevice* get_previous_best_device(); - void set_previous_best_device(const BacklightDevice *device); + void set_previous_best_device(const BacklightDevice* device); - void set_brightness(const std::string &preferred_device, ChangeType change_type, double step); + void set_brightness(const std::string& preferred_device, ChangeType change_type, double step); - void set_scaled_brightness(const std::string &preferred_device, int brightness); - int get_scaled_brightness(const std::string &preferred_device); + void set_scaled_brightness(const std::string& preferred_device, int brightness); + int get_scaled_brightness(const std::string& preferred_device); bool is_login_proxy_initialized() const { return static_cast(login_proxy_); } - static const BacklightDevice *best_device(const std::vector &devices, + static const BacklightDevice* best_device(const std::vector& devices, std::string_view); std::vector devices_; std::mutex udev_thread_mutex_; private: - void set_brightness_internal(const std::string &device_name, int brightness, int max_brightness); + void set_brightness_internal(const std::string& device_name, int brightness, int max_brightness); std::function on_updated_cb_; std::chrono::milliseconds polling_interval_; diff --git a/include/util/clara.hpp b/include/util/clara.hpp index 73fa5415..48123549 100644 --- a/include/util/clara.hpp +++ b/include/util/clara.hpp @@ -78,7 +78,7 @@ class Column { class iterator { friend Column; - Column const &m_column; + Column const& m_column; size_t m_stringIndex = 0; size_t m_pos = 0; @@ -86,10 +86,10 @@ class Column { size_t m_end = 0; bool m_suffix = false; - iterator(Column const &column, size_t stringIndex) + iterator(Column const& column, size_t stringIndex) : m_column(column), m_stringIndex(stringIndex) {} - auto line() const -> std::string const & { return m_column.m_strings[m_stringIndex]; } + auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } auto isBoundary(size_t at) const -> bool { assert(at > 0); @@ -129,18 +129,18 @@ class Column { return initial == std::string::npos ? m_column.m_indent : initial; } - auto addIndentAndSuffix(std::string const &plain) const -> std::string { + auto addIndentAndSuffix(std::string const& plain) const -> std::string { return std::string(indent(), ' ') + (m_suffix ? plain + "-" : plain); } public: using difference_type = std::ptrdiff_t; using value_type = std::string; - using pointer = value_type *; - using reference = value_type &; + using pointer = value_type*; + using reference = value_type&; using iterator_category = std::forward_iterator_tag; - explicit iterator(Column const &column) : m_column(column) { + explicit iterator(Column const& column) : m_column(column) { assert(m_column.m_width > m_column.m_indent); assert(m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent); @@ -154,7 +154,7 @@ class Column { return addIndentAndSuffix(line().substr(m_pos, m_len)); } - auto operator++() -> iterator & { + auto operator++() -> iterator& { m_pos += m_len; if (m_pos < line().size() && line()[m_pos] == '\n') m_pos += 1; @@ -174,26 +174,26 @@ class Column { return prev; } - auto operator==(iterator const &other) const -> bool { + auto operator==(iterator const& other) const -> bool { return m_pos == other.m_pos && m_stringIndex == other.m_stringIndex && &m_column == &other.m_column; } - auto operator!=(iterator const &other) const -> bool { return !operator==(other); } + auto operator!=(iterator const& other) const -> bool { return !operator==(other); } }; using const_iterator = iterator; - explicit Column(std::string const &text) { m_strings.push_back(text); } + explicit Column(std::string const& text) { m_strings.push_back(text); } - auto width(size_t newWidth) -> Column & { + auto width(size_t newWidth) -> Column& { assert(newWidth > 0); m_width = newWidth; return *this; } - auto indent(size_t newIndent) -> Column & { + auto indent(size_t newIndent) -> Column& { m_indent = newIndent; return *this; } - auto initialIndent(size_t newIndent) -> Column & { + auto initialIndent(size_t newIndent) -> Column& { m_initialIndent = newIndent; return *this; } @@ -202,7 +202,7 @@ class Column { auto begin() const -> iterator { return iterator(*this); } auto end() const -> iterator { return {*this, m_strings.size()}; } - inline friend std::ostream &operator<<(std::ostream &os, Column const &col) { + inline friend std::ostream& operator<<(std::ostream& os, Column const& col) { bool first = true; for (auto line : col) { if (first) @@ -214,7 +214,7 @@ class Column { return os; } - auto operator+(Column const &other) -> Columns; + auto operator+(Column const& other) -> Columns; auto toString() const -> std::string { std::ostringstream oss; @@ -236,34 +236,34 @@ class Columns { friend Columns; struct EndTag {}; - std::vector const &m_columns; + std::vector const& m_columns; std::vector m_iterators; size_t m_activeIterators; - iterator(Columns const &columns, EndTag) : m_columns(columns.m_columns), m_activeIterators(0) { + iterator(Columns const& columns, EndTag) : m_columns(columns.m_columns), m_activeIterators(0) { m_iterators.reserve(m_columns.size()); - for (auto const &col : m_columns) m_iterators.push_back(col.end()); + for (auto const& col : m_columns) m_iterators.push_back(col.end()); } public: using difference_type = std::ptrdiff_t; using value_type = std::string; - using pointer = value_type *; - using reference = value_type &; + using pointer = value_type*; + using reference = value_type&; using iterator_category = std::forward_iterator_tag; - explicit iterator(Columns const &columns) + explicit iterator(Columns const& columns) : m_columns(columns.m_columns), m_activeIterators(m_columns.size()) { m_iterators.reserve(m_columns.size()); - for (auto const &col : m_columns) m_iterators.push_back(col.begin()); + for (auto const& col : m_columns) m_iterators.push_back(col.begin()); } - auto operator==(iterator const &other) const -> bool { + auto operator==(iterator const& other) const -> bool { return m_iterators == other.m_iterators; } - auto operator!=(iterator const &other) const -> bool { + auto operator!=(iterator const& other) const -> bool { return m_iterators != other.m_iterators; } auto operator*() const -> std::string { @@ -284,7 +284,7 @@ class Columns { } return row; } - auto operator++() -> iterator & { + auto operator++() -> iterator& { for (size_t i = 0; i < m_columns.size(); ++i) { if (m_iterators[i] != m_columns[i].end()) ++m_iterators[i]; } @@ -301,17 +301,17 @@ class Columns { auto begin() const -> iterator { return iterator(*this); } auto end() const -> iterator { return {*this, iterator::EndTag()}; } - auto operator+=(Column const &col) -> Columns & { + auto operator+=(Column const& col) -> Columns& { m_columns.push_back(col); return *this; } - auto operator+(Column const &col) -> Columns { + auto operator+(Column const& col) -> Columns { Columns combined = *this; combined += col; return combined; } - inline friend std::ostream &operator<<(std::ostream &os, Columns const &cols) { + inline friend std::ostream& operator<<(std::ostream& os, Columns const& cols) { bool first = true; for (auto line : cols) { if (first) @@ -330,7 +330,7 @@ class Columns { } }; -inline auto Column::operator+(Column const &other) -> Columns { +inline auto Column::operator+(Column const& other) -> Columns { Columns cols; cols += *this; cols += other; @@ -381,7 +381,7 @@ class Args { std::vector m_args; public: - Args(int argc, char const *const *argv) : m_exeName(argv[0]), m_args(argv + 1, argv + argc) {} + Args(int argc, char const* const* argv) : m_exeName(argv[0]), m_args(argv + 1, argv + argc) {} Args(std::initializer_list args) : m_exeName(*args.begin()), m_args(args.begin() + 1, args.end()) {} @@ -419,7 +419,7 @@ class TokenStream { while (it != itEnd && it->empty()) ++it; if (it != itEnd) { - auto const &next = *it; + auto const& next = *it; if (isOptPrefix(next[0])) { auto delimiterPos = next.find_first_of(" :="); if (delimiterPos != std::string::npos) { @@ -443,7 +443,7 @@ class TokenStream { } public: - explicit TokenStream(Args const &args) : TokenStream(args.m_args.begin(), args.m_args.end()) {} + explicit TokenStream(Args const& args) : TokenStream(args.m_args.begin(), args.m_args.end()) {} TokenStream(Iterator it, Iterator itEnd) : it(it), itEnd(itEnd) { loadBuffer(); } @@ -456,12 +456,12 @@ class TokenStream { return m_tokenBuffer.front(); } - auto operator->() const -> Token const * { + auto operator->() const -> Token const* { assert(!m_tokenBuffer.empty()); return &m_tokenBuffer.front(); } - auto operator++() -> TokenStream & { + auto operator++() -> TokenStream& { if (m_tokenBuffer.size() >= 2) { m_tokenBuffer.erase(m_tokenBuffer.begin()); } else { @@ -488,7 +488,7 @@ class ResultBase { template class ResultValueBase : public ResultBase { public: - auto value() const -> T const & { + auto value() const -> T const& { enforceOk(); return m_value; } @@ -496,13 +496,13 @@ class ResultValueBase : public ResultBase { protected: ResultValueBase(Type type) : ResultBase(type) {} - ResultValueBase(ResultValueBase const &other) : ResultBase(other) { + ResultValueBase(ResultValueBase const& other) : ResultBase(other) { if (m_type == ResultBase::Ok) new (&m_value) T(other.m_value); } - ResultValueBase(Type, T const &value) : ResultBase(Ok) { new (&m_value) T(value); } + ResultValueBase(Type, T const& value) : ResultBase(Ok) { new (&m_value) T(value); } - auto operator=(ResultValueBase const &other) -> ResultValueBase & { + auto operator=(ResultValueBase const& other) -> ResultValueBase& { if (m_type == ResultBase::Ok) m_value.~T(); ResultBase::operator=(other); if (m_type == ResultBase::Ok) new (&m_value) T(other.m_value); @@ -528,20 +528,20 @@ template class BasicResult : public ResultValueBase { public: template - explicit BasicResult(BasicResult const &other) + explicit BasicResult(BasicResult const& other) : ResultValueBase(other.type()), m_errorMessage(other.errorMessage()) { assert(type() != ResultBase::Ok); } template - static auto ok(U const &value) -> BasicResult { + static auto ok(U const& value) -> BasicResult { return {ResultBase::Ok, value}; } static auto ok() -> BasicResult { return {ResultBase::Ok}; } - static auto logicError(std::string const &message) -> BasicResult { + static auto logicError(std::string const& message) -> BasicResult { return {ResultBase::LogicError, message}; } - static auto runtimeError(std::string const &message) -> BasicResult { + static auto runtimeError(std::string const& message) -> BasicResult { return {ResultBase::RuntimeError, message}; } @@ -560,7 +560,7 @@ class BasicResult : public ResultValueBase { std::string m_errorMessage; // Only populated if resultType is an error - BasicResult(ResultBase::Type type, std::string const &message) + BasicResult(ResultBase::Type type, std::string const& message) : ResultValueBase(type), m_errorMessage(message) { assert(m_type != ResultBase::Ok); } @@ -573,7 +573,7 @@ enum class ParseResultType { Matched, NoMatch, ShortCircuitAll, ShortCircuitSame class ParseState { public: - ParseState(ParseResultType type, TokenStream const &remainingTokens) + ParseState(ParseResultType type, TokenStream const& remainingTokens) : m_type(type), m_remainingTokens(remainingTokens) {} auto type() const -> ParseResultType { return m_type; } @@ -594,7 +594,7 @@ struct HelpColumns { }; template -inline auto convertInto(std::string const &source, T &target) -> ParserResult { +inline auto convertInto(std::string const& source, T& target) -> ParserResult { std::stringstream ss; ss << source; ss >> target; @@ -603,11 +603,11 @@ inline auto convertInto(std::string const &source, T &target) -> ParserResult { else return ParserResult::ok(ParseResultType::Matched); } -inline auto convertInto(std::string const &source, std::string &target) -> ParserResult { +inline auto convertInto(std::string const& source, std::string& target) -> ParserResult { target = source; return ParserResult::ok(ParseResultType::Matched); } -inline auto convertInto(std::string const &source, bool &target) -> ParserResult { +inline auto convertInto(std::string const& source, bool& target) -> ParserResult { std::string srcLC = source; std::transform(srcLC.begin(), srcLC.end(), srcLC.begin(), [](char c) { return static_cast(::tolower(c)); }); @@ -622,7 +622,7 @@ inline auto convertInto(std::string const &source, bool &target) -> ParserResult } #ifdef CLARA_CONFIG_OPTIONAL_TYPE template -inline auto convertInto(std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE &target) +inline auto convertInto(std::string const& source, CLARA_CONFIG_OPTIONAL_TYPE& target) -> ParserResult { T temp; auto result = convertInto(source, temp); @@ -633,10 +633,10 @@ inline auto convertInto(std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE struct NonCopyable { NonCopyable() = default; - NonCopyable(NonCopyable const &) = delete; - NonCopyable(NonCopyable &&) = delete; - NonCopyable &operator=(NonCopyable const &) = delete; - NonCopyable &operator=(NonCopyable &&) = delete; + NonCopyable(NonCopyable const&) = delete; + NonCopyable(NonCopyable&&) = delete; + NonCopyable& operator=(NonCopyable const&) = delete; + NonCopyable& operator=(NonCopyable&&) = delete; }; struct BoundRef : NonCopyable { @@ -645,7 +645,7 @@ struct BoundRef : NonCopyable { virtual auto isFlag() const -> bool { return false; } }; struct BoundValueRefBase : BoundRef { - virtual auto setValue(std::string const &arg) -> ParserResult = 0; + virtual auto setValue(std::string const& arg) -> ParserResult = 0; }; struct BoundFlagRefBase : BoundRef { virtual auto setFlag(bool flag) -> ParserResult = 0; @@ -654,22 +654,22 @@ struct BoundFlagRefBase : BoundRef { template struct BoundValueRef : BoundValueRefBase { - T &m_ref; + T& m_ref; - explicit BoundValueRef(T &ref) : m_ref(ref) {} + explicit BoundValueRef(T& ref) : m_ref(ref) {} - auto setValue(std::string const &arg) -> ParserResult override { return convertInto(arg, m_ref); } + auto setValue(std::string const& arg) -> ParserResult override { return convertInto(arg, m_ref); } }; template struct BoundValueRef> : BoundValueRefBase { - std::vector &m_ref; + std::vector& m_ref; - explicit BoundValueRef(std::vector &ref) : m_ref(ref) {} + explicit BoundValueRef(std::vector& ref) : m_ref(ref) {} auto isContainer() const -> bool override { return true; } - auto setValue(std::string const &arg) -> ParserResult override { + auto setValue(std::string const& arg) -> ParserResult override { T temp; auto result = convertInto(arg, temp); if (result) m_ref.push_back(temp); @@ -678,9 +678,9 @@ struct BoundValueRef> : BoundValueRefBase { }; struct BoundFlagRef : BoundFlagRefBase { - bool &m_ref; + bool& m_ref; - explicit BoundFlagRef(bool &ref) : m_ref(ref) {} + explicit BoundFlagRef(bool& ref) : m_ref(ref) {} auto setFlag(bool flag) -> ParserResult override { m_ref = flag; @@ -694,7 +694,7 @@ struct LambdaInvoker { "Lambda must return void or clara::ParserResult"); template - static auto invoke(L const &lambda, ArgType const &arg) -> ParserResult { + static auto invoke(L const& lambda, ArgType const& arg) -> ParserResult { return lambda(arg); } }; @@ -702,14 +702,14 @@ struct LambdaInvoker { template <> struct LambdaInvoker { template - static auto invoke(L const &lambda, ArgType const &arg) -> ParserResult { + static auto invoke(L const& lambda, ArgType const& arg) -> ParserResult { lambda(arg); return ParserResult::ok(ParseResultType::Matched); } }; template -inline auto invokeLambda(L const &lambda, std::string const &arg) -> ParserResult { +inline auto invokeLambda(L const& lambda, std::string const& arg) -> ParserResult { ArgType temp{}; auto result = convertInto(arg, temp); return !result ? result @@ -721,9 +721,9 @@ struct BoundLambda : BoundValueRefBase { L m_lambda; static_assert(UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument"); - explicit BoundLambda(L const &lambda) : m_lambda(lambda) {} + explicit BoundLambda(L const& lambda) : m_lambda(lambda) {} - auto setValue(std::string const &arg) -> ParserResult override { + auto setValue(std::string const& arg) -> ParserResult override { return invokeLambda::ArgType>(m_lambda, arg); } }; @@ -736,7 +736,7 @@ struct BoundFlagLambda : BoundFlagRefBase { static_assert(std::is_same::ArgType, bool>::value, "flags must be boolean"); - explicit BoundFlagLambda(L const &lambda) : m_lambda(lambda) {} + explicit BoundFlagLambda(L const& lambda) : m_lambda(lambda) {} auto setFlag(bool flag) -> ParserResult override { return LambdaInvoker::ReturnType>::invoke(m_lambda, flag); @@ -751,11 +751,11 @@ class ParserBase { public: virtual ~ParserBase() = default; virtual auto validate() const -> Result { return Result::ok(); } - virtual auto parse(std::string const &exeName, TokenStream const &tokens) const + virtual auto parse(std::string const& exeName, TokenStream const& tokens) const -> InternalParseResult = 0; virtual auto cardinality() const -> size_t { return 1; } - auto parse(Args const &args) const -> InternalParseResult { + auto parse(Args const& args) const -> InternalParseResult { return parse(args.exeName(), TokenStream(args)); } }; @@ -764,10 +764,10 @@ template class ComposableParserImpl : public ParserBase { public: template - auto operator|(T const &other) const -> Parser; + auto operator|(T const& other) const -> Parser; template - auto operator+(T const &other) const -> Parser; + auto operator+(T const& other) const -> Parser; }; // Common code and state for Args and Opts @@ -779,30 +779,30 @@ class ParserRefImpl : public ComposableParserImpl { std::string m_hint; std::string m_description; - explicit ParserRefImpl(std::shared_ptr const &ref) : m_ref(ref) {} + explicit ParserRefImpl(std::shared_ptr const& ref) : m_ref(ref) {} public: template - ParserRefImpl(T &ref, std::string const &hint) + ParserRefImpl(T& ref, std::string const& hint) : m_ref(std::make_shared>(ref)), m_hint(hint) {} template - ParserRefImpl(LambdaT const &ref, std::string const &hint) + ParserRefImpl(LambdaT const& ref, std::string const& hint) : m_ref(std::make_shared>(ref)), m_hint(hint) {} - auto operator()(std::string const &description) -> DerivedT & { + auto operator()(std::string const& description) -> DerivedT& { m_description = description; - return static_cast(*this); + return static_cast(*this); } - auto optional() -> DerivedT & { + auto optional() -> DerivedT& { m_optionality = Optionality::Optional; - return static_cast(*this); + return static_cast(*this); }; - auto required() -> DerivedT & { + auto required() -> DerivedT& { m_optionality = Optionality::Required; - return static_cast(*this); + return static_cast(*this); }; auto isOptional() const -> bool { return m_optionality == Optionality::Optional; } @@ -822,29 +822,29 @@ class ExeName : public ComposableParserImpl { std::shared_ptr m_ref; template - static auto makeRef(LambdaT const &lambda) -> std::shared_ptr { + static auto makeRef(LambdaT const& lambda) -> std::shared_ptr { return std::make_shared>(lambda); } public: ExeName() : m_name(std::make_shared("")) {} - explicit ExeName(std::string &ref) : ExeName() { + explicit ExeName(std::string& ref) : ExeName() { m_ref = std::make_shared>(ref); } template - explicit ExeName(LambdaT const &lambda) : ExeName() { + explicit ExeName(LambdaT const& lambda) : ExeName() { m_ref = std::make_shared>(lambda); } // The exe name is not parsed out of the normal tokens, but is handled specially - auto parse(std::string const &, TokenStream const &tokens) const -> InternalParseResult override { + auto parse(std::string const&, TokenStream const& tokens) const -> InternalParseResult override { return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, tokens)); } auto name() const -> std::string { return *m_name; } - auto set(std::string const &newName) -> ParserResult { + auto set(std::string const& newName) -> ParserResult { auto lastSlash = newName.find_last_of("\\/"); auto filename = (lastSlash == std::string::npos) ? newName : newName.substr(lastSlash + 1); @@ -860,17 +860,17 @@ class Arg : public ParserRefImpl { public: using ParserRefImpl::ParserRefImpl; - auto parse(std::string const &, TokenStream const &tokens) const -> InternalParseResult override { + auto parse(std::string const&, TokenStream const& tokens) const -> InternalParseResult override { auto validationResult = validate(); if (!validationResult) return InternalParseResult(validationResult); auto remainingTokens = tokens; - auto const &token = *remainingTokens; + auto const& token = *remainingTokens; if (token.type != TokenType::Argument) return InternalParseResult::ok(ParseState(ParseResultType::NoMatch, remainingTokens)); assert(!m_ref->isFlag()); - auto valueRef = static_cast(m_ref.get()); + auto valueRef = static_cast(m_ref.get()); auto result = valueRef->setValue(remainingTokens->token); if (!result) @@ -880,7 +880,7 @@ class Arg : public ParserRefImpl { } }; -inline auto normaliseOpt(std::string const &optName) -> std::string { +inline auto normaliseOpt(std::string const& optName) -> std::string { #ifdef CLARA_PLATFORM_WINDOWS if (optName[0] == '/') return "-" + optName.substr(1); @@ -895,18 +895,18 @@ class Opt : public ParserRefImpl { public: template - explicit Opt(LambdaT const &ref) + explicit Opt(LambdaT const& ref) : ParserRefImpl(std::make_shared>(ref)) {} - explicit Opt(bool &ref) : ParserRefImpl(std::make_shared(ref)) {} + explicit Opt(bool& ref) : ParserRefImpl(std::make_shared(ref)) {} template - Opt(LambdaT const &ref, std::string const &hint) : ParserRefImpl(ref, hint) {} + Opt(LambdaT const& ref, std::string const& hint) : ParserRefImpl(ref, hint) {} template - Opt(T &ref, std::string const &hint) : ParserRefImpl(ref, hint) {} + Opt(T& ref, std::string const& hint) : ParserRefImpl(ref, hint) {} - auto operator[](std::string const &optName) -> Opt & { + auto operator[](std::string const& optName) -> Opt& { m_optNames.push_back(optName); return *this; } @@ -914,7 +914,7 @@ class Opt : public ParserRefImpl { auto getHelpColumns() const -> std::vector { std::ostringstream oss; bool first = true; - for (auto const &opt : m_optNames) { + for (auto const& opt : m_optNames) { if (first) first = false; else @@ -925,9 +925,9 @@ class Opt : public ParserRefImpl { return {{oss.str(), m_description}}; } - auto isMatch(std::string const &optToken) const -> bool { + auto isMatch(std::string const& optToken) const -> bool { auto normalisedToken = normaliseOpt(optToken); - for (auto const &name : m_optNames) { + for (auto const& name : m_optNames) { if (normaliseOpt(name) == normalisedToken) return true; } return false; @@ -935,26 +935,26 @@ class Opt : public ParserRefImpl { using ParserBase::parse; - auto parse(std::string const &, TokenStream const &tokens) const -> InternalParseResult override { + auto parse(std::string const&, TokenStream const& tokens) const -> InternalParseResult override { auto validationResult = validate(); if (!validationResult) return InternalParseResult(validationResult); auto remainingTokens = tokens; if (remainingTokens && remainingTokens->type == TokenType::Option) { - auto const &token = *remainingTokens; + auto const& token = *remainingTokens; if (isMatch(token.token)) { if (m_ref->isFlag()) { - auto flagRef = static_cast(m_ref.get()); + auto flagRef = static_cast(m_ref.get()); auto result = flagRef->setFlag(true); if (!result) return InternalParseResult(result); if (result.value() == ParseResultType::ShortCircuitAll) return InternalParseResult::ok(ParseState(result.value(), remainingTokens)); } else { - auto valueRef = static_cast(m_ref.get()); + auto valueRef = static_cast(m_ref.get()); ++remainingTokens; if (!remainingTokens) return InternalParseResult::runtimeError("Expected argument following " + token.token); - auto const &argToken = *remainingTokens; + auto const& argToken = *remainingTokens; if (argToken.type != TokenType::Argument) return InternalParseResult::runtimeError("Expected argument following " + token.token); auto result = valueRef->setValue(argToken.token); @@ -970,7 +970,7 @@ class Opt : public ParserRefImpl { auto validate() const -> Result override { if (m_optNames.empty()) return Result::logicError("No options supplied to Opt"); - for (auto const &name : m_optNames) { + for (auto const& name : m_optNames) { if (name.empty()) return Result::logicError("Option name cannot be empty"); #ifdef CLARA_PLATFORM_WINDOWS if (name[0] != '-' && name[0] != '/') @@ -984,12 +984,12 @@ class Opt : public ParserRefImpl { }; struct Help : Opt { - Help(bool &showHelpFlag) + Help(bool& showHelpFlag) : Opt([&](bool flag) { showHelpFlag = flag; return ParserResult::ok(ParseResultType::ShortCircuitAll); }) { - static_cast(*this)("display usage information")["-?"]["-h"]["--help"].optional(); + static_cast(*this)("display usage information")["-?"]["-h"]["--help"].optional(); } }; @@ -998,57 +998,57 @@ struct Parser : ParserBase { std::vector m_options; std::vector m_args; - auto operator|=(ExeName const &exeName) -> Parser & { + auto operator|=(ExeName const& exeName) -> Parser& { m_exeName = exeName; return *this; } - auto operator|=(Arg const &arg) -> Parser & { + auto operator|=(Arg const& arg) -> Parser& { m_args.push_back(arg); return *this; } - auto operator|=(Opt const &opt) -> Parser & { + auto operator|=(Opt const& opt) -> Parser& { m_options.push_back(opt); return *this; } - auto operator|=(Parser const &other) -> Parser & { + auto operator|=(Parser const& other) -> Parser& { m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); return *this; } template - auto operator|(T const &other) const -> Parser { + auto operator|(T const& other) const -> Parser { return Parser(*this) |= other; } // Forward deprecated interface with '+' instead of '|' template - auto operator+=(T const &other) -> Parser & { + auto operator+=(T const& other) -> Parser& { return operator|=(other); } template - auto operator+(T const &other) const -> Parser { + auto operator+(T const& other) const -> Parser { return operator|(other); } auto getHelpColumns() const -> std::vector { std::vector cols; - for (auto const &o : m_options) { + for (auto const& o : m_options) { auto childCols = o.getHelpColumns(); cols.insert(cols.end(), childCols.begin(), childCols.end()); } return cols; } - void writeToStream(std::ostream &os) const { + void writeToStream(std::ostream& os) const { if (!m_exeName.name().empty()) { os << "usage:\n" << " " << m_exeName.name() << " "; bool required = true, first = true; - for (auto const &arg : m_args) { + for (auto const& arg : m_args) { if (first) first = false; else @@ -1068,28 +1068,28 @@ struct Parser : ParserBase { auto rows = getHelpColumns(); size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; size_t optWidth = 0; - for (auto const &cols : rows) optWidth = (std::max)(optWidth, cols.left.size() + 2); + for (auto const& cols : rows) optWidth = (std::max)(optWidth, cols.left.size() + 2); optWidth = (std::min)(optWidth, consoleWidth / 2); - for (auto const &cols : rows) { + for (auto const& cols : rows) { auto row = TextFlow::Column(cols.left).width(optWidth).indent(2) + TextFlow::Spacer(4) + TextFlow::Column(cols.right).width(consoleWidth - 7 - optWidth); os << row << std::endl; } } - friend auto operator<<(std::ostream &os, Parser const &parser) -> std::ostream & { + friend auto operator<<(std::ostream& os, Parser const& parser) -> std::ostream& { parser.writeToStream(os); return os; } auto validate() const -> Result override { - for (auto const &opt : m_options) { + for (auto const& opt : m_options) { auto result = opt.validate(); if (!result) return result; } - for (auto const &arg : m_args) { + for (auto const& arg : m_args) { auto result = arg.validate(); if (!result) return result; } @@ -1098,10 +1098,10 @@ struct Parser : ParserBase { using ParserBase::parse; - auto parse(std::string const &exeName, TokenStream const &tokens) const + auto parse(std::string const& exeName, TokenStream const& tokens) const -> InternalParseResult override { struct ParserInfo { - ParserBase const *parser = nullptr; + ParserBase const* parser = nullptr; size_t count = 0; }; const size_t totalParsers = m_options.size() + m_args.size(); @@ -1111,8 +1111,8 @@ struct Parser : ParserBase { { size_t i = 0; - for (auto const &opt : m_options) parseInfos[i++].parser = &opt; - for (auto const &arg : m_args) parseInfos[i++].parser = &arg; + for (auto const& opt : m_options) parseInfos[i++].parser = &opt; + for (auto const& arg : m_args) parseInfos[i++].parser = &arg; } m_exeName.set(exeName); @@ -1122,7 +1122,7 @@ struct Parser : ParserBase { bool tokenParsed = false; for (size_t i = 0; i < totalParsers; ++i) { - auto &parseInfo = parseInfos[i]; + auto& parseInfo = parseInfos[i]; if (parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality()) { result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); @@ -1147,8 +1147,8 @@ struct Parser : ParserBase { template template -auto ComposableParserImpl::operator|(T const &other) const -> Parser { - return Parser() | static_cast(*this) | other; +auto ComposableParserImpl::operator|(T const& other) const -> Parser { + return Parser() | static_cast(*this) | other; } } // namespace detail diff --git a/include/util/icon_loader.hpp b/include/util/icon_loader.hpp index 664e510c..f9256eb5 100644 --- a/include/util/icon_loader.hpp +++ b/include/util/icon_loader.hpp @@ -17,18 +17,18 @@ class IconLoader { std::vector> custom_icon_themes_; Glib::RefPtr default_icon_theme_ = Gtk::IconTheme::get_default(); static std::vector search_prefix(); - static Glib::RefPtr get_app_info_by_name(const std::string &app_id); - static Glib::RefPtr get_desktop_app_info(const std::string &app_id); - static Glib::RefPtr load_icon_from_file(std::string const &icon_path, int size); - static std::string get_icon_name_from_icon_theme(const Glib::RefPtr &icon_theme, - const std::string &app_id); - static bool image_load_icon(Gtk::Image &image, const Glib::RefPtr &icon_theme, + static Glib::RefPtr get_app_info_by_name(const std::string& app_id); + static Glib::RefPtr get_desktop_app_info(const std::string& app_id); + static Glib::RefPtr load_icon_from_file(std::string const& icon_path, int size); + static std::string get_icon_name_from_icon_theme(const Glib::RefPtr& icon_theme, + const std::string& app_id); + static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr& icon_theme, Glib::RefPtr app_info, int size); public: - void add_custom_icon_theme(const std::string &theme_name); - bool image_load_icon(Gtk::Image &image, Glib::RefPtr app_info, + void add_custom_icon_theme(const std::string& theme_name); + bool image_load_icon(Gtk::Image& image, Glib::RefPtr app_info, int size) const; static Glib::RefPtr get_app_info_from_app_id_list( - const std::string &app_id_list); + const std::string& app_id_list); }; diff --git a/include/util/pipewire/privacy_node_info.hpp b/include/util/pipewire/privacy_node_info.hpp index 54da7d16..54acad42 100644 --- a/include/util/pipewire/privacy_node_info.hpp +++ b/include/util/pipewire/privacy_node_info.hpp @@ -33,14 +33,14 @@ class PrivacyNodeInfo { struct spa_hook object_listener; struct spa_hook proxy_listener; - void *data; + void* data; std::string getName(); std::string getIconName(); // Handlers for PipeWire events void handleProxyEventDestroy(); - void handleNodeEventInfo(const struct pw_node_info *info); + void handleNodeEventInfo(const struct pw_node_info* info); }; } // namespace waybar::util::PipewireBackend diff --git a/include/util/udev_deleter.hpp b/include/util/udev_deleter.hpp index b2f1e538..77493c4a 100644 --- a/include/util/udev_deleter.hpp +++ b/include/util/udev_deleter.hpp @@ -4,18 +4,18 @@ namespace waybar::util { struct UdevDeleter { - void operator()(udev *ptr) const { udev_unref(ptr); } + void operator()(udev* ptr) const { udev_unref(ptr); } }; struct UdevDeviceDeleter { - void operator()(udev_device *ptr) const { udev_device_unref(ptr); } + void operator()(udev_device* ptr) const { udev_device_unref(ptr); } }; struct UdevEnumerateDeleter { - void operator()(udev_enumerate *ptr) const { udev_enumerate_unref(ptr); } + void operator()(udev_enumerate* ptr) const { udev_enumerate_unref(ptr); } }; struct UdevMonitorDeleter { - void operator()(udev_monitor *ptr) const { udev_monitor_unref(ptr); } + void operator()(udev_monitor* ptr) const { udev_monitor_unref(ptr); } }; } // namespace waybar::util \ No newline at end of file diff --git a/include/util/ustring_clen.hpp b/include/util/ustring_clen.hpp index cddd2e1a..63311d53 100644 --- a/include/util/ustring_clen.hpp +++ b/include/util/ustring_clen.hpp @@ -2,4 +2,4 @@ #include // calculate column width of ustring -int ustring_clen(const Glib::ustring &str); \ No newline at end of file +int ustring_clen(const Glib::ustring& str); \ No newline at end of file diff --git a/src/AIconLabel.cpp b/src/AIconLabel.cpp index a20c22e9..5c7df0ed 100644 --- a/src/AIconLabel.cpp +++ b/src/AIconLabel.cpp @@ -5,8 +5,8 @@ namespace waybar { -AIconLabel::AIconLabel(const Json::Value &config, const std::string &name, const std::string &id, - const std::string &format, uint16_t interval, bool ellipsize, +AIconLabel::AIconLabel(const Json::Value& config, const std::string& name, const std::string& id, + const std::string& format, uint16_t interval, bool ellipsize, bool enable_click, bool enable_scroll) : ALabel(config, name, id, format, interval, ellipsize, enable_click, enable_scroll) { event_box_.remove(); diff --git a/src/bar.cpp b/src/bar.cpp index 4a202d7a..6a78707e 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -229,7 +229,8 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) gtk_layer_init_for_window(gtk_window); gtk_layer_set_keyboard_mode(gtk_window, GTK_LAYER_SHELL_KEYBOARD_MODE_NONE); gtk_layer_set_monitor(gtk_window, output->monitor->gobj()); - gtk_layer_set_namespace(gtk_window, config["name"].isString() ? config["name"].asCString() : "waybar"); + gtk_layer_set_namespace(gtk_window, + config["name"].isString() ? config["name"].asCString() : "waybar"); gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, margins_.left); gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, margins_.right); diff --git a/src/client.cpp b/src/client.cpp index f60e01a3..103b093f 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -11,60 +11,60 @@ #include "util/clara.hpp" #include "util/format.hpp" -waybar::Client *waybar::Client::inst() { - static auto *c = new Client(); +waybar::Client* waybar::Client::inst() { + static auto* c = new Client(); return c; } -void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint32_t name, - const char *interface, uint32_t version) { - auto *client = static_cast(data); +void waybar::Client::handleGlobal(void* data, struct wl_registry* registry, uint32_t name, + const char* interface, uint32_t version) { + auto* client = static_cast(data); if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 && version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) { - client->xdg_output_manager = static_cast(wl_registry_bind( + client->xdg_output_manager = static_cast(wl_registry_bind( registry, name, &zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION)); } else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) { - client->idle_inhibit_manager = static_cast( + client->idle_inhibit_manager = static_cast( wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1)); } } -void waybar::Client::handleGlobalRemove(void *data, struct wl_registry * /*registry*/, +void waybar::Client::handleGlobalRemove(void* data, struct wl_registry* /*registry*/, uint32_t name) { // Nothing here } -void waybar::Client::handleOutput(struct waybar_output &output) { +void waybar::Client::handleOutput(struct waybar_output& output) { static const struct zxdg_output_v1_listener xdgOutputListener = { - .logical_position = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {}, - .logical_size = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {}, + .logical_position = [](void*, struct zxdg_output_v1*, int32_t, int32_t) {}, + .logical_size = [](void*, struct zxdg_output_v1*, int32_t, int32_t) {}, .done = &handleOutputDone, .name = &handleOutputName, .description = &handleOutputDescription, }; // owned by output->monitor; no need to destroy - auto *wl_output = gdk_wayland_monitor_get_wl_output(output.monitor->gobj()); + auto* wl_output = gdk_wayland_monitor_get_wl_output(output.monitor->gobj()); output.xdg_output.reset(zxdg_output_manager_v1_get_xdg_output(xdg_output_manager, wl_output)); zxdg_output_v1_add_listener(output.xdg_output.get(), &xdgOutputListener, &output); } -struct waybar::waybar_output &waybar::Client::getOutput(void *addr) { +struct waybar::waybar_output& waybar::Client::getOutput(void* addr) { auto it = std::find_if(outputs_.begin(), outputs_.end(), - [&addr](const auto &output) { return &output == addr; }); + [&addr](const auto& output) { return &output == addr; }); if (it == outputs_.end()) { throw std::runtime_error("Unable to find valid output"); } return *it; } -std::vector waybar::Client::getOutputConfigs(struct waybar_output &output) { +std::vector waybar::Client::getOutputConfigs(struct waybar_output& output) { return config.getOutputConfigs(output.name, output.identifier); } -void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_output*/) { - auto *client = waybar::Client::inst(); +void waybar::Client::handleOutputDone(void* data, struct zxdg_output_v1* /*xdg_output*/) { + auto* client = waybar::Client::inst(); try { - auto &output = client->getOutput(data); + auto& output = client->getOutput(data); /** * Multiple .done events may arrive in batch. In this case libwayland would queue * xdg_output.destroy and dispatch all pending events, triggering this callback several times @@ -80,44 +80,44 @@ void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_ auto configs = client->getOutputConfigs(output); if (!configs.empty()) { - for (const auto &config : configs) { + for (const auto& config : configs) { client->bars.emplace_back(std::make_unique(&output, config)); } } } - } catch (const std::exception &e) { + } catch (const std::exception& e) { spdlog::warn("caught exception in zxdg_output_v1_listener::done: {}", e.what()); } } -void waybar::Client::handleOutputName(void *data, struct zxdg_output_v1 * /*xdg_output*/, - const char *name) { - auto *client = waybar::Client::inst(); +void waybar::Client::handleOutputName(void* data, struct zxdg_output_v1* /*xdg_output*/, + const char* name) { + auto* client = waybar::Client::inst(); try { - auto &output = client->getOutput(data); + auto& output = client->getOutput(data); output.name = name; - } catch (const std::exception &e) { + } catch (const std::exception& e) { spdlog::warn("caught exception in zxdg_output_v1_listener::name: {}", e.what()); } } -void waybar::Client::handleOutputDescription(void *data, struct zxdg_output_v1 * /*xdg_output*/, - const char *description) { - auto *client = waybar::Client::inst(); +void waybar::Client::handleOutputDescription(void* data, struct zxdg_output_v1* /*xdg_output*/, + const char* description) { + auto* client = waybar::Client::inst(); try { - auto &output = client->getOutput(data); + auto& output = client->getOutput(data); // Description format: "identifier (name)" auto s = std::string(description); auto pos = s.find(" ("); output.identifier = pos != std::string::npos ? s.substr(0, pos) : s; - } catch (const std::exception &e) { + } catch (const std::exception& e) { spdlog::warn("caught exception in zxdg_output_v1_listener::description: {}", e.what()); } } void waybar::Client::handleMonitorAdded(Glib::RefPtr monitor) { - auto &output = outputs_.emplace_back(); + auto& output = outputs_.emplace_back(); output.monitor = std::move(monitor); handleOutput(output); } @@ -146,10 +146,10 @@ void waybar::Client::handleDeferredMonitorRemoval(Glib::RefPtr mon ++it; } } - outputs_.remove_if([&monitor](const auto &output) { return output.monitor == monitor; }); + outputs_.remove_if([&monitor](const auto& output) { return output.monitor == monitor; }); } -const std::string waybar::Client::getStyle(const std::string &style, +const std::string waybar::Client::getStyle(const std::string& style, std::optional appearance = std::nullopt) { auto gtk_settings = Gtk::Settings::get_default(); std::optional css_file; @@ -182,7 +182,7 @@ const std::string waybar::Client::getStyle(const std::string &style, return css_file.value(); }; -auto waybar::Client::setupCss(const std::string &css_file) -> void { +auto waybar::Client::setupCss(const std::string& css_file) -> void { auto screen = Gdk::Screen::get_default(); if (!screen) { throw std::runtime_error("No default screen"); @@ -238,7 +238,7 @@ void waybar::Client::bindInterfaces() { sigc::mem_fun(*this, &Client::handleMonitorRemoved)); } -int waybar::Client::main(int argc, char *argv[]) { +int waybar::Client::main(int argc, char* argv[]) { bool show_help = false; bool show_version = false; std::string config_opt; @@ -299,7 +299,7 @@ int waybar::Client::main(int argc, char *argv[]) { if (m_config.isObject() && m_config["reload_style_on_change"].asBool()) { m_cssReloadHelper->monitorChanges(); } else if (m_config.isArray()) { - for (const auto &conf : m_config) { + for (const auto& conf : m_config) { if (conf["reload_style_on_change"].asBool()) { m_cssReloadHelper->monitorChanges(); break; diff --git a/src/config.cpp b/src/config.cpp index 145056dd..266342ae 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -23,10 +23,10 @@ const std::vector Config::CONFIG_DIRS = { "/etc/xdg/waybar/", SYSCONFDIR "/xdg/waybar/", "./resources/", }; -const char *Config::CONFIG_PATH_ENV = "WAYBAR_CONFIG_DIR"; +const char* Config::CONFIG_PATH_ENV = "WAYBAR_CONFIG_DIR"; -std::vector Config::tryExpandPath(const std::string &base, - const std::string &filename) { +std::vector Config::tryExpandPath(const std::string& base, + const std::string& filename) { fs::path path; if (!filename.empty()) { @@ -65,18 +65,18 @@ std::vector Config::tryExpandPath(const std::string &base, return results; } -std::optional Config::findConfigPath(const std::vector &names, - const std::vector &dirs) { - if (const char *dir = std::getenv(Config::CONFIG_PATH_ENV)) { - for (const auto &name : names) { +std::optional Config::findConfigPath(const std::vector& names, + const std::vector& dirs) { + if (const char* dir = std::getenv(Config::CONFIG_PATH_ENV)) { + for (const auto& name : names) { if (auto res = tryExpandPath(dir, name); !res.empty()) { return res.front(); } } } - for (const auto &dir : dirs) { - for (const auto &name : names) { + for (const auto& dir : dirs) { + for (const auto& name : names) { if (auto res = tryExpandPath(dir, name); !res.empty()) { return res.front(); } @@ -85,7 +85,7 @@ std::optional Config::findConfigPath(const std::vector return std::nullopt; } -void Config::setupConfig(Json::Value &dst, const std::string &config_file, int depth) { +void Config::setupConfig(Json::Value& dst, const std::string& config_file, int depth) { if (depth > 100) { throw std::runtime_error("Aborting due to likely recursive include in config files"); } @@ -97,7 +97,7 @@ void Config::setupConfig(Json::Value &dst, const std::string &config_file, int d util::JsonParser parser; Json::Value tmp_config = parser.parse(str); if (tmp_config.isArray()) { - for (auto &config_part : tmp_config) { + for (auto& config_part : tmp_config) { resolveConfigIncludes(config_part, depth); } } else { @@ -106,18 +106,18 @@ void Config::setupConfig(Json::Value &dst, const std::string &config_file, int d mergeConfig(dst, tmp_config); } -std::vector Config::findIncludePath(const std::string &name, - const std::vector &dirs) { +std::vector Config::findIncludePath(const std::string& name, + const std::vector& dirs) { auto match1 = tryExpandPath(name, ""); if (!match1.empty()) { return match1; } - if (const char *dir = std::getenv(Config::CONFIG_PATH_ENV)) { + 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) { + for (const auto& dir : dirs) { if (auto res = tryExpandPath(dir, name); !res.empty()) { return res; } @@ -126,14 +126,14 @@ std::vector Config::findIncludePath(const std::string &name, return {}; } -void Config::resolveConfigIncludes(Json::Value &config, int depth) { +void Config::resolveConfigIncludes(Json::Value& config, int depth) { Json::Value includes = config["include"]; if (includes.isArray()) { - for (const auto &include : includes) { + for (const auto& include : includes) { spdlog::info("Including resource file: {}", include.asString()); auto matches = findIncludePath(include.asString()); if (!matches.empty()) { - for (const auto &match : matches) { + for (const auto& match : matches) { setupConfig(config, match, depth + 1); } } else { @@ -144,7 +144,7 @@ void Config::resolveConfigIncludes(Json::Value &config, int depth) { spdlog::info("Including resource file: {}", includes.asString()); auto matches = findIncludePath(includes.asString()); if (!matches.empty()) { - for (const auto &match : matches) { + for (const auto& match : matches) { setupConfig(config, match, depth + 1); } } else { @@ -153,12 +153,12 @@ void Config::resolveConfigIncludes(Json::Value &config, int depth) { } } -void Config::mergeConfig(Json::Value &a_config_, Json::Value &b_config_) { +void Config::mergeConfig(Json::Value& a_config_, Json::Value& b_config_) { if (!a_config_) { // For the first config a_config_ = b_config_; } else if (a_config_.isObject() && b_config_.isObject()) { - for (const auto &key : b_config_.getMemberNames()) { + for (const auto& key : b_config_.getMemberNames()) { // [] creates key with default value. Use `get` to avoid that. if (a_config_.get(key, Json::Value::nullSingleton()).isObject() && b_config_[key].isObject()) { @@ -174,10 +174,10 @@ void Config::mergeConfig(Json::Value &a_config_, Json::Value &b_config_) { spdlog::error("Cannot merge config, conflicting or invalid JSON types"); } } -bool isValidOutput(const Json::Value &config, const std::string &name, - const std::string &identifier) { +bool isValidOutput(const Json::Value& config, const std::string& name, + const std::string& identifier) { if (config["output"].isArray()) { - for (auto const &output_conf : config["output"]) { + for (auto const& output_conf : config["output"]) { if (output_conf.isString()) { auto config_output = output_conf.asString(); if (config_output.substr(0, 1) == "!") { @@ -211,7 +211,7 @@ bool isValidOutput(const Json::Value &config, const std::string &name, return true; } -void Config::load(const std::string &config) { +void Config::load(const std::string& config) { auto file = config.empty() ? findConfigPath({"config", "config.jsonc"}) : config; if (!file) { throw std::runtime_error("Missing required resource files"); @@ -222,11 +222,11 @@ void Config::load(const std::string &config) { setupConfig(config_, config_file_, 0); } -std::vector Config::getOutputConfigs(const std::string &name, - const std::string &identifier) { +std::vector Config::getOutputConfigs(const std::string& name, + const std::string& identifier) { std::vector configs; if (config_.isArray()) { - for (auto const &config : config_) { + for (auto const& config : config_) { if (config.isObject() && isValidOutput(config, name, identifier)) { configs.push_back(config); } diff --git a/src/modules/backlight.cpp b/src/modules/backlight.cpp index 3c50f1d1..aabfe3f1 100644 --- a/src/modules/backlight.cpp +++ b/src/modules/backlight.cpp @@ -13,7 +13,7 @@ #include "util/backend_common.hpp" #include "util/backlight_backend.hpp" -waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &config) +waybar::modules::Backlight::Backlight(const std::string& id, const Json::Value& config) : ALabel(config, "backlight", id, "{percent}%", 2), preferred_device_(config["device"].isString() ? config["device"].asString() : ""), backend(interval_, [this] { dp.emit(); }) { @@ -79,7 +79,7 @@ auto waybar::modules::Backlight::update() -> void { ALabel::update(); } -bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) { +bool waybar::modules::Backlight::handleScroll(GdkEventScroll* e) { // Check if the user has set a custom command for scrolling if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { return AModule::handleScroll(e); diff --git a/src/modules/cpu_usage/bsd.cpp b/src/modules/cpu_usage/bsd.cpp index d795a817..e49498e4 100644 --- a/src/modules/cpu_usage/bsd.cpp +++ b/src/modules/cpu_usage/bsd.cpp @@ -33,7 +33,7 @@ std::vector> waybar::modules::CpuUsage::parseCpuinfo( size_t sum_sz = sizeof(sum_cp_time); int ncpu = sysconf(_SC_NPROCESSORS_CONF); size_t sz = CPUSTATES * (ncpu + 1) * sizeof(pcp_time_t); - pcp_time_t *cp_time = static_cast(malloc(sz)), *pcp_time = cp_time; + pcp_time_t *cp_time = static_cast(malloc(sz)), *pcp_time = cp_time; waybar::util::ScopeGuard cp_time_deleter([cp_time]() { if (cp_time) { free(cp_time); diff --git a/src/modules/dwl/tags.cpp b/src/modules/dwl/tags.cpp index c916eaf1..fb065650 100644 --- a/src/modules/dwl/tags.cpp +++ b/src/modules/dwl/tags.cpp @@ -21,39 +21,39 @@ wl_array tags, layouts; static uint num_tags = 0; -static void toggle_visibility(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) { +static void toggle_visibility(void* data, zdwl_ipc_output_v2* zdwl_output_v2) { // Intentionally empty } -static void active(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t active) { +static void active(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t active) { // Intentionally empty } -static void set_tag(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t tag, uint32_t state, +static void set_tag(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t tag, uint32_t state, uint32_t clients, uint32_t focused) { - static_cast(data)->handle_view_tags(tag, state, clients, focused); + static_cast(data)->handle_view_tags(tag, state, clients, focused); num_tags = (state & ZDWL_IPC_OUTPUT_V2_TAG_STATE_ACTIVE) ? num_tags | (1 << tag) : num_tags & ~(1 << tag); } -static void set_layout_symbol(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *layout) { +static void set_layout_symbol(void* data, zdwl_ipc_output_v2* zdwl_output_v2, const char* layout) { // Intentionally empty } -static void title(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *title) { +static void title(void* data, zdwl_ipc_output_v2* zdwl_output_v2, const char* title) { // Intentionally empty } -static void dwl_frame(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) { +static void dwl_frame(void* data, zdwl_ipc_output_v2* zdwl_output_v2) { // Intentionally empty } -static void set_layout(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t layout) { +static void set_layout(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t layout) { // Intentionally empty } -static void appid(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *appid) { +static void appid(void* data, zdwl_ipc_output_v2* zdwl_output_v2, const char* appid) { // Intentionally empty }; @@ -68,35 +68,35 @@ static const zdwl_ipc_output_v2_listener output_status_listener_impl{ .frame = dwl_frame, }; -static void handle_global(void *data, struct wl_registry *registry, uint32_t name, - const char *interface, uint32_t version) { +static void handle_global(void* data, struct wl_registry* registry, uint32_t name, + const char* interface, uint32_t version) { if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) { - static_cast(data)->status_manager_ = static_cast( - (zdwl_ipc_manager_v2 *)wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1)); + static_cast(data)->status_manager_ = static_cast( + (zdwl_ipc_manager_v2*)wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1)); } if (std::strcmp(interface, wl_seat_interface.name) == 0) { version = std::min(version, 1); - static_cast(data)->seat_ = static_cast( - wl_registry_bind(registry, name, &wl_seat_interface, version)); + static_cast(data)->seat_ = + static_cast(wl_registry_bind(registry, name, &wl_seat_interface, version)); } } -static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { +static void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) { /* Ignore event */ } static const wl_registry_listener registry_listener_impl = {.global = handle_global, .global_remove = handle_global_remove}; -Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config) +Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& config) : waybar::AModule(config, "tags", id, false, false), status_manager_{nullptr}, seat_{nullptr}, bar_(bar), box_{bar.orientation, 0}, output_status_{nullptr} { - struct wl_display *display = Client::inst()->wl_display; - struct wl_registry *registry = wl_display_get_registry(display); + struct wl_display* display = Client::inst()->wl_display; + struct wl_registry* registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener_impl, this); wl_display_roundtrip(display); @@ -133,8 +133,8 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con } uint32_t i = 1; - for (const auto &tag_label : tag_labels) { - Gtk::Button &button = buttons_.emplace_back(tag_label); + for (const auto& tag_label : tag_labels) { + Gtk::Button& button = buttons_.emplace_back(tag_label); button.set_relief(Gtk::RELIEF_NONE); box_.pack_start(button, false, false, 0); if (!config_["disable-click"].asBool()) { @@ -147,7 +147,7 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con i <<= 1; } - struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + struct wl_output* output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); output_status_ = zdwl_ipc_manager_v2_get_output(status_manager_, output); zdwl_ipc_output_v2_add_listener(output_status_, &output_status_listener_impl, this); @@ -170,7 +170,7 @@ void Tags::handle_primary_clicked(uint32_t tag) { zdwl_ipc_output_v2_set_tags(output_status_, tag, 1); } -bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) { +bool Tags::handle_button_press(GdkEventButton* event_button, uint32_t tag) { if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) { if (!output_status_) return true; zdwl_ipc_output_v2_set_tags(output_status_, num_tags ^ tag, 0); @@ -180,7 +180,7 @@ bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) { void Tags::handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint32_t focused) { // First clear all occupied state - auto &button = buttons_[tag]; + auto& button = buttons_[tag]; if (clients) { button.get_style_context()->add_class("occupied"); } else { diff --git a/src/modules/dwl/window.cpp b/src/modules/dwl/window.cpp index a960a1f0..2b679c9f 100644 --- a/src/modules/dwl/window.cpp +++ b/src/modules/dwl/window.cpp @@ -14,37 +14,37 @@ namespace waybar::modules::dwl { -static void toggle_visibility(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) { +static void toggle_visibility(void* data, zdwl_ipc_output_v2* zdwl_output_v2) { // Intentionally empty } -static void active(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t active) { +static void active(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t active) { // Intentionally empty } -static void set_tag(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t tag, uint32_t state, +static void set_tag(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t tag, uint32_t state, uint32_t clients, uint32_t focused) { // Intentionally empty } -static void set_layout_symbol(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *layout) { - static_cast(data)->handle_layout_symbol(layout); +static void set_layout_symbol(void* data, zdwl_ipc_output_v2* zdwl_output_v2, const char* layout) { + static_cast(data)->handle_layout_symbol(layout); } -static void title(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *title) { - static_cast(data)->handle_title(title); +static void title(void* data, zdwl_ipc_output_v2* zdwl_output_v2, const char* title) { + static_cast(data)->handle_title(title); } -static void dwl_frame(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) { - static_cast(data)->handle_frame(); +static void dwl_frame(void* data, zdwl_ipc_output_v2* zdwl_output_v2) { + static_cast(data)->handle_frame(); } -static void set_layout(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t layout) { - static_cast(data)->handle_layout(layout); +static void set_layout(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t layout) { + static_cast(data)->handle_layout(layout); } -static void appid(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *appid) { - static_cast(data)->handle_appid(appid); +static void appid(void* data, zdwl_ipc_output_v2* zdwl_output_v2, const char* appid) { + static_cast(data)->handle_appid(appid); }; static const zdwl_ipc_output_v2_listener output_status_listener_impl{ @@ -58,25 +58,25 @@ static const zdwl_ipc_output_v2_listener output_status_listener_impl{ .frame = dwl_frame, }; -static void handle_global(void *data, struct wl_registry *registry, uint32_t name, - const char *interface, uint32_t version) { +static void handle_global(void* data, struct wl_registry* registry, uint32_t name, + const char* interface, uint32_t version) { if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) { - static_cast(data)->status_manager_ = static_cast( - (zdwl_ipc_manager_v2 *)wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1)); + static_cast(data)->status_manager_ = static_cast( + (zdwl_ipc_manager_v2*)wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1)); } } -static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { +static void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) { /* Ignore event */ } static const wl_registry_listener registry_listener_impl = {.global = handle_global, .global_remove = handle_global_remove}; -Window::Window(const std::string &id, const Bar &bar, const Json::Value &config) +Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) : AAppIconLabel(config, "window", id, "{}", 0, true), bar_(bar) { - struct wl_display *display = Client::inst()->wl_display; - struct wl_registry *registry = wl_display_get_registry(display); + struct wl_display* display = Client::inst()->wl_display; + struct wl_registry* registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener_impl, this); wl_display_roundtrip(display); @@ -86,7 +86,7 @@ Window::Window(const std::string &id, const Bar &bar, const Json::Value &config) return; } - struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + struct wl_output* output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); output_status_ = zdwl_ipc_manager_v2_get_output(status_manager_, output); zdwl_ipc_output_v2_add_listener(output_status_, &output_status_listener_impl, this); zdwl_ipc_manager_v2_destroy(status_manager_); @@ -98,11 +98,11 @@ Window::~Window() { } } -void Window::handle_title(const char *title) { title_ = Glib::Markup::escape_text(title); } +void Window::handle_title(const char* title) { title_ = Glib::Markup::escape_text(title); } -void Window::handle_appid(const char *appid) { appid_ = Glib::Markup::escape_text(appid); } +void Window::handle_appid(const char* appid) { appid_ = Glib::Markup::escape_text(appid); } -void Window::handle_layout_symbol(const char *layout_symbol) { +void Window::handle_layout_symbol(const char* layout_symbol) { layout_symbol_ = Glib::Markup::escape_text(layout_symbol); } diff --git a/src/modules/ext/workspace_manager.cpp b/src/modules/ext/workspace_manager.cpp index 64c4be23..637177d3 100644 --- a/src/modules/ext/workspace_manager.cpp +++ b/src/modules/ext/workspace_manager.cpp @@ -20,8 +20,8 @@ uint32_t WorkspaceManager::group_global_id = 0; uint32_t WorkspaceManager::workspace_global_id = 0; std::map Workspace::icon_map_; -WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar, - const Json::Value &config) +WorkspaceManager::WorkspaceManager(const std::string& id, const waybar::Bar& bar, + const Json::Value& config) : waybar::AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) { add_registry_listener(this); @@ -70,7 +70,7 @@ WorkspaceManager::~WorkspaceManager() { groups_.clear(); if (ext_manager_ != nullptr) { - auto *display = Client::inst()->wl_display; + auto* display = Client::inst()->wl_display; // Send `stop` request and wait for one roundtrip. ext_workspace_manager_v1_stop(ext_manager_); wl_display_roundtrip(display); @@ -84,7 +84,7 @@ WorkspaceManager::~WorkspaceManager() { spdlog::debug("[ext/workspaces]: Workspace manager destroyed"); } -void WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version) { +void WorkspaceManager::register_manager(wl_registry* registry, uint32_t name, uint32_t version) { if (ext_manager_ != nullptr) { spdlog::warn("[ext/workspaces]: Register workspace manager again although already registered!"); return; @@ -99,7 +99,7 @@ void WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, ui void WorkspaceManager::remove_workspace_group(uint32_t id) { const auto it = - std::find_if(groups_.begin(), groups_.end(), [id](const auto &g) { return g->id() == id; }); + std::find_if(groups_.begin(), groups_.end(), [id](const auto& g) { return g->id() == id; }); if (it == groups_.end()) { spdlog::warn("[ext/workspaces]: Can't find workspace group with id {}", id); @@ -111,7 +111,7 @@ void WorkspaceManager::remove_workspace_group(uint32_t id) { void WorkspaceManager::remove_workspace(uint32_t id) { const auto it = std::find_if(workspaces_.begin(), workspaces_.end(), - [id](const auto &w) { return w->id() == id; }); + [id](const auto& w) { return w->id() == id; }); if (it == workspaces_.end()) { spdlog::warn("[ext/workspaces]: Can't find workspace with id {}", id); @@ -121,13 +121,13 @@ void WorkspaceManager::remove_workspace(uint32_t id) { workspaces_.erase(it); } -void WorkspaceManager::handle_workspace_group(ext_workspace_group_handle_v1 *handle) { +void WorkspaceManager::handle_workspace_group(ext_workspace_group_handle_v1* handle) { const auto new_id = ++group_global_id; groups_.push_back(std::make_unique(*this, handle, new_id)); spdlog::debug("[ext/workspaces]: Workspace group {} created", new_id); } -void WorkspaceManager::handle_workspace(ext_workspace_handle_v1 *handle) { +void WorkspaceManager::handle_workspace(ext_workspace_handle_v1* handle) { const auto new_id = ++workspace_global_id; const auto new_name = std::to_string(++workspace_name); workspaces_.push_back(std::make_unique(config_, *this, handle, new_id, new_name)); @@ -158,7 +158,7 @@ void WorkspaceManager::update() { AModule::update(); } -bool WorkspaceManager::has_button(const Gtk::Button *button) { +bool WorkspaceManager::has_button(const Gtk::Button* button) { const auto buttons = box_.get_children(); return std::find(buttons.begin(), buttons.end(), button) != buttons.end(); } @@ -166,20 +166,20 @@ bool WorkspaceManager::has_button(const Gtk::Button *button) { void WorkspaceManager::sort_workspaces() { // determine if workspace ID's and names can be sort numerically or literally - auto is_numeric = [](const std::string &s) { + auto is_numeric = [](const std::string& s) { return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit); }; auto sort_by_workspace_id_numerically = std::all_of(workspaces_.begin(), workspaces_.end(), - [&](const auto &w) { return is_numeric(w->workspace_id()); }); + [&](const auto& w) { return is_numeric(w->workspace_id()); }); auto sort_by_name_numerically = std::all_of(workspaces_.begin(), workspaces_.end(), - [&](const auto &w) { return is_numeric(w->name()); }); + [&](const auto& w) { return is_numeric(w->name()); }); // sort based on configuration setting with sort-by-id as fallback - std::sort(workspaces_.begin(), workspaces_.end(), [&](const auto &w1, const auto &w2) { + std::sort(workspaces_.begin(), workspaces_.end(), [&](const auto& w1, const auto& w2) { if (sort_by_id_ || (!sort_by_name_ && !sort_by_coordinates_)) { if (w1->workspace_id() == w2->workspace_id()) { return w1->id() < w2->id(); @@ -218,7 +218,7 @@ void WorkspaceManager::sort_workspaces() { } void WorkspaceManager::clear_buttons() { - for (const auto &workspace : workspaces_) { + for (const auto& workspace : workspaces_) { if (has_button(&workspace->button())) { box_.remove(workspace->button()); } @@ -226,13 +226,13 @@ void WorkspaceManager::clear_buttons() { } void WorkspaceManager::update_buttons() { - const auto *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + const auto* output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); // go through all workspace - for (const auto &workspace : workspaces_) { + for (const auto& workspace : workspaces_) { const bool workspace_on_any_group_for_output = - std::any_of(groups_.begin(), groups_.end(), [&](const auto &group) { + std::any_of(groups_.begin(), groups_.end(), [&](const auto& group) { const bool group_on_output = group->has_output(output) || all_outputs_; const bool workspace_on_group = group->has_workspace(workspace->handle()); return group_on_output && workspace_on_group; @@ -259,7 +259,7 @@ void WorkspaceManager::update_buttons() { // WorkspaceGroup -WorkspaceGroup::WorkspaceGroup(WorkspaceManager &manager, ext_workspace_group_handle_v1 *handle, +WorkspaceGroup::WorkspaceGroup(WorkspaceManager& manager, ext_workspace_group_handle_v1* handle, uint32_t id) : workspaces_manager_(manager), ext_handle_(handle), id_(id) { add_workspace_group_listener(ext_handle_, this); @@ -272,11 +272,11 @@ WorkspaceGroup::~WorkspaceGroup() { spdlog::debug("[ext/workspaces]: Workspace group {} destroyed", id_); } -bool WorkspaceGroup::has_output(const wl_output *output) { +bool WorkspaceGroup::has_output(const wl_output* output) { return std::find(outputs_.begin(), outputs_.end(), output) != outputs_.end(); } -bool WorkspaceGroup::has_workspace(const ext_workspace_handle_v1 *workspace) { +bool WorkspaceGroup::has_workspace(const ext_workspace_handle_v1* workspace) { return std::find(workspaces_.begin(), workspaces_.end(), workspace) != workspaces_.end(); } @@ -288,20 +288,20 @@ void WorkspaceGroup::handle_capabilities(uint32_t capabilities) { } } -void WorkspaceGroup::handle_output_enter(wl_output *output) { outputs_.push_back(output); } +void WorkspaceGroup::handle_output_enter(wl_output* output) { outputs_.push_back(output); } -void WorkspaceGroup::handle_output_leave(wl_output *output) { +void WorkspaceGroup::handle_output_leave(wl_output* output) { const auto it = std::find(outputs_.begin(), outputs_.end(), output); if (it != outputs_.end()) { outputs_.erase(it); } } -void WorkspaceGroup::handle_workspace_enter(ext_workspace_handle_v1 *handle) { +void WorkspaceGroup::handle_workspace_enter(ext_workspace_handle_v1* handle) { workspaces_.push_back(handle); } -void WorkspaceGroup::handle_workspace_leave(ext_workspace_handle_v1 *handle) { +void WorkspaceGroup::handle_workspace_leave(ext_workspace_handle_v1* handle) { const auto it = std::find(workspaces_.begin(), workspaces_.end(), handle); if (it != workspaces_.end()) { workspaces_.erase(it); @@ -315,30 +315,30 @@ void WorkspaceGroup::handle_removed() { // Workspace -Workspace::Workspace(const Json::Value &config, WorkspaceManager &manager, - ext_workspace_handle_v1 *handle, uint32_t id, const std::string &name) +Workspace::Workspace(const Json::Value& config, WorkspaceManager& manager, + ext_workspace_handle_v1* handle, uint32_t id, const std::string& name) : workspace_manager_(manager), ext_handle_(handle), id_(id), workspace_id_(name), name_(name) { add_workspace_listener(ext_handle_, this); // parse configuration - const auto &config_active_only = config["active-only"]; + const auto& config_active_only = config["active-only"]; if (config_active_only.isBool()) { active_only_ = config_active_only.asBool(); } - const auto &config_ignore_hidden = config["ignore-hidden"]; + const auto& config_ignore_hidden = config["ignore-hidden"]; if (config_ignore_hidden.isBool()) { ignore_hidden_ = config_ignore_hidden.asBool(); } - const auto &config_format = config["format"]; + const auto& config_format = config["format"]; format_ = config_format.isString() ? config_format.asString() : "{name}"; with_icon_ = format_.find("{icon}") != std::string::npos; if (with_icon_ && icon_map_.empty()) { - const auto &format_icons = config["format-icons"]; - for (auto &n : format_icons.getMemberNames()) { + const auto& format_icons = config["format-icons"]; + for (auto& n : format_icons.getMemberNames()) { icon_map_.emplace(n, format_icons[n].asString()); } } @@ -413,19 +413,19 @@ void Workspace::update() { fmt::arg("icon", with_icon_ ? icon() : ""))); } -void Workspace::handle_id(const std::string &id) { +void Workspace::handle_id(const std::string& id) { spdlog::debug("[ext/workspaces]: ID for workspace {}: {}", id_, id); workspace_id_ = id; workspace_manager_.set_needs_sorting(); } -void Workspace::handle_name(const std::string &name) { +void Workspace::handle_name(const std::string& name) { spdlog::debug("[ext/workspaces]: Name for workspace {}: {}", id_, name); name_ = name; workspace_manager_.set_needs_sorting(); } -void Workspace::handle_coordinates(const std::vector &coordinates) { +void Workspace::handle_coordinates(const std::vector& coordinates) { coordinates_ = coordinates; workspace_manager_.set_needs_sorting(); } @@ -453,7 +453,7 @@ void Workspace::handle_removed() { workspace_manager_.remove_workspace(id_); } -bool Workspace::handle_clicked(const GdkEventButton *button) const { +bool Workspace::handle_clicked(const GdkEventButton* button) const { std::string action; if (button->button == GDK_BUTTON_PRIMARY) { action = on_click_action_; diff --git a/src/modules/ext/workspace_manager_binding.cpp b/src/modules/ext/workspace_manager_binding.cpp index 2d9c6b48..91110faf 100644 --- a/src/modules/ext/workspace_manager_binding.cpp +++ b/src/modules/ext/workspace_manager_binding.cpp @@ -9,44 +9,44 @@ namespace waybar::modules::ext { -static void handle_global(void *data, wl_registry *registry, uint32_t name, const char *interface, +static void handle_global(void* data, wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { if (std::strcmp(interface, ext_workspace_manager_v1_interface.name) == 0) { - static_cast(data)->register_manager(registry, name, version); + static_cast(data)->register_manager(registry, name, version); } } -static void handle_global_remove(void *data, wl_registry *registry, uint32_t name) { +static void handle_global_remove(void* data, wl_registry* registry, uint32_t name) { /* Nothing to do here */ } static const wl_registry_listener registry_listener_impl = {.global = handle_global, .global_remove = handle_global_remove}; -void add_registry_listener(void *data) { - wl_display *display = Client::inst()->wl_display; - wl_registry *registry = wl_display_get_registry(display); +void add_registry_listener(void* data) { + wl_display* display = Client::inst()->wl_display; + wl_registry* registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener_impl, data); wl_display_roundtrip(display); } static void workspace_manager_handle_workspace_group( - void *data, ext_workspace_manager_v1 *_, ext_workspace_group_handle_v1 *workspace_group) { - static_cast(data)->handle_workspace_group(workspace_group); + void* data, ext_workspace_manager_v1* _, ext_workspace_group_handle_v1* workspace_group) { + static_cast(data)->handle_workspace_group(workspace_group); } -static void workspace_manager_handle_workspace(void *data, ext_workspace_manager_v1 *_, - ext_workspace_handle_v1 *workspace) { - static_cast(data)->handle_workspace(workspace); +static void workspace_manager_handle_workspace(void* data, ext_workspace_manager_v1* _, + ext_workspace_handle_v1* workspace) { + static_cast(data)->handle_workspace(workspace); } -static void workspace_manager_handle_done(void *data, ext_workspace_manager_v1 *_) { - static_cast(data)->handle_done(); +static void workspace_manager_handle_done(void* data, ext_workspace_manager_v1* _) { + static_cast(data)->handle_done(); } -static void workspace_manager_handle_finished(void *data, ext_workspace_manager_v1 *_) { - static_cast(data)->handle_finished(); +static void workspace_manager_handle_finished(void* data, ext_workspace_manager_v1* _) { + static_cast(data)->handle_finished(); } static const ext_workspace_manager_v1_listener workspace_manager_impl = { @@ -56,9 +56,9 @@ static const ext_workspace_manager_v1_listener workspace_manager_impl = { .finished = workspace_manager_handle_finished, }; -ext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t name, - uint32_t version, void *data) { - auto *workspace_manager = static_cast( +ext_workspace_manager_v1* workspace_manager_bind(wl_registry* registry, uint32_t name, + uint32_t version, void* data) { + auto* workspace_manager = static_cast( wl_registry_bind(registry, name, &ext_workspace_manager_v1_interface, version)); if (workspace_manager) @@ -69,33 +69,33 @@ ext_workspace_manager_v1 *workspace_manager_bind(wl_registry *registry, uint32_t return workspace_manager; } -static void workspace_group_handle_capabilities(void *data, ext_workspace_group_handle_v1 *_, +static void workspace_group_handle_capabilities(void* data, ext_workspace_group_handle_v1* _, uint32_t capabilities) { - static_cast(data)->handle_capabilities(capabilities); + static_cast(data)->handle_capabilities(capabilities); } -static void workspace_group_handle_output_enter(void *data, ext_workspace_group_handle_v1 *_, - wl_output *output) { - static_cast(data)->handle_output_enter(output); +static void workspace_group_handle_output_enter(void* data, ext_workspace_group_handle_v1* _, + wl_output* output) { + static_cast(data)->handle_output_enter(output); } -static void workspace_group_handle_output_leave(void *data, ext_workspace_group_handle_v1 *_, - wl_output *output) { - static_cast(data)->handle_output_leave(output); +static void workspace_group_handle_output_leave(void* data, ext_workspace_group_handle_v1* _, + wl_output* output) { + static_cast(data)->handle_output_leave(output); } -static void workspace_group_handle_workspace_enter(void *data, ext_workspace_group_handle_v1 *_, - ext_workspace_handle_v1 *workspace) { - static_cast(data)->handle_workspace_enter(workspace); +static void workspace_group_handle_workspace_enter(void* data, ext_workspace_group_handle_v1* _, + ext_workspace_handle_v1* workspace) { + static_cast(data)->handle_workspace_enter(workspace); } -static void workspace_group_handle_workspace_leave(void *data, ext_workspace_group_handle_v1 *_, - ext_workspace_handle_v1 *workspace) { - static_cast(data)->handle_workspace_leave(workspace); +static void workspace_group_handle_workspace_leave(void* data, ext_workspace_group_handle_v1* _, + ext_workspace_handle_v1* workspace) { + static_cast(data)->handle_workspace_leave(workspace); } -static void workspace_group_handle_removed(void *data, ext_workspace_group_handle_v1 *_) { - static_cast(data)->handle_removed(); +static void workspace_group_handle_removed(void* data, ext_workspace_group_handle_v1* _) { + static_cast(data)->handle_removed(); } static const ext_workspace_group_handle_v1_listener workspace_group_impl = { @@ -106,43 +106,43 @@ static const ext_workspace_group_handle_v1_listener workspace_group_impl = { .workspace_leave = workspace_group_handle_workspace_leave, .removed = workspace_group_handle_removed}; -void add_workspace_group_listener(ext_workspace_group_handle_v1 *workspace_group_handle, - void *data) { +void add_workspace_group_listener(ext_workspace_group_handle_v1* workspace_group_handle, + void* data) { ext_workspace_group_handle_v1_add_listener(workspace_group_handle, &workspace_group_impl, data); } -void workspace_handle_name(void *data, struct ext_workspace_handle_v1 *_, const char *name) { - static_cast(data)->handle_name(name); +void workspace_handle_name(void* data, struct ext_workspace_handle_v1* _, const char* name) { + static_cast(data)->handle_name(name); } -void workspace_handle_id(void *data, struct ext_workspace_handle_v1 *_, const char *id) { - static_cast(data)->handle_id(id); +void workspace_handle_id(void* data, struct ext_workspace_handle_v1* _, const char* id) { + static_cast(data)->handle_id(id); } -void workspace_handle_coordinates(void *data, struct ext_workspace_handle_v1 *_, - struct wl_array *coordinates) { +void workspace_handle_coordinates(void* data, struct ext_workspace_handle_v1* _, + struct wl_array* coordinates) { std::vector coords_vec; - auto coords = static_cast(coordinates->data); + auto coords = static_cast(coordinates->data); for (size_t i = 0; i < coordinates->size / sizeof(uint32_t); ++i) { coords_vec.push_back(coords[i]); } - static_cast(data)->handle_coordinates(coords_vec); + static_cast(data)->handle_coordinates(coords_vec); } -void workspace_handle_state(void *data, struct ext_workspace_handle_v1 *workspace_handle, +void workspace_handle_state(void* data, struct ext_workspace_handle_v1* workspace_handle, uint32_t state) { - static_cast(data)->handle_state(state); + static_cast(data)->handle_state(state); } -static void workspace_handle_capabilities(void *data, - struct ext_workspace_handle_v1 *workspace_handle, +static void workspace_handle_capabilities(void* data, + struct ext_workspace_handle_v1* workspace_handle, uint32_t capabilities) { - static_cast(data)->handle_capabilities(capabilities); + static_cast(data)->handle_capabilities(capabilities); } -void workspace_handle_removed(void *data, struct ext_workspace_handle_v1 *workspace_handle) { - static_cast(data)->handle_removed(); +void workspace_handle_removed(void* data, struct ext_workspace_handle_v1* workspace_handle) { + static_cast(data)->handle_removed(); } static const ext_workspace_handle_v1_listener workspace_impl = { @@ -153,7 +153,7 @@ static const ext_workspace_handle_v1_listener workspace_impl = { .capabilities = workspace_handle_capabilities, .removed = workspace_handle_removed}; -void add_workspace_listener(ext_workspace_handle_v1 *workspace_handle, void *data) { +void add_workspace_listener(ext_workspace_handle_v1* workspace_handle, void* data) { ext_workspace_handle_v1_add_listener(workspace_handle, &workspace_impl, data); } } // namespace waybar::modules::ext diff --git a/src/modules/hyprland/windowcreationpayload.cpp b/src/modules/hyprland/windowcreationpayload.cpp index 055b0f0c..61ea606f 100644 --- a/src/modules/hyprland/windowcreationpayload.cpp +++ b/src/modules/hyprland/windowcreationpayload.cpp @@ -11,7 +11,7 @@ namespace waybar::modules::hyprland { -WindowCreationPayload::WindowCreationPayload(Json::Value const &client_data) +WindowCreationPayload::WindowCreationPayload(Json::Value const& client_data) : m_window(std::make_pair(client_data["class"].asString(), client_data["title"].asString())), m_windowAddress(client_data["address"].asString()), m_workspaceName(client_data["workspace"]["name"].asString()) { @@ -73,7 +73,7 @@ void WindowCreationPayload::clearWorkspaceName() { } } -bool WindowCreationPayload::isEmpty(Workspaces &workspace_manager) { +bool WindowCreationPayload::isEmpty(Workspaces& workspace_manager) { if (std::holds_alternative(m_window)) { return std::get(m_window).empty(); } @@ -89,16 +89,16 @@ bool WindowCreationPayload::isEmpty(Workspaces &workspace_manager) { int WindowCreationPayload::incrementTimeSpentUncreated() { return m_timeSpentUncreated++; } -void WindowCreationPayload::moveToWorkspace(std::string &new_workspace_name) { +void WindowCreationPayload::moveToWorkspace(std::string& new_workspace_name) { m_workspaceName = new_workspace_name; } -WindowRepr WindowCreationPayload::repr(Workspaces &workspace_manager) { +WindowRepr WindowCreationPayload::repr(Workspaces& workspace_manager) { if (std::holds_alternative(m_window)) { return std::get(m_window); } if (std::holds_alternative(m_window)) { - auto const &[window_class, window_title] = std::get(m_window); + auto const& [window_class, window_title] = std::get(m_window); return {m_windowAddress, window_class, window_title, workspace_manager.getRewrite(window_class, window_title), m_isActive}; } diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index ae66bc84..4cdd8910 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -11,8 +11,8 @@ namespace waybar::modules::hyprland { -Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_manager, - const Json::Value &clients_data) +Workspace::Workspace(const Json::Value& workspace_data, Workspaces& workspace_manager, + const Json::Value& clients_data) : m_workspaceManager(workspace_manager), m_id(workspace_data["id"].asInt()), m_name(workspace_data["name"].asString()), @@ -45,8 +45,8 @@ Workspace::Workspace(const Json::Value &workspace_data, Workspaces &workspace_ma initializeWindowMap(clients_data); } -void addOrRemoveClass(const Glib::RefPtr &context, bool condition, - const std::string &class_name) { +void addOrRemoveClass(const Glib::RefPtr& context, bool condition, + const std::string& class_name) { if (condition) { context->add_class(class_name); } else { @@ -54,9 +54,9 @@ void addOrRemoveClass(const Glib::RefPtr &context, bool condi } } -std::optional Workspace::closeWindow(WindowAddress const &addr) { +std::optional Workspace::closeWindow(WindowAddress const& addr) { auto it = std::ranges::find_if(m_windowMap, - [&addr](const auto &window) { return window.address == addr; }); + [&addr](const auto& window) { return window.address == addr; }); // If the vector contains the address, remove it and return the window representation if (it != m_windowMap.end()) { WindowRepr windowRepr = *it; @@ -66,7 +66,7 @@ std::optional Workspace::closeWindow(WindowAddress const &addr) { return std::nullopt; } -bool Workspace::handleClicked(GdkEventButton *bt) const { +bool Workspace::handleClicked(GdkEventButton* bt) const { if (bt->type == GDK_BUTTON_PRESS) { try { if (id() > 0) { // normal @@ -87,14 +87,14 @@ bool Workspace::handleClicked(GdkEventButton *bt) const { m_ipc.getSocket1Reply("dispatch togglespecialworkspace"); } return true; - } catch (const std::exception &e) { + } catch (const std::exception& e) { spdlog::error("Failed to dispatch workspace: {}", e.what()); } } return false; } -void Workspace::initializeWindowMap(const Json::Value &clients_data) { +void Workspace::initializeWindowMap(const Json::Value& clients_data) { m_windowMap.clear(); for (auto client : clients_data) { if (client["workspace"]["id"].asInt() == id()) { @@ -103,10 +103,10 @@ void Workspace::initializeWindowMap(const Json::Value &clients_data) { } } -void Workspace::setActiveWindow(WindowAddress const &addr) { +void Workspace::setActiveWindow(WindowAddress const& addr) { std::optional activeIdx; for (size_t i = 0; i < m_windowMap.size(); ++i) { - auto &window = m_windowMap[i]; + auto& window = m_windowMap[i]; bool isActive = (window.address == addr); window.setActive(isActive); if (isActive) { @@ -133,7 +133,7 @@ void Workspace::insertWindow(WindowCreationPayload create_window_payload) { if (!repr.empty() || m_workspaceManager.enableTaskbar()) { auto addr = create_window_payload.getAddress(); auto it = std::ranges::find_if( - m_windowMap, [&addr](const auto &window) { return window.address == addr; }); + m_windowMap, [&addr](const auto& window) { return window.address == addr; }); // If the vector contains the address, update the window representation, otherwise insert it if (it != m_windowMap.end()) { *it = repr; @@ -144,7 +144,7 @@ void Workspace::insertWindow(WindowCreationPayload create_window_payload) { } }; -bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_payload) { +bool Workspace::onWindowOpened(WindowCreationPayload const& create_window_payload) { if (create_window_payload.getWorkspaceName() == name()) { insertWindow(create_window_payload); return true; @@ -152,7 +152,7 @@ bool Workspace::onWindowOpened(WindowCreationPayload const &create_window_payloa return false; } -std::string &Workspace::selectIcon(std::map &icons_map) { +std::string& Workspace::selectIcon(std::map& icons_map) { spdlog::trace("Selecting icon for workspace {}", name()); if (isUrgent()) { auto urgentIconIt = icons_map.find("urgent"); @@ -209,7 +209,7 @@ std::string &Workspace::selectIcon(std::map &icons_map return m_name; } -void Workspace::update(const std::string &workspace_icon) { +void Workspace::update(const std::string& workspace_icon) { if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) { m_button.hide(); return; @@ -248,7 +248,7 @@ void Workspace::update(const std::string &workspace_icon) { bool isNotFirst = false; - for (const auto &window_repr : m_windowMap) { + for (const auto& window_repr : m_windowMap) { if (isNotFirst) { windows.append(windowSeparator); } @@ -276,10 +276,10 @@ bool Workspace::isEmpty() const { // 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); }); + [this, &ignore_list](const auto& window_repr) { return shouldSkipWindow(window_repr); }); } -void Workspace::updateTaskbar(const std::string &workspace_icon) { +void Workspace::updateTaskbar(const std::string& workspace_icon) { for (auto child : m_content.get_children()) { if (child != &m_labelBefore) { m_content.remove(*child); @@ -287,7 +287,7 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { } bool isFirst = true; - auto processWindow = [&](const WindowRepr &window_repr) { + auto processWindow = [&](const WindowRepr& window_repr) { if (shouldSkipWindow(window_repr)) { return; // skip } @@ -343,7 +343,7 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { processWindow(*it); } } else { - for (const auto &window_repr : m_windowMap) { + for (const auto& window_repr : m_windowMap) { processWindow(window_repr); } } @@ -358,7 +358,7 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { } } -bool Workspace::handleClick(const GdkEventButton *event_button, WindowAddress const &addr) const { +bool Workspace::handleClick(const GdkEventButton* event_button, WindowAddress const& addr) const { if (event_button->type == GDK_BUTTON_PRESS) { std::string command = std::regex_replace(m_workspaceManager.onClickWindow(), std::regex("\\{address\\}"), "0x" + addr); @@ -372,9 +372,9 @@ bool Workspace::handleClick(const GdkEventButton *event_button, WindowAddress co return true; } -bool Workspace::shouldSkipWindow(const WindowRepr &window_repr) const { +bool Workspace::shouldSkipWindow(const WindowRepr& window_repr) const { auto ignore_list = m_workspaceManager.getIgnoredWindows(); - auto it = std::ranges::find_if(ignore_list, [&window_repr](const auto &ignoreItem) { + auto it = std::ranges::find_if(ignore_list, [&window_repr](const auto& ignoreItem) { return std::regex_match(window_repr.window_class, ignoreItem) || std::regex_match(window_repr.window_title, ignoreItem); }); diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index fe38eabd..3bbe47df 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -14,7 +14,7 @@ namespace waybar::modules::hyprland { -Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) +Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config) : AModule(config, "workspaces", id, false, false), m_bar(bar), m_box(bar.orientation, 0), @@ -46,8 +46,8 @@ void Workspaces::init() { dp.emit(); } -Json::Value Workspaces::createMonitorWorkspaceData(std::string const &name, - std::string const &monitor) { +Json::Value Workspaces::createMonitorWorkspaceData(std::string const& name, + std::string const& monitor) { spdlog::trace("Creating persistent workspace: {} on monitor {}", name, monitor); Json::Value workspaceData; @@ -62,14 +62,14 @@ Json::Value Workspaces::createMonitorWorkspaceData(std::string const &name, return workspaceData; } -void Workspaces::createWorkspace(Json::Value const &workspace_data, - Json::Value const &clients_data) { +void Workspaces::createWorkspace(Json::Value const& workspace_data, + Json::Value const& clients_data) { auto workspaceName = workspace_data["name"].asString(); auto workspaceId = workspace_data["id"].asInt(); spdlog::debug("Creating workspace {}", workspaceName); // avoid recreating existing workspaces - auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr const &w) { + auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr const& w) { if (workspaceId > 0) { return w->id() == workspaceId; } @@ -81,7 +81,7 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data, // don't recreate workspace, but update persistency if necessary const auto keys = workspace_data.getMemberNames(); - const auto *k = "persistent-rule"; + const auto* k = "persistent-rule"; if (std::ranges::find(keys, k) != keys.end()) { spdlog::debug("Set dynamic persistency of workspace {} to: {}", workspaceName, workspace_data[k].asBool() ? "true" : "false"); @@ -100,14 +100,14 @@ void Workspaces::createWorkspace(Json::Value const &workspace_data, // create new workspace m_workspaces.emplace_back(std::make_unique(workspace_data, *this, clients_data)); - Gtk::Button &newWorkspaceButton = m_workspaces.back()->button(); + Gtk::Button& newWorkspaceButton = m_workspaces.back()->button(); m_box.pack_start(newWorkspaceButton, false, false); sortWorkspaces(); newWorkspaceButton.show_all(); } void Workspaces::createWorkspacesToCreate() { - for (const auto &[workspaceData, clientsData] : m_workspacesToCreate) { + for (const auto& [workspaceData, clientsData] : m_workspacesToCreate) { createWorkspace(workspaceData, clientsData); } if (!m_workspacesToCreate.empty()) { @@ -139,9 +139,9 @@ void Workspaces::doUpdate() { } } -void Workspaces::extendOrphans(int workspaceId, Json::Value const &clientsJson) { +void Workspaces::extendOrphans(int workspaceId, Json::Value const& clientsJson) { spdlog::trace("Extending orphans with workspace {}", workspaceId); - for (const auto &client : clientsJson) { + for (const auto& client : clientsJson) { if (client["workspace"]["id"].asInt() == workspaceId) { registerOrphanWindow({client}); } @@ -163,7 +163,7 @@ std::string Workspaces::getRewrite(std::string window_class, std::string window_ std::vector Workspaces::getVisibleWorkspaces() { std::vector visibleWorkspaces; auto monitors = IPC::inst().getSocket1JsonReply("monitors"); - for (const auto &monitor : monitors) { + for (const auto& monitor : monitors) { auto ws = monitor["activeWorkspace"]; if (ws.isObject() && ws["id"].isInt()) { visibleWorkspaces.push_back(ws["id"].asInt()); @@ -181,7 +181,7 @@ void Workspaces::initializeWorkspaces() { spdlog::debug("Initializing workspaces"); // if the workspace rules changed since last initialization, make sure we reset everything: - for (auto &workspace : m_workspaces) { + for (auto& workspace : m_workspaces) { m_workspacesToRemove.push_back(std::to_string(workspace->id())); } @@ -209,7 +209,7 @@ void Workspaces::initializeWorkspaces() { loadPersistentWorkspacesFromWorkspaceRules(clientsJson); } -bool isDoubleSpecial(std::string const &workspace_name) { +bool isDoubleSpecial(std::string const& workspace_name) { // Hyprland's IPC sometimes reports the creation of workspaces strangely named // `special:special:`. This function checks for that and is used // to avoid creating (and then removing) such workspaces. @@ -217,8 +217,8 @@ bool isDoubleSpecial(std::string const &workspace_name) { return workspace_name.find("special:special:") != std::string::npos; } -bool Workspaces::isWorkspaceIgnored(std::string const &name) { - for (auto &rule : m_ignoreWorkspaces) { +bool Workspaces::isWorkspaceIgnored(std::string const& name) { + for (auto& rule : m_ignoreWorkspaces) { if (std::regex_match(name, rule)) { return true; break; @@ -228,19 +228,19 @@ bool Workspaces::isWorkspaceIgnored(std::string const &name) { return false; } -void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJson) { +void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const& clientsJson) { spdlog::info("Loading persistent workspaces from Waybar config"); const std::vector keys = m_persistentWorkspaceConfig.getMemberNames(); std::vector persistentWorkspacesToCreate; const std::string currentMonitor = m_bar.output->name; const bool monitorInConfig = std::ranges::find(keys, currentMonitor) != keys.end(); - for (const std::string &key : keys) { + for (const std::string& key : keys) { // only add if either: // 1. key is the current monitor name // 2. key is "*" and this monitor is not already defined in the config bool canCreate = key == currentMonitor || (key == "*" && !monitorInConfig); - const Json::Value &value = m_persistentWorkspaceConfig[key]; + const Json::Value& value = m_persistentWorkspaceConfig[key]; spdlog::trace("Parsing persistent workspace config: {} => {}", key, value.toStyledString()); if (value.isInt()) { @@ -255,13 +255,13 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs } else if (value.isArray() && !value.empty()) { // value is an array => create defined workspaces for this monitor if (canCreate) { - for (const Json::Value &workspace : value) { + for (const Json::Value& workspace : value) { spdlog::debug("Creating workspace {} on monitor {}", workspace, currentMonitor); persistentWorkspacesToCreate.emplace_back(workspace.asString()); } } else { // key is the workspace and value is array of monitors to create on - for (const Json::Value &monitor : value) { + for (const Json::Value& monitor : value) { if (monitor.isString() && monitor.asString() == currentMonitor) { persistentWorkspacesToCreate.emplace_back(currentMonitor); break; @@ -274,18 +274,18 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const &clientsJs } } - for (auto const &workspace : persistentWorkspacesToCreate) { + for (auto const& workspace : persistentWorkspacesToCreate) { auto workspaceData = createMonitorWorkspaceData(workspace, m_bar.output->name); workspaceData["persistent-config"] = true; m_workspacesToCreate.emplace_back(workspaceData, clientsJson); } } -void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &clientsJson) { +void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value& clientsJson) { spdlog::info("Loading persistent workspaces from Hyprland workspace rules"); auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules"); - for (Json::Value const &rule : workspaceRules) { + for (Json::Value const& rule : workspaceRules) { if (!rule["workspaceString"].isString()) { spdlog::warn("Workspace rules: invalid workspaceString, skipping: {}", rule); continue; @@ -305,7 +305,7 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c if (workspace.starts_with("name:")) { workspace = workspace.substr(5); } - auto const &monitor = rule["monitor"].asString(); + auto const& monitor = rule["monitor"].asString(); // create this workspace persistently if: // 1. the allOutputs config option is enabled // 2. the rule's monitor is the current monitor @@ -322,7 +322,7 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value &c } } -void Workspaces::onEvent(const std::string &ev) { +void Workspaces::onEvent(const std::string& ev) { std::lock_guard lock(m_mutex); std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>')); std::string payload = ev.substr(eventName.size() + 2); @@ -360,7 +360,7 @@ void Workspaces::onEvent(const std::string &ev) { dp.emit(); } -void Workspaces::onWorkspaceActivated(std::string const &payload) { +void Workspaces::onWorkspaceActivated(std::string const& payload) { const auto [workspaceIdStr, workspaceName] = splitDoublePayload(payload); const auto workspaceId = parseWorkspaceId(workspaceIdStr); if (workspaceId.has_value()) { @@ -368,19 +368,19 @@ void Workspaces::onWorkspaceActivated(std::string const &payload) { } } -void Workspaces::onSpecialWorkspaceActivated(std::string const &payload) { +void Workspaces::onSpecialWorkspaceActivated(std::string const& payload) { std::string name(begin(payload), begin(payload) + payload.find_first_of(',')); m_activeSpecialWorkspaceName = (!name.starts_with("special:") ? name : name.substr(8)); } -void Workspaces::onWorkspaceDestroyed(std::string const &payload) { +void Workspaces::onWorkspaceDestroyed(std::string const& payload) { const auto [workspaceId, workspaceName] = splitDoublePayload(payload); if (!isDoubleSpecial(workspaceName)) { m_workspacesToRemove.push_back(workspaceId); } } -void Workspaces::onWorkspaceCreated(std::string const &payload, Json::Value const &clientsData) { +void Workspaces::onWorkspaceCreated(std::string const& payload, Json::Value const& clientsData) { spdlog::debug("Workspace created: {}", payload); const auto [workspaceIdStr, _] = splitDoublePayload(payload); @@ -407,7 +407,7 @@ void Workspaces::onWorkspaceCreated(std::string const &payload, Json::Value cons if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && (showSpecial() || !workspaceName.starts_with("special")) && !isDoubleSpecial(workspaceName)) { - for (Json::Value const &rule : workspaceRules) { + for (Json::Value const& rule : workspaceRules) { auto ruleWorkspaceName = rule.isMember("defaultName") ? rule["defaultName"].asString() : rule["workspaceString"].asString(); @@ -426,7 +426,7 @@ void Workspaces::onWorkspaceCreated(std::string const &payload, Json::Value cons } } -void Workspaces::onWorkspaceMoved(std::string const &payload) { +void Workspaces::onWorkspaceMoved(std::string const& payload) { spdlog::debug("Workspace moved: {}", payload); // Update active workspace @@ -447,7 +447,7 @@ void Workspaces::onWorkspaceMoved(std::string const &payload) { } } -void Workspaces::onWorkspaceRenamed(std::string const &payload) { +void Workspaces::onWorkspaceRenamed(std::string const& payload) { spdlog::debug("Workspace renamed: {}", payload); const auto [workspaceIdStr, newName] = splitDoublePayload(payload); @@ -456,7 +456,7 @@ void Workspaces::onWorkspaceRenamed(std::string const &payload) { return; } - for (auto &workspace : m_workspaces) { + for (auto& workspace : m_workspaces) { if (workspace->id() == *workspaceId) { workspace->setName(newName); break; @@ -465,7 +465,7 @@ void Workspaces::onWorkspaceRenamed(std::string const &payload) { sortWorkspaces(); } -void Workspaces::onMonitorFocused(std::string const &payload) { +void Workspaces::onMonitorFocused(std::string const& payload) { spdlog::trace("Monitor focused: {}", payload); const auto [monitorName, workspaceIdStr] = splitDoublePayload(payload); @@ -477,7 +477,7 @@ void Workspaces::onMonitorFocused(std::string const &payload) { m_activeWorkspaceId = *workspaceId; - for (Json::Value &monitor : m_ipc.getSocket1JsonReply("monitors")) { + for (Json::Value& monitor : m_ipc.getSocket1JsonReply("monitors")) { if (monitor["name"].asString() == monitorName) { const auto name = monitor["specialWorkspace"]["name"].asString(); m_activeSpecialWorkspaceName = !name.starts_with("special:") ? name : name.substr(8); @@ -485,7 +485,7 @@ void Workspaces::onMonitorFocused(std::string const &payload) { } } -void Workspaces::onWindowOpened(std::string const &payload) { +void Workspaces::onWindowOpened(std::string const& payload) { spdlog::trace("Window opened: {}", payload); updateWindowCount(); size_t lastCommaIdx = 0; @@ -506,18 +506,18 @@ void Workspaces::onWindowOpened(std::string const &payload) { m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle, isActive); } -void Workspaces::onWindowClosed(std::string const &addr) { +void Workspaces::onWindowClosed(std::string const& addr) { spdlog::trace("Window closed: {}", addr); updateWindowCount(); m_orphanWindowMap.erase(addr); - for (auto &workspace : m_workspaces) { + for (auto& workspace : m_workspaces) { if (workspace->closeWindow(addr)) { break; } } } -void Workspaces::onWindowMoved(std::string const &payload) { +void Workspaces::onWindowMoved(std::string const& payload) { spdlog::trace("Window moved: {}", payload); updateWindowCount(); auto [windowAddress, _, workspaceName] = splitTriplePayload(payload); @@ -526,7 +526,7 @@ void Workspaces::onWindowMoved(std::string const &payload) { // If the window was still queued to be created, just change its destination // and exit - for (auto &window : m_windowsToCreate) { + for (auto& window : m_windowsToCreate) { if (window.getAddress() == windowAddress) { window.moveToWorkspace(workspaceName); return; @@ -534,7 +534,7 @@ void Workspaces::onWindowMoved(std::string const &payload) { } // Take the window's representation from the old workspace... - for (auto &workspace : m_workspaces) { + for (auto& workspace : m_workspaces) { if (auto windowAddr = workspace->closeWindow(windowAddress); windowAddr != std::nullopt) { windowRepr = windowAddr.value(); break; @@ -553,7 +553,7 @@ void Workspaces::onWindowMoved(std::string const &payload) { } } -void Workspaces::onWindowTitleEvent(std::string const &payload) { +void Workspaces::onWindowTitleEvent(std::string const& payload) { spdlog::trace("Window title changed: {}", payload); std::optional> inserter; @@ -563,7 +563,7 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { if (m_orphanWindowMap.contains(windowAddress)) { inserter = [this](WindowCreationPayload wcp) { this->registerOrphanWindow(std::move(wcp)); }; } else { - auto windowWorkspace = std::ranges::find_if(m_workspaces, [windowAddress](auto &workspace) { + auto windowWorkspace = std::ranges::find_if(m_workspaces, [windowAddress](auto& workspace) { return workspace->containsWindow(windowAddress); }); @@ -575,7 +575,7 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { }; } else { auto queuedWindow = - std::ranges::find_if(m_windowsToCreate, [&windowAddress](auto &windowPayload) { + std::ranges::find_if(m_windowsToCreate, [&windowAddress](auto& windowPayload) { return windowPayload.getAddress() == windowAddress; }); @@ -590,7 +590,7 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { Json::Value clientsData = m_ipc.getSocket1JsonReply("clients"); std::string jsonWindowAddress = fmt::format("0x{}", windowAddress); - auto client = std::ranges::find_if(clientsData, [jsonWindowAddress](auto &client) { + auto client = std::ranges::find_if(clientsData, [jsonWindowAddress](auto& client) { return client["address"].asString() == jsonWindowAddress; }); @@ -600,17 +600,17 @@ void Workspaces::onWindowTitleEvent(std::string const &payload) { } } -void Workspaces::onActiveWindowChanged(WindowAddress const &activeWindowAddress) { +void Workspaces::onActiveWindowChanged(WindowAddress const& activeWindowAddress) { spdlog::trace("Active window changed: {}", activeWindowAddress); m_currentActiveWindowAddress = activeWindowAddress; - for (auto &[address, window] : m_orphanWindowMap) { + for (auto& [address, window] : m_orphanWindowMap) { window.setActive(address == activeWindowAddress); } - for (auto const &workspace : m_workspaces) { + for (auto const& workspace : m_workspaces) { workspace->setActiveWindow(activeWindowAddress); } - for (auto &window : m_windowsToCreate) { + for (auto& window : m_windowsToCreate) { window.setActive(window.getAddress() == activeWindowAddress); } } @@ -620,8 +620,8 @@ void Workspaces::onConfigReloaded() { init(); } -auto Workspaces::parseConfig(const Json::Value &config) -> void { - const auto &configFormat = config["format"]; +auto Workspaces::parseConfig(const Json::Value& config) -> void { + const auto& configFormat = config["format"]; m_formatBefore = configFormat.isString() ? configFormat.asString() : "{name}"; m_withIcon = m_formatBefore.find("{icon}") != std::string::npos; auto withWindows = m_formatBefore.find("{windows}") != std::string::npos; @@ -653,28 +653,28 @@ auto Workspaces::parseConfig(const Json::Value &config) -> void { } } -auto Workspaces::populateIconsMap(const Json::Value &formatIcons) -> void { - for (const auto &name : formatIcons.getMemberNames()) { +auto Workspaces::populateIconsMap(const Json::Value& formatIcons) -> void { + for (const auto& name : formatIcons.getMemberNames()) { m_iconsMap.emplace(name, formatIcons[name].asString()); } m_iconsMap.emplace("", ""); } -auto Workspaces::populateBoolConfig(const Json::Value &config, const std::string &key, bool &member) +auto Workspaces::populateBoolConfig(const Json::Value& config, const std::string& key, bool& member) -> void { - const auto &configValue = config[key]; + const auto& configValue = config[key]; if (configValue.isBool()) { member = configValue.asBool(); } } -auto Workspaces::populateSortByConfig(const Json::Value &config) -> void { - const auto &configSortBy = config["sort-by"]; +auto Workspaces::populateSortByConfig(const Json::Value& config) -> void { + const auto& configSortBy = config["sort-by"]; if (configSortBy.isString()) { auto sortByStr = configSortBy.asString(); try { m_sortBy = m_enumParser.parseStringToEnum(sortByStr, m_sortMap); - } catch (const std::invalid_argument &e) { + } catch (const std::invalid_argument& e) { m_sortBy = SortMethod::DEFAULT; spdlog::warn( "Invalid string representation for sort-by. Falling back to default sort method."); @@ -682,16 +682,16 @@ auto Workspaces::populateSortByConfig(const Json::Value &config) -> void { } } -auto Workspaces::populateIgnoreWorkspacesConfig(const Json::Value &config) -> void { +auto Workspaces::populateIgnoreWorkspacesConfig(const Json::Value& config) -> void { auto ignoreWorkspaces = config["ignore-workspaces"]; if (ignoreWorkspaces.isArray()) { - for (const auto &workspaceRegex : ignoreWorkspaces) { + for (const auto& workspaceRegex : ignoreWorkspaces) { if (workspaceRegex.isString()) { std::string ruleString = workspaceRegex.asString(); try { const std::regex rule{ruleString, std::regex_constants::icase}; m_ignoreWorkspaces.emplace_back(rule); - } catch (const std::regex_error &e) { + } catch (const std::regex_error& e) { spdlog::error("Invalid rule {}: {}", ruleString, e.what()); } } else { @@ -701,30 +701,30 @@ auto Workspaces::populateIgnoreWorkspacesConfig(const Json::Value &config) -> vo } } -auto Workspaces::populateFormatWindowSeparatorConfig(const Json::Value &config) -> void { - const auto &formatWindowSeparator = config["format-window-separator"]; +auto Workspaces::populateFormatWindowSeparatorConfig(const Json::Value& config) -> void { + const auto& formatWindowSeparator = config["format-window-separator"]; m_formatWindowSeparator = formatWindowSeparator.isString() ? formatWindowSeparator.asString() : " "; } -auto Workspaces::populateWindowRewriteConfig(const Json::Value &config) -> void { - const auto &windowRewrite = config["window-rewrite"]; +auto Workspaces::populateWindowRewriteConfig(const Json::Value& config) -> void { + const auto& windowRewrite = config["window-rewrite"]; if (!windowRewrite.isObject()) { spdlog::debug("window-rewrite is not defined or is not an object, using default rules."); return; } - const auto &windowRewriteDefaultConfig = config["window-rewrite-default"]; + const auto& windowRewriteDefaultConfig = config["window-rewrite-default"]; std::string windowRewriteDefault = windowRewriteDefaultConfig.isString() ? windowRewriteDefaultConfig.asString() : "?"; m_windowRewriteRules = util::RegexCollection( windowRewrite, windowRewriteDefault, - [this](std::string &window_rule) { return windowRewritePriorityFunction(window_rule); }); + [this](std::string& window_rule) { return windowRewritePriorityFunction(window_rule); }); } -auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> void { - const auto &workspaceTaskbar = config["workspace-taskbar"]; +auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value& config) -> void { + const auto& workspaceTaskbar = config["workspace-taskbar"]; if (!workspaceTaskbar.isObject()) { spdlog::debug("workspace-taskbar is not defined or is not an object, using default rules."); return; @@ -751,7 +751,7 @@ auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> vo auto iconTheme = workspaceTaskbar["icon-theme"]; if (iconTheme.isArray()) { - for (auto &c : iconTheme) { + for (auto& c : iconTheme) { m_iconLoader.add_custom_icon_theme(c.asString()); } } else if (iconTheme.isString()) { @@ -771,11 +771,11 @@ auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> vo } if (workspaceTaskbar["ignore-list"].isArray()) { - for (auto &windowRegex : workspaceTaskbar["ignore-list"]) { + for (auto& windowRegex : workspaceTaskbar["ignore-list"]) { std::string ruleString = windowRegex.asString(); try { m_ignoreWindows.emplace_back(ruleString, std::regex_constants::icase); - } catch (const std::regex_error &e) { + } catch (const std::regex_error& e) { spdlog::error("Invalid rule {}: {}", ruleString, e.what()); } } @@ -786,7 +786,7 @@ auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> vo try { m_activeWindowPosition = m_activeWindowEnumParser.parseStringToEnum(posStr, m_activeWindowPositionMap); - } catch (const std::invalid_argument &e) { + } catch (const std::invalid_argument& e) { spdlog::warn( "Invalid string representation for active-window-position. Falling back to 'none'."); m_activeWindowPosition = ActiveWindowPosition::NONE; @@ -829,13 +829,13 @@ auto Workspaces::registerIpc() -> void { } void Workspaces::removeWorkspacesToRemove() { - for (const auto &workspaceString : m_workspacesToRemove) { + for (const auto& workspaceString : m_workspacesToRemove) { removeWorkspace(workspaceString); } m_workspacesToRemove.clear(); } -void Workspaces::removeWorkspace(std::string const &workspaceString) { +void Workspaces::removeWorkspace(std::string const& workspaceString) { spdlog::debug("Removing workspace {}", workspaceString); // If this succeeds, we have a workspace ID. @@ -853,7 +853,7 @@ void Workspaces::removeWorkspace(std::string const &workspaceString) { name = workspaceString; } - const auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr &x) { + const auto workspace = std::ranges::find_if(m_workspaces, [&](std::unique_ptr& x) { if (workspaceId.has_value()) { return *workspaceId == x->id(); } @@ -879,7 +879,7 @@ void Workspaces::setCurrentMonitorId() { // get monitor ID from name (used by persistent workspaces) m_monitorId = 0; auto monitors = m_ipc.getSocket1JsonReply("monitors"); - auto currentMonitor = std::ranges::find_if(monitors, [this](const Json::Value &m) { + auto currentMonitor = std::ranges::find_if(monitors, [this](const Json::Value& m) { return m["name"].asString() == m_bar.output->name; }); if (currentMonitor == monitors.end()) { @@ -895,7 +895,7 @@ void Workspaces::sortSpecialCentered() { std::vector> hiddenWorkspaces; std::vector> normalWorkspaces; - for (auto &workspace : m_workspaces) { + for (auto& workspace : m_workspaces) { if (workspace->isSpecial()) { specialWorkspaces.push_back(std::move(workspace)); } else { @@ -926,7 +926,7 @@ void Workspaces::sortSpecialCentered() { void Workspaces::sortWorkspaces() { std::ranges::sort( // - m_workspaces, [&](std::unique_ptr &a, std::unique_ptr &b) { + m_workspaces, [&](std::unique_ptr& a, std::unique_ptr& b) { // Helper comparisons auto isIdLess = a->id() < b->id(); auto isNameLess = a->name() < b->name(); @@ -939,7 +939,7 @@ void Workspaces::sortWorkspaces() { case SortMethod::NUMBER: try { return std::stoi(a->name()) < std::stoi(b->name()); - } catch (const std::invalid_argument &) { + } catch (const std::invalid_argument&) { // Handle the exception if necessary. break; } @@ -991,7 +991,7 @@ void Workspaces::sortWorkspaces() { } } -void Workspaces::setUrgentWorkspace(std::string const &windowaddress) { +void Workspaces::setUrgentWorkspace(std::string const& windowaddress) { const Json::Value clientsJson = m_ipc.getSocket1JsonReply("clients"); int workspaceId = -1; @@ -1002,7 +1002,7 @@ void Workspaces::setUrgentWorkspace(std::string const &windowaddress) { } } - auto workspace = std::ranges::find_if(m_workspaces, [workspaceId](std::unique_ptr &x) { + auto workspace = std::ranges::find_if(m_workspaces, [workspaceId](std::unique_ptr& x) { return x->id() == workspaceId; }); if (workspace != m_workspaces.end()) { @@ -1017,8 +1017,8 @@ auto Workspaces::update() -> void { void Workspaces::updateWindowCount() { const Json::Value workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); - for (auto const &workspace : m_workspaces) { - auto workspaceJson = std::ranges::find_if(workspacesJson, [&](Json::Value const &x) { + for (auto const& workspace : m_workspaces) { + auto workspaceJson = std::ranges::find_if(workspacesJson, [&](Json::Value const& x) { return x["name"].asString() == workspace->name() || (workspace->isSpecial() && x["name"].asString() == "special:" + workspace->name()); }); @@ -1026,7 +1026,7 @@ void Workspaces::updateWindowCount() { if (workspaceJson != workspacesJson.end()) { try { count = (*workspaceJson)["windows"].asUInt(); - } catch (const std::exception &e) { + } catch (const std::exception& e) { spdlog::error("Failed to update window count: {}", e.what()); } } @@ -1037,9 +1037,9 @@ void Workspaces::updateWindowCount() { bool Workspaces::updateWindowsToCreate() { bool anyWindowCreated = false; std::vector notCreated; - for (auto &windowPayload : m_windowsToCreate) { + for (auto& windowPayload : m_windowsToCreate) { bool created = false; - for (auto &workspace : m_workspaces) { + for (auto& workspace : m_workspaces) { if (workspace->onWindowOpened(windowPayload)) { created = true; anyWindowCreated = true; @@ -1068,7 +1068,7 @@ void Workspaces::updateWorkspaceStates() { std::string currentWorkspaceName = currentWorkspace.isMember("name") ? currentWorkspace["name"].asString() : ""; - for (auto &workspace : m_workspaces) { + for (auto& workspace : m_workspaces) { bool isActiveByName = !currentWorkspaceName.empty() && workspace->name() == currentWorkspaceName; @@ -1080,11 +1080,11 @@ void Workspaces::updateWorkspaceStates() { } workspace->setVisible(std::ranges::find(visibleWorkspaces, workspace->id()) != visibleWorkspaces.end()); - std::string &workspaceIcon = m_iconsMap[""]; + std::string& workspaceIcon = m_iconsMap[""]; if (m_withIcon) { workspaceIcon = workspace->selectIcon(m_iconsMap); } - auto updatedWorkspace = std::ranges::find_if(updatedWorkspaces, [&workspace](const auto &w) { + auto updatedWorkspace = std::ranges::find_if(updatedWorkspaces, [&workspace](const auto& w) { auto wNameRaw = w["name"].asString(); auto wName = wNameRaw.starts_with("special:") ? wNameRaw.substr(8) : wNameRaw; return wName == workspace->name(); @@ -1096,7 +1096,7 @@ void Workspaces::updateWorkspaceStates() { } } -int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) { +int Workspaces::windowRewritePriorityFunction(std::string const& window_rule) { // Rules that match against title are prioritized // Rules that don't specify if they're matching against either title or class are deprioritized bool const hasTitle = window_rule.find("title") != std::string::npos; @@ -1117,21 +1117,21 @@ int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) { } template -std::string Workspaces::makePayload(Args const &...args) { +std::string Workspaces::makePayload(Args const&... args) { std::ostringstream result; bool first = true; ((result << (first ? "" : ",") << args, first = false), ...); return result.str(); } -std::pair Workspaces::splitDoublePayload(std::string const &payload) { +std::pair Workspaces::splitDoublePayload(std::string const& payload) { const std::string part1 = payload.substr(0, payload.find(',')); const std::string part2 = payload.substr(part1.size() + 1); return {part1, part2}; } std::tuple Workspaces::splitTriplePayload( - std::string const &payload) { + std::string const& payload) { const size_t firstComma = payload.find(','); const size_t secondComma = payload.find(',', firstComma + 1); @@ -1142,10 +1142,10 @@ std::tuple Workspaces::splitTriplePayload return {part1, part2, part3}; } -std::optional Workspaces::parseWorkspaceId(std::string const &workspaceIdStr) { +std::optional Workspaces::parseWorkspaceId(std::string const& workspaceIdStr) { try { return workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr); - } catch (std::exception const &e) { + } catch (std::exception const& e) { spdlog::debug("Workspace \"{}\" is not bound to an id: {}", workspaceIdStr, e.what()); return std::nullopt; } diff --git a/src/modules/jack.cpp b/src/modules/jack.cpp index 9bd6fcd6..678f986b 100644 --- a/src/modules/jack.cpp +++ b/src/modules/jack.cpp @@ -2,7 +2,7 @@ namespace waybar::modules { -JACK::JACK(const std::string &id, const Json::Value &config) +JACK::JACK(const std::string& id, const Json::Value& config) : ALabel(config, "jack", id, "{load}%", 1) { running_ = false; client_ = NULL; @@ -113,14 +113,14 @@ void JACK::shutdown() { } // namespace waybar::modules -int bufSizeCallback(jack_nframes_t size, void *obj) { - return static_cast(obj)->bufSize(size); +int bufSizeCallback(jack_nframes_t size, void* obj) { + return static_cast(obj)->bufSize(size); } -int sampleRateCallback(jack_nframes_t rate, void *obj) { - return static_cast(obj)->sampleRate(rate); +int sampleRateCallback(jack_nframes_t rate, void* obj) { + return static_cast(obj)->sampleRate(rate); } -int xrunCallback(void *obj) { return static_cast(obj)->xrun(); } +int xrunCallback(void* obj) { return static_cast(obj)->xrun(); } -void shutdownCallback(void *obj) { return static_cast(obj)->shutdown(); } +void shutdownCallback(void* obj) { return static_cast(obj)->shutdown(); } diff --git a/src/modules/network.cpp b/src/modules/network.cpp index d0b7970c..c33e750d 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -21,10 +21,10 @@ namespace { using namespace waybar::util; -constexpr const char *DEFAULT_FORMAT = "{ifname}"; +constexpr const char* DEFAULT_FORMAT = "{ifname}"; } // namespace -constexpr const char *NETDEV_FILE = +constexpr const char* NETDEV_FILE = "/proc/net/dev"; // std::ifstream does not take std::string_view as param std::optional> waybar::modules::Network::readBandwidthUsage() { @@ -82,7 +82,7 @@ waybar::modules::Network::readBandwidthUsage() { return {{receivedBytes, transmittedBytes}}; } -waybar::modules::Network::Network(const std::string &id, const Json::Value &config) +waybar::modules::Network::Network(const std::string& id, const Json::Value& config) : ALabel(config, "network", id, DEFAULT_FORMAT, 60) { // Start with some "text" in the module's label_. update() will then // update it. Since the text should be different, update() will be able @@ -216,7 +216,7 @@ void waybar::modules::Network::worker() { thread_timer_.sleep_for(interval_); }; #ifdef WANT_RFKILL - rfkill_.on_update.connect([this](auto &) { + rfkill_.on_update.connect([this](auto&) { /* If we are here, it's likely that the network thread already holds the mutex and will be * holding it for a next few seconds. * Let's delegate the update to the timer thread instead of blocking the main thread. @@ -397,7 +397,7 @@ auto waybar::modules::Network::update() -> void { } // https://gist.github.com/rressi/92af77630faf055934c723ce93ae2495 -static bool wildcardMatch(const std::string &pattern, const std::string &text) { +static bool wildcardMatch(const std::string& pattern, const std::string& text) { auto P = int(pattern.size()); auto T = int(text.size()); @@ -435,9 +435,9 @@ static bool wildcardMatch(const std::string &pattern, const std::string &text) { return p == P; } -bool waybar::modules::Network::matchInterface(const std::string &ifname, - const std::vector &altnames, - std::string &matched) const { +bool waybar::modules::Network::matchInterface(const std::string& ifname, + const std::vector& altnames, + std::string& matched) const { if (!config_["interface"].isString()) { return false; } @@ -448,7 +448,7 @@ bool waybar::modules::Network::matchInterface(const std::string &ifname, return true; } - for (const auto &altname : altnames) { + for (const auto& altname : altnames) { if (config_ifname == altname || wildcardMatch(config_ifname, altname)) { matched = altname; return true; @@ -478,8 +478,8 @@ void waybar::modules::Network::clearIface() { frequency_ = 0.0; } -int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { - auto net = static_cast(data); +int waybar::modules::Network::handleEvents(struct nl_msg* msg, void* data) { + auto net = static_cast(data); std::lock_guard lock(net->mutex_); auto nh = nlmsg_hdr(msg); bool is_del_event = false; @@ -488,8 +488,8 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { case RTM_DELLINK: is_del_event = true; case RTM_NEWLINK: { - struct ifinfomsg *ifi = static_cast(NLMSG_DATA(nh)); - struct nlattr *attrs[IFLA_MAX + 1]; + struct ifinfomsg* ifi = static_cast(NLMSG_DATA(nh)); + struct nlattr* attrs[IFLA_MAX + 1]; std::string ifname; std::vector altnames; std::optional carrier; @@ -517,7 +517,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { } if (attrs[IFLA_IFNAME] != nullptr) { - const char *ifname_ptr = nla_get_string(attrs[IFLA_IFNAME]); + const char* ifname_ptr = nla_get_string(attrs[IFLA_IFNAME]); size_t ifname_len = nla_len(attrs[IFLA_IFNAME]) - 1; // minus \0 ifname = std::string(ifname_ptr, ifname_len); } @@ -527,12 +527,12 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { } if (attrs[IFLA_PROP_LIST] != nullptr) { - struct nlattr *prop; + struct nlattr* prop; int rem; nla_for_each_nested(prop, attrs[IFLA_PROP_LIST], rem) { if (nla_type(prop) == IFLA_ALT_IFNAME) { - const char *altname_ptr = nla_get_string(prop); + const char* altname_ptr = nla_get_string(prop); size_t altname_len = nla_len(prop) - 1; // minus \0 altnames.emplace_back(altname_ptr, altname_len); } @@ -605,9 +605,9 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { case RTM_DELADDR: is_del_event = true; case RTM_NEWADDR: { - struct ifaddrmsg *ifa = static_cast(NLMSG_DATA(nh)); + struct ifaddrmsg* ifa = static_cast(NLMSG_DATA(nh)); ssize_t attrlen = IFA_PAYLOAD(nh); - struct rtattr *ifa_rta = IFA_RTA(ifa); + struct rtattr* ifa_rta = IFA_RTA(ifa); if ((int)ifa->ifa_index != net->ifid_) { return NL_OK; @@ -681,10 +681,10 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { // Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698 // to find the interface used to reach the outside world - struct rtmsg *rtm = static_cast(NLMSG_DATA(nh)); + struct rtmsg* rtm = static_cast(NLMSG_DATA(nh)); int family = rtm->rtm_family; ssize_t attrlen = RTM_PAYLOAD(nh); - struct rtattr *attr = RTM_RTA(rtm); + struct rtattr* attr = RTM_RTA(rtm); char gateway_addr[INET6_ADDRSTRLEN]; bool has_gateway = false; bool has_destination = false; @@ -725,17 +725,17 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) { break; } for (uint32_t i = 0; i < dstlen; i += 1) { - c |= *((unsigned char *)RTA_DATA(attr) + i); + c |= *((unsigned char*)RTA_DATA(attr) + i); } has_destination = (c == 0); break; } case RTA_OIF: /* The output interface index. */ - temp_idx = *static_cast(RTA_DATA(attr)); + temp_idx = *static_cast(RTA_DATA(attr)); break; case RTA_PRIORITY: - priority = *(uint32_t *)RTA_DATA(attr); + priority = *(uint32_t*)RTA_DATA(attr); break; default: break; @@ -826,18 +826,18 @@ void waybar::modules::Network::askForStateDump(void) { } } -int waybar::modules::Network::handleEventsDone(struct nl_msg *msg, void *data) { - auto net = static_cast(data); +int waybar::modules::Network::handleEventsDone(struct nl_msg* msg, void* data) { + auto net = static_cast(data); net->dump_in_progress_ = false; net->askForStateDump(); return NL_OK; } -int waybar::modules::Network::handleScan(struct nl_msg *msg, void *data) { - auto net = static_cast(data); - auto gnlh = static_cast(nlmsg_data(nlmsg_hdr(msg))); - struct nlattr *tb[NL80211_ATTR_MAX + 1]; - struct nlattr *bss[NL80211_BSS_MAX + 1]; +int waybar::modules::Network::handleScan(struct nl_msg* msg, void* data) { + auto net = static_cast(data); + auto gnlh = static_cast(nlmsg_data(nlmsg_hdr(msg))); + struct nlattr* tb[NL80211_ATTR_MAX + 1]; + struct nlattr* bss[NL80211_BSS_MAX + 1]; struct nla_policy bss_policy[NL80211_BSS_MAX + 1]{}; bss_policy[NL80211_BSS_TSF].type = NLA_U64; bss_policy[NL80211_BSS_FREQUENCY].type = NLA_U32; @@ -869,9 +869,9 @@ int waybar::modules::Network::handleScan(struct nl_msg *msg, void *data) { return NL_OK; } -void waybar::modules::Network::parseEssid(struct nlattr **bss) { +void waybar::modules::Network::parseEssid(struct nlattr** bss) { if (bss[NL80211_BSS_INFORMATION_ELEMENTS] != nullptr) { - auto ies = static_cast(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS])); + auto ies = static_cast(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS])); auto ies_len = nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]); const auto hdr_len = 2; while (ies_len > hdr_len && ies[0] != 0) { @@ -888,7 +888,7 @@ void waybar::modules::Network::parseEssid(struct nlattr **bss) { } } -void waybar::modules::Network::parseSignal(struct nlattr **bss) { +void waybar::modules::Network::parseSignal(struct nlattr** bss) { if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) { // signalstrength in dBm from mBm signal_strength_dbm_ = nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM]) / 100; @@ -924,16 +924,16 @@ void waybar::modules::Network::parseSignal(struct nlattr **bss) { } } -void waybar::modules::Network::parseFreq(struct nlattr **bss) { +void waybar::modules::Network::parseFreq(struct nlattr** bss) { if (bss[NL80211_BSS_FREQUENCY] != nullptr) { // in GHz frequency_ = (double)nla_get_u32(bss[NL80211_BSS_FREQUENCY]) / 1000; } } -void waybar::modules::Network::parseBssid(struct nlattr **bss) { +void waybar::modules::Network::parseBssid(struct nlattr** bss) { if (bss[NL80211_BSS_BSSID] != nullptr) { - auto bssid = static_cast(nla_data(bss[NL80211_BSS_BSSID])); + auto bssid = static_cast(nla_data(bss[NL80211_BSS_BSSID])); auto bssid_len = nla_len(bss[NL80211_BSS_BSSID]); if (bssid_len == 6) { bssid_ = fmt::format("{:x}:{:x}:{:x}:{:x}:{:x}:{:x}", bssid[0], bssid[1], bssid[2], bssid[3], @@ -942,7 +942,7 @@ void waybar::modules::Network::parseBssid(struct nlattr **bss) { } } -bool waybar::modules::Network::associatedOrJoined(struct nlattr **bss) { +bool waybar::modules::Network::associatedOrJoined(struct nlattr** bss) { if (bss[NL80211_BSS_STATUS] == nullptr) { return false; } @@ -958,7 +958,7 @@ bool waybar::modules::Network::associatedOrJoined(struct nlattr **bss) { } auto waybar::modules::Network::getInfo() -> void { - struct nl_msg *nl_msg = nlmsg_alloc(); + struct nl_msg* nl_msg = nlmsg_alloc(); if (nl_msg == nullptr) { return; } diff --git a/src/modules/niri/backend.cpp b/src/modules/niri/backend.cpp index fa4dc287..1ee1bf3f 100644 --- a/src/modules/niri/backend.cpp +++ b/src/modules/niri/backend.cpp @@ -21,7 +21,7 @@ namespace waybar::modules::niri { int IPC::connectToSocket() { - const char *socket_path = getenv("NIRI_SOCKET"); + const char* socket_path = getenv("NIRI_SOCKET"); if (socket_path == nullptr) { spdlog::warn("Niri is not running, niri IPC will not be available."); @@ -43,7 +43,7 @@ int IPC::connectToSocket() { int l = sizeof(struct sockaddr_un); - if (connect(socketfd, (struct sockaddr *)&addr, l) == -1) { + if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) { close(socketfd); throw std::runtime_error("unable to connect"); } @@ -58,7 +58,7 @@ void IPC::startIPC() { int socketfd; try { socketfd = connectToSocket(); - } catch (std::exception &e) { + } catch (std::exception& e) { spdlog::error("Niri IPC: failed to start, reason: {}", e.what()); return; } @@ -87,7 +87,7 @@ void IPC::startIPC() { try { parseIPC(line); - } catch (std::exception &e) { + } catch (std::exception& e) { spdlog::warn("Failed to parse IPC message: {}, reason: {}", line, e.what()); } catch (...) { throw; @@ -98,7 +98,7 @@ void IPC::startIPC() { }).detach(); } -void IPC::parseIPC(const std::string &line) { +void IPC::parseIPC(const std::string& line) { const auto ev = parser_.parse(line); const auto members = ev.getMemberNames(); if (members.size() != 1) throw std::runtime_error("Event must have a single member"); @@ -106,28 +106,28 @@ void IPC::parseIPC(const std::string &line) { { auto lock = lockData(); - if (const auto &payload = ev["WorkspacesChanged"]) { + if (const auto& payload = ev["WorkspacesChanged"]) { workspaces_.clear(); - const auto &values = payload["workspaces"]; + const auto& values = payload["workspaces"]; std::copy(values.begin(), values.end(), std::back_inserter(workspaces_)); - std::sort(workspaces_.begin(), workspaces_.end(), [](const auto &a, const auto &b) { - const auto &aOutput = a["output"].asString(); - const auto &bOutput = b["output"].asString(); + std::sort(workspaces_.begin(), workspaces_.end(), [](const auto& a, const auto& b) { + const auto& aOutput = a["output"].asString(); + const auto& bOutput = b["output"].asString(); const auto aIdx = a["idx"].asUInt(); const auto bIdx = b["idx"].asUInt(); if (aOutput == bOutput) return aIdx < bIdx; return aOutput < bOutput; }); - } else if (const auto &payload = ev["WorkspaceActivated"]) { + } else if (const auto& payload = ev["WorkspaceActivated"]) { const auto id = payload["id"].asUInt64(); const auto focused = payload["focused"].asBool(); auto it = std::find_if(workspaces_.begin(), workspaces_.end(), - [id](const auto &ws) { return ws["id"].asUInt64() == id; }); + [id](const auto& ws) { return ws["id"].asUInt64() == id; }); if (it != workspaces_.end()) { - const auto &ws = *it; - const auto &output = ws["output"].asString(); - for (auto &ws : workspaces_) { + const auto& ws = *it; + const auto& output = ws["output"].asString(); + for (auto& ws : workspaces_) { const auto got_activated = (ws["id"].asUInt64() == id); if (ws["output"] == output) ws["is_active"] = got_activated; @@ -136,70 +136,70 @@ void IPC::parseIPC(const std::string &line) { } else { spdlog::error("Activated unknown workspace"); } - } else if (const auto &payload = ev["WorkspaceActiveWindowChanged"]) { + } else if (const auto& payload = ev["WorkspaceActiveWindowChanged"]) { const auto workspaceId = payload["workspace_id"].asUInt64(); - auto it = std::find_if(workspaces_.begin(), workspaces_.end(), [workspaceId](const auto &ws) { + auto it = std::find_if(workspaces_.begin(), workspaces_.end(), [workspaceId](const auto& ws) { return ws["id"].asUInt64() == workspaceId; }); if (it != workspaces_.end()) { - auto &ws = *it; + auto& ws = *it; ws["active_window_id"] = payload["active_window_id"]; } else { spdlog::error("Active window changed on unknown workspace"); } - } else if (const auto &payload = ev["WorkspaceUrgencyChanged"]) { + } else if (const auto& payload = ev["WorkspaceUrgencyChanged"]) { const auto id = payload["id"].asUInt64(); const auto urgent = payload["urgent"].asBool(); auto it = std::find_if(workspaces_.begin(), workspaces_.end(), - [id](const auto &ws) { return ws["id"].asUInt64() == id; }); + [id](const auto& ws) { return ws["id"].asUInt64() == id; }); if (it != workspaces_.end()) { - auto &ws = *it; + auto& ws = *it; ws["is_urgent"] = urgent; } else { spdlog::error("Urgency changed for unknown workspace"); } - } else if (const auto &payload = ev["KeyboardLayoutsChanged"]) { - const auto &layouts = payload["keyboard_layouts"]; - const auto &names = layouts["names"]; + } else if (const auto& payload = ev["KeyboardLayoutsChanged"]) { + const auto& layouts = payload["keyboard_layouts"]; + const auto& names = layouts["names"]; keyboardLayoutCurrent_ = layouts["current_idx"].asUInt(); keyboardLayoutNames_.clear(); - for (const auto &fullName : names) keyboardLayoutNames_.push_back(fullName.asString()); - } else if (const auto &payload = ev["KeyboardLayoutSwitched"]) { + for (const auto& fullName : names) keyboardLayoutNames_.push_back(fullName.asString()); + } else if (const auto& payload = ev["KeyboardLayoutSwitched"]) { keyboardLayoutCurrent_ = payload["idx"].asUInt(); - } else if (const auto &payload = ev["WindowsChanged"]) { + } else if (const auto& payload = ev["WindowsChanged"]) { windows_.clear(); - const auto &values = payload["windows"]; + const auto& values = payload["windows"]; std::copy(values.begin(), values.end(), std::back_inserter(windows_)); - } else if (const auto &payload = ev["WindowOpenedOrChanged"]) { - const auto &window = payload["window"]; + } else if (const auto& payload = ev["WindowOpenedOrChanged"]) { + const auto& window = payload["window"]; const auto id = window["id"].asUInt64(); auto it = std::find_if(windows_.begin(), windows_.end(), - [id](const auto &win) { return win["id"].asUInt64() == id; }); + [id](const auto& win) { return win["id"].asUInt64() == id; }); if (it == windows_.end()) { windows_.push_back(window); if (window["is_focused"].asBool()) { - for (auto &win : windows_) { + for (auto& win : windows_) { win["is_focused"] = win["id"].asUInt64() == id; } } } else { *it = window; } - } else if (const auto &payload = ev["WindowClosed"]) { + } else if (const auto& payload = ev["WindowClosed"]) { const auto id = payload["id"].asUInt64(); auto it = std::find_if(windows_.begin(), windows_.end(), - [id](const auto &win) { return win["id"].asUInt64() == id; }); + [id](const auto& win) { return win["id"].asUInt64() == id; }); if (it != windows_.end()) { windows_.erase(it); } else { spdlog::error("Unknown window closed"); } - } else if (const auto &payload = ev["WindowFocusChanged"]) { + } else if (const auto& payload = ev["WindowFocusChanged"]) { const auto focused = !payload["id"].isNull(); const auto id = payload["id"].asUInt64(); - for (auto &win : windows_) { + for (auto& win : windows_) { win["is_focused"] = focused && win["id"].asUInt64() == id; } } @@ -207,14 +207,14 @@ void IPC::parseIPC(const std::string &line) { std::unique_lock lock(callbackMutex_); - for (auto &[eventname, handler] : callbacks_) { + for (auto& [eventname, handler] : callbacks_) { if (eventname == members[0]) { handler->onEvent(ev); } } } -void IPC::registerForIPC(const std::string &ev, EventHandler *ev_handler) { +void IPC::registerForIPC(const std::string& ev, EventHandler* ev_handler) { if (ev_handler == nullptr) { return; } @@ -223,7 +223,7 @@ void IPC::registerForIPC(const std::string &ev, EventHandler *ev_handler) { callbacks_.emplace_back(ev, ev_handler); } -void IPC::unregisterForIPC(EventHandler *ev_handler) { +void IPC::unregisterForIPC(EventHandler* ev_handler) { if (ev_handler == nullptr) { return; } @@ -231,7 +231,7 @@ void IPC::unregisterForIPC(EventHandler *ev_handler) { std::unique_lock lock(callbackMutex_); for (auto it = callbacks_.begin(); it != callbacks_.end();) { - auto &[eventname, handler] = *it; + auto& [eventname, handler] = *it; if (handler == ev_handler) { it = callbacks_.erase(it); } else { @@ -240,7 +240,7 @@ void IPC::unregisterForIPC(EventHandler *ev_handler) { } } -Json::Value IPC::send(const Json::Value &request) { +Json::Value IPC::send(const Json::Value& request) { int socketfd = connectToSocket(); if (socketfd == -1) throw std::runtime_error("Niri is not running"); diff --git a/src/modules/niri/language.cpp b/src/modules/niri/language.cpp index 496e5404..3359ef4c 100644 --- a/src/modules/niri/language.cpp +++ b/src/modules/niri/language.cpp @@ -8,7 +8,7 @@ namespace waybar::modules::niri { -Language::Language(const std::string &id, const Bar &bar, const Json::Value &config) +Language::Language(const std::string& id, const Bar& bar, const Json::Value& config) : ALabel(config, "language", id, "{}", 0, false), bar_(bar) { label_.hide(); @@ -32,7 +32,7 @@ void Language::updateFromIPC() { auto ipcLock = gIPC->lockData(); layouts_.clear(); - for (const auto &fullName : gIPC->keyboardLayoutNames()) layouts_.push_back(getLayout(fullName)); + for (const auto& fullName : gIPC->keyboardLayoutNames()) layouts_.push_back(getLayout(fullName)); current_idx_ = gIPC->keyboardLayoutCurrent(); } @@ -51,7 +51,7 @@ void Language::doUpdate() { label_.hide(); return; } - const auto &layout = layouts_[current_idx_]; + const auto& layout = layouts_[current_idx_]; spdlog::debug("niri language update with full name {}", layout.full_name); spdlog::debug("niri language update with short name {}", layout.short_name); @@ -97,7 +97,7 @@ void Language::update() { ALabel::update(); } -void Language::onEvent(const Json::Value &ev) { +void Language::onEvent(const Json::Value& ev) { if (ev["KeyboardLayoutsChanged"]) { updateFromIPC(); } else if (ev["KeyboardLayoutSwitched"]) { @@ -109,11 +109,11 @@ void Language::onEvent(const Json::Value &ev) { dp.emit(); } -Language::Layout Language::getLayout(const std::string &fullName) { - auto *const context = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES); +Language::Layout Language::getLayout(const std::string& fullName) { + auto* const context = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES); rxkb_context_parse_default_ruleset(context); - rxkb_layout *layout = rxkb_layout_first(context); + rxkb_layout* layout = rxkb_layout_first(context); while (layout != nullptr) { std::string nameOfLayout = rxkb_layout_get_description(layout); @@ -123,10 +123,10 @@ Language::Layout Language::getLayout(const std::string &fullName) { } auto name = std::string(rxkb_layout_get_name(layout)); - const auto *variantPtr = rxkb_layout_get_variant(layout); + const auto* variantPtr = rxkb_layout_get_variant(layout); std::string variant = variantPtr == nullptr ? "" : std::string(variantPtr); - const auto *descriptionPtr = rxkb_layout_get_brief(layout); + const auto* descriptionPtr = rxkb_layout_get_brief(layout); std::string description = descriptionPtr == nullptr ? "" : std::string(descriptionPtr); Layout info = Layout{nameOfLayout, name, variant, description}; diff --git a/src/modules/niri/window.cpp b/src/modules/niri/window.cpp index 6e6fd36f..c3537769 100644 --- a/src/modules/niri/window.cpp +++ b/src/modules/niri/window.cpp @@ -9,7 +9,7 @@ namespace waybar::modules::niri { -Window::Window(const std::string &id, const Bar &bar, const Json::Value &config) +Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) { if (!gIPC) gIPC = std::make_unique(); @@ -23,16 +23,16 @@ Window::Window(const std::string &id, const Bar &bar, const Json::Value &config) Window::~Window() { gIPC->unregisterForIPC(this); } -void Window::onEvent(const Json::Value &ev) { dp.emit(); } +void Window::onEvent(const Json::Value& ev) { dp.emit(); } void Window::doUpdate() { auto ipcLock = gIPC->lockData(); - const auto &windows = gIPC->windows(); - const auto &workspaces = gIPC->workspaces(); + const auto& windows = gIPC->windows(); + const auto& workspaces = gIPC->workspaces(); const auto separateOutputs = config_["separate-outputs"].asBool(); - const auto ws_it = std::find_if(workspaces.cbegin(), workspaces.cend(), [&](const auto &ws) { + const auto ws_it = std::find_if(workspaces.cbegin(), workspaces.cend(), [&](const auto& ws) { if (separateOutputs) { return ws["is_active"].asBool() && ws["output"].asString() == bar_.output->name; } @@ -46,13 +46,13 @@ void Window::doUpdate() { } else { const auto id = (*ws_it)["active_window_id"].asUInt64(); it = std::find_if(windows.cbegin(), windows.cend(), - [id](const auto &win) { return win["id"].asUInt64() == id; }); + [id](const auto& win) { return win["id"].asUInt64() == id; }); } setClass("empty", ws_it == workspaces.cend() || (*ws_it)["active_window_id"].isNull()); if (it != windows.cend()) { - const auto &window = *it; + const auto& window = *it; const auto title = window["title"].asString(); const auto appId = window["app_id"].asString(); @@ -71,7 +71,7 @@ void Window::doUpdate() { const auto id = window["id"].asUInt64(); const auto workspaceId = window["workspace_id"].asUInt64(); - const auto isSolo = std::none_of(windows.cbegin(), windows.cend(), [&](const auto &win) { + const auto isSolo = std::none_of(windows.cbegin(), windows.cend(), [&](const auto& win) { return win["id"].asUInt64() != id && win["workspace_id"].asUInt64() == workspaceId; }); setClass("solo", isSolo); @@ -95,7 +95,7 @@ void Window::update() { AAppIconLabel::update(); } -void Window::setClass(const std::string &className, bool enable) { +void Window::setClass(const std::string& className, bool enable) { auto styleContext = bar_.window.get_style_context(); if (enable) { if (!styleContext->has_class(className)) { diff --git a/src/modules/niri/workspaces.cpp b/src/modules/niri/workspaces.cpp index 7dfc0b35..8eb912bc 100644 --- a/src/modules/niri/workspaces.cpp +++ b/src/modules/niri/workspaces.cpp @@ -6,7 +6,7 @@ namespace waybar::modules::niri { -Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) +Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config) : AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) { box_.set_name("workspaces"); if (!id.empty()) { @@ -27,16 +27,16 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value Workspaces::~Workspaces() { gIPC->unregisterForIPC(this); } -void Workspaces::onEvent(const Json::Value &ev) { dp.emit(); } +void Workspaces::onEvent(const Json::Value& ev) { dp.emit(); } void Workspaces::doUpdate() { auto ipcLock = gIPC->lockData(); const auto alloutputs = config_["all-outputs"].asBool(); std::vector my_workspaces; - const auto &workspaces = gIPC->workspaces(); + const auto& workspaces = gIPC->workspaces(); std::copy_if(workspaces.cbegin(), workspaces.cend(), std::back_inserter(my_workspaces), - [&](const auto &ws) { + [&](const auto& ws) { if (alloutputs) return true; return ws["output"].asString() == bar_.output->name; }); @@ -44,7 +44,7 @@ void Workspaces::doUpdate() { // Remove buttons for removed workspaces. for (auto it = buttons_.begin(); it != buttons_.end();) { auto ws = std::find_if(my_workspaces.begin(), my_workspaces.end(), - [it](const auto &ws) { return ws["id"].asUInt64() == it->first; }); + [it](const auto& ws) { return ws["id"].asUInt64() == it->first; }); if (ws == my_workspaces.end()) { it = buttons_.erase(it); } else { @@ -53,9 +53,9 @@ void Workspaces::doUpdate() { } // Add buttons for new workspaces, update existing ones. - for (const auto &ws : my_workspaces) { + for (const auto& ws : my_workspaces) { auto bit = buttons_.find(ws["id"].asUInt64()); - auto &button = bit == buttons_.end() ? addButton(ws) : bit->second; + auto& button = bit == buttons_.end() ? addButton(ws) : bit->second; auto style_context = button.get_style_context(); if (ws["is_focused"].asBool()) @@ -103,13 +103,13 @@ void Workspaces::doUpdate() { fmt::arg("output", ws["output"].asString())); } if (!config_["disable-markup"].asBool()) { - static_cast(button.get_children()[0])->set_markup(name); + static_cast(button.get_children()[0])->set_markup(name); } else { button.set_label(name); } if (config_["current-only"].asBool()) { - const auto *property = alloutputs ? "is_focused" : "is_active"; + const auto* property = alloutputs ? "is_focused" : "is_active"; if (ws[property].asBool()) button.show(); else @@ -121,12 +121,12 @@ void Workspaces::doUpdate() { // Refresh the button order. for (auto it = my_workspaces.cbegin(); it != my_workspaces.cend(); ++it) { - const auto &ws = *it; + const auto& ws = *it; auto pos = ws["idx"].asUInt() - 1; if (alloutputs) pos = it - my_workspaces.cbegin(); - auto &button = buttons_[ws["id"].asUInt64()]; + auto& button = buttons_[ws["id"].asUInt64()]; box_.reorder_child(button, pos); } } @@ -136,7 +136,7 @@ void Workspaces::update() { AModule::update(); } -Gtk::Button &Workspaces::addButton(const Json::Value &ws) { +Gtk::Button& Workspaces::addButton(const Json::Value& ws) { std::string name; if (ws["name"]) { name = ws["name"].asString(); @@ -145,7 +145,7 @@ Gtk::Button &Workspaces::addButton(const Json::Value &ws) { } auto pair = buttons_.emplace(ws["id"].asUInt64(), name); - auto &&button = pair.first->second; + auto&& button = pair.first->second; box_.pack_start(button, false, false, 0); button.set_relief(Gtk::RELIEF_NONE); if (!config_["disable-click"].asBool()) { @@ -154,13 +154,13 @@ Gtk::Button &Workspaces::addButton(const Json::Value &ws) { try { // {"Action":{"FocusWorkspace":{"reference":{"Id":1}}}} Json::Value request(Json::objectValue); - auto &action = (request["Action"] = Json::Value(Json::objectValue)); - auto &focusWorkspace = (action["FocusWorkspace"] = Json::Value(Json::objectValue)); - auto &reference = (focusWorkspace["reference"] = Json::Value(Json::objectValue)); + auto& action = (request["Action"] = Json::Value(Json::objectValue)); + auto& focusWorkspace = (action["FocusWorkspace"] = Json::Value(Json::objectValue)); + auto& reference = (focusWorkspace["reference"] = Json::Value(Json::objectValue)); reference["Id"] = id; IPC::send(request); - } catch (const std::exception &e) { + } catch (const std::exception& e) { spdlog::error("Error switching workspace: {}", e.what()); } }); @@ -168,8 +168,8 @@ Gtk::Button &Workspaces::addButton(const Json::Value &ws) { return button; } -std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws) { - const auto &icons = config_["format-icons"]; +std::string Workspaces::getIcon(const std::string& value, const Json::Value& ws) { + const auto& icons = config_["format-icons"]; if (!icons) return value; if (ws["is_urgent"].asBool() && icons["urgent"]) return icons["urgent"].asString(); @@ -181,7 +181,7 @@ std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws) if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString(); if (ws["name"]) { - const auto &name = ws["name"].asString(); + const auto& name = ws["name"].asString(); if (icons[name]) return icons[name].asString(); } diff --git a/src/modules/privacy/privacy_item.cpp b/src/modules/privacy/privacy_item.cpp index 6424da9e..3a55eee7 100644 --- a/src/modules/privacy/privacy_item.cpp +++ b/src/modules/privacy/privacy_item.cpp @@ -12,9 +12,9 @@ namespace waybar::modules::privacy { -PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privacy_type_, - std::list *nodes_, Gtk::Orientation orientation, - const std::string &pos, const uint icon_size, +PrivacyItem::PrivacyItem(const Json::Value& config_, enum PrivacyNodeType privacy_type_, + std::list* nodes_, Gtk::Orientation orientation, + const std::string& pos, const uint icon_size, const uint transition_duration) : Gtk::Revealer(), privacy_type(privacy_type_), @@ -83,7 +83,7 @@ PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privac // Sets the window to use when showing the tooltip update_tooltip(); this->signal_query_tooltip().connect(sigc::track_obj( - [this](int x, int y, bool keyboard_tooltip, const Glib::RefPtr &tooltip) { + [this](int x, int y, bool keyboard_tooltip, const Glib::RefPtr& tooltip) { tooltip->set_custom(tooltip_window); return true; }, @@ -97,23 +97,23 @@ PrivacyItem::PrivacyItem(const Json::Value &config_, enum PrivacyNodeType privac void PrivacyItem::update_tooltip() { // Removes all old nodes - for (auto *child : tooltip_window.get_children()) { + for (auto* child : tooltip_window.get_children()) { tooltip_window.remove(*child); // despite the remove, still needs a delete to prevent memory leak. Speculating that this might // work differently in GTK4. delete child; } - for (auto *node : *nodes) { - auto *box = Gtk::make_managed(Gtk::ORIENTATION_HORIZONTAL, 4); + for (auto* node : *nodes) { + auto* box = Gtk::make_managed(Gtk::ORIENTATION_HORIZONTAL, 4); // Set device icon - auto *node_icon = Gtk::make_managed(); + auto* node_icon = Gtk::make_managed(); node_icon->set_pixel_size(tooltipIconSize); node_icon->set_from_icon_name(node->getIconName(), Gtk::ICON_SIZE_INVALID); box->add(*node_icon); // Set model - auto *nodeName = Gtk::make_managed(node->getName()); + auto* nodeName = Gtk::make_managed(node->getName()); box->add(*nodeName); tooltip_window.add(*box); diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index ceed20dd..f60c39ff 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -1,6 +1,6 @@ #include "modules/pulseaudio.hpp" -waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value &config) +waybar::modules::Pulseaudio::Pulseaudio(const std::string& id, const Json::Value& config) : ALabel(config, "pulseaudio", id, "{volume}%") { event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Pulseaudio::handleScroll)); @@ -9,7 +9,7 @@ waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value backend->setIgnoredSinks(config_["ignored-sinks"]); } -bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) { +bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll* e) { // change the pulse volume only when no user provided // events are configured if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { @@ -51,7 +51,7 @@ const std::vector waybar::modules::Pulseaudio::getPulseIcon() const res.push_back(backend->getDefaultSourceName()); std::string nameLC = backend->getSinkPortName() + backend->getFormFactor(); std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower); - for (auto const &port : ports) { + for (auto const& port : ports) { if (nameLC.find(port) != std::string::npos) { if (sink_muted) { res.emplace_back(port + "-muted"); diff --git a/src/modules/river/layout.cpp b/src/modules/river/layout.cpp index 308a0901..31358f80 100644 --- a/src/modules/river/layout.cpp +++ b/src/modules/river/layout.cpp @@ -7,48 +7,48 @@ namespace waybar::modules::river { -static void listen_focused_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, +static void listen_focused_tags(void* data, struct zriver_output_status_v1* zriver_output_status_v1, uint32_t tags) { // Intentionally empty } -static void listen_view_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, - struct wl_array *tags) { +static void listen_view_tags(void* data, struct zriver_output_status_v1* zriver_output_status_v1, + struct wl_array* tags) { // Intentionally empty } -static void listen_urgent_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, +static void listen_urgent_tags(void* data, struct zriver_output_status_v1* zriver_output_status_v1, uint32_t tags) { // Intentionally empty } -static void listen_layout_name(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, - const char *layout) { - static_cast(data)->handle_name(layout); +static void listen_layout_name(void* data, struct zriver_output_status_v1* zriver_output_status_v1, + const char* layout) { + static_cast(data)->handle_name(layout); } -static void listen_layout_name_clear(void *data, - struct zriver_output_status_v1 *zriver_output_status_v1) { - static_cast(data)->handle_clear(); +static void listen_layout_name_clear(void* data, + struct zriver_output_status_v1* zriver_output_status_v1) { + static_cast(data)->handle_clear(); } -static void listen_focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, - struct wl_output *output) { - static_cast(data)->handle_focused_output(output); +static void listen_focused_output(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1, + struct wl_output* output) { + static_cast(data)->handle_focused_output(output); } -static void listen_unfocused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, - struct wl_output *output) { - static_cast(data)->handle_unfocused_output(output); +static void listen_unfocused_output(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1, + struct wl_output* output) { + static_cast(data)->handle_unfocused_output(output); } -static void listen_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, - const char *title) { +static void listen_focused_view(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1, + const char* title) { // Intentionally empty } -static void listen_mode(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, - const char *mode) { +static void listen_mode(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1, + const char* mode) { // Intentionally empty } @@ -67,8 +67,8 @@ static const zriver_seat_status_v1_listener seat_status_listener_impl{ .mode = listen_mode, }; -static void handle_global(void *data, struct wl_registry *registry, uint32_t name, - const char *interface, uint32_t version) { +static void handle_global(void* data, struct wl_registry* registry, uint32_t name, + const char* interface, uint32_t version) { if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { version = std::min(version, 4); @@ -80,32 +80,32 @@ static void handle_global(void *data, struct wl_registry *registry, uint32_t nam std::to_string(version)); return; } - static_cast(data)->status_manager_ = static_cast( + static_cast(data)->status_manager_ = static_cast( wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version)); } if (std::strcmp(interface, wl_seat_interface.name) == 0) { version = std::min(version, 1); - static_cast(data)->seat_ = static_cast( - wl_registry_bind(registry, name, &wl_seat_interface, version)); + static_cast(data)->seat_ = + static_cast(wl_registry_bind(registry, name, &wl_seat_interface, version)); } } -static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { +static void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) { // Nobody cares } static const wl_registry_listener registry_listener_impl = {.global = handle_global, .global_remove = handle_global_remove}; -Layout::Layout(const std::string &id, const waybar::Bar &bar, const Json::Value &config) +Layout::Layout(const std::string& id, const waybar::Bar& bar, const Json::Value& config) : waybar::ALabel(config, "layout", id, "{}"), status_manager_{nullptr}, seat_{nullptr}, bar_(bar), output_status_{nullptr} { - struct wl_display *display = Client::inst()->wl_display; - struct wl_registry *registry = wl_display_get_registry(display); + struct wl_display* display = Client::inst()->wl_display; + struct wl_registry* registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener_impl, this); wl_display_roundtrip(display); @@ -141,7 +141,7 @@ Layout::~Layout() { } } -void Layout::handle_name(const char *name) { +void Layout::handle_name(const char* name) { if (std::strcmp(name, "") == 0 || format_.empty()) { label_.hide(); // hide empty labels or labels with empty format } else { @@ -162,7 +162,7 @@ void Layout::handle_clear() { ALabel::update(); } -void Layout::handle_focused_output(struct wl_output *output) { +void Layout::handle_focused_output(struct wl_output* output) { if (output_ == output) { // if we focused the output this bar belongs to label_.get_style_context()->add_class("focused"); ALabel::update(); @@ -170,7 +170,7 @@ void Layout::handle_focused_output(struct wl_output *output) { focused_output_ = output; } -void Layout::handle_unfocused_output(struct wl_output *output) { +void Layout::handle_unfocused_output(struct wl_output* output) { if (output_ == output) { // if we unfocused the output this bar belongs to label_.get_style_context()->remove_class("focused"); ALabel::update(); diff --git a/src/modules/river/mode.cpp b/src/modules/river/mode.cpp index 1f788e09..10f5089e 100644 --- a/src/modules/river/mode.cpp +++ b/src/modules/river/mode.cpp @@ -7,23 +7,23 @@ namespace waybar::modules::river { -static void listen_focused_output(void *data, struct zriver_seat_status_v1 *seat_status, - struct wl_output *output) { +static void listen_focused_output(void* data, struct zriver_seat_status_v1* seat_status, + struct wl_output* output) { // Intentionally empty } -static void listen_unfocused_output(void *data, struct zriver_seat_status_v1 *seat_status, - struct wl_output *output) { +static void listen_unfocused_output(void* data, struct zriver_seat_status_v1* seat_status, + struct wl_output* output) { // Intentionally empty } -static void listen_focused_view(void *data, struct zriver_seat_status_v1 *seat_status, - const char *title) { +static void listen_focused_view(void* data, struct zriver_seat_status_v1* seat_status, + const char* title) { // Intentionally empty } -static void listen_mode(void *data, struct zriver_seat_status_v1 *seat_status, const char *mode) { - static_cast(data)->handle_mode(mode); +static void listen_mode(void* data, struct zriver_seat_status_v1* seat_status, const char* mode) { + static_cast(data)->handle_mode(mode); } static const zriver_seat_status_v1_listener seat_status_listener_impl = { @@ -33,8 +33,8 @@ static const zriver_seat_status_v1_listener seat_status_listener_impl = { .mode = listen_mode, }; -static void handle_global(void *data, struct wl_registry *registry, uint32_t name, - const char *interface, uint32_t version) { +static void handle_global(void* data, struct wl_registry* registry, uint32_t name, + const char* interface, uint32_t version) { if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { version = std::min(version, 3); if (version < ZRIVER_SEAT_STATUS_V1_MODE_SINCE_VERSION) { @@ -42,31 +42,31 @@ static void handle_global(void *data, struct wl_registry *registry, uint32_t nam "river server does not support the \"mode\" event; the module will be disabled"); return; } - static_cast(data)->status_manager_ = static_cast( + static_cast(data)->status_manager_ = static_cast( wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version)); } else if (std::strcmp(interface, wl_seat_interface.name) == 0) { version = std::min(version, 1); - static_cast(data)->seat_ = static_cast( - wl_registry_bind(registry, name, &wl_seat_interface, version)); + static_cast(data)->seat_ = + static_cast(wl_registry_bind(registry, name, &wl_seat_interface, version)); } } -static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { +static void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) { // Nobody cares } static const wl_registry_listener registry_listener_impl = {.global = handle_global, .global_remove = handle_global_remove}; -Mode::Mode(const std::string &id, const waybar::Bar &bar, const Json::Value &config) +Mode::Mode(const std::string& id, const waybar::Bar& bar, const Json::Value& config) : waybar::ALabel(config, "mode", id, "{}"), status_manager_{nullptr}, seat_{nullptr}, bar_(bar), mode_{""}, seat_status_{nullptr} { - struct wl_display *display = Client::inst()->wl_display; - struct wl_registry *registry = wl_display_get_registry(display); + struct wl_display* display = Client::inst()->wl_display; + struct wl_registry* registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener_impl, this); wl_display_roundtrip(display); @@ -94,7 +94,7 @@ Mode::~Mode() { } } -void Mode::handle_mode(const char *mode) { +void Mode::handle_mode(const char* mode) { if (format_.empty()) { label_.hide(); } else { diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index 33be0e6f..fc4d98f9 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -12,19 +12,19 @@ namespace waybar::modules::river { -static void listen_focused_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, +static void listen_focused_tags(void* data, struct zriver_output_status_v1* zriver_output_status_v1, uint32_t tags) { - static_cast(data)->handle_focused_tags(tags); + static_cast(data)->handle_focused_tags(tags); } -static void listen_view_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, - struct wl_array *tags) { - static_cast(data)->handle_view_tags(tags); +static void listen_view_tags(void* data, struct zriver_output_status_v1* zriver_output_status_v1, + struct wl_array* tags) { + static_cast(data)->handle_view_tags(tags); } -static void listen_urgent_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1, +static void listen_urgent_tags(void* data, struct zriver_output_status_v1* zriver_output_status_v1, uint32_t tags) { - static_cast(data)->handle_urgent_tags(tags); + static_cast(data)->handle_urgent_tags(tags); } static const zriver_output_status_v1_listener output_status_listener_impl{ @@ -33,15 +33,15 @@ static const zriver_output_status_v1_listener output_status_listener_impl{ .urgent_tags = listen_urgent_tags, }; -static void listen_command_success(void *data, - struct zriver_command_callback_v1 *zriver_command_callback_v1, - const char *output) { +static void listen_command_success(void* data, + struct zriver_command_callback_v1* zriver_command_callback_v1, + const char* output) { // Do nothing but keep listener to avoid crashing when command was successful } -static void listen_command_failure(void *data, - struct zriver_command_callback_v1 *zriver_command_callback_v1, - const char *output) { +static void listen_command_failure(void* data, + struct zriver_command_callback_v1* zriver_command_callback_v1, + const char* output) { spdlog::error("failure when selecting/toggling tags {}", output); } @@ -50,38 +50,38 @@ static const zriver_command_callback_v1_listener command_callback_listener_impl{ .failure = listen_command_failure, }; -static void handle_global(void *data, struct wl_registry *registry, uint32_t name, - const char *interface, uint32_t version) { +static void handle_global(void* data, struct wl_registry* registry, uint32_t name, + const char* interface, uint32_t version) { if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { version = std::min(version, 2u); if (version < ZRIVER_OUTPUT_STATUS_V1_URGENT_TAGS_SINCE_VERSION) { spdlog::warn("river server does not support urgent tags"); } - static_cast(data)->status_manager_ = static_cast( + static_cast(data)->status_manager_ = static_cast( wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version)); } if (std::strcmp(interface, zriver_control_v1_interface.name) == 0) { version = std::min(version, 1u); - static_cast(data)->control_ = static_cast( + static_cast(data)->control_ = static_cast( wl_registry_bind(registry, name, &zriver_control_v1_interface, version)); } if (std::strcmp(interface, wl_seat_interface.name) == 0) { version = std::min(version, 1u); - static_cast(data)->seat_ = static_cast( - wl_registry_bind(registry, name, &wl_seat_interface, version)); + static_cast(data)->seat_ = + static_cast(wl_registry_bind(registry, name, &wl_seat_interface, version)); } } -static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { +static void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) { /* Ignore event */ } static const wl_registry_listener registry_listener_impl = {.global = handle_global, .global_remove = handle_global_remove}; -Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config) +Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& config) : waybar::AModule(config, "tags", id, false, false), status_manager_{nullptr}, control_{nullptr}, @@ -89,8 +89,8 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con bar_(bar), box_{bar.orientation, 0}, output_status_{nullptr} { - struct wl_display *display = Client::inst()->wl_display; - struct wl_registry *registry = wl_display_get_registry(display); + struct wl_display* display = Client::inst()->wl_display; + struct wl_registry* registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener_impl, this); wl_display_roundtrip(display); @@ -129,7 +129,7 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con buttons_.emplace_back(std::to_string(tag + 1)); } - auto &button = buttons_[tag]; + auto& button = buttons_[tag]; button.set_relief(Gtk::RELIEF_NONE); box_.pack_start(button, false, false, 0); @@ -168,7 +168,7 @@ Tags::~Tags() { } void Tags::handle_show() { - struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + 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); @@ -178,17 +178,17 @@ void Tags::handle_show() { void Tags::handle_primary_clicked(uint32_t tag) { // Send river command to select tag on left mouse click - zriver_command_callback_v1 *callback; + zriver_command_callback_v1* callback; zriver_control_v1_add_argument(control_, "set-focused-tags"); zriver_control_v1_add_argument(control_, std::to_string(tag).c_str()); callback = zriver_control_v1_run_command(control_, seat_); zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr); } -bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) { +bool Tags::handle_button_press(GdkEventButton* event_button, uint32_t tag) { if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) { // Send river command to toggle tag on right mouse click - zriver_command_callback_v1 *callback; + zriver_command_callback_v1* callback; zriver_control_v1_add_argument(control_, "toggle-focused-tags"); zriver_control_v1_add_argument(control_, std::to_string(tag).c_str()); callback = zriver_control_v1_run_command(control_, seat_); @@ -217,9 +217,9 @@ void Tags::handle_focused_tags(uint32_t tags) { } } -void Tags::handle_view_tags(struct wl_array *view_tags) { +void Tags::handle_view_tags(struct wl_array* view_tags) { uint32_t tags = 0; - auto view_tag = reinterpret_cast(view_tags->data); + auto view_tag = reinterpret_cast(view_tags->data); auto end = view_tag + (view_tags->size / sizeof(uint32_t)); for (; view_tag < end; ++view_tag) { tags |= *view_tag; diff --git a/src/modules/river/window.cpp b/src/modules/river/window.cpp index dc7a6459..1f41ab4c 100644 --- a/src/modules/river/window.cpp +++ b/src/modules/river/window.cpp @@ -9,23 +9,23 @@ namespace waybar::modules::river { -static void listen_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, - const char *title) { - static_cast(data)->handle_focused_view(title); +static void listen_focused_view(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1, + const char* title) { + static_cast(data)->handle_focused_view(title); } -static void listen_focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, - struct wl_output *output) { - static_cast(data)->handle_focused_output(output); +static void listen_focused_output(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1, + struct wl_output* output) { + static_cast(data)->handle_focused_output(output); } -static void listen_unfocused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, - struct wl_output *output) { - static_cast(data)->handle_unfocused_output(output); +static void listen_unfocused_output(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1, + struct wl_output* output) { + static_cast(data)->handle_unfocused_output(output); } -static void listen_mode(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, - const char *mode) { +static void listen_mode(void* data, struct zriver_seat_status_v1* zriver_seat_status_v1, + const char* mode) { // This module doesn't care } @@ -36,36 +36,36 @@ static const zriver_seat_status_v1_listener seat_status_listener_impl{ .mode = listen_mode, }; -static void handle_global(void *data, struct wl_registry *registry, uint32_t name, - const char *interface, uint32_t version) { +static void handle_global(void* data, struct wl_registry* registry, uint32_t name, + const char* interface, uint32_t version) { if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { version = std::min(version, 2); - static_cast(data)->status_manager_ = static_cast( + static_cast(data)->status_manager_ = static_cast( wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version)); } if (std::strcmp(interface, wl_seat_interface.name) == 0) { version = std::min(version, 1); - static_cast(data)->seat_ = static_cast( - wl_registry_bind(registry, name, &wl_seat_interface, version)); + static_cast(data)->seat_ = + static_cast(wl_registry_bind(registry, name, &wl_seat_interface, version)); } } -static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { +static void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) { /* Ignore event */ } static const wl_registry_listener registry_listener_impl = {.global = handle_global, .global_remove = handle_global_remove}; -Window::Window(const std::string &id, const waybar::Bar &bar, const Json::Value &config) +Window::Window(const std::string& id, const waybar::Bar& bar, const Json::Value& config) : waybar::ALabel(config, "window", id, "{}", 30), status_manager_{nullptr}, seat_{nullptr}, bar_(bar), seat_status_{nullptr} { - struct wl_display *display = Client::inst()->wl_display; - struct wl_registry *registry = wl_display_get_registry(display); + struct wl_display* display = Client::inst()->wl_display; + struct wl_registry* registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener_impl, this); wl_display_roundtrip(display); @@ -95,7 +95,7 @@ Window::~Window() { } } -void Window::handle_focused_view(const char *title) { +void Window::handle_focused_view(const char* title) { // don't change the label on unfocused outputs. // this makes the current output report its currently focused view, and unfocused outputs will // report their last focused views. when freshly starting the bar, unfocused outputs don't have a @@ -116,7 +116,7 @@ void Window::handle_focused_view(const char *title) { ALabel::update(); } -void Window::handle_focused_output(struct wl_output *output) { +void Window::handle_focused_output(struct wl_output* output) { if (output_ == output) { // if we focused the output this bar belongs to label_.get_style_context()->add_class("focused"); ALabel::update(); @@ -124,7 +124,7 @@ void Window::handle_focused_output(struct wl_output *output) { focused_output_ = output; } -void Window::handle_unfocused_output(struct wl_output *output) { +void Window::handle_unfocused_output(struct wl_output* output) { if (output_ == output) { // if we unfocused the output this bar belongs to label_.get_style_context()->remove_class("focused"); ALabel::update(); diff --git a/src/modules/sndio.cpp b/src/modules/sndio.cpp index 72e7207c..9779cd36 100644 --- a/src/modules/sndio.cpp +++ b/src/modules/sndio.cpp @@ -9,8 +9,8 @@ namespace waybar::modules { -void ondesc(void *arg, struct sioctl_desc *d, int curval) { - auto self = static_cast(arg); +void ondesc(void* arg, struct sioctl_desc* d, int curval) { + auto self = static_cast(arg); if (d == NULL) { // d is NULL when the list is done return; @@ -18,8 +18,8 @@ void ondesc(void *arg, struct sioctl_desc *d, int curval) { self->set_desc(d, curval); } -void onval(void *arg, unsigned int addr, unsigned int val) { - auto self = static_cast(arg); +void onval(void* arg, unsigned int addr, unsigned int val) { + auto self = static_cast(arg); self->put_val(addr, val); } @@ -40,7 +40,7 @@ auto Sndio::connect_to_sndio() -> void { pfds_.reserve(sioctl_nfds(hdl_)); } -Sndio::Sndio(const std::string &id, const Json::Value &config) +Sndio::Sndio(const std::string& id, const Json::Value& config) : ALabel(config, "sndio", id, "{volume}%", 1, false, true), hdl_(nullptr), pfds_(0), @@ -80,7 +80,7 @@ Sndio::Sndio(const std::string &id, const Json::Value &config) while (thread_.isRunning()) { try { connect_to_sndio(); - } catch (std::runtime_error const &e) { + } catch (std::runtime_error const& e) { // avoid leaking hdl_ if (hdl_) { sioctl_close(hdl_); @@ -122,7 +122,7 @@ auto Sndio::update() -> void { ALabel::update(); } -auto Sndio::set_desc(struct sioctl_desc *d, unsigned int val) -> void { +auto Sndio::set_desc(struct sioctl_desc* d, unsigned int val) -> void { std::string name{d->func}; std::string node_name{d->node0.name}; @@ -140,7 +140,7 @@ auto Sndio::put_val(unsigned int addr, unsigned int val) -> void { } } -bool Sndio::handleScroll(GdkEventScroll *e) { +bool Sndio::handleScroll(GdkEventScroll* e) { // change the volume only when no user provided // events are configured if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { @@ -180,7 +180,7 @@ bool Sndio::handleScroll(GdkEventScroll *e) { return true; } -bool Sndio::handleToggle(GdkEventButton *const &e) { +bool Sndio::handleToggle(GdkEventButton* const& e) { // toggle mute only when no user provided events are configured if (config_["on-click"].isString()) { return AModule::handleToggle(e); diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 182bf631..e0255893 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -13,7 +13,7 @@ namespace waybar::modules::sway { int Workspaces::convertWorkspaceNameToNum(std::string name) { if (isdigit(name[0]) != 0) { errno = 0; - char *endptr = nullptr; + char* endptr = nullptr; long long parsed_num = strtoll(name.c_str(), &endptr, 10); if (errno != 0 || parsed_num > INT32_MAX || parsed_num < 0 || endptr == name.c_str()) { return -1; @@ -23,7 +23,7 @@ int Workspaces::convertWorkspaceNameToNum(std::string name) { return -1; } -int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) { +int Workspaces::windowRewritePriorityFunction(std::string const& window_rule) { // Rules that match against title are prioritized // Rules that don't specify if they're matching against either title or class are deprioritized bool const hasTitle = window_rule.find("title") != std::string::npos; @@ -41,12 +41,12 @@ int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) { return 0; } -Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) +Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config) : AModule(config, "workspaces", id, false, !config["disable-scroll"].asBool()), bar_(bar), box_(bar.orientation, 0) { if (config["format-icons"]["high-priority-named"].isArray()) { - for (const auto &it : config["format-icons"]["high-priority-named"]) { + for (const auto& it : config["format-icons"]["high-priority-named"]) { high_priority_named_.push_back(it.asString()); } } @@ -61,9 +61,9 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value } else { m_formatWindowSeparator = " "; } - const Json::Value &windowRewrite = config["window-rewrite"]; + const Json::Value& windowRewrite = config["window-rewrite"]; if (windowRewrite.isObject()) { - const Json::Value &windowRewriteDefaultConfig = config["window-rewrite-default"]; + const Json::Value& windowRewriteDefaultConfig = config["window-rewrite-default"]; std::string windowRewriteDefault = windowRewriteDefaultConfig.isString() ? windowRewriteDefaultConfig.asString() : "?"; m_windowRewriteRules = waybar::util::RegexCollection( @@ -75,7 +75,7 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Workspaces::onCmd)); ipc_.sendCmd(IPC_GET_TREE); if (config["enable-bar-scroll"].asBool()) { - auto &window = const_cast(bar_).window; + auto& window = const_cast(bar_).window; window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll)); } @@ -83,21 +83,21 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value ipc_.setWorker([this] { try { ipc_.handleEvent(); - } catch (const std::exception &e) { + } catch (const std::exception& e) { spdlog::error("Workspaces: {}", e.what()); } }); } -void Workspaces::onEvent(const struct Ipc::ipc_response &res) { +void Workspaces::onEvent(const struct Ipc::ipc_response& res) { try { ipc_.sendCmd(IPC_GET_TREE); - } catch (const std::exception &e) { + } catch (const std::exception& e) { spdlog::error("Workspaces: {}", e.what()); } } -void Workspaces::onCmd(const struct Ipc::ipc_response &res) { +void Workspaces::onCmd(const struct Ipc::ipc_response& res) { if (res.type == IPC_GET_TREE) { try { { @@ -107,7 +107,7 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) { std::vector outputs; bool alloutputs = config_["all-outputs"].asBool(); std::copy_if(payload["nodes"].begin(), payload["nodes"].end(), std::back_inserter(outputs), - [&](const auto &output) { + [&](const auto& output) { if (alloutputs && output["name"].asString() != "__i3") { return true; } @@ -117,7 +117,7 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) { return false; }); - for (auto &output : outputs) { + for (auto& output : outputs) { std::copy(output["nodes"].begin(), output["nodes"].end(), std::back_inserter(workspaces_)); std::copy(output["floating_nodes"].begin(), output["floating_nodes"].end(), @@ -126,13 +126,13 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) { // adding persistent workspaces (as per the config file) if (config_["persistent-workspaces"].isObject()) { - const Json::Value &p_workspaces = config_["persistent-workspaces"]; + const Json::Value& p_workspaces = config_["persistent-workspaces"]; const std::vector p_workspaces_names = p_workspaces.getMemberNames(); - for (const std::string &p_w_name : p_workspaces_names) { - const Json::Value &p_w = p_workspaces[p_w_name]; + for (const std::string& p_w_name : p_workspaces_names) { + const Json::Value& p_w = p_workspaces[p_w_name]; auto it = std::find_if(workspaces_.begin(), workspaces_.end(), - [&p_w_name](const Json::Value &node) { + [&p_w_name](const Json::Value& node) { return node["name"].asString() == p_w_name; }); @@ -142,7 +142,7 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) { if (p_w.isArray() && !p_w.empty()) { // Adding to target outputs - for (const Json::Value &output : p_w) { + for (const Json::Value& output : p_w) { if (output.asString() == bar_.output->name) { Json::Value v; v["name"] = p_w_name; @@ -185,10 +185,10 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) { // agreement that the "workspace prev/next" commands may not follow // the order displayed in Waybar. int max_num = -1; - for (auto &workspace : workspaces_) { + for (auto& workspace : workspaces_) { max_num = std::max(workspace["num"].asInt(), max_num); } - for (auto &workspace : workspaces_) { + for (auto& workspace : workspaces_) { auto workspace_num = workspace["num"].asInt(); if (workspace_num > -1) { workspace["sort"] = workspace_num; @@ -197,7 +197,7 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) { } } std::sort(workspaces_.begin(), workspaces_.end(), - [this](const Json::Value &lhs, const Json::Value &rhs) { + [this](const Json::Value& lhs, const Json::Value& rhs) { auto lname = lhs["name"].asString(); auto rname = rhs["name"].asString(); int l = lhs["sort"].asInt(); @@ -217,7 +217,7 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) { }); } dp.emit(); - } catch (const std::exception &e) { + } catch (const std::exception& e) { spdlog::error("Workspaces: {}", e.what()); } } @@ -227,7 +227,7 @@ bool Workspaces::filterButtons() { bool needReorder = false; for (auto it = buttons_.begin(); it != buttons_.end();) { auto ws = std::find_if(workspaces_.begin(), workspaces_.end(), - [it](const auto &node) { return node["name"].asString() == it->first; }); + [it](const auto& node) { return node["name"].asString() == it->first; }); if (ws == workspaces_.end() || (!config_["all-outputs"].asBool() && (*ws)["output"].asString() != bar_.output->name)) { it = buttons_.erase(it); @@ -239,23 +239,23 @@ bool Workspaces::filterButtons() { return needReorder; } -bool Workspaces::hasFlag(const Json::Value &node, const std::string &flag) { +bool Workspaces::hasFlag(const Json::Value& node, const std::string& flag) { if (node[flag].asBool()) { return true; } if (std::any_of(node["nodes"].begin(), node["nodes"].end(), - [&](auto const &e) { return hasFlag(e, flag); })) { + [&](auto const& e) { return hasFlag(e, flag); })) { return true; } if (std::any_of(node["floating_nodes"].begin(), node["floating_nodes"].end(), - [&](auto const &e) { return hasFlag(e, flag); })) { + [&](auto const& e) { return hasFlag(e, flag); })) { return true; } return false; } -void Workspaces::updateWindows(const Json::Value &node, std::string &windows) { +void Workspaces::updateWindows(const Json::Value& node, std::string& windows) { if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") && node["name"].isString()) { std::string title = g_markup_escape_text(node["name"].asString().c_str(), -1); @@ -274,10 +274,10 @@ void Workspaces::updateWindows(const Json::Value &node, std::string &windows) { windows.append(m_formatWindowSeparator); } } - for (const Json::Value &child : node["nodes"]) { + for (const Json::Value& child : node["nodes"]) { updateWindows(child, windows); } - for (const Json::Value &child : node["floating_nodes"]) { + for (const Json::Value& child : node["floating_nodes"]) { updateWindows(child, windows); } } @@ -290,7 +290,7 @@ auto Workspaces::update() -> void { if (bit == buttons_.end()) { needReorder = true; } - auto &button = bit == buttons_.end() ? addButton(*it) : bit->second; + auto& button = bit == buttons_.end() ? addButton(*it) : bit->second; if (needReorder) { box_.reorder_child(button, it - workspaces_.begin()); } @@ -344,7 +344,7 @@ auto Workspaces::update() -> void { fmt::arg("output", (*it)["output"].asString())); } if (!config_["disable-markup"].asBool()) { - static_cast(button.get_children()[0])->set_markup(output); + static_cast(button.get_children()[0])->set_markup(output); } else { button.set_label(output); } @@ -354,9 +354,9 @@ auto Workspaces::update() -> void { AModule::update(); } -Gtk::Button &Workspaces::addButton(const Json::Value &node) { +Gtk::Button& Workspaces::addButton(const Json::Value& node) { auto pair = buttons_.emplace(node["name"].asString(), node["name"].asString()); - auto &&button = pair.first->second; + auto&& button = pair.first->second; box_.pack_start(button, false, false, 0); button.set_name("sway-workspace-" + node["name"].asString()); button.set_relief(Gtk::RELIEF_NONE); @@ -375,7 +375,7 @@ Gtk::Button &Workspaces::addButton(const Json::Value &node) { : "", node["name"].asString())); } - } catch (const std::exception &e) { + } catch (const std::exception& e) { spdlog::error("Workspaces: {}", e.what()); } }); @@ -383,18 +383,18 @@ Gtk::Button &Workspaces::addButton(const Json::Value &node) { return button; } -std::string Workspaces::getIcon(const std::string &name, const Json::Value &node) { +std::string Workspaces::getIcon(const std::string& name, const Json::Value& node) { std::vector keys = {"high-priority-named", "urgent", "focused", name, "default"}; - for (auto const &key : keys) { + for (auto const& key : keys) { if (key == "high-priority-named") { auto it = std::find_if(high_priority_named_.begin(), high_priority_named_.end(), - [&](const std::string &member) { return member == name; }); + [&](const std::string& member) { return member == name; }); if (it != high_priority_named_.end()) { return config_["format-icons"][name].asString(); } it = std::find_if(high_priority_named_.begin(), high_priority_named_.end(), - [&](const std::string &member) { + [&](const std::string& member) { return trimWorkspaceName(member) == trimWorkspaceName(name); }); if (it != high_priority_named_.end()) { @@ -417,8 +417,8 @@ std::string Workspaces::getIcon(const std::string &name, const Json::Value &node return name; } -bool Workspaces::handleScroll(GdkEventScroll *e) { - if (gdk_event_get_pointer_emulated((GdkEvent *)e) != 0) { +bool Workspaces::handleScroll(GdkEventScroll* e) { + if (gdk_event_get_pointer_emulated((GdkEvent*)e) != 0) { /** * Ignore emulated scroll events on window */ @@ -433,7 +433,7 @@ bool Workspaces::handleScroll(GdkEventScroll *e) { bool alloutputs = config_["all-outputs"].asBool(); std::lock_guard lock(mutex_); auto it = - std::find_if(workspaces_.begin(), workspaces_.end(), [alloutputs](const auto &workspace) { + std::find_if(workspaces_.begin(), workspaces_.end(), [alloutputs](const auto& workspace) { if (alloutputs) { return hasFlag(workspace, "focused"); } @@ -460,7 +460,7 @@ bool Workspaces::handleScroll(GdkEventScroll *e) { } try { ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_cmd_, "--no-auto-back-and-forth", name)); - } catch (const std::exception &e) { + } catch (const std::exception& e) { spdlog::error("Workspaces: {}", e.what()); } if (!config_["warp-on-scroll"].isNull() && !config_["warp-on-scroll"].asBool()) { @@ -495,7 +495,7 @@ std::string Workspaces::trimWorkspaceName(std::string name) { return name; } -bool is_focused_recursive(const Json::Value &node) { +bool is_focused_recursive(const Json::Value& node) { // If a workspace has a focused container then get_tree will say // that the workspace itself isn't focused. Therefore we need to // check if any of its nodes are focused as well. @@ -505,13 +505,13 @@ bool is_focused_recursive(const Json::Value &node) { return true; } - for (const auto &child : node["nodes"]) { + for (const auto& child : node["nodes"]) { if (is_focused_recursive(child)) { return true; } } - for (const auto &child : node["floating_nodes"]) { + for (const auto& child : node["floating_nodes"]) { if (is_focused_recursive(child)) { return true; } @@ -520,7 +520,7 @@ bool is_focused_recursive(const Json::Value &node) { return false; } -void Workspaces::onButtonReady(const Json::Value &node, Gtk::Button &button) { +void Workspaces::onButtonReady(const Json::Value& node, Gtk::Button& button) { if (config_["current-only"].asBool()) { if (is_focused_recursive(node)) { button.show(); diff --git a/src/modules/upower.cpp b/src/modules/upower.cpp index 4844582f..7530890c 100644 --- a/src/modules/upower.cpp +++ b/src/modules/upower.cpp @@ -6,7 +6,7 @@ namespace waybar::modules { -UPower::UPower(const std::string &id, const Json::Value &config) +UPower::UPower(const std::string& id, const Json::Value& config) : AIconLabel(config, "upower", id, "{percentage}", 0, true, true, true), sleeping_{false} { box_.set_name(name_); box_.set_spacing(0); @@ -59,7 +59,7 @@ UPower::UPower(const std::string &id, const Json::Value &config) sigc::mem_fun(*this, &UPower::getConn_cb)); // Make UPower client - GError **gErr = NULL; + GError** gErr = NULL; upClient_ = up_client_new_full(NULL, gErr); if (upClient_ == NULL) spdlog::error("Upower. UPower client connection error. {}", (*gErr)->message); @@ -92,7 +92,7 @@ UPower::~UPower() { removeDevices(); } -static const std::string getDeviceStatus(UpDeviceState &state) { +static const std::string getDeviceStatus(UpDeviceState& state) { switch (state) { case UP_DEVICE_STATE_CHARGING: case UP_DEVICE_STATE_PENDING_CHARGE: @@ -109,7 +109,7 @@ static const std::string getDeviceStatus(UpDeviceState &state) { } } -static const std::string getDeviceIcon(UpDeviceKind &kind) { +static const std::string getDeviceIcon(UpDeviceKind& kind) { switch (kind) { case UP_DEVICE_KIND_LINE_POWER: return "ac-adapter-symbolic"; @@ -222,7 +222,7 @@ auto UPower::update() -> void { label_.set_markup(getText(upDevice_, format_)); // Set icon if (upDevice_.icon_name == NULL || !gtkTheme_->has_icon(upDevice_.icon_name)) - upDevice_.icon_name = (char *)NO_BATTERY.c_str(); + upDevice_.icon_name = (char*)NO_BATTERY.c_str(); image_.set_from_icon_name(upDevice_.icon_name, Gtk::ICON_SIZE_INVALID); box_.show(); @@ -231,7 +231,7 @@ auto UPower::update() -> void { ALabel::update(); } -void UPower::getConn_cb(Glib::RefPtr &result) { +void UPower::getConn_cb(Glib::RefPtr& result) { try { conn_ = Gio::DBus::Connection::get_finish(result); // Subscribe DBUs events @@ -239,26 +239,26 @@ void UPower::getConn_cb(Glib::RefPtr &result) { "org.freedesktop.login1", "org.freedesktop.login1.Manager", "PrepareForSleep", "/org/freedesktop/login1"); - } catch (const Glib::Error &e) { + } catch (const Glib::Error& e) { spdlog::error("Upower. DBus connection error. {}", e.what().c_str()); } } -void UPower::onAppear(const Glib::RefPtr &conn, const Glib::ustring &name, - const Glib::ustring &name_owner) { +void UPower::onAppear(const Glib::RefPtr& conn, const Glib::ustring& name, + const Glib::ustring& name_owner) { upRunning_ = true; } -void UPower::onVanished(const Glib::RefPtr &conn, - const Glib::ustring &name) { +void UPower::onVanished(const Glib::RefPtr& conn, + const Glib::ustring& name) { upRunning_ = false; } -void UPower::prepareForSleep_cb(const Glib::RefPtr &connection, - const Glib::ustring &sender_name, const Glib::ustring &object_path, - const Glib::ustring &interface_name, - const Glib::ustring &signal_name, - const Glib::VariantContainerBase ¶meters) { +void UPower::prepareForSleep_cb(const Glib::RefPtr& connection, + const Glib::ustring& sender_name, const Glib::ustring& object_path, + const Glib::ustring& interface_name, + const Glib::ustring& signal_name, + const Glib::VariantContainerBase& parameters) { if (parameters.is_of_type(Glib::VariantType("(b)"))) { Glib::Variant sleeping; parameters.get_child(sleeping, 0); @@ -273,33 +273,33 @@ void UPower::prepareForSleep_cb(const Glib::RefPtr &conne } } -void UPower::deviceAdded_cb(UpClient *client, UpDevice *device, gpointer data) { - UPower *up{static_cast(data)}; +void UPower::deviceAdded_cb(UpClient* client, UpDevice* device, gpointer data) { + UPower* up{static_cast(data)}; up->addDevice(device); up->setDisplayDevice(); // Update the widget up->dp.emit(); } -void UPower::deviceRemoved_cb(UpClient *client, const gchar *objectPath, gpointer data) { - UPower *up{static_cast(data)}; +void UPower::deviceRemoved_cb(UpClient* client, const gchar* objectPath, gpointer data) { + UPower* up{static_cast(data)}; up->removeDevice(objectPath); up->setDisplayDevice(); // Update the widget up->dp.emit(); } -void UPower::deviceNotify_cb(UpDevice *device, GParamSpec *pspec, gpointer data) { - UPower *up{static_cast(data)}; +void UPower::deviceNotify_cb(UpDevice* device, GParamSpec* pspec, gpointer data) { + UPower* up{static_cast(data)}; // Update the widget up->dp.emit(); } -void UPower::addDevice(UpDevice *device) { +void UPower::addDevice(UpDevice* device) { std::lock_guard guard{mutex_}; if (G_IS_OBJECT(device)) { - const gchar *objectPath{up_device_get_object_path(device)}; + const gchar* objectPath{up_device_get_object_path(device)}; // Due to the device getting cleared after this event is fired, we // create a new object pointing to its objectPath @@ -322,7 +322,7 @@ void UPower::addDevice(UpDevice *device) { } } -void UPower::removeDevice(const gchar *objectPath) { +void UPower::removeDevice(const gchar* objectPath) { std::lock_guard guard{mutex_}; if (devices_.find(objectPath) != devices_.cend()) { auto upDevice{devices_[objectPath]}; @@ -348,10 +348,10 @@ void UPower::resetDevices() { removeDevices(); // Adds all devices - GPtrArray *newDevices = up_client_get_devices2(upClient_); + GPtrArray* newDevices = up_client_get_devices2(upClient_); if (newDevices != NULL) for (guint i{0}; i < newDevices->len; ++i) { - UpDevice *device{(UpDevice *)g_ptr_array_index(newDevices, i)}; + UpDevice* device{(UpDevice*)g_ptr_array_index(newDevices, i)}; if (device && G_IS_OBJECT(device)) addDevice(device); } } @@ -372,8 +372,8 @@ void UPower::setDisplayDevice() { up_client_get_devices2(upClient_), [](gpointer data, gpointer user_data) { upDevice_output upDevice; - auto thisPtr{static_cast(user_data)}; - upDevice.upDevice = static_cast(data); + auto thisPtr{static_cast(user_data)}; + upDevice.upDevice = static_cast(data); thisPtr->getUpDeviceInfo(upDevice); upDevice_output displayDevice{NULL}; if (!thisPtr->nativePath_.empty()) { @@ -399,7 +399,7 @@ void UPower::setDisplayDevice() { g_signal_connect(upDevice_.upDevice, "notify", G_CALLBACK(deviceNotify_cb), this); } -void UPower::getUpDeviceInfo(upDevice_output &upDevice_) { +void UPower::getUpDeviceInfo(upDevice_output& upDevice_) { if (upDevice_.upDevice != NULL && G_IS_OBJECT(upDevice_.upDevice)) { g_object_get(upDevice_.upDevice, "kind", &upDevice_.kind, "state", &upDevice_.state, "percentage", &upDevice_.percentage, "icon-name", &upDevice_.icon_name, @@ -416,7 +416,7 @@ native_path: \"{7}\". model: \"{8}\"", } } -const Glib::ustring UPower::getText(const upDevice_output &upDevice_, const std::string &format) { +const Glib::ustring UPower::getText(const upDevice_output& upDevice_, const std::string& format) { Glib::ustring ret{""}; if (upDevice_.upDevice != NULL) { std::string timeStr{""}; @@ -445,11 +445,11 @@ const Glib::ustring UPower::getText(const upDevice_output &upDevice_, const std: } bool UPower::queryTooltipCb(int x, int y, bool keyboard_tooltip, - const Glib::RefPtr &tooltip) { + const Glib::RefPtr& tooltip) { std::lock_guard guard{mutex_}; // Clear content box - contentBox_.forall([this](Gtk::Widget &wg) { contentBox_.remove(wg); }); + contentBox_.forall([this](Gtk::Widget& wg) { contentBox_.remove(wg); }); // Fill content box with the content for (auto pairDev : devices_) { @@ -459,33 +459,33 @@ bool UPower::queryTooltipCb(int x, int y, bool keyboard_tooltip, if (pairDev.second.kind != UpDeviceKind::UP_DEVICE_KIND_UNKNOWN && pairDev.second.kind != UpDeviceKind::UP_DEVICE_KIND_LINE_POWER) { // Make box record - Gtk::Box *boxRec{new Gtk::Box{box_.get_orientation(), tooltip_spacing_}}; + Gtk::Box* boxRec{new Gtk::Box{box_.get_orientation(), tooltip_spacing_}}; contentBox_.add(*boxRec); - Gtk::Box *boxDev{new Gtk::Box{box_.get_orientation()}}; - Gtk::Box *boxUsr{new Gtk::Box{box_.get_orientation()}}; + Gtk::Box* boxDev{new Gtk::Box{box_.get_orientation()}}; + Gtk::Box* boxUsr{new Gtk::Box{box_.get_orientation()}}; boxRec->add(*boxDev); boxRec->add(*boxUsr); // Construct device box // Set icon from kind std::string iconNameDev{getDeviceIcon(pairDev.second.kind)}; - if (!gtkTheme_->has_icon(iconNameDev)) iconNameDev = (char *)NO_BATTERY.c_str(); - Gtk::Image *iconDev{new Gtk::Image{}}; + if (!gtkTheme_->has_icon(iconNameDev)) iconNameDev = (char*)NO_BATTERY.c_str(); + Gtk::Image* iconDev{new Gtk::Image{}}; iconDev->set_from_icon_name(iconNameDev, Gtk::ICON_SIZE_INVALID); iconDev->set_pixel_size(iconSize_); boxDev->add(*iconDev); // Set label from model - Gtk::Label *labelDev{new Gtk::Label{pairDev.second.model}}; + Gtk::Label* labelDev{new Gtk::Label{pairDev.second.model}}; boxDev->add(*labelDev); // Construct user box // Set icon from icon state if (pairDev.second.icon_name == NULL || !gtkTheme_->has_icon(pairDev.second.icon_name)) - pairDev.second.icon_name = (char *)NO_BATTERY.c_str(); - Gtk::Image *iconTooltip{new Gtk::Image{}}; + pairDev.second.icon_name = (char*)NO_BATTERY.c_str(); + Gtk::Image* iconTooltip{new Gtk::Image{}}; iconTooltip->set_from_icon_name(pairDev.second.icon_name, Gtk::ICON_SIZE_INVALID); iconTooltip->set_pixel_size(iconSize_); boxUsr->add(*iconTooltip); // Set markup text - Gtk::Label *labelTooltip{new Gtk::Label{}}; + Gtk::Label* labelTooltip{new Gtk::Label{}}; labelTooltip->set_markup(getText(pairDev.second, tooltipFormat_)); boxUsr->add(*labelTooltip); } diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index c5c522d8..d58303da 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -29,42 +29,42 @@ namespace waybar::modules::wlr { /* Task class implementation */ uint32_t Task::global_id = 0; -static void tl_handle_title(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, - const char *title) { - return static_cast(data)->handle_title(title); +static void tl_handle_title(void* data, struct zwlr_foreign_toplevel_handle_v1* handle, + const char* title) { + return static_cast(data)->handle_title(title); } -static void tl_handle_app_id(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, - const char *app_id) { - return static_cast(data)->handle_app_id(app_id); +static void tl_handle_app_id(void* data, struct zwlr_foreign_toplevel_handle_v1* handle, + const char* app_id) { + return static_cast(data)->handle_app_id(app_id); } -static void tl_handle_output_enter(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, - struct wl_output *output) { - return static_cast(data)->handle_output_enter(output); +static void tl_handle_output_enter(void* data, struct zwlr_foreign_toplevel_handle_v1* handle, + struct wl_output* output) { + return static_cast(data)->handle_output_enter(output); } -static void tl_handle_output_leave(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, - struct wl_output *output) { - return static_cast(data)->handle_output_leave(output); +static void tl_handle_output_leave(void* data, struct zwlr_foreign_toplevel_handle_v1* handle, + struct wl_output* output) { + return static_cast(data)->handle_output_leave(output); } -static void tl_handle_state(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, - struct wl_array *state) { - return static_cast(data)->handle_state(state); +static void tl_handle_state(void* data, struct zwlr_foreign_toplevel_handle_v1* handle, + struct wl_array* state) { + return static_cast(data)->handle_state(state); } -static void tl_handle_done(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle) { - return static_cast(data)->handle_done(); +static void tl_handle_done(void* data, struct zwlr_foreign_toplevel_handle_v1* handle) { + return static_cast(data)->handle_done(); } -static void tl_handle_parent(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, - struct zwlr_foreign_toplevel_handle_v1 *parent) { +static void tl_handle_parent(void* data, struct zwlr_foreign_toplevel_handle_v1* handle, + struct zwlr_foreign_toplevel_handle_v1* parent) { /* This is explicitly left blank */ } -static void tl_handle_closed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle) { - return static_cast(data)->handle_closed(); +static void tl_handle_closed(void* data, struct zwlr_foreign_toplevel_handle_v1* handle) { + return static_cast(data)->handle_closed(); } static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_handle_impl = { @@ -81,8 +81,8 @@ static const struct zwlr_foreign_toplevel_handle_v1_listener toplevel_handle_imp static const std::vector target_entries = { Gtk::TargetEntry("WAYBAR_TOPLEVEL", Gtk::TARGET_SAME_APP, 0)}; -Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar, - struct zwlr_foreign_toplevel_handle_v1 *tl_handle, struct wl_seat *seat) +Task::Task(const waybar::Bar& bar, const Json::Value& config, Taskbar* tbar, + struct zwlr_foreign_toplevel_handle_v1* tl_handle, struct wl_seat* seat) : bar_{bar}, config_{config}, tbar_{tbar}, @@ -189,7 +189,7 @@ std::string Task::state_string(bool shortened) const { return res.substr(0, res.size() - 1); } -void Task::handle_title(const char *title) { +void Task::handle_title(const char* title) { if (title_.empty()) { spdlog::debug(fmt::format("Task ({}) setting title to {}", id_, title_)); } else { @@ -238,7 +238,7 @@ void Task::hide_if_ignored() { } } -void Task::handle_app_id(const char *app_id) { +void Task::handle_app_id(const char* app_id) { if (app_id_.empty()) { spdlog::debug(fmt::format("Task ({}) setting app_id to {}", id_, app_id)); } else { @@ -273,20 +273,20 @@ void Task::handle_app_id(const char *app_id) { spdlog::debug("Couldn't find icon for {}", app_id_); } -void Task::on_button_size_allocated(Gtk::Allocation &alloc) { +void Task::on_button_size_allocated(Gtk::Allocation& alloc) { gtk_widget_translate_coordinates(GTK_WIDGET(button.gobj()), GTK_WIDGET(bar_.window.gobj()), 0, 0, &minimize_hint.x, &minimize_hint.y); minimize_hint.w = button.get_width(); minimize_hint.h = button.get_height(); } -void Task::handle_output_enter(struct wl_output *output) { +void Task::handle_output_enter(struct wl_output* output) { if (ignored_) { spdlog::debug("{} is ignored", repr()); return; } - spdlog::debug("{} entered output {}", repr(), (void *)output); + spdlog::debug("{} entered output {}", repr(), (void*)output); if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) { /* The task entered the output of the current bar make the button visible */ @@ -299,8 +299,8 @@ void Task::handle_output_enter(struct wl_output *output) { } } -void Task::handle_output_leave(struct wl_output *output) { - spdlog::debug("{} left output {}", repr(), (void *)output); +void Task::handle_output_leave(struct wl_output* output) { + spdlog::debug("{} left output {}", repr(), (void*)output); if (button_visible_ && !tbar_->all_outputs() && tbar_->show_output(output)) { /* The task left the output of the current bar, make the button invisible */ @@ -311,11 +311,11 @@ void Task::handle_output_leave(struct wl_output *output) { } } -void Task::handle_state(struct wl_array *state) { +void Task::handle_state(struct wl_array* state) { state_ = 0; size_t size = state->size / sizeof(uint32_t); for (size_t i = 0; i < size; ++i) { - auto entry = static_cast(state->data)[i]; + auto entry = static_cast(state->data)[i]; if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED) state_ |= MAXIMIZED; if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED) state_ |= MINIMIZED; if (entry == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) state_ |= ACTIVE; @@ -367,7 +367,7 @@ void Task::handle_closed() { tbar_->remove_task(id_); } -bool Task::handle_clicked(GdkEventButton *bt) { +bool Task::handle_clicked(GdkEventButton* bt) { /* filter out additional events for double/triple clicks */ if (bt->type == GDK_BUTTON_PRESS) { /* save where the button press occurred in case it becomes a drag */ @@ -412,7 +412,7 @@ bool Task::handle_clicked(GdkEventButton *bt) { return true; } -bool Task::handle_motion_notify(GdkEventMotion *mn) { +bool Task::handle_motion_notify(GdkEventMotion* mn) { if (drag_start_button == -1) return false; if (button.drag_check_threshold(drag_start_x, drag_start_y, mn->x, mn->y)) { @@ -420,25 +420,25 @@ bool Task::handle_motion_notify(GdkEventMotion *mn) { auto target_list = Gtk::TargetList::create(target_entries); auto refptr = Glib::RefPtr(target_list); auto drag_context = - button.drag_begin(refptr, Gdk::DragAction::ACTION_MOVE, drag_start_button, (GdkEvent *)mn); + button.drag_begin(refptr, Gdk::DragAction::ACTION_MOVE, drag_start_button, (GdkEvent*)mn); } return false; } -void Task::handle_drag_data_get(const Glib::RefPtr &context, - Gtk::SelectionData &selection_data, guint info, guint time) { +void Task::handle_drag_data_get(const Glib::RefPtr& context, + Gtk::SelectionData& selection_data, guint info, guint time) { spdlog::debug("drag_data_get"); - void *button_addr = (void *)&this->button; + void* button_addr = (void*)&this->button; - selection_data.set("WAYBAR_TOPLEVEL", 32, (const guchar *)&button_addr, sizeof(gpointer)); + selection_data.set("WAYBAR_TOPLEVEL", 32, (const guchar*)&button_addr, sizeof(gpointer)); } -void Task::handle_drag_data_received(const Glib::RefPtr &context, int x, int y, +void Task::handle_drag_data_received(const Glib::RefPtr& context, int x, int y, Gtk::SelectionData selection_data, guint info, guint time) { spdlog::debug("drag_data_received"); - gpointer handle = *(gpointer *)selection_data.get_data(); - auto dragged_button = (Gtk::Button *)handle; + gpointer handle = *(gpointer*)selection_data.get_data(); + auto dragged_button = (Gtk::Button*)handle; if (dragged_button == &this->button) return; @@ -447,7 +447,7 @@ void Task::handle_drag_data_received(const Glib::RefPtr &conte if (parent_of_dragged != parent_of_dest) return; - auto box = (Gtk::Box *)parent_of_dragged; + auto box = (Gtk::Box*)parent_of_dragged; auto position_prop = box->child_property_position(this->button); auto position = position_prop.get_value(); @@ -455,9 +455,9 @@ void Task::handle_drag_data_received(const Glib::RefPtr &conte box->reorder_child(*dragged_button, position); } -bool Task::operator==(const Task &o) const { return o.id_ == id_; } +bool Task::operator==(const Task& o) const { return o.id_ == id_; } -bool Task::operator!=(const Task &o) const { return o.id_ != id_; } +bool Task::operator!=(const Task& o) const { return o.id_ != id_; } void Task::update() { bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false; @@ -545,23 +545,23 @@ void Task::fullscreen(bool set) { void Task::close() { zwlr_foreign_toplevel_handle_v1_close(handle_); } /* Taskbar class implementation */ -static void handle_global(void *data, struct wl_registry *registry, uint32_t name, - const char *interface, uint32_t version) { +static void handle_global(void* data, struct wl_registry* registry, uint32_t name, + const char* interface, uint32_t version) { if (std::strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) { - static_cast(data)->register_manager(registry, name, version); + static_cast(data)->register_manager(registry, name, version); } else if (std::strcmp(interface, wl_seat_interface.name) == 0) { - static_cast(data)->register_seat(registry, name, version); + static_cast(data)->register_seat(registry, name, version); } } -static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { +static void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) { /* Nothing to do here */ } static const wl_registry_listener registry_listener_impl = {.global = handle_global, .global_remove = handle_global_remove}; -Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Value &config) +Taskbar::Taskbar(const std::string& id, const waybar::Bar& bar, const Json::Value& config) : waybar::AModule(config, "taskbar", id, false, false), bar_(bar), box_{bar.orientation, 0}, @@ -575,8 +575,8 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu box_.get_style_context()->add_class("empty"); event_box_.add(box_); - struct wl_display *display = Client::inst()->wl_display; - struct wl_registry *registry = wl_display_get_registry(display); + struct wl_display* display = Client::inst()->wl_display; + struct wl_registry* registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener_impl, this); wl_display_roundtrip(display); @@ -592,7 +592,7 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu /* Get the configured icon theme if specified */ if (config_["icon-theme"].isArray()) { - for (auto &c : config_["icon-theme"]) { + for (auto& c : config_["icon-theme"]) { icon_loader_.add_custom_icon_theme(c.asString()); } } else if (config_["icon-theme"].isString()) { @@ -601,28 +601,28 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu // Load ignore-list if (config_["ignore-list"].isArray()) { - for (auto &app_name : config_["ignore-list"]) { + for (auto& app_name : config_["ignore-list"]) { ignore_list_.emplace(app_name.asString()); } } // Load app_id remappings if (config_["app_ids-mapping"].isObject()) { - const Json::Value &mapping = config_["app_ids-mapping"]; + const Json::Value& mapping = config_["app_ids-mapping"]; const std::vector app_ids = config_["app_ids-mapping"].getMemberNames(); - for (auto &app_id : app_ids) { + for (auto& app_id : app_ids) { app_ids_replace_map_.emplace(app_id, mapping[app_id].asString()); } } - for (auto &t : tasks_) { + for (auto& t : tasks_) { t->handle_app_id(t->app_id().c_str()); } } Taskbar::~Taskbar() { if (manager_) { - struct wl_display *display = Client::inst()->wl_display; + struct wl_display* display = Client::inst()->wl_display; /* * Send `stop` request and wait for one roundtrip. * This is not quite correct as the protocol encourages us to wait for the .finished event, @@ -640,13 +640,13 @@ Taskbar::~Taskbar() { } void Taskbar::update() { - for (auto &t : tasks_) { + for (auto& t : tasks_) { t->update(); } if (config_["sort-by-app-id"].asBool()) { std::stable_sort(tasks_.begin(), tasks_.end(), - [](const std::unique_ptr &a, const std::unique_ptr &b) { + [](const std::unique_ptr& a, const std::unique_ptr& b) { return a->app_id() < b->app_id(); }); @@ -658,13 +658,13 @@ void Taskbar::update() { AModule::update(); } -static void tm_handle_toplevel(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager, - struct zwlr_foreign_toplevel_handle_v1 *tl_handle) { - return static_cast(data)->handle_toplevel_create(tl_handle); +static void tm_handle_toplevel(void* data, struct zwlr_foreign_toplevel_manager_v1* manager, + struct zwlr_foreign_toplevel_handle_v1* tl_handle) { + return static_cast(data)->handle_toplevel_create(tl_handle); } -static void tm_handle_finished(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager) { - return static_cast(data)->handle_finished(); +static void tm_handle_finished(void* data, struct zwlr_foreign_toplevel_manager_v1* manager) { + return static_cast(data)->handle_finished(); } static const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_impl = { @@ -672,7 +672,7 @@ static const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_i .finished = tm_handle_finished, }; -void Taskbar::register_manager(struct wl_registry *registry, uint32_t name, uint32_t version) { +void Taskbar::register_manager(struct wl_registry* registry, uint32_t name, uint32_t version) { if (manager_) { spdlog::warn("Register foreign toplevel manager again although already existing!"); return; @@ -687,7 +687,7 @@ void Taskbar::register_manager(struct wl_registry *registry, uint32_t name, uint // limit version to a highest supported by the client protocol file version = std::min(version, zwlr_foreign_toplevel_manager_v1_interface.version); - manager_ = static_cast( + manager_ = static_cast( wl_registry_bind(registry, name, &zwlr_foreign_toplevel_manager_v1_interface, version)); if (manager_) @@ -696,17 +696,17 @@ void Taskbar::register_manager(struct wl_registry *registry, uint32_t name, uint spdlog::debug("Failed to register manager"); } -void Taskbar::register_seat(struct wl_registry *registry, uint32_t name, uint32_t version) { +void Taskbar::register_seat(struct wl_registry* registry, uint32_t name, uint32_t version) { if (seat_) { spdlog::warn("Register seat again although already existing!"); return; } version = std::min(version, wl_seat_interface.version); - seat_ = static_cast(wl_registry_bind(registry, name, &wl_seat_interface, version)); + seat_ = static_cast(wl_registry_bind(registry, name, &wl_seat_interface, version)); } -void Taskbar::handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1 *tl_handle) { +void Taskbar::handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1* tl_handle) { tasks_.push_back(std::make_unique(bar_, config_, this, tl_handle, seat_)); } @@ -715,14 +715,14 @@ void Taskbar::handle_finished() { manager_ = nullptr; } -void Taskbar::add_button(Gtk::Button &bt) { +void Taskbar::add_button(Gtk::Button& bt) { box_.pack_start(bt, false, false); box_.get_style_context()->remove_class("empty"); } -void Taskbar::move_button(Gtk::Button &bt, int pos) { box_.reorder_child(bt, pos); } +void Taskbar::move_button(Gtk::Button& bt, int pos) { box_.reorder_child(bt, pos); } -void Taskbar::remove_button(Gtk::Button &bt) { +void Taskbar::remove_button(Gtk::Button& bt) { box_.remove(bt); if (box_.get_children().empty()) { box_.get_style_context()->add_class("empty"); @@ -731,7 +731,7 @@ void Taskbar::remove_button(Gtk::Button &bt) { void Taskbar::remove_task(uint32_t id) { auto it = std::find_if(std::begin(tasks_), std::end(tasks_), - [id](const TaskPtr &p) { return p->id() == id; }); + [id](const TaskPtr& p) { return p->id() == id; }); if (it == std::end(tasks_)) { spdlog::warn("Can't find task with id {}", id); @@ -741,7 +741,7 @@ void Taskbar::remove_task(uint32_t id) { tasks_.erase(it); } -bool Taskbar::show_output(struct wl_output *output) const { +bool Taskbar::show_output(struct wl_output* output) const { return output == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); } @@ -749,11 +749,11 @@ bool Taskbar::all_outputs() const { return config_["all-outputs"].isBool() && config_["all-outputs"].asBool(); } -const IconLoader &Taskbar::icon_loader() const { return icon_loader_; } +const IconLoader& Taskbar::icon_loader() const { return icon_loader_; } -const std::unordered_set &Taskbar::ignore_list() const { return ignore_list_; } +const std::unordered_set& Taskbar::ignore_list() const { return ignore_list_; } -const std::map &Taskbar::app_ids_replace_map() const { +const std::map& Taskbar::app_ids_replace_map() const { return app_ids_replace_map_; } diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index 860168fd..342d40b2 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -69,8 +69,8 @@ void AudioBackend::connectContext() { } } -void AudioBackend::contextStateCb(pa_context *c, void *data) { - auto *backend = static_cast(data); +void AudioBackend::contextStateCb(pa_context* c, void* data) { + auto* backend = static_cast(data); switch (pa_context_get_state(c)) { case PA_CONTEXT_TERMINATED: backend->mainloop_api_->quit(backend->mainloop_api_, 0); @@ -107,8 +107,8 @@ void AudioBackend::contextStateCb(pa_context *c, void *data) { /* * Called when an event we subscribed to occurs. */ -void AudioBackend::subscribeCb(pa_context *context, pa_subscription_event_type_t type, uint32_t idx, - void *data) { +void AudioBackend::subscribeCb(pa_context* context, pa_subscription_event_type_t type, uint32_t idx, + void* data) { unsigned facility = type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; unsigned operation = type & PA_SUBSCRIPTION_EVENT_TYPE_MASK; if (operation != PA_SUBSCRIPTION_EVENT_CHANGE) { @@ -130,8 +130,8 @@ void AudioBackend::subscribeCb(pa_context *context, pa_subscription_event_type_t /* * Called in response to a volume change request */ -void AudioBackend::volumeModifyCb(pa_context *c, int success, void *data) { - auto *backend = static_cast(data); +void AudioBackend::volumeModifyCb(pa_context* c, int success, void* data) { + auto* backend = static_cast(data); if (success != 0) { if ((backend->context_ != nullptr) && pa_context_get_state(backend->context_) == PA_CONTEXT_READY) { @@ -145,18 +145,18 @@ void AudioBackend::volumeModifyCb(pa_context *c, int success, void *data) { /* * Called when the requested sink information is ready. */ -void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, int /*eol*/, - void *data) { +void AudioBackend::sinkInfoCb(pa_context* /*context*/, const pa_sink_info* i, int /*eol*/, + void* data) { if (i == nullptr) return; auto running = i->state == PA_SINK_RUNNING; auto idle = i->state == PA_SINK_IDLE; spdlog::trace("Sink name {} Running:[{}] Idle:[{}]", i->name, running, idle); - auto *backend = static_cast(data); + auto* backend = static_cast(data); if (!backend->ignored_sinks_.empty()) { - for (const auto &ignored_sink : backend->ignored_sinks_) { + for (const auto& ignored_sink : backend->ignored_sinks_) { if (ignored_sink == i->description) { if (i->name == backend->current_sink_name_) { // If the current sink happens to be ignored it is never considered running @@ -205,7 +205,7 @@ void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, i backend->desc_ = i->description; backend->monitor_ = i->monitor_source_name; backend->port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown"; - if (const auto *ff = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_FORM_FACTOR)) { + if (const auto* ff = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_FORM_FACTOR)) { backend->form_factor_ = ff; } else { backend->form_factor_ = ""; @@ -217,9 +217,9 @@ void AudioBackend::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, i /* * Called when the requested source information is ready. */ -void AudioBackend::sourceInfoCb(pa_context * /*context*/, const pa_source_info *i, int /*eol*/, - void *data) { - auto *backend = static_cast(data); +void AudioBackend::sourceInfoCb(pa_context* /*context*/, const pa_source_info* i, int /*eol*/, + void* data) { + auto* backend = static_cast(data); if (i != nullptr && backend->default_source_name_ == i->name) { auto source_volume = static_cast(pa_cvolume_avg(&(i->volume))) / float{PA_VOLUME_NORM}; backend->source_volume_ = std::round(source_volume * 100.0F); @@ -235,8 +235,8 @@ void AudioBackend::sourceInfoCb(pa_context * /*context*/, const pa_source_info * * Called when the requested information on the server is ready. This is * used to find the default PulseAudio sink. */ -void AudioBackend::serverInfoCb(pa_context *context, const pa_server_info *i, void *data) { - auto *backend = static_cast(data); +void AudioBackend::serverInfoCb(pa_context* context, const pa_server_info* i, void* data) { + auto* backend = static_cast(data); backend->current_sink_name_ = i->default_sink_name; backend->default_sink_name = i->default_sink_name; backend->default_source_name_ = i->default_source_name; @@ -392,9 +392,9 @@ bool AudioBackend::isBluetooth() { monitor_.find("bluez") != std::string::npos; } -void AudioBackend::setIgnoredSinks(const Json::Value &config) { +void AudioBackend::setIgnoredSinks(const Json::Value& config) { if (config.isArray()) { - for (const auto &ignored_sink : config) { + for (const auto& ignored_sink : config) { if (ignored_sink.isString()) { ignored_sinks_.push_back(ignored_sink.asString()); } diff --git a/src/util/backlight_backend.cpp b/src/util/backlight_backend.cpp index 8ae52fb9..a136f635 100644 --- a/src/util/backlight_backend.cpp +++ b/src/util/backlight_backend.cpp @@ -14,10 +14,10 @@ namespace { class FileDescriptor { public: explicit FileDescriptor(int fd) : fd_(fd) {} - FileDescriptor(const FileDescriptor &other) = delete; - FileDescriptor(FileDescriptor &&other) noexcept = delete; - FileDescriptor &operator=(const FileDescriptor &other) = delete; - FileDescriptor &operator=(FileDescriptor &&other) noexcept = delete; + FileDescriptor(const FileDescriptor& other) = delete; + FileDescriptor(FileDescriptor&& other) noexcept = delete; + FileDescriptor& operator=(const FileDescriptor& other) = delete; + FileDescriptor& operator=(FileDescriptor&& other) noexcept = delete; ~FileDescriptor() { if (fd_ != -1) { if (close(fd_) != 0) { @@ -31,27 +31,27 @@ class FileDescriptor { int fd_; }; -void check_eq(int rc, int expected, const char *message = "eq, rc was: ") { +void check_eq(int rc, int expected, const char* message = "eq, rc was: ") { if (rc != expected) { throw std::runtime_error(fmt::format(fmt::runtime(message), rc)); } } -void check_neq(int rc, int bad_rc, const char *message = "neq, rc was: ") { +void check_neq(int rc, int bad_rc, const char* message = "neq, rc was: ") { if (rc == bad_rc) { throw std::runtime_error(fmt::format(fmt::runtime(message), rc)); } } -void check0(int rc, const char *message = "rc wasn't 0") { check_eq(rc, 0, message); } +void check0(int rc, const char* message = "rc wasn't 0") { check_eq(rc, 0, message); } -void check_gte(int rc, int gte, const char *message = "rc was: ") { +void check_gte(int rc, int gte, const char* message = "rc was: ") { if (rc < gte) { throw std::runtime_error(fmt::format(fmt::runtime(message), rc)); } } -void check_nn(const void *ptr, const char *message = "ptr was null") { +void check_nn(const void* ptr, const char* message = "ptr was null") { if (ptr == nullptr) { throw std::runtime_error(message); } @@ -61,20 +61,20 @@ void check_nn(const void *ptr, const char *message = "ptr was null") { namespace waybar::util { -static void upsert_device(std::vector &devices, udev_device *dev) { - const char *name = udev_device_get_sysname(dev); +static void upsert_device(std::vector& devices, udev_device* dev) { + const char* name = udev_device_get_sysname(dev); check_nn(name); - const char *actual_brightness_attr = + const char* actual_brightness_attr = strncmp(name, "amdgpu_bl", 9) == 0 || strcmp(name, "apple-panel-bl") == 0 ? "brightness" : "actual_brightness"; - const char *actual = udev_device_get_sysattr_value(dev, actual_brightness_attr); - const char *max = udev_device_get_sysattr_value(dev, "max_brightness"); - const char *power = udev_device_get_sysattr_value(dev, "bl_power"); + const char* actual = udev_device_get_sysattr_value(dev, actual_brightness_attr); + const char* max = udev_device_get_sysattr_value(dev, "max_brightness"); + const char* power = udev_device_get_sysattr_value(dev, "bl_power"); - auto found = std::find_if(devices.begin(), devices.end(), [name](const BacklightDevice &device) { + auto found = std::find_if(devices.begin(), devices.end(), [name](const BacklightDevice& device) { return device.name() == name; }); if (found != devices.end()) { @@ -95,14 +95,14 @@ static void upsert_device(std::vector &devices, udev_device *de } } -static void enumerate_devices(std::vector &devices, udev *udev) { +static void enumerate_devices(std::vector& devices, udev* udev) { std::unique_ptr enumerate{udev_enumerate_new(udev)}; udev_enumerate_add_match_subsystem(enumerate.get(), "backlight"); udev_enumerate_scan_devices(enumerate.get()); - udev_list_entry *enum_devices = udev_enumerate_get_list_entry(enumerate.get()); - udev_list_entry *dev_list_entry; + udev_list_entry* enum_devices = udev_enumerate_get_list_entry(enumerate.get()); + udev_list_entry* dev_list_entry; udev_list_entry_foreach(dev_list_entry, enum_devices) { - const char *path = udev_list_entry_get_name(dev_list_entry); + const char* path = udev_list_entry_get_name(dev_list_entry); std::unique_ptr dev{udev_device_new_from_syspath(udev, path)}; check_nn(dev.get(), "dev new failed"); upsert_device(devices, dev.get()); @@ -184,7 +184,7 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval, devices = devices_; } for (int i = 0; i < event_count; ++i) { - const auto &event = events[i]; + const auto& event = events[i]; check_eq(event.data.fd, udev_fd, "unexpected udev fd"); std::unique_ptr dev{udev_monitor_receive_device(mon.get())}; if (!dev) { @@ -206,27 +206,27 @@ BacklightBackend::BacklightBackend(std::chrono::milliseconds interval, }; } -const BacklightDevice *BacklightBackend::best_device(const std::vector &devices, +const BacklightDevice* BacklightBackend::best_device(const std::vector& devices, std::string_view preferred_device) { const auto found = std::find_if( devices.begin(), devices.end(), - [preferred_device](const BacklightDevice &dev) { return dev.name() == preferred_device; }); + [preferred_device](const BacklightDevice& dev) { return dev.name() == preferred_device; }); if (found != devices.end()) { return &(*found); } const auto max = std::max_element( devices.begin(), devices.end(), - [](const BacklightDevice &l, const BacklightDevice &r) { return l.get_max() < r.get_max(); }); + [](const BacklightDevice& l, const BacklightDevice& r) { return l.get_max() < r.get_max(); }); return max == devices.end() ? nullptr : &(*max); } -const BacklightDevice *BacklightBackend::get_previous_best_device() { +const BacklightDevice* BacklightBackend::get_previous_best_device() { return previous_best_.has_value() ? &(*previous_best_) : nullptr; } -void BacklightBackend::set_previous_best_device(const BacklightDevice *device) { +void BacklightBackend::set_previous_best_device(const BacklightDevice* device) { if (device == nullptr) { previous_best_ = std::nullopt; } else { @@ -234,7 +234,7 @@ void BacklightBackend::set_previous_best_device(const BacklightDevice *device) { } } -void BacklightBackend::set_scaled_brightness(const std::string &preferred_device, int brightness) { +void BacklightBackend::set_scaled_brightness(const std::string& preferred_device, int brightness) { GET_BEST_DEVICE(best, (*this), preferred_device); if (best != nullptr) { @@ -244,7 +244,7 @@ void BacklightBackend::set_scaled_brightness(const std::string &preferred_device } } -void BacklightBackend::set_brightness(const std::string &preferred_device, ChangeType change_type, +void BacklightBackend::set_brightness(const std::string& preferred_device, ChangeType change_type, double step) { GET_BEST_DEVICE(best, (*this), preferred_device); @@ -259,7 +259,7 @@ void BacklightBackend::set_brightness(const std::string &preferred_device, Chang } } -void BacklightBackend::set_brightness_internal(const std::string &device_name, int brightness, +void BacklightBackend::set_brightness_internal(const std::string& device_name, int brightness, int max_brightness) { brightness = std::clamp(brightness, 0, max_brightness); @@ -269,7 +269,7 @@ void BacklightBackend::set_brightness_internal(const std::string &device_name, i login_proxy_->call_sync("SetBrightness", call_args); } -int BacklightBackend::get_scaled_brightness(const std::string &preferred_device) { +int BacklightBackend::get_scaled_brightness(const std::string& preferred_device) { GET_BEST_DEVICE(best, (*this), preferred_device); if (best != nullptr) { diff --git a/src/util/icon_loader.cpp b/src/util/icon_loader.cpp index 69e25dbc..fe534f69 100644 --- a/src/util/icon_loader.cpp +++ b/src/util/icon_loader.cpp @@ -26,12 +26,12 @@ std::vector IconLoader::search_prefix() { } while (end != std::string::npos); } - for (auto &p : prefixes) spdlog::debug("Using 'desktop' search path prefix: {}", p); + for (auto& p : prefixes) spdlog::debug("Using 'desktop' search path prefix: {}", p); return prefixes; } -Glib::RefPtr IconLoader::get_app_info_by_name(const std::string &app_id) { +Glib::RefPtr IconLoader::get_app_info_by_name(const std::string& app_id) { static std::vector prefixes = search_prefix(); std::vector app_folders = {"", "applications/", "applications/kde/", @@ -39,9 +39,9 @@ Glib::RefPtr IconLoader::get_app_info_by_name(const std::st std::vector suffixes = {"", ".desktop"}; - for (auto const &prefix : prefixes) { - for (auto const &folder : app_folders) { - for (auto const &suffix : suffixes) { + for (auto const& prefix : prefixes) { + for (auto const& folder : app_folders) { + for (auto const& suffix : suffixes) { auto app_info_ = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix); if (!app_info_) { @@ -56,7 +56,7 @@ Glib::RefPtr IconLoader::get_app_info_by_name(const std::st return {}; } -Glib::RefPtr IconLoader::get_desktop_app_info(const std::string &app_id) { +Glib::RefPtr IconLoader::get_desktop_app_info(const std::string& app_id) { auto app_info = get_app_info_by_name(app_id); if (app_info) { return app_info; @@ -64,7 +64,7 @@ Glib::RefPtr IconLoader::get_desktop_app_info(const std::st std::string desktop_file = ""; - gchar ***desktop_list = g_desktop_app_info_search(app_id.c_str()); + gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str()); if (desktop_list != nullptr && desktop_list[0] != nullptr) { for (size_t i = 0; desktop_list[0][i]; i++) { if (desktop_file == "") { @@ -89,7 +89,7 @@ Glib::RefPtr IconLoader::get_desktop_app_info(const std::st return get_app_info_by_name(desktop_file); } -Glib::RefPtr IconLoader::load_icon_from_file(std::string const &icon_path, int size) { +Glib::RefPtr IconLoader::load_icon_from_file(std::string const& icon_path, int size) { try { auto pb = Gdk::Pixbuf::create_from_file(icon_path, size, size); return pb; @@ -99,13 +99,13 @@ Glib::RefPtr IconLoader::load_icon_from_file(std::string const &ico } std::string IconLoader::get_icon_name_from_icon_theme( - const Glib::RefPtr &icon_theme, const std::string &app_id) { + const Glib::RefPtr& icon_theme, const std::string& app_id) { if (icon_theme->lookup_icon(app_id, 24)) return app_id; return ""; } -bool IconLoader::image_load_icon(Gtk::Image &image, const Glib::RefPtr &icon_theme, +bool IconLoader::image_load_icon(Gtk::Image& image, const Glib::RefPtr& icon_theme, Glib::RefPtr app_info, int size) { std::string ret_icon_name = "unknown"; if (app_info) { @@ -152,16 +152,16 @@ bool IconLoader::image_load_icon(Gtk::Image &image, const Glib::RefPtrset_custom_theme(theme_name); custom_icon_themes_.push_back(icon_theme); spdlog::debug("Use custom icon theme: {}", theme_name); } -bool IconLoader::image_load_icon(Gtk::Image &image, Glib::RefPtr app_info, +bool IconLoader::image_load_icon(Gtk::Image& image, Glib::RefPtr app_info, int size) const { - for (auto &icon_theme : custom_icon_themes_) { + for (auto& icon_theme : custom_icon_themes_) { if (image_load_icon(image, icon_theme, app_info, size)) { return true; } @@ -170,7 +170,7 @@ bool IconLoader::image_load_icon(Gtk::Image &image, Glib::RefPtr IconLoader::get_app_info_from_app_id_list( - const std::string &app_id_list) { + const std::string& app_id_list) { std::string app_id; std::istringstream stream(app_id_list); Glib::RefPtr app_info_; diff --git a/src/util/pipewire/pipewire_backend.cpp b/src/util/pipewire/pipewire_backend.cpp index 6d859a91..a88a3fbe 100644 --- a/src/util/pipewire/pipewire_backend.cpp +++ b/src/util/pipewire/pipewire_backend.cpp @@ -4,11 +4,11 @@ namespace waybar::util::PipewireBackend { -static void getNodeInfo(void *data_, const struct pw_node_info *info) { - auto *pNodeInfo = static_cast(data_); +static void getNodeInfo(void* data_, const struct pw_node_info* info) { + auto* pNodeInfo = static_cast(data_); pNodeInfo->handleNodeEventInfo(info); - static_cast(pNodeInfo->data)->privacy_nodes_changed_signal_event.emit(); + static_cast(pNodeInfo->data)->privacy_nodes_changed_signal_event.emit(); } static const struct pw_node_events NODE_EVENTS = { @@ -16,8 +16,8 @@ static const struct pw_node_events NODE_EVENTS = { .info = getNodeInfo, }; -static void proxyDestroy(void *data) { - static_cast(data)->handleProxyEventDestroy(); +static void proxyDestroy(void* data) { + static_cast(data)->handleProxyEventDestroy(); } static const struct pw_proxy_events PROXY_EVENTS = { @@ -25,14 +25,14 @@ static const struct pw_proxy_events PROXY_EVENTS = { .destroy = proxyDestroy, }; -static void registryEventGlobal(void *_data, uint32_t id, uint32_t permissions, const char *type, - uint32_t version, const struct spa_dict *props) { - static_cast(_data)->handleRegistryEventGlobal(id, permissions, type, version, - props); +static void registryEventGlobal(void* _data, uint32_t id, uint32_t permissions, const char* type, + uint32_t version, const struct spa_dict* props) { + static_cast(_data)->handleRegistryEventGlobal(id, permissions, type, version, + props); } -static void registryEventGlobalRemove(void *_data, uint32_t id) { - static_cast(_data)->handleRegistryEventGlobalRemove(id); +static void registryEventGlobalRemove(void* _data, uint32_t id) { + static_cast(_data)->handleRegistryEventGlobalRemove(id); } static const struct pw_registry_events REGISTRY_EVENTS = { @@ -78,7 +78,7 @@ PipewireBackend::~PipewireBackend() { } if (registry_ != nullptr) { - pw_proxy_destroy((struct pw_proxy *)registry_); + pw_proxy_destroy((struct pw_proxy*)registry_); } spa_zero(registryListener_); @@ -103,11 +103,11 @@ std::shared_ptr PipewireBackend::getInstance() { return std::make_shared(tag); } -void PipewireBackend::handleRegistryEventGlobal(uint32_t id, uint32_t permissions, const char *type, - uint32_t version, const struct spa_dict *props) { +void PipewireBackend::handleRegistryEventGlobal(uint32_t id, uint32_t permissions, const char* type, + uint32_t version, const struct spa_dict* props) { if (props == nullptr || strcmp(type, PW_TYPE_INTERFACE_Node) != 0) return; - const char *lookupStr = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); + const char* lookupStr = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); if (lookupStr == nullptr) return; std::string mediaClass = lookupStr; enum PrivacyNodeType mediaType = PRIVACY_NODE_TYPE_NONE; @@ -121,11 +121,11 @@ void PipewireBackend::handleRegistryEventGlobal(uint32_t id, uint32_t permission return; } - auto *proxy = (pw_proxy *)pw_registry_bind(registry_, id, type, version, sizeof(PrivacyNodeInfo)); + auto* proxy = (pw_proxy*)pw_registry_bind(registry_, id, type, version, sizeof(PrivacyNodeInfo)); if (proxy == nullptr) return; - auto *pNodeInfo = (PrivacyNodeInfo *)pw_proxy_get_user_data(proxy); + auto* pNodeInfo = (PrivacyNodeInfo*)pw_proxy_get_user_data(proxy); new (pNodeInfo) PrivacyNodeInfo{}; pNodeInfo->id = id; pNodeInfo->data = this; diff --git a/src/util/pipewire/privacy_node_info.cpp b/src/util/pipewire/privacy_node_info.cpp index ec110b86..dbd6ed85 100644 --- a/src/util/pipewire/privacy_node_info.cpp +++ b/src/util/pipewire/privacy_node_info.cpp @@ -3,9 +3,9 @@ namespace waybar::util::PipewireBackend { std::string PrivacyNodeInfo::getName() { - const std::vector names{&application_name, &node_name}; + const std::vector names{&application_name, &node_name}; std::string name = "Unknown Application"; - for (const auto &item : names) { + for (const auto& item : names) { if (item != nullptr && !item->empty()) { name = *item; name[0] = toupper(name[0]); @@ -16,10 +16,10 @@ std::string PrivacyNodeInfo::getName() { } std::string PrivacyNodeInfo::getIconName() { - const std::vector names{&application_icon_name, &pipewire_access_portal_app_id, - &application_name, &node_name}; + const std::vector names{&application_icon_name, &pipewire_access_portal_app_id, + &application_name, &node_name}; std::string name = "application-x-executable-symbolic"; - for (const auto &item : names) { + for (const auto& item : names) { if (item != nullptr && !item->empty() && DefaultGtkIconThemeWrapper::has_icon(*item)) { return *item; } @@ -32,10 +32,10 @@ void PrivacyNodeInfo::handleProxyEventDestroy() { spa_hook_remove(&object_listener); } -void PrivacyNodeInfo::handleNodeEventInfo(const struct pw_node_info *info) { +void PrivacyNodeInfo::handleNodeEventInfo(const struct pw_node_info* info) { state = info->state; - const struct spa_dict_item *item; + const struct spa_dict_item* item; spa_dict_for_each(item, info->props) { if (strcmp(item->key, PW_KEY_CLIENT_ID) == 0) { client_id = strtoul(item->value, nullptr, 10); diff --git a/src/util/prepare_for_sleep.cpp b/src/util/prepare_for_sleep.cpp index 3adcdf67..ed4100be 100644 --- a/src/util/prepare_for_sleep.cpp +++ b/src/util/prepare_for_sleep.cpp @@ -18,21 +18,21 @@ class PrepareForSleep { } } - static void prepareForSleep_cb(GDBusConnection *system_bus, const gchar *sender_name, - const gchar *object_path, const gchar *interface_name, - const gchar *signal_name, GVariant *parameters, + static void prepareForSleep_cb(GDBusConnection* system_bus, const gchar* sender_name, + const gchar* object_path, const gchar* interface_name, + const gchar* signal_name, GVariant* parameters, gpointer user_data) { if (g_variant_is_of_type(parameters, G_VARIANT_TYPE("(b)")) != 0) { gboolean sleeping; g_variant_get(parameters, "(b)", &sleeping); - auto *self = static_cast(user_data); + auto* self = static_cast(user_data); self->signal.emit(sleeping); } } public: - static PrepareForSleep &GetInstance() { + static PrepareForSleep& GetInstance() { static PrepareForSleep instance; return instance; } @@ -40,10 +40,10 @@ class PrepareForSleep { private: guint login1_id; - GDBusConnection *login1_connection; + GDBusConnection* login1_connection; }; } // namespace -waybar::SafeSignal &waybar::util::prepare_for_sleep() { +waybar::SafeSignal& waybar::util::prepare_for_sleep() { return PrepareForSleep::GetInstance().signal; } diff --git a/src/util/ustring_clen.cpp b/src/util/ustring_clen.cpp index a8b9c9af..5a5b0028 100644 --- a/src/util/ustring_clen.cpp +++ b/src/util/ustring_clen.cpp @@ -1,6 +1,6 @@ #include "util/ustring_clen.hpp" -int ustring_clen(const Glib::ustring &str) { +int ustring_clen(const Glib::ustring& str) { int total = 0; for (unsigned int i : str) { total += g_unichar_iswide(i) + 1; diff --git a/test/utils/date.cpp b/test/utils/date.cpp index 576a4799..14694e2f 100644 --- a/test/utils/date.cpp +++ b/test/utils/date.cpp @@ -45,7 +45,7 @@ static const bool LC_TIME_is_sane = []() { ss << std::put_time(&tm, "%x %X"); return ss.str() == "01/03/2022 12:00:00 PM"; - } catch (std::exception &) { + } catch (std::exception&) { return false; } }(); @@ -77,7 +77,7 @@ TEST_CASE("Format UTC time", "[clock][util]") { CHECK(fmt_lib::format(loc, "{:%F %r}", tm) == "2022-01-03 01:04:05 PM"); #endif CHECK(fmt_lib::format(loc, "{:%Y%m%d%H%M%S}", tm) == "20220103130405"); - } catch (const std::runtime_error &) { + } catch (const std::runtime_error&) { WARN("Locale en_US not found, skip tests"); } } @@ -94,7 +94,7 @@ TEST_CASE("Format UTC time", "[clock][util]") { CHECK(fmt_lib::format(loc, "{:%F %T}", tm) == "2022-01-03 13:04:05"); #endif CHECK(fmt_lib::format(loc, "{:%Y%m%d%H%M%S}", tm) == "20220103130405"); - } catch (const std::runtime_error &) { + } catch (const std::runtime_error&) { WARN("Locale en_GB not found, skip tests"); } } @@ -113,7 +113,7 @@ TEST_CASE("Format UTC time", "[clock][util]") { CHECK(fmt_lib::format("{:%Y%m%d%H%M%S}", tm) == "20220103130405"); std::locale::global(loc); - } catch (const std::runtime_error &) { + } catch (const std::runtime_error&) { WARN("Locale en_US not found, skip tests"); } } @@ -147,7 +147,7 @@ TEST_CASE("Format zoned time", "[clock][util]") { CHECK(fmt_lib::format(loc, "{:%F %r}", tm) == "2022-01-03 08:04:05 AM"); #endif CHECK(fmt_lib::format(loc, "{:%Y%m%d%H%M%S}", tm) == "20220103080405"); - } catch (const std::runtime_error &) { + } catch (const std::runtime_error&) { WARN("Locale en_US not found, skip tests"); } } @@ -164,7 +164,7 @@ TEST_CASE("Format zoned time", "[clock][util]") { CHECK(fmt_lib::format(loc, "{:%F %T}", tm) == "2022-01-03 08:04:05"); #endif CHECK(fmt_lib::format(loc, "{:%Y%m%d%H%M%S}", tm) == "20220103080405"); - } catch (const std::runtime_error &) { + } catch (const std::runtime_error&) { WARN("Locale en_GB not found, skip tests"); } } @@ -183,7 +183,7 @@ TEST_CASE("Format zoned time", "[clock][util]") { CHECK(fmt_lib::format("{:%Y%m%d%H%M%S}", tm) == "20220103080405"); std::locale::global(loc); - } catch (const std::runtime_error &) { + } catch (const std::runtime_error&) { WARN("Locale en_US not found, skip tests"); } } From 39e59e557fb468f66133ee80c2600b90ae84bda7 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 4 Feb 2026 09:47:00 +0100 Subject: [PATCH 498/780] fix: double free / uncatched errors --- src/modules/hyprland/workspaces.cpp | 2 +- src/modules/sni/item.cpp | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 3bbe47df..6fa38260 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -939,7 +939,7 @@ void Workspaces::sortWorkspaces() { case SortMethod::NUMBER: try { return std::stoi(a->name()) < std::stoi(b->name()); - } catch (const std::invalid_argument&) { + } catch (const std::exception& e) { // Handle the exception if necessary. break; } diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 398afac6..ef2543b5 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -79,6 +79,10 @@ Item::~Item() { this->gtk_menu->popdown(); this->gtk_menu->detach(); } + if (this->dbus_menu != nullptr) { + g_object_weak_unref(G_OBJECT(this->dbus_menu), (GWeakNotify)onMenuDestroyed, this); + this->dbus_menu = nullptr; + } } bool Item::handleMouseEnter(GdkEventCrossing* const& e) { @@ -233,9 +237,13 @@ void Item::setCustomIcon(const std::string& id) { std::string custom_icon = IconManager::instance().getIconForApp(id); if (!custom_icon.empty()) { if (std::filesystem::exists(custom_icon)) { - Glib::RefPtr custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon); - icon_name = ""; // icon_name has priority over pixmap - icon_pixmap = custom_pixbuf; + try { + Glib::RefPtr custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon); + icon_name = ""; // icon_name has priority over pixmap + icon_pixmap = custom_pixbuf; + } catch (const Glib::Error& e) { + spdlog::error("Failed to load custom icon {}: {}", custom_icon, e.what()); + } } else { // if file doesn't exist it's most likely an icon_name icon_name = custom_icon; } From 68e1e6dc2696d0c8afac386a11fecae59760b70c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:50:37 +0000 Subject: [PATCH 499/780] Initial plan From f16a6c7dcf789b8ea7a21a63247d107c78a32131 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:51:07 +0000 Subject: [PATCH 500/780] Initial plan From 2c3aab88de03300073eba839d282ebb9cc44d26b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:51:23 +0000 Subject: [PATCH 501/780] Initial plan From c34b1b6a19161b23142d5fb3c3ac365581023141 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:53:47 +0000 Subject: [PATCH 502/780] Fix unhandled JSON exception in signal handlers Cache signal value during module construction to avoid accessing JSON config in signal handler context. This prevents crashes when signal field is missing or not an integer type. - Custom module: Cache signal_ value in constructor - Image module: Cache signal_ value in constructor - Both modules: Use cached value in refresh() method Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- include/modules/custom.hpp | 1 + include/modules/image.hpp | 1 + src/modules/custom.cpp | 5 +++-- src/modules/image.cpp | 6 ++++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/include/modules/custom.hpp b/include/modules/custom.hpp index 6c17c6e4..a00faaa5 100644 --- a/include/modules/custom.hpp +++ b/include/modules/custom.hpp @@ -42,6 +42,7 @@ class Custom : public ALabel { int pid_; util::command::res output_; util::JsonParser parser_; + int signal_; // Cached signal value to avoid JSON access in signal handler util::SleeperThread thread_; }; diff --git a/include/modules/image.hpp b/include/modules/image.hpp index 510dad94..e31de0fc 100644 --- a/include/modules/image.hpp +++ b/include/modules/image.hpp @@ -33,6 +33,7 @@ class Image : public AModule { int size_; std::chrono::milliseconds interval_; util::command::res output_; + int signal_; // Cached signal value to avoid JSON access in signal handler util::SleeperThread thread_; }; diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index d75633e9..a3ea7b27 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -13,7 +13,8 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id, tooltip_format_enabled_{config_["tooltip-format"].isString()}, percentage_(0), fp_(nullptr), - pid_(-1) { + pid_(-1), + signal_(config_["signal"].isInt() ? config_["signal"].asInt() : -1) { if (config.isNull()) { spdlog::warn("There is no configuration for 'custom/{}', element will be hidden", name); } @@ -136,7 +137,7 @@ void waybar::modules::Custom::waitingWorker() { } void waybar::modules::Custom::refresh(int sig) { - if (sig == SIGRTMIN + config_["signal"].asInt()) { + if (signal_ != -1 && sig == SIGRTMIN + signal_) { thread_.wake_up(); } } diff --git a/src/modules/image.cpp b/src/modules/image.cpp index 173aabd3..4e830608 100644 --- a/src/modules/image.cpp +++ b/src/modules/image.cpp @@ -1,7 +1,9 @@ #include "modules/image.hpp" waybar::modules::Image::Image(const std::string& id, const Json::Value& config) - : AModule(config, "image", id), box_(Gtk::ORIENTATION_HORIZONTAL, 0) { + : AModule(config, "image", id), + box_(Gtk::ORIENTATION_HORIZONTAL, 0), + signal_(config_["signal"].isInt() ? config_["signal"].asInt() : -1) { box_.pack_start(image_); box_.set_name("image"); if (!id.empty()) { @@ -41,7 +43,7 @@ void waybar::modules::Image::delayWorker() { } void waybar::modules::Image::refresh(int sig) { - if (sig == SIGRTMIN + config_["signal"].asInt()) { + if (signal_ != -1 && sig == SIGRTMIN + signal_) { thread_.wake_up(); } } From 4cadae83b3d210fc3894d097ab097eb275260d9c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:54:14 +0000 Subject: [PATCH 503/780] Improve comments for cached signal values Clarify that -1 indicates signal-based refresh is disabled Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- include/modules/custom.hpp | 2 +- include/modules/image.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/modules/custom.hpp b/include/modules/custom.hpp index a00faaa5..40406e96 100644 --- a/include/modules/custom.hpp +++ b/include/modules/custom.hpp @@ -42,7 +42,7 @@ class Custom : public ALabel { int pid_; util::command::res output_; util::JsonParser parser_; - int signal_; // Cached signal value to avoid JSON access in signal handler + int signal_; // Cached signal value (-1 = disabled) to avoid JSON access in signal handler util::SleeperThread thread_; }; diff --git a/include/modules/image.hpp b/include/modules/image.hpp index e31de0fc..40be856a 100644 --- a/include/modules/image.hpp +++ b/include/modules/image.hpp @@ -33,7 +33,7 @@ class Image : public AModule { int size_; std::chrono::milliseconds interval_; util::command::res output_; - int signal_; // Cached signal value to avoid JSON access in signal handler + int signal_; // Cached signal value (-1 = disabled) to avoid JSON access in signal handler util::SleeperThread thread_; }; From 03de38b4d0884be0700d97f21562bf8acf8ee009 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:54:32 +0000 Subject: [PATCH 504/780] Fix Pango NULL layout crash by clearing markup before hiding label Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- src/modules/cava/cavaRaw.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/modules/cava/cavaRaw.cpp b/src/modules/cava/cavaRaw.cpp index edec902b..d6a9e001 100644 --- a/src/modules/cava/cavaRaw.cpp +++ b/src/modules/cava/cavaRaw.cpp @@ -47,9 +47,11 @@ auto waybar::modules::cava::Cava::onSilence() -> void { if (label_.get_style_context()->has_class("updated")) label_.get_style_context()->remove_class("updated"); - if (hide_on_silence_) + if (hide_on_silence_) { + // Clear the label markup before hiding to prevent GTK from rendering a NULL Pango layout + label_.set_markup(""); label_.hide(); - else if (config_["format_silent"].isString()) + } else if (config_["format_silent"].isString()) label_.set_markup(format_silent_); silence_ = true; label_.get_style_context()->add_class("silent"); From 19ede92d7f88e8e7ab5333094d13d4a3c0819f6d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:54:36 +0000 Subject: [PATCH 505/780] Complete fix for unhandled exception in signal handler Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- _codeql_detected_source_root | 1 + 1 file changed, 1 insertion(+) create mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 00000000..945c9b46 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file From 330c77c87ae46693cfc211833122bc0216906eb3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:54:36 +0000 Subject: [PATCH 506/780] Fix thread safety in Hyprland modules to prevent corrupted double-linked list crash - Move GTK operations from IPC thread to GTK main thread in Window module - Move GTK operations from IPC thread to GTK main thread in WindowCount module - Move GTK style class operations from IPC thread to GTK main thread in Submap module - Language and Workspaces modules already safe (only update internal state) Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- include/modules/hyprland/submap.hpp | 1 + src/modules/hyprland/submap.cpp | 17 +++++++++++------ src/modules/hyprland/window.cpp | 4 ++-- src/modules/hyprland/windowcount.cpp | 3 +-- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/include/modules/hyprland/submap.hpp b/include/modules/hyprland/submap.hpp index 7e3425ef..0dcb5d5a 100644 --- a/include/modules/hyprland/submap.hpp +++ b/include/modules/hyprland/submap.hpp @@ -26,6 +26,7 @@ class Submap : public waybar::ALabel, public EventHandler { const Bar& bar_; util::JsonParser parser_; std::string submap_; + std::string prev_submap_; bool always_on_ = false; std::string default_submap_ = "Default"; diff --git a/src/modules/hyprland/submap.cpp b/src/modules/hyprland/submap.cpp index eb9b1de8..5d587d02 100644 --- a/src/modules/hyprland/submap.cpp +++ b/src/modules/hyprland/submap.cpp @@ -44,6 +44,17 @@ auto Submap::parseConfig(const Json::Value& config) -> void { auto Submap::update() -> void { std::lock_guard lg(mutex_); + // Handle style class changes + if (!prev_submap_.empty()) { + label_.get_style_context()->remove_class(prev_submap_); + } + + if (!submap_.empty()) { + label_.get_style_context()->add_class(submap_); + } + + prev_submap_ = submap_; + if (submap_.empty()) { event_box_.hide(); } else { @@ -66,18 +77,12 @@ void Submap::onEvent(const std::string& ev) { auto submapName = ev.substr(ev.find_first_of('>') + 2); - if (!submap_.empty()) { - label_.get_style_context()->remove_class(submap_); - } - submap_ = submapName; if (submap_.empty() && always_on_) { submap_ = default_submap_; } - label_.get_style_context()->add_class(submap_); - spdlog::debug("hyprland submap onevent with {}", submap_); dp.emit(); diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index 23437c5f..e02a7691 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -45,6 +45,8 @@ Window::~Window() { auto Window::update() -> void { std::shared_lock windowIpcShareLock(windowIpcSmtx); + queryActiveWorkspace(); + std::string windowName = waybar::util::sanitize_string(workspace_.last_window_title); std::string windowAddress = workspace_.last_window; @@ -236,8 +238,6 @@ void Window::queryActiveWorkspace() { } void Window::onEvent(const std::string& ev) { - queryActiveWorkspace(); - dp.emit(); } diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index b2721aea..91589189 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -37,7 +37,7 @@ WindowCount::~WindowCount() { } auto WindowCount::update() -> void { - std::lock_guard lg(mutex_); + queryActiveWorkspace(); std::string format = config_["format"].asString(); std::string formatEmpty = config_["format-empty"].asString(); @@ -126,7 +126,6 @@ void WindowCount::queryActiveWorkspace() { } void WindowCount::onEvent(const std::string& ev) { - queryActiveWorkspace(); dp.emit(); } From 74fb2f252fb484887726cfa64bbaa8fb0b19f620 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:54:48 +0000 Subject: [PATCH 507/780] Remove CodeQL artifact from repository Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- .gitignore | 1 + _codeql_detected_source_root | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 120000 _codeql_detected_source_root diff --git a/.gitignore b/.gitignore index 8bf6481d..f42bf85e 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ result result-* .ccls-cache +_codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root deleted file mode 120000 index 945c9b46..00000000 --- a/_codeql_detected_source_root +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file From e0a2cb8f8f547b3fa0f2f4122d9ff30641c4b786 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:55:08 +0000 Subject: [PATCH 508/780] Complete fix for Pango NULL layout crash Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- _codeql_detected_source_root | 1 + 1 file changed, 1 insertion(+) create mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 00000000..945c9b46 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file From c1240a98aad66672f39ac3d545ee38413d3f5ff3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:56:17 +0000 Subject: [PATCH 509/780] Fix mutex locking in WindowCount module Ensure workspace_ accesses are properly protected by mutex after queryActiveWorkspace call Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- src/modules/hyprland/windowcount.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index 91589189..5d9ee1c3 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -44,6 +44,8 @@ auto WindowCount::update() -> void { std::string formatWindowed = config_["format-windowed"].asString(); std::string formatFullscreen = config_["format-fullscreen"].asString(); + std::lock_guard lg(mutex_); + setClass("empty", workspace_.windows == 0); setClass("fullscreen", workspace_.hasfullscreen); From 0a8b7b096a3e4295ad729659c229fba2d1930828 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:57:08 +0000 Subject: [PATCH 510/780] Simplify fix: only add isInt() check in refresh methods Changed approach per maintainer feedback - instead of caching signal values, just add isInt() check before accessing config_["signal"].asInt() in refresh() methods. This is simpler and more minimal. Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- include/modules/custom.hpp | 1 - include/modules/image.hpp | 1 - src/modules/custom.cpp | 5 ++--- src/modules/image.cpp | 6 ++---- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/include/modules/custom.hpp b/include/modules/custom.hpp index 40406e96..6c17c6e4 100644 --- a/include/modules/custom.hpp +++ b/include/modules/custom.hpp @@ -42,7 +42,6 @@ class Custom : public ALabel { int pid_; util::command::res output_; util::JsonParser parser_; - int signal_; // Cached signal value (-1 = disabled) to avoid JSON access in signal handler util::SleeperThread thread_; }; diff --git a/include/modules/image.hpp b/include/modules/image.hpp index 40be856a..510dad94 100644 --- a/include/modules/image.hpp +++ b/include/modules/image.hpp @@ -33,7 +33,6 @@ class Image : public AModule { int size_; std::chrono::milliseconds interval_; util::command::res output_; - int signal_; // Cached signal value (-1 = disabled) to avoid JSON access in signal handler util::SleeperThread thread_; }; diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index a3ea7b27..a01ca924 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -13,8 +13,7 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id, tooltip_format_enabled_{config_["tooltip-format"].isString()}, percentage_(0), fp_(nullptr), - pid_(-1), - signal_(config_["signal"].isInt() ? config_["signal"].asInt() : -1) { + pid_(-1) { if (config.isNull()) { spdlog::warn("There is no configuration for 'custom/{}', element will be hidden", name); } @@ -137,7 +136,7 @@ void waybar::modules::Custom::waitingWorker() { } void waybar::modules::Custom::refresh(int sig) { - if (signal_ != -1 && sig == SIGRTMIN + signal_) { + if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) { thread_.wake_up(); } } diff --git a/src/modules/image.cpp b/src/modules/image.cpp index 4e830608..189deee6 100644 --- a/src/modules/image.cpp +++ b/src/modules/image.cpp @@ -1,9 +1,7 @@ #include "modules/image.hpp" waybar::modules::Image::Image(const std::string& id, const Json::Value& config) - : AModule(config, "image", id), - box_(Gtk::ORIENTATION_HORIZONTAL, 0), - signal_(config_["signal"].isInt() ? config_["signal"].asInt() : -1) { + : AModule(config, "image", id), box_(Gtk::ORIENTATION_HORIZONTAL, 0) { box_.pack_start(image_); box_.set_name("image"); if (!id.empty()) { @@ -43,7 +41,7 @@ void waybar::modules::Image::delayWorker() { } void waybar::modules::Image::refresh(int sig) { - if (signal_ != -1 && sig == SIGRTMIN + signal_) { + if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) { thread_.wake_up(); } } From da8fd864c62d0d52e0376213b7379d4c11f09c68 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:57:53 +0000 Subject: [PATCH 511/780] Simplify mutex locking in WindowCount module - Acquire lock once at start of update() and hold for entire function - Remove redundant lock from queryActiveWorkspace() since it's only called from update() This addresses code review feedback and makes the locking strategy clearer Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- src/modules/hyprland/windowcount.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index 5d9ee1c3..487b0083 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -37,6 +37,8 @@ WindowCount::~WindowCount() { } auto WindowCount::update() -> void { + std::lock_guard lg(mutex_); + queryActiveWorkspace(); std::string format = config_["format"].asString(); @@ -44,8 +46,6 @@ auto WindowCount::update() -> void { std::string formatWindowed = config_["format-windowed"].asString(); std::string formatFullscreen = config_["format-fullscreen"].asString(); - std::lock_guard lg(mutex_); - setClass("empty", workspace_.windows == 0); setClass("fullscreen", workspace_.hasfullscreen); @@ -118,8 +118,6 @@ auto WindowCount::Workspace::parse(const Json::Value& value) -> WindowCount::Wor } void WindowCount::queryActiveWorkspace() { - std::lock_guard lg(mutex_); - if (separateOutputs_) { workspace_ = getActiveWorkspace(this->bar_.output->name); } else { From cb54b98fd5bdb42bf6a2d040ba11d8ab0b8bcbd9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:00:09 +0000 Subject: [PATCH 512/780] Final updates - thread safety fix complete Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- _codeql_detected_source_root | 1 + 1 file changed, 1 insertion(+) create mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 00000000..945c9b46 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file From 7921ab40085ec367ec39321fd2d592696fddfdae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:05:41 +0000 Subject: [PATCH 513/780] Initial plan From e8558dd696ae254ab15d474d002731edfb135d30 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:05:50 +0000 Subject: [PATCH 514/780] Initial plan From be9d9c87e6b3ad2a53413e24cd2612de5a7364b2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:06:33 +0000 Subject: [PATCH 515/780] Initial plan From 717cc4a4d15e9c18101721697403f4ce6dd7c975 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:07:46 +0000 Subject: [PATCH 516/780] Fix Pango markup rendering in bluetooth module tooltip Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- src/modules/bluetooth.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp index 06475a2e..f491f19b 100644 --- a/src/modules/bluetooth.cpp +++ b/src/modules/bluetooth.cpp @@ -264,7 +264,7 @@ auto waybar::modules::Bluetooth::update() -> void { device_enumerate_.erase(0, 1); } } - label_.set_tooltip_text(fmt::format( + label_.set_tooltip_markup(fmt::format( fmt::runtime(tooltip_format), fmt::arg("status", state_), fmt::arg("num_connections", connected_devices_.size()), fmt::arg("controller_address", cur_controller_ ? cur_controller_->address : "null"), From 2b29c9a5d6e353ce6d6686be2d51a6240c0778bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:08:42 +0000 Subject: [PATCH 517/780] Fix tooltip sync issue by removing conditional checks Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- src/modules/custom.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index a01ca924..0a8d9cf6 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -187,13 +187,9 @@ auto waybar::modules::Custom::update() -> void { fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("percentage", percentage_)); label_.set_tooltip_markup(tooltip); } else if (text_ == tooltip_) { - if (label_.get_tooltip_markup() != str) { - label_.set_tooltip_markup(str); - } + label_.set_tooltip_markup(str); } else { - if (label_.get_tooltip_markup() != tooltip_) { - label_.set_tooltip_markup(tooltip_); - } + label_.set_tooltip_markup(tooltip_); } } auto style = label_.get_style_context(); From 68d3e13fdd9e2d1109de08ec6cf20d46aaf5d91d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:09:44 +0000 Subject: [PATCH 518/780] Fix menu freezing when launching applications Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- src/ALabel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ALabel.cpp b/src/ALabel.cpp index 4e6d3349..598d016f 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -190,7 +190,7 @@ bool waybar::ALabel::handleToggle(GdkEventButton* const& e) { } void ALabel::handleGtkMenuEvent(GtkMenuItem* /*menuitem*/, gpointer data) { - waybar::util::command::res res = waybar::util::command::exec((char*)data, "GtkMenu"); + waybar::util::command::forkExec((char*)data); } std::string ALabel::getState(uint8_t value, bool lesser) { From c4982efa951d9b0b37232ca28a0fa883cdda31f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:14:45 +0000 Subject: [PATCH 519/780] Add output_name parameter to forkExec for WAYBAR_OUTPUT_NAME env var Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- include/util/command.hpp | 7 +++++++ src/ALabel.cpp | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/util/command.hpp b/include/util/command.hpp index 4b9decaa..4348845a 100644 --- a/include/util/command.hpp +++ b/include/util/command.hpp @@ -139,6 +139,10 @@ inline struct res execNoRead(const std::string& cmd) { } inline int32_t forkExec(const std::string& cmd) { + return forkExec(cmd, ""); +} + +inline int32_t forkExec(const std::string& cmd, const std::string& output_name) { if (cmd == "") return -1; pid_t pid = fork(); @@ -157,6 +161,9 @@ inline int32_t forkExec(const std::string& cmd) { err = pthread_sigmask(SIG_UNBLOCK, &mask, nullptr); if (err != 0) spdlog::error("pthread_sigmask in forkExec failed: {}", strerror(err)); setpgid(pid, pid); + if (output_name != "") { + setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1); + } execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); exit(0); } else { diff --git a/src/ALabel.cpp b/src/ALabel.cpp index 598d016f..d251d896 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -190,7 +190,7 @@ bool waybar::ALabel::handleToggle(GdkEventButton* const& e) { } void ALabel::handleGtkMenuEvent(GtkMenuItem* /*menuitem*/, gpointer data) { - waybar::util::command::forkExec((char*)data); + waybar::util::command::forkExec((char*)data, "GtkMenu"); } std::string ALabel::getState(uint8_t value, bool lesser) { From 04766452b4e3b0e0bcfcb073b6d857ffbca41abf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:15:25 +0000 Subject: [PATCH 520/780] Use idiomatic empty() check for strings Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- include/util/command.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/util/command.hpp b/include/util/command.hpp index 4348845a..0023df83 100644 --- a/include/util/command.hpp +++ b/include/util/command.hpp @@ -109,7 +109,7 @@ inline FILE* open(const std::string& cmd, int& pid, const std::string& output_na ::close(fd[0]); dup2(fd[1], 1); setpgid(child_pid, child_pid); - if (output_name != "") { + if (!output_name.empty()) { setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1); } execlp("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); @@ -161,7 +161,7 @@ inline int32_t forkExec(const std::string& cmd, const std::string& output_name) err = pthread_sigmask(SIG_UNBLOCK, &mask, nullptr); if (err != 0) spdlog::error("pthread_sigmask in forkExec failed: {}", strerror(err)); setpgid(pid, pid); - if (output_name != "") { + if (!output_name.empty()) { setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1); } execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); From 65e708779b4a08937e7fa219ef466375454b6d89 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:26:20 +0000 Subject: [PATCH 521/780] Fix function overload order to resolve compilation error Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- include/util/command.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/util/command.hpp b/include/util/command.hpp index 0023df83..58c59a96 100644 --- a/include/util/command.hpp +++ b/include/util/command.hpp @@ -138,10 +138,6 @@ inline struct res execNoRead(const std::string& cmd) { return {WEXITSTATUS(stat), ""}; } -inline int32_t forkExec(const std::string& cmd) { - return forkExec(cmd, ""); -} - inline int32_t forkExec(const std::string& cmd, const std::string& output_name) { if (cmd == "") return -1; @@ -176,4 +172,8 @@ inline int32_t forkExec(const std::string& cmd, const std::string& output_name) return pid; } +inline int32_t forkExec(const std::string& cmd) { + return forkExec(cmd, ""); +} + } // namespace waybar::util::command From ca0b068ad86ed3b6ba949a699fbfe676d1d3f320 Mon Sep 17 00:00:00 2001 From: szbmrk Date: Fri, 9 Jan 2026 17:59:11 +0100 Subject: [PATCH 522/780] feat(disk)!: handle multiple disks at once --- include/modules/disk.hpp | 5 +- man/waybar-disk.5.scd | 45 ++++++++-- src/modules/disk.cpp | 175 +++++++++++++++++++++++++-------------- 3 files changed, 152 insertions(+), 73 deletions(-) diff --git a/include/modules/disk.hpp b/include/modules/disk.hpp index 1b4f3176..ea6f89c8 100644 --- a/include/modules/disk.hpp +++ b/include/modules/disk.hpp @@ -4,6 +4,7 @@ #include #include +#include #include "ALabel.hpp" #include "util/format.hpp" @@ -19,7 +20,9 @@ class Disk : public ALabel { private: util::SleeperThread thread_; - std::string path_; + std::string header_; + std::vector paths_; + std::string separator_; std::string unit_; float calc_specific_divisor(const std::string divisor); diff --git a/man/waybar-disk.5.scd b/man/waybar-disk.5.scd index 00af2b90..dab6af7e 100644 --- a/man/waybar-disk.5.scd +++ b/man/waybar-disk.5.scd @@ -6,17 +6,12 @@ waybar - disk module # DESCRIPTION -The *disk* module displays the current disk space used. +The *disk* module displays information of multiple disks. # CONFIGURATION Addressed by *disk* -*path*: ++ - typeof: string ++ - default: "/" ++ - Any path residing in the filesystem or mountpoint for which the information should be displayed. - *interval*: ++ typeof: integer++ default: 30 ++ @@ -25,7 +20,7 @@ Addressed by *disk* *format*: ++ typeof: string ++ default: "{percentage_used}%" ++ - The format, how information should be displayed. + The format, how information for each disk should be displayed. *rotate*: ++ typeof: integer ++ @@ -75,6 +70,26 @@ Addressed by *disk* typeof: string ++ Command to execute when scrolling down on the module. +*path*: ++ + typeof: string ++ + default: "/" ++ + Deprecated path of filesystem or mountpoint to monitor. + +*paths*: ++ + typeof: array ++ + default: ["/"] ++ + Array of paths residing in the filesystem or mountpoint for which the information should be displayed. + +*header*: ++ + typeof: string ++ + default: "" ++ + Text to appear before the disk information defined in the format. + +*separator*: ++ + typeof: string ++ + default: " " ++ + Separator string between multiple disk information. + *smooth-scrolling-threshold*: ++ typeof: double ++ Threshold to be used when scrolling. @@ -123,7 +138,7 @@ Addressed by *disk* *{free}*: Amount of available disk space for normal users. Automatically selects unit based on size remaining. -*{path}*: The path specified in the configuration. +*{path}*: The path for each disk specified in the configuration. *{specific_total}*: Total amount of space on the disk, partition, or mountpoint in a specific unit. Defaults to bytes. @@ -143,10 +158,22 @@ Addressed by *disk* ``` "disk": { "interval": 30, + "format": "{percentage_free}% free on {path}", + "header": "Disks: ", + "paths": ["/", "/home"], + "separator": " ", +} +``` + +``` +"disk": { + "interval": 30, + "paths": ["/"], "format": "{specific_free:0.2f} GB out of {specific_total:0.2f} GB available. Alternatively {free} out of {total} available", "unit": "GB" - // 1434.25 GB out of 2000.00 GB available. Alternatively 1.4TiB out of 1.9TiB available. } + + // 1434.25 GB out of 2000.00 GB available. Alternatively 1.4TiB out of 1.9TiB available. ``` # STYLE diff --git a/src/modules/disk.cpp b/src/modules/disk.cpp index ef257b72..695ae157 100644 --- a/src/modules/disk.cpp +++ b/src/modules/disk.cpp @@ -1,15 +1,32 @@ #include "modules/disk.hpp" +#include + using namespace waybar::util; waybar::modules::Disk::Disk(const std::string& id, const Json::Value& config) - : ALabel(config, "disk", id, "{}%", 30), path_("/") { + : ALabel(config, "disk", id, "{}%", 30), header_(""), paths_(), separator_(" ") { thread_ = [this] { dp.emit(); thread_.sleep_for(interval_); }; - if (config["path"].isString()) { - path_ = config["path"].asString(); + if (config["header"].isString()) { + header_ = config["header"].asString(); + } + if (config["path"].isString() && !config["paths"].isArray()) { + spdlog::warn("Disk: path is deprecated use paths instead!"); + paths_.push_back(config["path"].asString()); + } + if (config["paths"].isArray()) { + for (const auto& path : config["paths"]) { + paths_.push_back(path.asString()); + } + } + if (!config["path"].isString() && !config["paths"].isArray()) { + paths_.emplace_back("/"); + } + if (config["separator"].isString()) { + separator_ = config["separator"].asString(); } if (config["unit"].isString()) { unit_ = config["unit"].asString(); @@ -17,76 +34,108 @@ waybar::modules::Disk::Disk(const std::string& id, const Json::Value& config) } auto waybar::modules::Disk::update() -> void { - struct statvfs /* { - unsigned long f_bsize; // filesystem block size - unsigned long f_frsize; // fragment size - fsblkcnt_t f_blocks; // size of fs in f_frsize units - fsblkcnt_t f_bfree; // # free blocks - fsblkcnt_t f_bavail; // # free blocks for unprivileged users - fsfilcnt_t f_files; // # inodes - fsfilcnt_t f_ffree; // # free inodes - fsfilcnt_t f_favail; // # free inodes for unprivileged users - unsigned long f_fsid; // filesystem ID - unsigned long f_flag; // mount flags - unsigned long f_namemax; // maximum filename length - }; */ - stats; - int err = statvfs(path_.c_str(), &stats); + std::string tooltip_label; + std::string label = header_; - /* Conky options - fs_bar - Bar that shows how much space is used - fs_free - Free space on a file system - fs_free_perc - Free percentage of space - fs_size - File system size - fs_used - File system used space - */ + bool had_valid_disk = false; - if (err != 0) { - event_box_.hide(); - return; - } + for (size_t i = 0; i < paths_.size(); ++i) { + const auto& path = paths_[i]; - float specific_free, specific_used, specific_total, divisor; + struct statvfs /* { + unsigned long f_bsize; // filesystem block size + unsigned long f_frsize; // fragment size + fsblkcnt_t f_blocks; // size of fs in f_frsize units + fsblkcnt_t f_bfree; // # free blocks + fsblkcnt_t f_bavail; // # free blocks for unprivileged users + fsfilcnt_t f_files; // # inodes + fsfilcnt_t f_ffree; // # free inodes + fsfilcnt_t f_favail; // # free inodes for unprivileged users + unsigned long f_fsid; // filesystem ID + unsigned long f_flag; // mount flags + unsigned long f_namemax; // maximum filename length + }; */ + stats; - divisor = calc_specific_divisor(unit_); - specific_free = (stats.f_bavail * stats.f_frsize) / divisor; - specific_used = ((stats.f_blocks - stats.f_bfree) * stats.f_frsize) / divisor; - specific_total = (stats.f_blocks * stats.f_frsize) / divisor; + int err = statvfs(path.c_str(), &stats); - auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true); - auto used = pow_format((stats.f_blocks - stats.f_bfree) * stats.f_frsize, "B", true); - auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true); - auto percentage_used = (stats.f_blocks - stats.f_bfree) * 100 / stats.f_blocks; + /* Conky options + fs_bar - Bar that shows how much space is used + fs_free - Free space on a file system + fs_free_perc - Free percentage of space + fs_size - File system size + fs_used - File system used space + */ - auto format = format_; - auto state = getState(percentage_used); - if (!state.empty() && config_["format-" + state].isString()) { - format = config_["format-" + state].asString(); - } + if (err != 0) { + spdlog::warn("Disk: statvfs failed for path '{}' (errno={})", path, errno); + continue; + } - if (format.empty()) { - event_box_.hide(); - } else { - event_box_.show(); - label_.set_markup(fmt::format( - fmt::runtime(format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), - fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used), - fmt::arg("percentage_used", percentage_used), fmt::arg("total", total), - fmt::arg("path", path_), fmt::arg("specific_free", specific_free), - fmt::arg("specific_used", specific_used), fmt::arg("specific_total", specific_total))); - } + float specific_free, specific_used, specific_total, divisor; + + divisor = calc_specific_divisor(unit_); + specific_free = (stats.f_bavail * stats.f_frsize) / divisor; + specific_used = ((stats.f_blocks - stats.f_bfree) * stats.f_frsize) / divisor; + specific_total = (stats.f_blocks * stats.f_frsize) / divisor; + + auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true); + auto used = pow_format((stats.f_blocks - stats.f_bfree) * stats.f_frsize, "B", true); + auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true); + auto percentage_used = (stats.f_blocks - stats.f_bfree) * 100 / stats.f_blocks; + + std::string disk_format = format_; + auto state = getState(percentage_used); + if (!state.empty() && config_["format-" + state].isString()) { + disk_format = config_["format-" + state].asString(); + } + + if (!disk_format.empty()) { + if (had_valid_disk) { + label += separator_; + } + + label += fmt::format( + fmt::runtime(disk_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), + fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), + fmt::arg("used", used), fmt::arg("percentage_used", percentage_used), + fmt::arg("total", total), fmt::arg("path", path), + fmt::arg("specific_free", specific_free), fmt::arg("specific_used", specific_used), + fmt::arg("specific_total", specific_total)); + } - if (tooltipEnabled()) { std::string tooltip_format = "{used} used out of {total} on {path} ({percentage_used}%)"; if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text(fmt::format( - fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), - fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used), - fmt::arg("percentage_used", percentage_used), fmt::arg("total", total), - fmt::arg("path", path_), fmt::arg("specific_free", specific_free), - fmt::arg("specific_used", specific_used), fmt::arg("specific_total", specific_total))); + + if (!tooltip_format.empty()) { + if (had_valid_disk) { + tooltip_label += "\n"; + } + + tooltip_label += fmt::format( + fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, + fmt::arg("free", free), + fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), + fmt::arg("used", used), fmt::arg("percentage_used", percentage_used), + fmt::arg("total", total), fmt::arg("path", path), + fmt::arg("specific_free", specific_free), fmt::arg("specific_used", specific_used), + fmt::arg("specific_total", specific_total)); + } + + had_valid_disk = true; + } + if (had_valid_disk) { + event_box_.show(); + } else { + event_box_.hide(); + } + + label_.set_markup(label); + + if (tooltipEnabled() && !tooltip_label.empty()) { + label_.set_tooltip_text(tooltip_label); } // Call parent update ALabel::update(); @@ -109,7 +158,7 @@ float waybar::modules::Disk::calc_specific_divisor(std::string divisor) { return 1000.0 * 1000.0 * 1000.0 * 1000.0; } else if (divisor == "TiB") { return 1024.0 * 1024.0 * 1024.0 * 1024.0; - } else { // default to Bytes if it is anything that we don't recongnise + } else { // default to Bytes if it is anything that we don't recognise return 1.0; } -} \ No newline at end of file +} From 429d7df52bb36b43942a3140606d10e7bd747e7c Mon Sep 17 00:00:00 2001 From: cebem1nt Date: Thu, 5 Feb 2026 23:38:47 -0300 Subject: [PATCH 523/780] fix: backlight/slider fix rounding issue on scroll up --- src/util/backlight_backend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/backlight_backend.cpp b/src/util/backlight_backend.cpp index a136f635..48473dd2 100644 --- a/src/util/backlight_backend.cpp +++ b/src/util/backlight_backend.cpp @@ -273,7 +273,7 @@ int BacklightBackend::get_scaled_brightness(const std::string& preferred_device) GET_BEST_DEVICE(best, (*this), preferred_device); if (best != nullptr) { - return best->get_actual() * 100 / best->get_max(); + return static_cast(std::round(best->get_actual() * 100.0F / best->get_max())); } return 0; From 40200afb6890f4488b9c850b6d3a98526828d212 Mon Sep 17 00:00:00 2001 From: cebem1nt Date: Fri, 6 Feb 2026 14:43:12 -0300 Subject: [PATCH 524/780] fix: owerwrite handleScroll to remove any scrolling handling from group module --- include/group.hpp | 1 + src/group.cpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/include/group.hpp b/include/group.hpp index 41cb5ee0..d5cbb289 100644 --- a/include/group.hpp +++ b/include/group.hpp @@ -30,6 +30,7 @@ class Group : public AModule { bool handleMouseEnter(GdkEventCrossing* const& ev) override; bool handleMouseLeave(GdkEventCrossing* const& ev) override; bool handleToggle(GdkEventButton* const& ev) override; + bool handleScroll(GdkEventScroll* e) override; void show_group(); void hide_group(); }; diff --git a/src/group.cpp b/src/group.cpp index 294743d7..51caf0e2 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -124,6 +124,11 @@ auto Group::update() -> void { // noop } +bool Group::handleScroll(GdkEventScroll* e) { + // no scroll. + return true; +} + Gtk::Box& Group::getBox() { return is_drawer ? (is_first_widget ? box : revealer_box) : box; } void Group::addWidget(Gtk::Widget& widget) { From 90b209add8937514d0a987aa842e701bd8f1232e Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 6 Feb 2026 21:15:03 +0100 Subject: [PATCH 525/780] chore: 0.15.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index c6b7f90a..db9407eb 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project( 'waybar', 'cpp', 'c', - version: '0.14.0', + version: '0.15.0', license: 'MIT', meson_version: '>= 0.59.0', default_options : [ From d95809e11ba634e19ae9cf2a28c6ddf072de1c97 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Fri, 6 Feb 2026 15:12:28 -0600 Subject: [PATCH 526/780] chore(ci): bump flake lock actions Multiple releases old, keep up to date. Signed-off-by: Austin Horstman --- .github/workflows/clang-format.yml | 2 +- .github/workflows/clang-tidy.yml.bak | 2 +- .github/workflows/docker.yml | 2 +- .github/workflows/freebsd.yml | 4 ++-- .github/workflows/linux.yml | 2 +- .github/workflows/nix-tests.yml | 18 +++++++++--------- .github/workflows/nix-update-flake-lock.yml | 10 +++++----- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 29b3e23b..25f62c8e 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -10,7 +10,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - uses: RafikFarhad/clang-format-github-action@v6 name: clang-format with: diff --git a/.github/workflows/clang-tidy.yml.bak b/.github/workflows/clang-tidy.yml.bak index ec67fb7e..9e9da80c 100644 --- a/.github/workflows/clang-tidy.yml.bak +++ b/.github/workflows/clang-tidy.yml.bak @@ -12,7 +12,7 @@ jobs: container: image: alexays/waybar:debian steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: configure run: | meson -Dcpp_std=c++20 build # necessary to generate compile_commands.json diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 0e7e2944..ea8eb08b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Login to Docker Hub uses: docker/login-action@v3 diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index b9114c31..f6c96f95 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -12,13 +12,13 @@ jobs: # https://github.com/actions/runner/issues/385 - for FreeBSD runner support runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Test in FreeBSD VM uses: cross-platform-actions/action@v0.28.0 timeout-minutes: 180 env: CPPFLAGS: '-isystem/usr/local/include' - LDFLAGS: '-L/usr/local/lib' + LDFLAGS: '-L/usr/local/lib' with: operating_system: freebsd version: "14.2" diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index c36f68e2..12a995d7 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -25,7 +25,7 @@ jobs: image: alexays/waybar:${{ matrix.distro }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: configure run: meson setup -Dman-pages=enabled -Dcpp_std=${{matrix.cpp_std}} build - name: build diff --git a/.github/workflows/nix-tests.yml b/.github/workflows/nix-tests.yml index 8859ecb5..43aa30d8 100644 --- a/.github/workflows/nix-tests.yml +++ b/.github/workflows/nix-tests.yml @@ -6,12 +6,12 @@ jobs: nix-flake-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: cachix/install-nix-action@v27 - with: - extra_nix_config: | - experimental-features = nix-command flakes - access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} - - run: nix flake show - - run: nix flake check --print-build-logs - - run: nix build --print-build-logs + - uses: actions/checkout@v6 + - uses: cachix/install-nix-action@v31 + with: + extra_nix_config: | + experimental-features = nix-command flakes + access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} + - run: nix flake show + - run: nix flake check --print-build-logs + - run: nix build --print-build-logs diff --git a/.github/workflows/nix-update-flake-lock.yml b/.github/workflows/nix-update-flake-lock.yml index a1679ead..de8a0ea1 100644 --- a/.github/workflows/nix-update-flake-lock.yml +++ b/.github/workflows/nix-update-flake-lock.yml @@ -4,19 +4,19 @@ on: schedule: - cron: '0 0 1 * *' # Run monthly push: - paths: - - 'flake.nix' + paths: + - 'flake.nix' jobs: lockfile: runs-on: ubuntu-latest if: github.event_name != 'schedule' || github.repository == 'Alexays/Waybar' steps: - name: Checkout repository - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install Nix - uses: cachix/install-nix-action@v27 + uses: cachix/install-nix-action@v31 with: extra_nix_config: | access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} - name: Update flake.lock - uses: DeterminateSystems/update-flake-lock@v21 + uses: DeterminateSystems/update-flake-lock@v28 From 833c40a84bce39d3833114dc14259dcb83949d16 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Fri, 6 Feb 2026 15:19:04 -0600 Subject: [PATCH 527/780] feat(ci): enable concurrency for nix-tests Don't let multiple tests run, cancel existing for same pr Signed-off-by: Austin Horstman --- .github/workflows/nix-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/nix-tests.yml b/.github/workflows/nix-tests.yml index 43aa30d8..c2193b99 100644 --- a/.github/workflows/nix-tests.yml +++ b/.github/workflows/nix-tests.yml @@ -2,6 +2,9 @@ name: "Nix-Tests" on: pull_request: push: +concurrency: + group: ${{ github.workflow }}-nix-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: nix-flake-check: runs-on: ubuntu-latest From 3655d735254411b44031cddc9eb460f4e923220f Mon Sep 17 00:00:00 2001 From: anton <1711399+AntGarSil@users.noreply.github.com> Date: Sun, 8 Feb 2026 00:44:14 +0000 Subject: [PATCH 528/780] Ignore Tray entries by service name and when dbus properties load --- include/modules/sni/host.hpp | 5 ++++ include/modules/sni/tray.hpp | 3 +++ src/modules/sni/host.cpp | 48 ++++++++++++++++++++++++++++++++++++ src/modules/sni/tray.cpp | 38 +++++++++++++++++++++++++++- 4 files changed, 93 insertions(+), 1 deletion(-) diff --git a/include/modules/sni/host.hpp b/include/modules/sni/host.hpp index 6c62ac31..5b475708 100644 --- a/include/modules/sni/host.hpp +++ b/include/modules/sni/host.hpp @@ -15,9 +15,13 @@ namespace waybar::modules::SNI { class Host { public: Host(const std::size_t id, const Json::Value&, const Bar&, + const std::vector&, const std::function&)>&, const std::function&)>&); ~Host(); + + void checkIgnoreList(const std::vector& ignore_list, + const std::function&)>& on_remove); private: void busAcquired(const Glib::RefPtr&, Glib::ustring); @@ -41,6 +45,7 @@ class Host { SnWatcher* watcher_ = nullptr; const Json::Value& config_; const Bar& bar_; + const std::vector ignore_list_; const std::function&)> on_add_; const std::function&)> on_remove_; }; diff --git a/include/modules/sni/tray.hpp b/include/modules/sni/tray.hpp index 5f12d7f2..9288ec9a 100644 --- a/include/modules/sni/tray.hpp +++ b/include/modules/sni/tray.hpp @@ -19,11 +19,14 @@ class Tray : public AModule { private: void onAdd(std::unique_ptr& item); void onRemove(std::unique_ptr& item); + void checkIgnoreList(std::unique_ptr* item); + std::vector parseIgnoreList(const Json::Value& config); static inline std::size_t nb_hosts_ = 0; bool show_passive_ = false; Gtk::Box box_; SNI::Watcher::singleton watcher_; + std::vector ignore_list_; SNI::Host host_; }; diff --git a/src/modules/sni/host.cpp b/src/modules/sni/host.cpp index 54faa16c..e9893465 100644 --- a/src/modules/sni/host.cpp +++ b/src/modules/sni/host.cpp @@ -7,6 +7,7 @@ namespace waybar::modules::SNI { Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar, + const std::vector& ignore_list, const std::function&)>& on_add, const std::function&)>& on_remove) : bus_name_("org.kde.StatusNotifierHost-" + std::to_string(getpid()) + "-" + @@ -16,6 +17,7 @@ Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar, sigc::mem_fun(*this, &Host::busAcquired))), config_(config), bar_(bar), + ignore_list_(ignore_list), on_add_(on_add), on_remove_(on_remove) {} @@ -33,6 +35,39 @@ Host::~Host() { g_clear_object(&watcher_); } +void Host::checkIgnoreList(const std::vector& ignore_list, + const std::function&)>& on_remove) { + spdlog::debug("Host::checkIgnoreList - checking {} items against {} patterns", items_.size(), ignore_list.size()); + + for (auto it = items_.begin(); it != items_.end(); ) { + auto& item = *it; + spdlog::debug(" Checking item: bus_name='{}', category='{}', icon_name='{}', title='{}'", + item->bus_name, item->category, item->icon_name, item->title); + + bool should_remove = false; + + for (const auto& ignored : ignore_list) { + if (item->bus_name.find(ignored) != std::string::npos || + item->category.find(ignored) != std::string::npos || + item->icon_name.find(ignored) != std::string::npos || + item->id.find(ignored) != std::string::npos || + item->title.find(ignored) != std::string::npos) { + spdlog::info("Host: Ignoring item bus_name='{}', category='{}', icon_name='{}', title='{}' - matched pattern '{}'", + item->bus_name, item->category, item->icon_name, item->title, ignored); + on_remove(item); + should_remove = true; + break; + } + } + + if (should_remove) { + it = items_.erase(it); + } else { + ++it; + } + } +} + void Host::busAcquired(const Glib::RefPtr& conn, Glib::ustring name) { watcher_id_ = Gio::DBus::watch_name(conn, "org.kde.StatusNotifierWatcher", sigc::mem_fun(*this, &Host::nameAppeared), @@ -100,7 +135,9 @@ void Host::registerHost(GObject* src, GAsyncResult* res, gpointer data) { g_signal_connect(host->watcher_, "item-unregistered", G_CALLBACK(&Host::itemUnregistered), data); auto items = sn_watcher_dup_registered_items(host->watcher_); if (items != nullptr) { + spdlog::info("Host: Found {} pre-registered SNI items", g_strv_length(items)); for (uint32_t i = 0; items[i] != nullptr; i += 1) { + spdlog::info("Host: Processing pre-registered item: {}", items[i]); host->addRegisteredItem(items[i]); } } @@ -109,7 +146,9 @@ void Host::registerHost(GObject* src, GAsyncResult* res, gpointer data) { void Host::itemRegistered(SnWatcher* watcher, const gchar* service, gpointer data) { auto host = static_cast(data); + spdlog::info("Host::itemRegistered called with service: {}", service); host->addRegisteredItem(service); + //host->checkIgnoreList(host->ignore_list_, std::bind(&Host::itemUnregistered, host, std::placeholders::_1, std::placeholders::_2, data)); } void Host::itemUnregistered(SnWatcher* watcher, const gchar* service, gpointer data) { @@ -133,12 +172,21 @@ std::tuple Host::getBusNameAndObjectPath(const std::st } void Host::addRegisteredItem(std::string service) { + // Check service string directly before parsing + for (const auto& ignored : ignore_list_) { + if (service.find(ignored) != std::string::npos) { + spdlog::info("Host: Ignoring service '{}' - matched pattern '{}'", service, ignored); + return; + } + } std::string bus_name, object_path; std::tie(bus_name, object_path) = getBusNameAndObjectPath(service); + spdlog::debug("SNI item registered: bus_name={}, object_path={}, full_service={}", bus_name, object_path, service); auto it = std::find_if(items_.begin(), items_.end(), [&bus_name, &object_path](const auto& item) { return bus_name == item->bus_name && object_path == item->object_path; }); if (it == items_.end()) { + spdlog::debug("Adding SNI item: {}", bus_name); items_.emplace_back(new Item(bus_name, object_path, config_, bar_)); on_add_(items_.back()); } diff --git a/src/modules/sni/tray.cpp b/src/modules/sni/tray.cpp index 34a3c05f..248ddf8e 100644 --- a/src/modules/sni/tray.cpp +++ b/src/modules/sni/tray.cpp @@ -8,11 +8,29 @@ namespace waybar::modules::SNI { +std::vector Tray::parseIgnoreList(const Json::Value& config) { + std::vector ignore_list; + if (config["ignore-list"].isArray()) { + spdlog::info("Tray: Found ignore-list with {} items", config["ignore-list"].size()); + for (const auto& item : config["ignore-list"]) { + if (item.isString()) { + ignore_list.push_back(item.asString()); + spdlog::info("Tray: Adding to ignore list: {}", item.asString()); + } + } + } else { + spdlog::info("Tray: No ignore-list configured"); + } + return ignore_list; +} + Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) : AModule(config, "tray", id), box_(bar.orientation, 0), watcher_(SNI::Watcher::getInstance()), - host_(nb_hosts_, config, bar, std::bind(&Tray::onAdd, this, std::placeholders::_1), + ignore_list_(parseIgnoreList(config)), + host_(nb_hosts_, config, bar, ignore_list_, + std::bind(&Tray::onAdd, this, std::placeholders::_1), std::bind(&Tray::onRemove, this, std::placeholders::_1)) { box_.set_name("tray"); event_box_.add(box_); @@ -33,12 +51,24 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) dp.emit(); } +void Tray::checkIgnoreList(std::unique_ptr* item_ptr) { + // Delegate to Host's checkIgnoreList method + host_.checkIgnoreList(ignore_list_, std::bind(&Tray::onRemove, this, std::placeholders::_1)); +} + void Tray::onAdd(std::unique_ptr& item) { + spdlog::info("Tray::onAdd - item bus_name='{}', category='{}', icon_name='{}', title='{}'", + item->bus_name, item->category, item->icon_name, item->title); + if (config_["reverse-direction"].isBool() && config_["reverse-direction"].asBool()) { box_.pack_end(item->event_box); } else { box_.pack_start(item->event_box); } + + spdlog::debug("Tray::onAdd deferred check - checking ignore list"); + host_.checkIgnoreList(ignore_list_, std::bind(&Tray::onRemove, this, std::placeholders::_1)); + dp.emit(); } @@ -48,6 +78,12 @@ void Tray::onRemove(std::unique_ptr& item) { } auto Tray::update() -> void { + // Check if any items should be ignored now that properties have loaded + if (!ignore_list_.empty()) { + spdlog::debug("Tray::update() - checking ignore list"); + host_.checkIgnoreList(ignore_list_, std::bind(&Tray::onRemove, this, std::placeholders::_1)); + } + // Show tray only when items are available std::vector children = box_.get_children(); if (show_passive_) { From f373ebfcbbd44f9ee998d56ee5cb23a80f947e2d Mon Sep 17 00:00:00 2001 From: Hannes Braun Date: Sun, 8 Feb 2026 13:32:41 +0100 Subject: [PATCH 529/780] Fix include of poll.h --- include/modules/battery.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/modules/battery.hpp b/include/modules/battery.hpp index ffc82aba..09c172b0 100644 --- a/include/modules/battery.hpp +++ b/include/modules/battery.hpp @@ -6,7 +6,7 @@ #if defined(__linux__) #include #endif -#include +#include #include #include From 7e6da1adb2dad2705bb17491fe416ebe126befb8 Mon Sep 17 00:00:00 2001 From: Prakhar Chhalotre Date: Mon, 9 Feb 2026 00:16:11 +0530 Subject: [PATCH 530/780] fix: update tooltip method to use pango markup formatting --- src/modules/temperature.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index fa23ef56..bb5ff02c 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -101,7 +101,7 @@ auto waybar::modules::Temperature::update() -> void { if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text(fmt::format( + label_.set_tooltip_markup(fmt::format( fmt::runtime(tooltip_format), fmt::arg("temperatureC", temperature_c), fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k))); } From f96fb53eebba39130cd7ec20b06c77aaff066472 Mon Sep 17 00:00:00 2001 From: Prakhar Chhalotre Date: Tue, 10 Feb 2026 00:30:03 +0530 Subject: [PATCH 531/780] feat: add tooltip in format replacments --- src/modules/custom.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 0a8d9cf6..e2b705da 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -182,9 +182,10 @@ auto waybar::modules::Custom::update() -> void { if (tooltipEnabled()) { if (tooltip_format_enabled_) { auto tooltip = config_["tooltip-format"].asString(); - tooltip = fmt::format( - fmt::runtime(tooltip), fmt::arg("text", text_), fmt::arg("alt", alt_), - fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("percentage", percentage_)); + tooltip = fmt::format(fmt::runtime(tooltip), fmt::arg("text", text_), + fmt::arg("tooltip", tooltip_), fmt::arg("alt", alt_), + fmt::arg("icon", getIcon(percentage_, alt_)), + fmt::arg("percentage", percentage_)); label_.set_tooltip_markup(tooltip); } else if (text_ == tooltip_) { label_.set_tooltip_markup(str); From e67b230dbe5d0f58546a0b052e2c752f5dc53586 Mon Sep 17 00:00:00 2001 From: Prakhar Chhalotre Date: Tue, 10 Feb 2026 01:52:59 +0530 Subject: [PATCH 532/780] fix: prioritize plugged status over default when battery is full --- src/modules/battery.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index c706eb3c..bb1bb6fa 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -685,13 +685,20 @@ auto waybar::modules::Battery::update() -> void { if (status == "Unknown") { status = getAdapterStatus(capacity); } + auto state = getState(capacity, true); + bool adapter_online = false; + if (!adapter_.empty()) { + std::ifstream(adapter_ / "online") >> adapter_online; + } + if (status == "Full" && adapter_online) { + status = "Plugged"; + } auto status_pretty = status; puts(status.c_str()); // Transform to lowercase and replace space with dash std::ranges::transform(status.begin(), status.end(), status.begin(), [](char ch) { return ch == ' ' ? '-' : std::tolower(ch); }); auto format = format_; - auto state = getState(capacity, true); processEvents(state, status, capacity); setBarClass(state); auto time_remaining_formatted = formatTimeRemaining(time_remaining); From c9a7cbbdb385521f992d60539daa6a94d403d253 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 9 Feb 2026 15:26:23 -0600 Subject: [PATCH 533/780] fix(image): treat missing interval as once PR #4390 enabled millisecond intervals but changed image interval parsing so a missing interval could resolve to 1ms. That creates a hot update loop and can spike CPU and destabilize rendering in drawer/group setups (issue #4835). Use explicit semantics: missing, null, non-numeric, or <=0 interval is treated as "once" (max sleep), while positive numeric values still support ms precision with a 1ms floor. This keeps the intended feature (sub-second polling when requested) without defaulting to busy looping. Also align waybar-image(5) docs with runtime behavior. Signed-off-by: Austin Horstman --- man/waybar-image.5.scd | 3 ++- src/modules/image.cpp | 27 ++++++++++++++++----------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/man/waybar-image.5.scd b/man/waybar-image.5.scd index 8c991265..8be182a6 100644 --- a/man/waybar-image.5.scd +++ b/man/waybar-image.5.scd @@ -26,7 +26,8 @@ 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. ++ + If set to a positive value, the minimum is 0.001 (1ms). Values smaller than 1ms will be set to 1ms. ++ + Zero or negative values are treated as "once". ++ 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/modules/image.cpp b/src/modules/image.cpp index 189deee6..98bf3c46 100644 --- a/src/modules/image.cpp +++ b/src/modules/image.cpp @@ -14,22 +14,27 @@ 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))); + const auto once = std::chrono::milliseconds::max(); + if (!config_.isMember("interval") || config_["interval"].isNull() || + config_["interval"] == "once") { + interval_ = once; + } else if (config_["interval"].isNumeric()) { + const auto interval_seconds = config_["interval"].asDouble(); + if (interval_seconds <= 0) { + interval_ = once; + } else { + interval_ = + std::chrono::milliseconds(std::max(1L, // Minimum 1ms due to millisecond precision + static_cast(interval_seconds * 1000))); + } + } else { + interval_ = once; + } if (size_ == 0) { size_ = 16; } - if (interval_.count() == 0) { - interval_ = std::chrono::milliseconds::max(); - } - delayWorker(); } From 3b478ee6a53f153f35f35729c26bf11b15f692a2 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 9 Feb 2026 15:53:44 -0600 Subject: [PATCH 534/780] chore: format Some unrelated files failed formatting. Signed-off-by: Austin Horstman --- include/util/command.hpp | 4 +--- src/modules/hyprland/window.cpp | 4 +--- src/modules/hyprland/windowcount.cpp | 6 ++---- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/include/util/command.hpp b/include/util/command.hpp index 58c59a96..89d4372a 100644 --- a/include/util/command.hpp +++ b/include/util/command.hpp @@ -172,8 +172,6 @@ inline int32_t forkExec(const std::string& cmd, const std::string& output_name) return pid; } -inline int32_t forkExec(const std::string& cmd) { - return forkExec(cmd, ""); -} +inline int32_t forkExec(const std::string& cmd) { return forkExec(cmd, ""); } } // namespace waybar::util::command diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index e02a7691..197ed092 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -237,9 +237,7 @@ void Window::queryActiveWorkspace() { } } -void Window::onEvent(const std::string& ev) { - dp.emit(); -} +void Window::onEvent(const std::string& ev) { dp.emit(); } void Window::setClass(const std::string& classname, bool enable) { if (enable) { diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index 487b0083..d881a361 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -38,7 +38,7 @@ WindowCount::~WindowCount() { auto WindowCount::update() -> void { std::lock_guard lg(mutex_); - + queryActiveWorkspace(); std::string format = config_["format"].asString(); @@ -125,9 +125,7 @@ void WindowCount::queryActiveWorkspace() { } } -void WindowCount::onEvent(const std::string& ev) { - dp.emit(); -} +void WindowCount::onEvent(const std::string& ev) { dp.emit(); } void WindowCount::setClass(const std::string& classname, bool enable) { if (enable) { From bd222984bb61ce09f942cba94204d1d53b41e365 Mon Sep 17 00:00:00 2001 From: Prakhar Chhalotre Date: Wed, 11 Feb 2026 01:07:49 +0530 Subject: [PATCH 535/780] fix: remove unnecessary console log for battery status --- src/modules/battery.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index c706eb3c..8bf4a431 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -686,7 +686,7 @@ auto waybar::modules::Battery::update() -> void { status = getAdapterStatus(capacity); } auto status_pretty = status; - puts(status.c_str()); + // Transform to lowercase and replace space with dash std::ranges::transform(status.begin(), status.end(), status.begin(), [](char ch) { return ch == ' ' ? '-' : std::tolower(ch); }); From a69b7a553676720d2782431dc100e93ab8c0b3b2 Mon Sep 17 00:00:00 2001 From: BlueManCZ Date: Wed, 11 Feb 2026 10:53:48 +0100 Subject: [PATCH 536/780] Handle fallback player for ignored MPRIS players --- src/modules/mpris/mpris.cpp | 50 ++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 2b345fc5..94939311 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -488,7 +488,13 @@ auto Mpris::getPlayerInfo() -> std::optional { char* player_status = nullptr; auto player_playback_status = PLAYERCTL_PLAYBACK_STATUS_STOPPED; - g_object_get(player, "status", &player_status, "playback-status", &player_playback_status, NULL); + + // When using playerctld and the most active player is ignored, we create a + // direct connection to the first non-ignored player for correct metadata. + PlayerctlPlayer* fallback_player = nullptr; + waybar::util::ScopeGuard fallback_deleter([&fallback_player]() { + if (fallback_player) g_object_unref(fallback_player); + }); std::string player_name = player_; if (player_name == "playerctld") { @@ -498,19 +504,33 @@ auto Mpris::getPlayerInfo() -> std::optional { } // > get the list of players [..] in order of activity // https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249 - players = g_list_first(players); - if (players) - player_name = static_cast(players->data)->name; - else - return std::nullopt; // no players found, hide the widget - } - - if (std::any_of(ignored_players_.begin(), ignored_players_.end(), - [&](const std::string& pn) { return player_name == pn; })) { + bool found = false; + for (auto* p = g_list_first(players); p != nullptr; p = p->next) { + auto* pn = static_cast(p->data); + std::string name = pn->name; + if (std::none_of(ignored_players_.begin(), ignored_players_.end(), + [&](const std::string& ignored) { return name == ignored; })) { + player_name = name; + if (p != g_list_first(players)) { + fallback_player = playerctl_player_new_from_name(pn, &error); + if (error || !fallback_player) return std::nullopt; + } + found = true; + break; + } + spdlog::warn("mpris[{}]: ignoring player update", name); + } + if (!found) return std::nullopt; + } else if (std::any_of(ignored_players_.begin(), ignored_players_.end(), + [&](const std::string& pn) { return player_name == pn; })) { spdlog::warn("mpris[{}]: ignoring player update", player_name); return std::nullopt; } + auto* source_player = fallback_player ? fallback_player : player; + g_object_get(source_player, "status", &player_status, "playback-status", &player_playback_status, + NULL); + // make status lowercase player_status[0] = std::tolower(player_status[0]); @@ -524,28 +544,28 @@ auto Mpris::getPlayerInfo() -> std::optional { .length = std::nullopt, }; - if (auto* artist_ = playerctl_player_get_artist(player, &error)) { + if (auto* artist_ = playerctl_player_get_artist(source_player, &error)) { spdlog::debug("mpris[{}]: artist = {}", info.name, artist_); info.artist = artist_; g_free(artist_); } if (error) goto errorexit; - if (auto* album_ = playerctl_player_get_album(player, &error)) { + if (auto* album_ = playerctl_player_get_album(source_player, &error)) { spdlog::debug("mpris[{}]: album = {}", info.name, album_); info.album = album_; g_free(album_); } if (error) goto errorexit; - if (auto* title_ = playerctl_player_get_title(player, &error)) { + if (auto* title_ = playerctl_player_get_title(source_player, &error)) { spdlog::debug("mpris[{}]: title = {}", info.name, title_); info.title = title_; g_free(title_); } if (error) goto errorexit; - if (auto* length_ = playerctl_player_print_metadata_prop(player, "mpris:length", &error)) { + if (auto* length_ = playerctl_player_print_metadata_prop(source_player, "mpris:length", &error)) { spdlog::debug("mpris[{}]: mpris:length = {}", info.name, length_); auto len = std::chrono::microseconds(std::strtol(length_, nullptr, 10)); auto len_h = std::chrono::duration_cast(len); @@ -557,7 +577,7 @@ auto Mpris::getPlayerInfo() -> std::optional { if (error) goto errorexit; { - auto position_ = playerctl_player_get_position(player, &error); + auto position_ = playerctl_player_get_position(source_player, &error); if (error) { // it's fine to have an error here because not all players report a position g_error_free(error); From 0a50e82d0da0108d6b9a792451b09d29cfbde29a Mon Sep 17 00:00:00 2001 From: BlueManCZ Date: Wed, 11 Feb 2026 11:47:05 +0100 Subject: [PATCH 537/780] Prioritize currently playing player --- src/modules/mpris/mpris.cpp | 40 +++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 94939311..58c4f138 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -458,10 +458,7 @@ auto Mpris::onPlayerStop(PlayerctlPlayer* player, gpointer data) -> void { if (!mpris) return; spdlog::debug("mpris: player-stop callback"); - - // hide widget - mpris->event_box_.set_visible(false); - // update widget + // update widget (update() handles visibility) mpris->dp.emit(); } @@ -504,23 +501,36 @@ auto Mpris::getPlayerInfo() -> std::optional { } // > get the list of players [..] in order of activity // https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249 - bool found = false; + PlayerctlPlayerName* best = nullptr; + PlayerctlPlayerName* first_valid = nullptr; for (auto* p = g_list_first(players); p != nullptr; p = p->next) { auto* pn = static_cast(p->data); std::string name = pn->name; - if (std::none_of(ignored_players_.begin(), ignored_players_.end(), - [&](const std::string& ignored) { return name == ignored; })) { - player_name = name; - if (p != g_list_first(players)) { - fallback_player = playerctl_player_new_from_name(pn, &error); - if (error || !fallback_player) return std::nullopt; - } - found = true; + if (std::any_of(ignored_players_.begin(), ignored_players_.end(), + [&](const std::string& ignored) { return name == ignored; })) { + spdlog::warn("mpris[{}]: ignoring player update", name); + continue; + } + if (!first_valid) first_valid = pn; + // Check if this player is currently playing + auto* tmp = playerctl_player_new_from_name(pn, &error); + if (error || !tmp) continue; + PlayerctlPlaybackStatus status; + g_object_get(tmp, "playback-status", &status, NULL); + if (status == PLAYERCTL_PLAYBACK_STATUS_PLAYING) { + best = pn; + g_object_unref(tmp); break; } - spdlog::warn("mpris[{}]: ignoring player update", name); + g_object_unref(tmp); + } + if (!best) best = first_valid; + if (!best) return std::nullopt; + player_name = best->name; + if (best != static_cast(g_list_first(players)->data)) { + fallback_player = playerctl_player_new_from_name(best, &error); + if (error || !fallback_player) return std::nullopt; } - if (!found) return std::nullopt; } else if (std::any_of(ignored_players_.begin(), ignored_players_.end(), [&](const std::string& pn) { return player_name == pn; })) { spdlog::warn("mpris[{}]: ignoring player update", player_name); From a871d90161fadab34b494d0ee5d018eb90496b8d Mon Sep 17 00:00:00 2001 From: BlueManCZ Date: Wed, 11 Feb 2026 12:09:20 +0100 Subject: [PATCH 538/780] Fix button action handling to consistently use the active player --- include/modules/mpris/mpris.hpp | 1 + src/modules/mpris/mpris.cpp | 68 +++++++++++++++++---------------- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/include/modules/mpris/mpris.hpp b/include/modules/mpris/mpris.hpp index ad4dac1e..a33db4d2 100644 --- a/include/modules/mpris/mpris.hpp +++ b/include/modules/mpris/mpris.hpp @@ -78,6 +78,7 @@ class Mpris : public ALabel { PlayerctlPlayerManager* manager; PlayerctlPlayer* player; + PlayerctlPlayer* last_active_player_ = nullptr; std::string lastStatus; std::string lastPlayer; diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 58c4f138..f8a341a8 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -178,6 +178,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) } Mpris::~Mpris() { + if (last_active_player_ && last_active_player_ != player) g_object_unref(last_active_player_); if (manager != nullptr) g_object_unref(manager); if (player != nullptr) g_object_unref(player); } @@ -486,12 +487,11 @@ auto Mpris::getPlayerInfo() -> std::optional { char* player_status = nullptr; auto player_playback_status = PLAYERCTL_PLAYBACK_STATUS_STOPPED; - // When using playerctld and the most active player is ignored, we create a - // direct connection to the first non-ignored player for correct metadata. - PlayerctlPlayer* fallback_player = nullptr; - waybar::util::ScopeGuard fallback_deleter([&fallback_player]() { - if (fallback_player) g_object_unref(fallback_player); - }); + // Clean up previous fallback player + if (last_active_player_ && last_active_player_ != player) { + g_object_unref(last_active_player_); + last_active_player_ = nullptr; + } std::string player_name = player_; if (player_name == "playerctld") { @@ -501,8 +501,8 @@ auto Mpris::getPlayerInfo() -> std::optional { } // > get the list of players [..] in order of activity // https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249 - PlayerctlPlayerName* best = nullptr; - PlayerctlPlayerName* first_valid = nullptr; + PlayerctlPlayer* first_valid_player = nullptr; + std::string first_valid_name; for (auto* p = g_list_first(players); p != nullptr; p = p->next) { auto* pn = static_cast(p->data); std::string name = pn->name; @@ -511,35 +511,37 @@ auto Mpris::getPlayerInfo() -> std::optional { spdlog::warn("mpris[{}]: ignoring player update", name); continue; } - if (!first_valid) first_valid = pn; - // Check if this player is currently playing auto* tmp = playerctl_player_new_from_name(pn, &error); if (error || !tmp) continue; + if (!first_valid_player) { + first_valid_player = tmp; + first_valid_name = name; + } PlayerctlPlaybackStatus status; g_object_get(tmp, "playback-status", &status, NULL); if (status == PLAYERCTL_PLAYBACK_STATUS_PLAYING) { - best = pn; - g_object_unref(tmp); + if (tmp != first_valid_player) g_object_unref(first_valid_player); + last_active_player_ = tmp; + player_name = name; break; } - g_object_unref(tmp); + if (tmp != first_valid_player) g_object_unref(tmp); } - if (!best) best = first_valid; - if (!best) return std::nullopt; - player_name = best->name; - if (best != static_cast(g_list_first(players)->data)) { - fallback_player = playerctl_player_new_from_name(best, &error); - if (error || !fallback_player) return std::nullopt; + if (!last_active_player_) { + if (!first_valid_player) return std::nullopt; + last_active_player_ = first_valid_player; + player_name = first_valid_name; } } else if (std::any_of(ignored_players_.begin(), ignored_players_.end(), [&](const std::string& pn) { return player_name == pn; })) { spdlog::warn("mpris[{}]: ignoring player update", player_name); return std::nullopt; + } else { + last_active_player_ = player; } - auto* source_player = fallback_player ? fallback_player : player; - g_object_get(source_player, "status", &player_status, "playback-status", &player_playback_status, - NULL); + g_object_get(last_active_player_, "status", &player_status, "playback-status", + &player_playback_status, NULL); // make status lowercase player_status[0] = std::tolower(player_status[0]); @@ -554,28 +556,29 @@ auto Mpris::getPlayerInfo() -> std::optional { .length = std::nullopt, }; - if (auto* artist_ = playerctl_player_get_artist(source_player, &error)) { + if (auto* artist_ = playerctl_player_get_artist(last_active_player_, &error)) { spdlog::debug("mpris[{}]: artist = {}", info.name, artist_); info.artist = artist_; g_free(artist_); } if (error) goto errorexit; - if (auto* album_ = playerctl_player_get_album(source_player, &error)) { + if (auto* album_ = playerctl_player_get_album(last_active_player_, &error)) { spdlog::debug("mpris[{}]: album = {}", info.name, album_); info.album = album_; g_free(album_); } if (error) goto errorexit; - if (auto* title_ = playerctl_player_get_title(source_player, &error)) { + if (auto* title_ = playerctl_player_get_title(last_active_player_, &error)) { spdlog::debug("mpris[{}]: title = {}", info.name, title_); info.title = title_; g_free(title_); } if (error) goto errorexit; - if (auto* length_ = playerctl_player_print_metadata_prop(source_player, "mpris:length", &error)) { + if (auto* length_ = + playerctl_player_print_metadata_prop(last_active_player_, "mpris:length", &error)) { spdlog::debug("mpris[{}]: mpris:length = {}", info.name, length_); auto len = std::chrono::microseconds(std::strtol(length_, nullptr, 10)); auto len_h = std::chrono::duration_cast(len); @@ -587,7 +590,7 @@ auto Mpris::getPlayerInfo() -> std::optional { if (error) goto errorexit; { - auto position_ = playerctl_player_get_position(source_player, &error); + auto position_ = playerctl_player_get_position(last_active_player_, &error); if (error) { // it's fine to have an error here because not all players report a position g_error_free(error); @@ -639,12 +642,13 @@ bool Mpris::handleToggle(GdkEventButton* const& e) { }); // Command pattern: encapsulate each button's action + auto* target = last_active_player_ ? last_active_player_ : player; const ButtonAction actions[] = { - {1, "on-click", [&]() { playerctl_player_play_pause(player, &error); }}, - {2, "on-click-middle", [&]() { playerctl_player_previous(player, &error); }}, - {3, "on-click-right", [&]() { playerctl_player_next(player, &error); }}, - {8, "on-click-backward", [&]() { playerctl_player_previous(player, &error); }}, - {9, "on-click-forward", [&]() { playerctl_player_next(player, &error); }}, + {1, "on-click", [&]() { playerctl_player_play_pause(target, &error); }}, + {2, "on-click-middle", [&]() { playerctl_player_previous(target, &error); }}, + {3, "on-click-right", [&]() { playerctl_player_next(target, &error); }}, + {8, "on-click-backward", [&]() { playerctl_player_previous(target, &error); }}, + {9, "on-click-forward", [&]() { playerctl_player_next(target, &error); }}, }; for (const auto& action : actions) { From c388208e21b151151bb5ff8a04d9ba648affff15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Wed, 11 Feb 2026 13:01:07 +0100 Subject: [PATCH 539/780] docs: fix typo in waybar.5 in `expand-right` field info --- 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 1b799275..624798ed 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -36,7 +36,7 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles( *expand-right* ++ typeof: bool ++ default: false ++ - Enables the modules-left to consume all left over space dynamically. + Enables the modules-right to consume all left over space dynamically. *layer* ++ typeof: string ++ From 3bcadfdf5a57b3598194c9c795e7b1e9d8dce3f6 Mon Sep 17 00:00:00 2001 From: Alexey Vladimirov <> Date: Thu, 12 Feb 2026 01:14:16 +0300 Subject: [PATCH 540/780] battery: power switch events --- man/waybar-battery.5.scd | 3 +++ src/modules/battery.cpp | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/man/waybar-battery.5.scd b/man/waybar-battery.5.scd index 6d98fd4e..c5450c8e 100644 --- a/man/waybar-battery.5.scd +++ b/man/waybar-battery.5.scd @@ -182,6 +182,7 @@ Every entry in the *events* object consists of a ** (typeof: *string - *on--* - *on--* +- *on-* Where: @@ -204,6 +205,8 @@ Where: "on-discharging-warning": "notify-send -u normal 'Low Battery'", "on-discharging-critical": "notify-send -u critical 'Very Low Battery'", "on-charging-100": "notify-send -u normal 'Battery Full!'" + "on-discharging": "notify-send -u normal 'Power Switch' Discharging" + "on-charging": "notify-send -u normal 'Power Switch' Charging'" }, "format": "{capacity}% {icon}", "format-icons": ["", "", "", "", ""], diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 8bf4a431..58a7f50e 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -790,16 +790,19 @@ void waybar::modules::Battery::processEvents(std::string& state, std::string& st if (!events.isObject() || events.empty()) { return; } - std::string event_name = fmt::format("on-{}-{}", status == "discharging" ? status : "charging", - state.empty() ? std::to_string(capacity) : state); + auto exec = [](Json::Value const& event) { + if (!event.isString()) return; + if (auto command = event.asString(); !command.empty()) { + util::command::exec(command, ""); + } + }; + std::string status_name = status == "discharging" ? "on-discharging" : "on-charging"; + std::string event_name = status_name + '-' + (state.empty() ? std::to_string(capacity) : state); if (last_event_ != event_name) { spdlog::debug("battery: triggering event {}", event_name); - if (events[event_name].isString()) { - std::string exec = events[event_name].asString(); - // Execute the command if it is not empty - if (!exec.empty()) { - util::command::exec(exec, ""); - } + exec(events[event_name]); + if (!last_event_.empty() && last_event_[3] != event_name[3]) { + exec(events[status_name]); } last_event_ = event_name; } From 7110c455e2fa5c9763681a72685b7ffc6f5d5c2b Mon Sep 17 00:00:00 2001 From: Alexey Vladimirov <> Date: Thu, 12 Feb 2026 02:12:00 +0300 Subject: [PATCH 541/780] fix of example config typos --- man/waybar-battery.5.scd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/man/waybar-battery.5.scd b/man/waybar-battery.5.scd index c5450c8e..42b9e046 100644 --- a/man/waybar-battery.5.scd +++ b/man/waybar-battery.5.scd @@ -204,8 +204,8 @@ Where: "events": { "on-discharging-warning": "notify-send -u normal 'Low Battery'", "on-discharging-critical": "notify-send -u critical 'Very Low Battery'", - "on-charging-100": "notify-send -u normal 'Battery Full!'" - "on-discharging": "notify-send -u normal 'Power Switch' Discharging" + "on-charging-100": "notify-send -u normal 'Battery Full!'", + "on-discharging": "notify-send -u normal 'Power Switch' Discharging", "on-charging": "notify-send -u normal 'Power Switch' Charging'" }, "format": "{capacity}% {icon}", From 13469a8847bac2d5452e59d4c93f373f2f57b641 Mon Sep 17 00:00:00 2001 From: Tobias Brox Date: Thu, 12 Feb 2026 11:46:24 +0100 Subject: [PATCH 542/780] fix(keyboard-state): fix segfault on device hotplug The keyboard-state module crashes with SIGSEGV in libinput_device_ref when a new input device appears in /dev/input/. Three bugs fixed: 1. Missing NULL check: tryAddDevice() calls libinput_path_add_device() which returns NULL on failure, then immediately passes the result to libinput_device_ref() without checking. On laptops, virtual input devices (power buttons, lid switch, etc.) appear and disappear in /dev/input/ triggering the hotplug handler; if libinput can't open one of these, the NULL return causes the segfault. 2. Missing cleanup on device removal: The IN_DELETE handler erased devices from the map without calling libinput_path_remove_device(), leaving dangling pointers in the libinput context. 3. Thread safety: libinput_devices_ was accessed from 3 threads (main/GTK, libinput_thread_, hotplug_thread_) without any mutex. Fixes #4851 Co-Authored-By: Claude Opus 4.6 --- include/modules/keyboard_state.hpp | 2 ++ src/modules/keyboard_state.cpp | 29 ++++++++++++++++++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/include/modules/keyboard_state.hpp b/include/modules/keyboard_state.hpp index be90eee4..db7f2550 100644 --- a/include/modules/keyboard_state.hpp +++ b/include/modules/keyboard_state.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -41,6 +42,7 @@ class KeyboardState : public AModule { struct libinput* libinput_; std::unordered_map libinput_devices_; + std::mutex devices_mutex_; // protects libinput_devices_ std::set binding_keys; util::SleeperThread libinput_thread_, hotplug_thread_; diff --git a/src/modules/keyboard_state.cpp b/src/modules/keyboard_state.cpp index 18ce0a7c..a2207fdd 100644 --- a/src/modules/keyboard_state.cpp +++ b/src/modules/keyboard_state.cpp @@ -232,9 +232,12 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& } tryAddDevice(dev_path); } else if (event->mask & IN_DELETE) { + std::lock_guard lock(devices_mutex_); auto it = libinput_devices_.find(dev_path); if (it != libinput_devices_.end()) { spdlog::info("Keyboard {} has been removed.", dev_path); + libinput_path_remove_device(it->second); + libinput_device_unref(it->second); libinput_devices_.erase(it); } } @@ -245,6 +248,7 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& } waybar::modules::KeyboardState::~KeyboardState() { + std::lock_guard lock(devices_mutex_); for (const auto& [_, dev_ptr] : libinput_devices_) { libinput_path_remove_device(dev_ptr); } @@ -256,11 +260,17 @@ auto waybar::modules::KeyboardState::update() -> void { try { std::string dev_path; - if (config_["device-path"].isString() && - libinput_devices_.find(config_["device-path"].asString()) != libinput_devices_.end()) { - dev_path = config_["device-path"].asString(); - } else { - dev_path = libinput_devices_.begin()->first; + { + std::lock_guard lock(devices_mutex_); + if (libinput_devices_.empty()) { + return; + } + if (config_["device-path"].isString() && + libinput_devices_.find(config_["device-path"].asString()) != libinput_devices_.end()) { + dev_path = config_["device-path"].asString(); + } else { + dev_path = libinput_devices_.begin()->first; + } } int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY); auto dev = openDevice(fd); @@ -308,10 +318,15 @@ auto waybar::modules ::KeyboardState::tryAddDevice(const std::string& dev_path) auto dev = openDevice(fd); if (supportsLockStates(dev)) { spdlog::info("Found device {} at '{}'", libevdev_get_name(dev), dev_path); + std::lock_guard lock(devices_mutex_); if (libinput_devices_.find(dev_path) == libinput_devices_.end()) { auto device = libinput_path_add_device(libinput_, dev_path.c_str()); - libinput_device_ref(device); - libinput_devices_[dev_path] = device; + if (device) { + libinput_device_ref(device); + libinput_devices_[dev_path] = device; + } else { + spdlog::warn("keyboard-state: Failed to add device to libinput: {}", dev_path); + } } } libevdev_free(dev); From a70651ed056ff787adb54fbc49941a0e60a07cda Mon Sep 17 00:00:00 2001 From: Sami Liedes Date: Thu, 12 Feb 2026 18:11:30 +0100 Subject: [PATCH 543/780] fix: use SafeSignal in ipc --- include/modules/sway/ipc/client.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/modules/sway/ipc/client.hpp b/include/modules/sway/ipc/client.hpp index 9033a688..c5178c2b 100644 --- a/include/modules/sway/ipc/client.hpp +++ b/include/modules/sway/ipc/client.hpp @@ -13,6 +13,7 @@ #include "ipc.hpp" #include "util/sleeper_thread.hpp" +#include "util/SafeSignal.hpp" namespace waybar::modules::sway { @@ -27,8 +28,8 @@ class Ipc { std::string payload; }; - sigc::signal signal_event; - sigc::signal signal_cmd; + ::waybar::SafeSignal signal_event; + ::waybar::SafeSignal signal_cmd; void sendCmd(uint32_t type, const std::string& payload = ""); void subscribe(const std::string& payload); From 4ac539206f932ef6c101a961bc5bb9a518e5dc21 Mon Sep 17 00:00:00 2001 From: Prakhar Chhalotre Date: Fri, 13 Feb 2026 02:00:17 +0530 Subject: [PATCH 544/780] feat: add tooltip-format config option in cpu module --- src/modules/cpu.cpp | 13 ++++++++++--- src/modules/cpu_frequency/common.cpp | 19 +++++++++++++------ src/modules/cpu_usage/common.cpp | 13 ++++++++++--- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/modules/cpu.cpp b/src/modules/cpu.cpp index 0703eaf7..ab75b911 100644 --- a/src/modules/cpu.cpp +++ b/src/modules/cpu.cpp @@ -26,9 +26,7 @@ auto waybar::modules::Cpu::update() -> void { auto [load1, load5, load15] = Load::getLoad(); auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_); auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency(); - if (tooltipEnabled()) { - label_.set_tooltip_text(tooltip); - } + auto format = format_; auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; auto state = getState(total_usage); @@ -56,6 +54,15 @@ auto waybar::modules::Cpu::update() -> void { store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); } label_.set_markup(fmt::vformat(format, store)); + + if (tooltipEnabled()) { + if (config_["tooltip-format"].isString()) { + tooltip = config_["tooltip-format"].asString(); + label_.set_tooltip_markup(fmt::vformat(tooltip, store)); + } else { + label_.set_tooltip_markup(tooltip); + } + } } // Call parent update diff --git a/src/modules/cpu_frequency/common.cpp b/src/modules/cpu_frequency/common.cpp index e47364ba..05adc2b3 100644 --- a/src/modules/cpu_frequency/common.cpp +++ b/src/modules/cpu_frequency/common.cpp @@ -20,12 +20,7 @@ waybar::modules::CpuFrequency::CpuFrequency(const std::string& id, const Json::V auto waybar::modules::CpuFrequency::update() -> void { // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency(); - if (tooltipEnabled()) { - auto tooltip = - fmt::format("Minimum frequency: {}\nAverage frequency: {}\nMaximum frequency: {}\n", - min_frequency, avg_frequency, max_frequency); - label_.set_tooltip_text(tooltip); - } + auto format = format_; auto state = getState(avg_frequency); if (!state.empty() && config_["format-" + state].isString()) { @@ -43,6 +38,18 @@ auto waybar::modules::CpuFrequency::update() -> void { store.push_back(fmt::arg("min_frequency", min_frequency)); store.push_back(fmt::arg("avg_frequency", avg_frequency)); label_.set_markup(fmt::vformat(format, store)); + + if (tooltipEnabled()) { + std::string tooltip; + if (config_["tooltip-format"].isString()) { + tooltip = config_["tooltip-format"].asString(); + label_.set_tooltip_markup(fmt::vformat(tooltip, store)); + } else { + tooltip = "Minimum frequency: {}\nAverage frequency: {}\nMaximum frequency: {}\n"; + label_.set_tooltip_markup( + fmt::format(fmt::runtime(tooltip), min_frequency, avg_frequency, max_frequency)); + } + } } // Call parent update diff --git a/src/modules/cpu_usage/common.cpp b/src/modules/cpu_usage/common.cpp index e3947967..a2bb4596 100644 --- a/src/modules/cpu_usage/common.cpp +++ b/src/modules/cpu_usage/common.cpp @@ -20,9 +20,7 @@ waybar::modules::CpuUsage::CpuUsage(const std::string& id, const Json::Value& co auto waybar::modules::CpuUsage::update() -> void { // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_); - if (tooltipEnabled()) { - label_.set_tooltip_text(tooltip); - } + auto format = format_; auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; auto state = getState(total_usage); @@ -46,6 +44,15 @@ auto waybar::modules::CpuUsage::update() -> void { store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); } label_.set_markup(fmt::vformat(format, store)); + + if (tooltipEnabled()) { + if (config_["tooltip-format"].isString()) { + tooltip = config_["tooltip-format"].asString(); + label_.set_tooltip_markup(fmt::vformat(tooltip, store)); + } else { + label_.set_tooltip_markup(tooltip); + } + } } // Call parent update From 3e7976c8eb5c112410be7f0860ce7ea413eaeb2e Mon Sep 17 00:00:00 2001 From: Prakhar Chhalotre Date: Fri, 13 Feb 2026 02:17:46 +0530 Subject: [PATCH 545/780] fix: use pango markup for consistent formatting in format and tooltip-format --- src/modules/backlight.cpp | 8 ++++---- src/modules/cpu.cpp | 2 +- src/modules/cpu_frequency/common.cpp | 2 +- src/modules/cpu_usage/common.cpp | 2 +- src/modules/disk.cpp | 4 ++-- src/modules/dwl/window.cpp | 2 +- src/modules/gamemode.cpp | 2 +- src/modules/hyprland/submap.cpp | 2 +- src/modules/hyprland/window.cpp | 4 ++-- src/modules/hyprland/windowcount.cpp | 2 +- src/modules/hyprland/workspace.cpp | 2 +- src/modules/inhibitor.cpp | 2 +- src/modules/jack.cpp | 2 +- src/modules/load.cpp | 2 +- src/modules/memory/common.cpp | 4 ++-- src/modules/mpd/mpd.cpp | 4 ++-- src/modules/mpris/mpris.cpp | 2 +- src/modules/niri/window.cpp | 2 +- src/modules/power_profiles_daemon.cpp | 2 +- src/modules/pulseaudio.cpp | 2 +- src/modules/simpleclock.cpp | 4 ++-- src/modules/sway/mode.cpp | 2 +- src/modules/sway/window.cpp | 2 +- src/modules/wireplumber.cpp | 4 ++-- src/modules/wlr/taskbar.cpp | 2 +- 25 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/modules/backlight.cpp b/src/modules/backlight.cpp index aabfe3f1..8f0d583f 100644 --- a/src/modules/backlight.cpp +++ b/src/modules/backlight.cpp @@ -58,11 +58,11 @@ auto waybar::modules::Backlight::update() -> void { tooltip_format = config_["tooltip-format"].asString(); } if (!tooltip_format.empty()) { - label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format), - fmt::arg("percent", percent), - fmt::arg("icon", getIcon(percent)))); + label_.set_tooltip_markup(fmt::format(fmt::runtime(tooltip_format), + fmt::arg("percent", percent), + fmt::arg("icon", getIcon(percent)))); } else { - label_.set_tooltip_text(desc); + label_.set_tooltip_markup(desc); } } } else { diff --git a/src/modules/cpu.cpp b/src/modules/cpu.cpp index 0703eaf7..0ab79698 100644 --- a/src/modules/cpu.cpp +++ b/src/modules/cpu.cpp @@ -27,7 +27,7 @@ auto waybar::modules::Cpu::update() -> void { auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_); auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency(); if (tooltipEnabled()) { - label_.set_tooltip_text(tooltip); + label_.set_tooltip_markup(tooltip); } auto format = format_; auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; diff --git a/src/modules/cpu_frequency/common.cpp b/src/modules/cpu_frequency/common.cpp index e47364ba..3808a3a4 100644 --- a/src/modules/cpu_frequency/common.cpp +++ b/src/modules/cpu_frequency/common.cpp @@ -24,7 +24,7 @@ auto waybar::modules::CpuFrequency::update() -> void { auto tooltip = fmt::format("Minimum frequency: {}\nAverage frequency: {}\nMaximum frequency: {}\n", min_frequency, avg_frequency, max_frequency); - label_.set_tooltip_text(tooltip); + label_.set_tooltip_markup(tooltip); } auto format = format_; auto state = getState(avg_frequency); diff --git a/src/modules/cpu_usage/common.cpp b/src/modules/cpu_usage/common.cpp index e3947967..0cf4786f 100644 --- a/src/modules/cpu_usage/common.cpp +++ b/src/modules/cpu_usage/common.cpp @@ -21,7 +21,7 @@ auto waybar::modules::CpuUsage::update() -> void { // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_); if (tooltipEnabled()) { - label_.set_tooltip_text(tooltip); + label_.set_tooltip_markup(tooltip); } auto format = format_; auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; diff --git a/src/modules/disk.cpp b/src/modules/disk.cpp index ef257b72..39703547 100644 --- a/src/modules/disk.cpp +++ b/src/modules/disk.cpp @@ -81,7 +81,7 @@ auto waybar::modules::Disk::update() -> void { if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_text(fmt::format( + label_.set_tooltip_markup(fmt::format( fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used), fmt::arg("percentage_used", percentage_used), fmt::arg("total", total), @@ -112,4 +112,4 @@ float waybar::modules::Disk::calc_specific_divisor(std::string divisor) { } else { // default to Bytes if it is anything that we don't recongnise return 1.0; } -} \ No newline at end of file +} diff --git a/src/modules/dwl/window.cpp b/src/modules/dwl/window.cpp index 2b679c9f..56e67895 100644 --- a/src/modules/dwl/window.cpp +++ b/src/modules/dwl/window.cpp @@ -116,7 +116,7 @@ void Window::handle_frame() { updateAppIconName(appid_, ""); updateAppIcon(); if (tooltipEnabled()) { - label_.set_tooltip_text(title_); + label_.set_tooltip_markup(title_); } } diff --git a/src/modules/gamemode.cpp b/src/modules/gamemode.cpp index 72ef9503..8c33fcf3 100644 --- a/src/modules/gamemode.cpp +++ b/src/modules/gamemode.cpp @@ -212,7 +212,7 @@ auto Gamemode::update() -> void { // Tooltip if (tooltip) { std::string text = fmt::format(fmt::runtime(tooltip_format), fmt::arg("count", gameCount)); - box_.set_tooltip_text(text); + box_.set_tooltip_markup(text); } // Label format diff --git a/src/modules/hyprland/submap.cpp b/src/modules/hyprland/submap.cpp index 5d587d02..ff18e7f3 100644 --- a/src/modules/hyprland/submap.cpp +++ b/src/modules/hyprland/submap.cpp @@ -60,7 +60,7 @@ auto Submap::update() -> void { } else { label_.set_markup(fmt::format(fmt::runtime(format_), submap_)); if (tooltipEnabled()) { - label_.set_tooltip_text(submap_); + label_.set_tooltip_markup(submap_); } event_box_.show(); } diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index 197ed092..1fddb45b 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -72,13 +72,13 @@ auto Window::update() -> void { tooltip_format = config_["tooltip-format"].asString(); } if (!tooltip_format.empty()) { - label_.set_tooltip_text( + label_.set_tooltip_markup( fmt::format(fmt::runtime(tooltip_format), fmt::arg("title", windowName), fmt::arg("initialTitle", windowData_.initial_title), fmt::arg("class", windowData_.class_name), fmt::arg("initialClass", windowData_.initial_class_name))); } else if (!label_text.empty()) { - label_.set_tooltip_text(label_text); + label_.set_tooltip_markup(label_text); } } diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index d881a361..ab573cca 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -58,7 +58,7 @@ auto WindowCount::update() -> void { } else if (!format.empty()) { label_.set_markup(fmt::format(fmt::runtime(format), workspace_.windows)); } else { - label_.set_text(fmt::format("{}", workspace_.windows)); + label_.set_markup(fmt::format("{}", workspace_.windows)); } label_.show(); diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 4cdd8910..87933ac0 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -300,7 +300,7 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) { } auto window_box = Gtk::make_managed(Gtk::ORIENTATION_HORIZONTAL); - window_box->set_tooltip_text(window_repr.window_title); + window_box->set_tooltip_markup(window_repr.window_title); window_box->get_style_context()->add_class("taskbar-window"); if (window_repr.isActive) { window_box->get_style_context()->add_class("active"); diff --git a/src/modules/inhibitor.cpp b/src/modules/inhibitor.cpp index fe2a4be4..170d0508 100644 --- a/src/modules/inhibitor.cpp +++ b/src/modules/inhibitor.cpp @@ -123,7 +123,7 @@ auto Inhibitor::update() -> void { label_.get_style_context()->add_class(status_text); if (tooltipEnabled()) { - label_.set_tooltip_text(status_text); + label_.set_tooltip_markup(status_text); } return ALabel::update(); diff --git a/src/modules/jack.cpp b/src/modules/jack.cpp index 678f986b..8e5a75a8 100644 --- a/src/modules/jack.cpp +++ b/src/modules/jack.cpp @@ -80,7 +80,7 @@ auto JACK::update() -> void { if (tooltipEnabled()) { std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms"; if (config_["tooltip-format"].isString()) tooltip_format = config_["tooltip-format"].asString(); - label_.set_tooltip_text(fmt::format( + label_.set_tooltip_markup(fmt::format( fmt::runtime(tooltip_format), fmt::arg("load", std::round(load_)), fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_), fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_))); diff --git a/src/modules/load.cpp b/src/modules/load.cpp index 69a37b4e..72627405 100644 --- a/src/modules/load.cpp +++ b/src/modules/load.cpp @@ -22,7 +22,7 @@ auto waybar::modules::Load::update() -> void { auto [load1, load5, load15] = Load::getLoad(); if (tooltipEnabled()) { auto tooltip = fmt::format("Load 1: {}\nLoad 5: {}\nLoad 15: {}", load1, load5, load15); - label_.set_tooltip_text(tooltip); + label_.set_tooltip_markup(tooltip); } auto format = format_; auto state = getState(load1); diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp index a83f6526..3c486a33 100644 --- a/src/modules/memory/common.cpp +++ b/src/modules/memory/common.cpp @@ -69,7 +69,7 @@ auto waybar::modules::Memory::update() -> void { if (tooltipEnabled()) { if (config_["tooltip-format"].isString()) { auto tooltip_format = config_["tooltip-format"].asString(); - label_.set_tooltip_text(fmt::format( + label_.set_tooltip_markup(fmt::format( fmt::runtime(tooltip_format), used_ram_percentage, fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), fmt::arg("percentage", used_ram_percentage), @@ -78,7 +78,7 @@ auto waybar::modules::Memory::update() -> void { fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes), fmt::arg("swapAvail", available_swap_gigabytes))); } else { - label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1)); + label_.set_tooltip_markup(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1)); } } } else { diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 8e6bbb25..f0cc5757 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -110,7 +110,7 @@ void waybar::modules::MPD::setLabel() { ? config_["tooltip-format-disconnected"].asString() : "MPD (disconnected)"; // Nothing to format - label_.set_tooltip_text(tooltip_format); + label_.set_tooltip_markup(tooltip_format); } return; } @@ -210,7 +210,7 @@ void waybar::modules::MPD::setLabel() { fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename), fmt::arg("uri", uri)); - label_.set_tooltip_text(tooltip_text); + label_.set_tooltip_markup(tooltip_text); } catch (fmt::format_error const& e) { spdlog::warn("mpd: format error (tooltip): {}", e.what()); } diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 2b345fc5..56f758b6 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -734,7 +734,7 @@ auto Mpris::update() -> void { fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)), fmt::arg("status_icon", getIconFromJson(config_["status-icons"], info.status_string))); - label_.set_tooltip_text(tooltip_text); + label_.set_tooltip_markup(tooltip_text); } catch (fmt::format_error const& e) { spdlog::warn("mpris: format error (tooltip): {}", e.what()); } diff --git a/src/modules/niri/window.cpp b/src/modules/niri/window.cpp index c3537769..61b3a7dd 100644 --- a/src/modules/niri/window.cpp +++ b/src/modules/niri/window.cpp @@ -67,7 +67,7 @@ void Window::doUpdate() { updateAppIconName(appId, ""); - if (tooltipEnabled()) label_.set_tooltip_text(title); + if (tooltipEnabled()) label_.set_tooltip_markup(title); const auto id = window["id"].asUInt64(); const auto workspaceId = window["workspace_id"].asUInt64(); diff --git a/src/modules/power_profiles_daemon.cpp b/src/modules/power_profiles_daemon.cpp index abad763d..60569521 100644 --- a/src/modules/power_profiles_daemon.cpp +++ b/src/modules/power_profiles_daemon.cpp @@ -157,7 +157,7 @@ auto PowerProfilesDaemon::update() -> void { store.push_back(fmt::arg("icon", getIcon(0, profile.name))); label_.set_markup(fmt::vformat(format_, store)); if (tooltipEnabled()) { - label_.set_tooltip_text(fmt::vformat(tooltipFormat_, store)); + label_.set_tooltip_markup(fmt::vformat(tooltipFormat_, store)); } // Set CSS class diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index f60c39ff..4d63ff3c 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -138,7 +138,7 @@ auto waybar::modules::Pulseaudio::update() -> void { fmt::arg("source_volume", source_volume), fmt::arg("source_desc", source_desc), fmt::arg("icon", getIcon(sink_volume, getPulseIcon())))); } else { - label_.set_tooltip_text(sink_desc); + label_.set_tooltip_markup(sink_desc); } } diff --git a/src/modules/simpleclock.cpp b/src/modules/simpleclock.cpp index b6a96ecc..59a2b1c1 100644 --- a/src/modules/simpleclock.cpp +++ b/src/modules/simpleclock.cpp @@ -25,9 +25,9 @@ auto waybar::modules::Clock::update() -> void { if (config_["tooltip-format"].isString()) { auto tooltip_format = config_["tooltip-format"].asString(); auto tooltip_text = fmt::format(fmt::runtime(tooltip_format), localtime); - label_.set_tooltip_text(tooltip_text); + label_.set_tooltip_markup(tooltip_text); } else { - label_.set_tooltip_text(text); + label_.set_tooltip_markup(text); } } // Call parent update diff --git a/src/modules/sway/mode.cpp b/src/modules/sway/mode.cpp index b81735e5..1f5f071a 100644 --- a/src/modules/sway/mode.cpp +++ b/src/modules/sway/mode.cpp @@ -44,7 +44,7 @@ auto Mode::update() -> void { } else { label_.set_markup(fmt::format(fmt::runtime(format_), mode_)); if (tooltipEnabled()) { - label_.set_tooltip_text(mode_); + label_.set_tooltip_markup(mode_); } event_box_.show(); } diff --git a/src/modules/sway/window.cpp b/src/modules/sway/window.cpp index 68655a76..830a4120 100644 --- a/src/modules/sway/window.cpp +++ b/src/modules/sway/window.cpp @@ -99,7 +99,7 @@ auto Window::update() -> void { fmt::arg("shell", shell_), fmt::arg("marks", marks_)), config_["rewrite"])); if (tooltipEnabled()) { - label_.set_tooltip_text(window_); + label_.set_tooltip_markup(window_); } updateAppIcon(); diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index bb3005bd..2480a0e7 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -479,12 +479,12 @@ auto waybar::modules::Wireplumber::update() -> void { } if (!tooltipFormat.empty()) { - label_.set_tooltip_text(fmt::format( + label_.set_tooltip_markup(fmt::format( fmt::runtime(tooltipFormat), fmt::arg("node_name", node_name_), fmt::arg("volume", vol), fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source), fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_))); } else { - label_.set_tooltip_text(node_name_); + label_.set_tooltip_markup(node_name_); } } diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index d58303da..c6985b94 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -509,7 +509,7 @@ void Task::update() { if (markup) button.set_tooltip_markup(txt); else - button.set_tooltip_text(txt); + button.set_tooltip_markup(txt); } } From e0e36b6d8186b8fbae30c93d8937dc2be6c7865c Mon Sep 17 00:00:00 2001 From: Chris Vittal Date: Fri, 13 Feb 2026 14:17:48 -0500 Subject: [PATCH 546/780] memory: use markup rather than text for memory tooltip Seems a little strange to me that we can format the tooltip text, but can't use markup like we can elsewhere. --- src/modules/memory/common.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp index a83f6526..ff2c4fd2 100644 --- a/src/modules/memory/common.cpp +++ b/src/modules/memory/common.cpp @@ -69,7 +69,7 @@ auto waybar::modules::Memory::update() -> void { if (tooltipEnabled()) { if (config_["tooltip-format"].isString()) { auto tooltip_format = config_["tooltip-format"].asString(); - label_.set_tooltip_text(fmt::format( + label_.set_tooltip_markup(fmt::format( fmt::runtime(tooltip_format), used_ram_percentage, fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), fmt::arg("percentage", used_ram_percentage), From 5b595a4dfe805f9059bbf2a34c3be415b9ba82c6 Mon Sep 17 00:00:00 2001 From: vawvaw Date: Tue, 13 Feb 2024 17:55:11 +0100 Subject: [PATCH 547/780] hyprland/workspaces: Add `enable-bar-scroll` option --- include/modules/hyprland/workspaces.hpp | 4 +++ man/waybar-hyprland-workspaces.5.scd | 5 ++++ src/modules/hyprland/workspaces.cpp | 35 +++++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index a5d94bbf..8bf88888 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -43,6 +43,7 @@ class Workspaces : public AModule, public EventHandler { auto moveToMonitor() const -> bool { return m_moveToMonitor; } auto enableTaskbar() const -> bool { return m_enableTaskbar; } auto taskbarWithIcon() const -> bool { return m_taskbarWithIcon; } + auto barScroll() const -> bool { return m_barScroll; } auto getBarOutput() const -> std::string { return m_bar.output->name; } auto formatBefore() const -> std::string { return m_formatBefore; } @@ -122,6 +123,8 @@ class Workspaces : public AModule, public EventHandler { static std::pair splitDoublePayload(std::string const& payload); static std::tuple splitTriplePayload( std::string const& payload); + // scroll events + bool handleScroll(GdkEventScroll* e) override; // Update methods void doUpdate(); @@ -145,6 +148,7 @@ class Workspaces : public AModule, public EventHandler { bool m_specialVisibleOnly = false; bool m_persistentOnly = false; bool m_moveToMonitor = false; + bool m_barScroll = false; Json::Value m_persistentWorkspaceConfig; // Map for windows stored in workspaces not present in the current bar. diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index 1d04157b..5284ce99 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -130,6 +130,11 @@ This setting is ignored if *workspace-taskbar.enable* is set to true. Otherwise, the workspace will open on the monitor where it was previously assigned. Analog to using `focusworkspaceoncurrentmonitor` dispatcher instead of `workspace` in Hyprland. +*enable-bar-scroll*: ++ + typeof: bool ++ + default: false ++ + If set to false, you can't scroll to cycle throughout workspaces from the entire bar. If set to true this behaviour is enabled. + *ignore-workspaces*: ++ typeof: array ++ default: [] ++ diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 6fa38260..8765d78b 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -43,6 +43,13 @@ void Workspaces::init() { m_activeWorkspaceId = m_ipc.getSocket1JsonReply("activeworkspace")["id"].asInt(); initializeWorkspaces(); + + if (barScroll()) { + auto &window = const_cast(m_bar).window; + window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); + window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll)); + } + dp.emit(); } @@ -636,6 +643,7 @@ auto Workspaces::parseConfig(const Json::Value& config) -> void { populateBoolConfig(config, "persistent-only", m_persistentOnly); populateBoolConfig(config, "active-only", m_activeOnly); populateBoolConfig(config, "move-to-monitor", m_moveToMonitor); + populateBoolConfig(config, "enable-bar-scroll", m_barScroll); m_persistentWorkspaceConfig = config.get("persistent-workspaces", Json::Value()); populateSortByConfig(config); @@ -1151,4 +1159,31 @@ std::optional Workspaces::parseWorkspaceId(std::string const& workspaceIdSt } } +bool Workspaces::handleScroll(GdkEventScroll *e) { + // Ignore emulated scroll events on window + if (gdk_event_get_pointer_emulated((GdkEvent *)e)) { + return false; + } + auto dir = AModule::getScrollDir(e); + if (dir == SCROLL_DIR::NONE) { + return true; + } + + if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) { + if (allOutputs()) { + m_ipc.getSocket1Reply("dispatch workspace e+1"); + } else { + m_ipc.getSocket1Reply("dispatch workspace m+1"); + } + } else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) { + if (allOutputs()) { + m_ipc.getSocket1Reply("dispatch workspace e-1"); + } else { + m_ipc.getSocket1Reply("dispatch workspace m-1"); + } + } + + return true; +} + } // namespace waybar::modules::hyprland From 736c05335020d5e8165806d0bbcba9094021f91f Mon Sep 17 00:00:00 2001 From: eonphi <259135569+eonphi@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:37:35 +0100 Subject: [PATCH 548/780] feat(hyprland/workspaces): grouping icons --- include/modules/hyprland/workspaces.hpp | 4 ++ src/modules/hyprland/workspace.cpp | 51 ++++++++++++++++++++++--- src/modules/hyprland/workspaces.cpp | 10 +++++ 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 8bf88888..a240549a 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -61,6 +61,8 @@ class Workspaces : public AModule, public EventHandler { std::string getRewrite(std::string window_class, std::string window_title); std::string& getWindowSeparator() { return m_formatWindowSeparator; } + auto windowRewriteGroupThreshold() const -> int { return m_windowRewriteGroupThreshold; } + auto const& getWindowRewriteGroupFormat() const { return m_windowRewriteGroupFormat; } bool isWorkspaceIgnored(std::string const& workspace_name); bool windowRewriteConfigUsesTitle() const { return m_anyWindowRewriteRuleUsesTitle; } @@ -172,6 +174,8 @@ class Workspaces : public AModule, public EventHandler { util::RegexCollection m_windowRewriteRules; bool m_anyWindowRewriteRuleUsesTitle = false; std::string m_formatWindowSeparator; + int m_windowRewriteGroupThreshold = 0; + std::string m_windowRewriteGroupFormat = "{icon}×{count}"; bool m_withIcon; uint64_t m_monitorId; diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 87933ac0..01da287e 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -245,15 +246,53 @@ void Workspace::update(const std::string& workspace_icon) { // need to compute this if enableTaskbar() is true if (!m_workspaceManager.enableTaskbar()) { auto windowSeparator = m_workspaceManager.getWindowSeparator(); + auto groupThreshold = m_workspaceManager.windowRewriteGroupThreshold(); - bool isNotFirst = false; + if (groupThreshold > 0) { + // Build ordered counts of each unique icon (including singular ones when threshold set to 1) + std::vector> iconCounts; + for (const auto& window_repr : m_windowMap) { + auto it = std::ranges::find_if(iconCounts, [&](const auto& p) { + return p.first == window_repr.repr_rewrite; + }); + if (it != iconCounts.end()) { + it->second++; + } else { + iconCounts.emplace_back(window_repr.repr_rewrite, 1); + } + } - for (const auto& window_repr : m_windowMap) { - if (isNotFirst) { - windows.append(windowSeparator); + // Format the group string + auto groupFormat = m_workspaceManager.getWindowRewriteGroupFormat(); + bool isNotFirst = false; + for (const auto& [icon, count] : iconCounts) { + if (count >= groupThreshold) { + if (isNotFirst) windows.append(windowSeparator); + isNotFirst = true; + try { + windows.append(fmt::format(fmt::runtime(groupFormat), + fmt::arg("icon", icon), + fmt::arg("count", count))); + } catch (const fmt::format_error& e) { + spdlog::warn("Formatting window-rewrite-group-format error: {}", e.what()); + windows.append(icon); + } + } else { + for (int i = 0; i < count; ++i) { + if (isNotFirst) windows.append(windowSeparator); + isNotFirst = true; + windows.append(icon); + } + } + } + } else { + // Not grouping icons + bool isNotFirst = false; + for (const auto& window_repr : m_windowMap) { + if (isNotFirst) windows.append(windowSeparator); + isNotFirst = true; + windows.append(window_repr.repr_rewrite); } - isNotFirst = true; - windows.append(window_repr.repr_rewrite); } } diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 8765d78b..bec503e9 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -649,6 +649,16 @@ auto Workspaces::parseConfig(const Json::Value& config) -> void { populateSortByConfig(config); populateIgnoreWorkspacesConfig(config); populateFormatWindowSeparatorConfig(config); + + const auto& groupThreshold = config["window-rewrite-group-threshold"]; + if (groupThreshold.isInt()) { + m_windowRewriteGroupThreshold = groupThreshold.asInt(); + } + const auto& groupFormat = config["window-rewrite-group-format"]; + if (groupFormat.isString()) { + m_windowRewriteGroupFormat = groupFormat.asString(); + } + populateWindowRewriteConfig(config); if (withWindows) { From 6f9dee979b4c53bcaa8ac803e6b9400fc3ac852b Mon Sep 17 00:00:00 2001 From: Ollie Stone Date: Sun, 15 Feb 2026 18:39:28 +0000 Subject: [PATCH 549/780] fix: ensure AAppIconLabel image remains not visibile if icons are disabled --- src/AAppIconLabel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AAppIconLabel.cpp b/src/AAppIconLabel.cpp index a309a6e0..3d62d69d 100644 --- a/src/AAppIconLabel.cpp +++ b/src/AAppIconLabel.cpp @@ -151,7 +151,7 @@ void AAppIconLabel::updateAppIconName(const std::string& app_identifier, } void AAppIconLabel::updateAppIcon() { - if (update_app_icon_) { + if (update_app_icon_ || (!iconEnabled() && image_.get_visible())) { update_app_icon_ = false; if (app_icon_name_.empty()) { image_.set_visible(false); From f1140f00f9a9514e000d33ce8ce5211aec767cd1 Mon Sep 17 00:00:00 2001 From: cebem1nt Date: Tue, 17 Feb 2026 11:23:27 -0300 Subject: [PATCH 550/780] niri/workspaces: change icons priority --- src/modules/niri/workspaces.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/niri/workspaces.cpp b/src/modules/niri/workspaces.cpp index 8eb912bc..3e8a432e 100644 --- a/src/modules/niri/workspaces.cpp +++ b/src/modules/niri/workspaces.cpp @@ -174,11 +174,11 @@ std::string Workspaces::getIcon(const std::string& value, const Json::Value& ws) if (ws["is_urgent"].asBool() && icons["urgent"]) return icons["urgent"].asString(); - if (ws["active_window_id"].isNull() && icons["empty"]) return icons["empty"].asString(); + if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString(); if (ws["is_focused"].asBool() && icons["focused"]) return icons["focused"].asString(); - if (ws["is_active"].asBool() && icons["active"]) return icons["active"].asString(); + if (ws["active_window_id"].isNull() && icons["empty"]) return icons["empty"].asString(); if (ws["name"]) { const auto& name = ws["name"].asString(); From 9c871c90a7306212669c6c0a17f581d9027b97ba Mon Sep 17 00:00:00 2001 From: GG2R10 Date: Thu, 19 Feb 2026 15:12:23 -0500 Subject: [PATCH 551/780] fix: move silence_ = false inside idle lambda --- src/modules/cava/cavaRaw.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/cava/cavaRaw.cpp b/src/modules/cava/cavaRaw.cpp index d6a9e001..2b0aca72 100644 --- a/src/modules/cava/cavaRaw.cpp +++ b/src/modules/cava/cavaRaw.cpp @@ -26,6 +26,7 @@ void waybar::modules::cava::Cava::pause_resume() { backend_->doPauseResume(); } auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void { Glib::signal_idle().connect_once([this, input]() { if (silence_) { + silence_ = false; label_.get_style_context()->remove_class("silent"); if (!label_.get_style_context()->has_class("updated")) label_.get_style_context()->add_class("updated"); @@ -38,7 +39,6 @@ auto waybar::modules::cava::Cava::onUpdate(const std::string& input) -> void { label_.show(); ALabel::update(); }); - silence_ = false; } auto waybar::modules::cava::Cava::onSilence() -> void { From a194b576be36423c64618148a6f789dd4362e9c1 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 9 Feb 2026 08:52:00 -0600 Subject: [PATCH 552/780] feat(niri): niri depends on socket Don't attempt to use niri modules when socket connection fails. Prevents rendering modules when running another compositor. In same concept as previous Hyprland change https://github.com/Alexays/Waybar/commit/4295faa7c4b23b7f6e86669d1fe8c93562e10241 Signed-off-by: Austin Horstman --- src/modules/niri/backend.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/modules/niri/backend.cpp b/src/modules/niri/backend.cpp index 1ee1bf3f..5284e0be 100644 --- a/src/modules/niri/backend.cpp +++ b/src/modules/niri/backend.cpp @@ -24,8 +24,7 @@ int IPC::connectToSocket() { const char* socket_path = getenv("NIRI_SOCKET"); if (socket_path == nullptr) { - spdlog::warn("Niri is not running, niri IPC will not be available."); - return -1; + throw std::runtime_error("Niri IPC: NIRI_SOCKET was not set! (Is Niri running?)"); } struct sockaddr_un addr; @@ -54,15 +53,9 @@ int IPC::connectToSocket() { void IPC::startIPC() { // will start IPC and relay events to parseIPC - std::thread([&]() { - int socketfd; - try { - socketfd = connectToSocket(); - } catch (std::exception& e) { - spdlog::error("Niri IPC: failed to start, reason: {}", e.what()); - return; - } - if (socketfd == -1) return; + int socketfd = connectToSocket(); + + std::thread([this, socketfd]() { spdlog::info("Niri IPC starting"); @@ -242,7 +235,6 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) { Json::Value IPC::send(const Json::Value& request) { int socketfd = connectToSocket(); - if (socketfd == -1) throw std::runtime_error("Niri is not running"); auto unix_istream = Gio::UnixInputStream::create(socketfd, true); auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false); From ae60ca62331edeba08d75ef68d0c6c5fe53c9c79 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 9 Feb 2026 08:53:04 -0600 Subject: [PATCH 553/780] refactor(niri): declared constructor Move constructor from hpp to cpp to align with other modules Signed-off-by: Austin Horstman --- include/modules/niri/backend.hpp | 2 +- src/modules/niri/backend.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/modules/niri/backend.hpp b/include/modules/niri/backend.hpp index 42b9ff7f..07be039a 100644 --- a/include/modules/niri/backend.hpp +++ b/include/modules/niri/backend.hpp @@ -17,7 +17,7 @@ class EventHandler { class IPC { public: - IPC() { startIPC(); } + IPC(); void registerForIPC(const std::string& ev, EventHandler* ev_handler); void unregisterForIPC(EventHandler* handler); diff --git a/src/modules/niri/backend.cpp b/src/modules/niri/backend.cpp index 5284e0be..ce96c51c 100644 --- a/src/modules/niri/backend.cpp +++ b/src/modules/niri/backend.cpp @@ -20,6 +20,8 @@ namespace waybar::modules::niri { +IPC::IPC() { startIPC(); } + int IPC::connectToSocket() { const char* socket_path = getenv("NIRI_SOCKET"); From 55fa8cf185e64041dfaea09048c46a4479acc359 Mon Sep 17 00:00:00 2001 From: BBaoVanC Date: Mon, 2 Feb 2026 14:03:51 -0600 Subject: [PATCH 554/780] Make CssReloadHelper watch CSS new style on appearance change If you have reload_style_on_change enabled, and use dynamic appearance styling (style-dark.css and style-light.css), then changing the appearance doesn't update the files that are being watched for reload. Add a method to CssReloadHelper to change the CSS file being watched, and also provide the CSS file as a parameter to the callback so setupCss can be called on the right file when the file watcher triggers. --- include/util/css_reload_helper.hpp | 6 ++++-- src/client.cpp | 3 ++- src/util/css_reload_helper.cpp | 11 +++++++++-- test/utils/css_reload_helper.cpp | 2 +- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/include/util/css_reload_helper.hpp b/include/util/css_reload_helper.hpp index 032b2382..e198a5b0 100644 --- a/include/util/css_reload_helper.hpp +++ b/include/util/css_reload_helper.hpp @@ -14,12 +14,14 @@ struct pollfd; namespace waybar { class CssReloadHelper { public: - CssReloadHelper(std::string cssFile, std::function callback); + CssReloadHelper(std::string cssFile, std::function callback); virtual ~CssReloadHelper() = default; virtual void monitorChanges(); + virtual void changeCssFile(const std::string& newCssFile); + protected: std::vector parseImports(const std::string& cssFile); @@ -42,7 +44,7 @@ class CssReloadHelper { private: std::string m_cssFile; - std::function m_callback; + std::function m_callback; std::vector>> m_fileMonitors; }; diff --git a/src/client.cpp b/src/client.cpp index 103b093f..ce178f0a 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -289,9 +289,10 @@ int waybar::Client::main(int argc, char* argv[]) { } m_cssFile = getStyle(style_opt); setupCss(m_cssFile); - m_cssReloadHelper = std::make_unique(m_cssFile, [&]() { setupCss(m_cssFile); }); + m_cssReloadHelper = std::make_unique(m_cssFile, [&](const std::string& css_file) { setupCss(css_file); }); portal->signal_appearance_changed().connect([&](waybar::Appearance appearance) { auto css_file = getStyle(style_opt, appearance); + m_cssReloadHelper->changeCssFile(css_file); setupCss(css_file); }); diff --git a/src/util/css_reload_helper.cpp b/src/util/css_reload_helper.cpp index 06381d60..dc686a4c 100644 --- a/src/util/css_reload_helper.cpp +++ b/src/util/css_reload_helper.cpp @@ -23,7 +23,8 @@ namespace { const std::regex IMPORT_REGEX(R"(@import\s+(?:url\()?(?:"|')([^"')]+)(?:"|')\)?;)"); } -waybar::CssReloadHelper::CssReloadHelper(std::string cssFile, std::function callback) +waybar::CssReloadHelper::CssReloadHelper(std::string cssFile, + std::function callback) : m_cssFile(std::move(cssFile)), m_callback(std::move(callback)) {} std::string waybar::CssReloadHelper::getFileContents(const std::string& filename) { @@ -88,6 +89,12 @@ void waybar::CssReloadHelper::monitorChanges() { } } +void waybar::CssReloadHelper::changeCssFile(const std::string& newCssFile) { + m_fileMonitors.clear(); + m_cssFile = newCssFile; + monitorChanges(); +} + void waybar::CssReloadHelper::handleFileChange(Glib::RefPtr const& file, Glib::RefPtr const& other_type, Gio::FileMonitorEvent event_type) { @@ -95,7 +102,7 @@ void waybar::CssReloadHelper::handleFileChange(Glib::RefPtr const& fi // fire for one if (event_type == Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { spdlog::debug("Reloading style, file changed: {}", file->get_path()); - m_callback(); + m_callback(m_cssFile); } } diff --git a/test/utils/css_reload_helper.cpp b/test/utils/css_reload_helper.cpp index f3888a83..0ba0bde4 100644 --- a/test/utils/css_reload_helper.cpp +++ b/test/utils/css_reload_helper.cpp @@ -10,7 +10,7 @@ class CssReloadHelperTest : public waybar::CssReloadHelper { public: - CssReloadHelperTest() : CssReloadHelper("/tmp/waybar_test.css", [this]() { callback(); }) {} + CssReloadHelperTest() : CssReloadHelper("/tmp/waybar_test.css", [this](const std::string&) { callback(); }) {} void callback() { m_callbackCounter++; } From f806ec03ed8412f79889de45879ace24016e3313 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 22:51:45 +0000 Subject: [PATCH 555/780] Initial plan From 65fadcf94b0df851eb43200b7efb04aead3e87d6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 22:52:00 +0000 Subject: [PATCH 556/780] Initial plan From 37d65415920b1ca3132de5b1fd01c143b150a830 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 22:53:14 +0000 Subject: [PATCH 557/780] Initial plan From 0c46818e95fb694394f5c06e3965c18d377cacd6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 22:54:23 +0000 Subject: [PATCH 558/780] Fix crash when bluetooth DBus manager returns null on timeout Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- src/modules/bluetooth.cpp | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp index f491f19b..c59af3b5 100644 --- a/src/modules/bluetooth.cpp +++ b/src/modules/bluetooth.cpp @@ -111,13 +111,16 @@ waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& findConnectedDevices(cur_controller_->path, connected_devices_); } - g_signal_connect(manager_.get(), "object-added", G_CALLBACK(onObjectAdded), this); - g_signal_connect(manager_.get(), "object-removed", G_CALLBACK(onObjectRemoved), this); - g_signal_connect(manager_.get(), "interface-proxy-properties-changed", - G_CALLBACK(onInterfaceProxyPropertiesChanged), this); - g_signal_connect(manager_.get(), "interface-added", G_CALLBACK(onInterfaceAddedOrRemoved), this); - g_signal_connect(manager_.get(), "interface-removed", G_CALLBACK(onInterfaceAddedOrRemoved), - this); + if (manager_) { + g_signal_connect(manager_.get(), "object-added", G_CALLBACK(onObjectAdded), this); + g_signal_connect(manager_.get(), "object-removed", G_CALLBACK(onObjectRemoved), this); + g_signal_connect(manager_.get(), "interface-proxy-properties-changed", + G_CALLBACK(onInterfaceProxyPropertiesChanged), this); + g_signal_connect(manager_.get(), "interface-added", G_CALLBACK(onInterfaceAddedOrRemoved), + this); + g_signal_connect(manager_.get(), "interface-removed", G_CALLBACK(onInterfaceAddedOrRemoved), + this); + } #ifdef WANT_RFKILL rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Bluetooth::update))); @@ -446,6 +449,10 @@ auto waybar::modules::Bluetooth::getControllerProperties(GDBusObject* object, auto waybar::modules::Bluetooth::findCurController() -> std::optional { std::optional controller_info; + if (!manager_) { + return controller_info; + } + GList* objects = g_dbus_object_manager_get_objects(manager_.get()); for (GList* l = objects; l != NULL; l = l->next) { GDBusObject* object = G_DBUS_OBJECT(l->data); @@ -465,6 +472,9 @@ auto waybar::modules::Bluetooth::findCurController() -> std::optional& connected_devices) -> void { + if (!manager_) { + return; + } GList* objects = g_dbus_object_manager_get_objects(manager_.get()); for (GList* l = objects; l != NULL; l = l->next) { GDBusObject* object = G_DBUS_OBJECT(l->data); From 49d4049ea3f347f207abafb03eea0bcd5f698838 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 22:55:05 +0000 Subject: [PATCH 559/780] Fix SIGABRT on USB unplug race condition in battery module Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- src/modules/battery.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 58a7f50e..bdf29ffb 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -139,7 +139,8 @@ void waybar::modules::Battery::refreshBatteries() { auto event_path = (node.path() / "uevent"); auto wd = inotify_add_watch(battery_watch_fd_, event_path.c_str(), IN_ACCESS); if (wd < 0) { - throw std::runtime_error("Could not watch events for " + node.path().string()); + spdlog::warn("Could not watch events for {} (device may have been removed)", node.path().string()); + continue; } batteries_[node.path()] = wd; } From e18939210bff31c44f7d6e7d56de2b7f9c3b4a68 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 24 Feb 2026 00:00:57 +0100 Subject: [PATCH 560/780] fix: lint --- include/modules/sway/ipc/client.hpp | 2 +- src/modules/battery.cpp | 3 ++- src/modules/hyprland/workspaces.cpp | 6 +++--- src/modules/niri/backend.cpp | 1 - 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/modules/sway/ipc/client.hpp b/include/modules/sway/ipc/client.hpp index c5178c2b..281df7ab 100644 --- a/include/modules/sway/ipc/client.hpp +++ b/include/modules/sway/ipc/client.hpp @@ -12,8 +12,8 @@ #include #include "ipc.hpp" -#include "util/sleeper_thread.hpp" #include "util/SafeSignal.hpp" +#include "util/sleeper_thread.hpp" namespace waybar::modules::sway { diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index bdf29ffb..d39c4920 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -139,7 +139,8 @@ void waybar::modules::Battery::refreshBatteries() { auto event_path = (node.path() / "uevent"); auto wd = inotify_add_watch(battery_watch_fd_, event_path.c_str(), IN_ACCESS); if (wd < 0) { - spdlog::warn("Could not watch events for {} (device may have been removed)", node.path().string()); + spdlog::warn("Could not watch events for {} (device may have been removed)", + node.path().string()); continue; } batteries_[node.path()] = wd; diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 8765d78b..88b01223 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -45,7 +45,7 @@ void Workspaces::init() { initializeWorkspaces(); if (barScroll()) { - auto &window = const_cast(m_bar).window; + auto& window = const_cast(m_bar).window; window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll)); } @@ -1159,9 +1159,9 @@ std::optional Workspaces::parseWorkspaceId(std::string const& workspaceIdSt } } -bool Workspaces::handleScroll(GdkEventScroll *e) { +bool Workspaces::handleScroll(GdkEventScroll* e) { // Ignore emulated scroll events on window - if (gdk_event_get_pointer_emulated((GdkEvent *)e)) { + if (gdk_event_get_pointer_emulated((GdkEvent*)e)) { return false; } auto dir = AModule::getScrollDir(e); diff --git a/src/modules/niri/backend.cpp b/src/modules/niri/backend.cpp index ce96c51c..68bb1724 100644 --- a/src/modules/niri/backend.cpp +++ b/src/modules/niri/backend.cpp @@ -58,7 +58,6 @@ void IPC::startIPC() { int socketfd = connectToSocket(); std::thread([this, socketfd]() { - spdlog::info("Niri IPC starting"); auto unix_istream = Gio::UnixInputStream::create(socketfd, true); From 82f076c6c200d0d7e29b0bc4cfd0a8955f3d1ead Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 23:04:36 +0000 Subject: [PATCH 561/780] mpd: fix FD leak by resetting connection on MPD_ERROR_SYSTEM errors Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- src/modules/mpd/mpd.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index f0cc5757..1e28a58a 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -323,6 +323,7 @@ void waybar::modules::MPD::checkErrors(mpd_connection* conn) { case MPD_ERROR_SYSTEM: if (auto ec = mpd_connection_get_system_error(conn); ec != 0) { mpd_connection_clear_error(conn); + connection_.reset(); throw std::system_error(ec, std::system_category()); } G_GNUC_FALLTHROUGH; From ef3d55980ed3d38989a6576af4d0c7b83adf00e6 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 24 Feb 2026 00:49:03 +0100 Subject: [PATCH 562/780] fix: some crashes --- include/AModule.hpp | 2 +- include/modules/wayfire/backend.hpp | 4 ++-- include/util/command.hpp | 1 + src/AAppIconLabel.cpp | 18 +++++++++------ src/ALabel.cpp | 16 ++++++++++--- src/AModule.cpp | 4 ++-- src/main.cpp | 14 +++++++----- src/modules/cava/cava_backend.cpp | 2 +- src/modules/cffi.cpp | 2 +- src/modules/cpu.cpp | 10 +++++---- src/modules/cpu_usage/common.cpp | 14 +++++++----- src/modules/disk.cpp | 2 +- src/modules/dwl/tags.cpp | 1 + src/modules/ext/workspace_manager.cpp | 4 +++- src/modules/gamemode.cpp | 6 ++--- src/modules/jack.cpp | 5 ++++- src/modules/mpris/mpris.cpp | 8 +++++-- src/modules/network.cpp | 1 + src/modules/power_profiles_daemon.cpp | 4 ++++ src/modules/river/layout.cpp | 1 + src/modules/river/mode.cpp | 1 + src/modules/river/tags.cpp | 3 +++ src/modules/river/window.cpp | 1 + src/modules/sndio.cpp | 4 +++- src/modules/sni/host.cpp | 4 ++-- src/modules/sni/item.cpp | 3 ++- src/modules/sni/watcher.cpp | 2 +- src/modules/systemd_failed_units.cpp | 8 +++---- src/modules/upower.cpp | 10 +++++---- src/modules/wayfire/backend.cpp | 31 +++++++++++++++++++------- src/modules/wayfire/window.cpp | 8 +++++-- src/modules/wayfire/workspaces.cpp | 16 +++++++++---- src/modules/wireplumber.cpp | 15 +++++++------ src/modules/wlr/taskbar.cpp | 4 +++- src/util/audio_backend.cpp | 13 +++++++---- src/util/backlight_backend.cpp | 20 ++++++++++++----- src/util/css_reload_helper.cpp | 6 ++++- src/util/icon_loader.cpp | 9 +++++--- src/util/pipewire/pipewire_backend.cpp | 11 ++++++++- src/util/portal.cpp | 31 +++++++++++++++----------- 40 files changed, 216 insertions(+), 103 deletions(-) diff --git a/include/AModule.hpp b/include/AModule.hpp index 6b29045c..a338ffe3 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -48,7 +48,7 @@ class AModule : public IModule { virtual bool handleMouseLeave(GdkEventCrossing* const& ev); virtual bool handleScroll(GdkEventScroll*); virtual bool handleRelease(GdkEventButton* const& ev); - GObject* menu_; + GObject* menu_ = nullptr; private: bool handleUserEvent(GdkEventButton* const& ev); diff --git a/include/modules/wayfire/backend.hpp b/include/modules/wayfire/backend.hpp index 9d55c820..d3173269 100644 --- a/include/modules/wayfire/backend.hpp +++ b/include/modules/wayfire/backend.hpp @@ -89,7 +89,7 @@ struct Sock { } }; -class IPC { +class IPC : public std::enable_shared_from_this { static std::weak_ptr instance; Json::CharReaderBuilder reader_builder; Json::StreamWriterBuilder writer_builder; @@ -98,7 +98,7 @@ class IPC { State state; std::mutex state_mutex; - IPC() { start(); } + IPC() = default; static auto connect() -> Sock; auto receive(Sock& sock) -> Json::Value; diff --git a/include/util/command.hpp b/include/util/command.hpp index 89d4372a..b1adcd7c 100644 --- a/include/util/command.hpp +++ b/include/util/command.hpp @@ -59,6 +59,7 @@ inline int close(FILE* fp, pid_t pid) { spdlog::debug("Cmd continued"); } else if (ret == -1) { spdlog::debug("waitpid failed: {}", strerror(errno)); + break; } else { break; } diff --git a/src/AAppIconLabel.cpp b/src/AAppIconLabel.cpp index 3d62d69d..55de78f5 100644 --- a/src/AAppIconLabel.cpp +++ b/src/AAppIconLabel.cpp @@ -156,14 +156,18 @@ void AAppIconLabel::updateAppIcon() { if (app_icon_name_.empty()) { image_.set_visible(false); } else if (app_icon_name_.front() == '/') { - auto pixbuf = Gdk::Pixbuf::create_from_file(app_icon_name_); - int scaled_icon_size = app_icon_size_ * image_.get_scale_factor(); - pixbuf = Gdk::Pixbuf::create_from_file(app_icon_name_, scaled_icon_size, scaled_icon_size); + try { + int scaled_icon_size = app_icon_size_ * image_.get_scale_factor(); + auto pixbuf = Gdk::Pixbuf::create_from_file(app_icon_name_, scaled_icon_size, scaled_icon_size); - auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), - image_.get_window()); - image_.set(surface); - image_.set_visible(true); + auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), + image_.get_window()); + image_.set(surface); + image_.set_visible(true); + } catch (const Glib::Exception& e) { + spdlog::warn("Failed to load app icon {}: {}", app_icon_name_, std::string(e.what())); + image_.set_visible(false); + } } else { image_.set_from_icon_name(app_icon_name_, Gtk::ICON_SIZE_INVALID); image_.set_visible(true); diff --git a/src/ALabel.cpp b/src/ALabel.cpp index d251d896..0d92c372 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -91,11 +91,13 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st // Make the GtkBuilder and check for errors in his parsing if (gtk_builder_add_from_string(builder, fileContent.str().c_str(), -1, nullptr) == 0U) { + g_object_unref(builder); throw std::runtime_error("Error found in the file " + menuFile); } menu_ = gtk_builder_get_object(builder, "menu"); if (menu_ == nullptr) { + g_object_unref(builder); throw std::runtime_error("Failed to get 'menu' object from GtkBuilder"); } submenus_ = std::map(); @@ -105,11 +107,17 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st for (Json::Value::const_iterator it = config_["menu-actions"].begin(); it != config_["menu-actions"].end(); ++it) { std::string key = it.key().asString(); - submenus_[key] = GTK_MENU_ITEM(gtk_builder_get_object(builder, key.c_str())); + auto* item = gtk_builder_get_object(builder, key.c_str()); + if (item == nullptr) { + spdlog::warn("Menu item '{}' not found in builder file", key); + continue; + } + submenus_[key] = GTK_MENU_ITEM(item); menuActionsMap_[key] = it->asString(); g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent), (gpointer)menuActionsMap_[key].c_str()); } + g_object_unref(builder); } catch (std::runtime_error& e) { spdlog::warn("Error while creating the menu : {}. Menu popup not activated.", e.what()); } @@ -141,7 +149,8 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_ if (format_icons.isArray()) { auto size = format_icons.size(); if (size != 0U) { - auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); + auto divisor = std::max(1U, (max == 0 ? 100U : static_cast(max)) / size); + auto idx = std::clamp(percentage / divisor, 0U, size - 1); format_icons = format_icons[idx]; } } @@ -167,7 +176,8 @@ std::string ALabel::getIcon(uint16_t percentage, const std::vector& if (format_icons.isArray()) { auto size = format_icons.size(); if (size != 0U) { - auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); + auto divisor = std::max(1U, (max == 0 ? 100U : static_cast(max)) / size); + auto idx = std::clamp(percentage / divisor, 0U, size - 1); format_icons = format_icons[idx]; } } diff --git a/src/AModule.cpp b/src/AModule.cpp index c6fdff3e..5f3a187a 100644 --- a/src/AModule.cpp +++ b/src/AModule.cpp @@ -166,9 +166,9 @@ bool AModule::handleUserEvent(GdkEventButton* const& e) { } // Check that a menu has been configured - if (config_["menu"].isString()) { + if (rec != eventMap_.cend() && config_["menu"].isString()) { // Check if the event is the one specified for the "menu" option - if (rec->second == config_["menu"].asString()) { + if (rec->second == config_["menu"].asString() && menu_ != nullptr) { // Popup the menu gtk_widget_show_all(GTK_WIDGET(menu_)); gtk_menu_popup_at_pointer(GTK_MENU(menu_), reinterpret_cast(e)); diff --git a/src/main.cpp b/src/main.cpp index 021f8e1a..166dd2bb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,7 +33,10 @@ static void writeSignalToPipe(int signum) { // to `signal_handler`. static void catchSignals(waybar::SafeSignal& signal_handler) { int fd[2]; - pipe(fd); + if (pipe(fd) != 0) { + spdlog::error("Failed to create signal pipe: {}", strerror(errno)); + return; + } int signal_pipe_read_fd = fd[0]; signal_pipe_write_fd = fd[1]; @@ -137,15 +140,16 @@ static void handleSignalMainThread(int signum, bool& reload) { break; case SIGCHLD: spdlog::debug("Received SIGCHLD in signalThread"); - if (!reap.empty()) { - reap_mtx.lock(); - for (auto it = reap.begin(); it != reap.end(); ++it) { + { + std::lock_guard lock(reap_mtx); + for (auto it = reap.begin(); it != reap.end(); ) { if (waitpid(*it, nullptr, WNOHANG) == *it) { spdlog::debug("Reaped child with PID: {}", *it); it = reap.erase(it); + } else { + ++it; } } - reap_mtx.unlock(); } break; default: diff --git a/src/modules/cava/cava_backend.cpp b/src/modules/cava/cava_backend.cpp index c576f0cf..6eb540c9 100644 --- a/src/modules/cava/cava_backend.cpp +++ b/src/modules/cava/cava_backend.cpp @@ -218,7 +218,7 @@ void waybar::modules::cava::CavaBackend::loadConfig() { prm_.input = ::cava::input_method_by_name(config_["method"].asString().c_str()); if (config_["source"].isString()) { if (prm_.audio_source) free(prm_.audio_source); - prm_.audio_source = config_["source"].asString().data(); + prm_.audio_source = strdup(config_["source"].asString().c_str()); } if (config_["sample_rate"].isNumeric()) prm_.samplerate = config_["sample_rate"].asLargestInt(); if (config_["sample_bits"].isInt()) prm_.samplebits = config_["sample_bits"].asInt(); diff --git a/src/modules/cffi.cpp b/src/modules/cffi.cpp index 5c095f46..930c4d47 100644 --- a/src/modules/cffi.cpp +++ b/src/modules/cffi.cpp @@ -35,7 +35,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co throw std::runtime_error{std::string{"Missing wbcffi_init function: "} + dlerror()}; } hooks_.deinit = reinterpret_cast(dlsym(handle, "wbcffi_deinit")); - if (!hooks_.init) { + if (!hooks_.deinit) { throw std::runtime_error{std::string{"Missing wbcffi_deinit function: "} + dlerror()}; } // Optional functions diff --git a/src/modules/cpu.cpp b/src/modules/cpu.cpp index ab75b911..8cfda2c1 100644 --- a/src/modules/cpu.cpp +++ b/src/modules/cpu.cpp @@ -46,12 +46,14 @@ auto waybar::modules::Cpu::update() -> void { store.push_back(fmt::arg("max_frequency", max_frequency)); store.push_back(fmt::arg("min_frequency", min_frequency)); store.push_back(fmt::arg("avg_frequency", avg_frequency)); + std::vector arg_names; + arg_names.reserve(cpu_usage.size() * 2); for (size_t i = 1; i < cpu_usage.size(); ++i) { auto core_i = i - 1; - auto core_format = fmt::format("usage{}", core_i); - store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i])); - auto icon_format = fmt::format("icon{}", core_i); - store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); + arg_names.push_back(fmt::format("usage{}", core_i)); + store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i])); + arg_names.push_back(fmt::format("icon{}", core_i)); + store.push_back(fmt::arg(arg_names.back().c_str(), getIcon(cpu_usage[i], icons))); } label_.set_markup(fmt::vformat(format, store)); diff --git a/src/modules/cpu_usage/common.cpp b/src/modules/cpu_usage/common.cpp index a2bb4596..82126dac 100644 --- a/src/modules/cpu_usage/common.cpp +++ b/src/modules/cpu_usage/common.cpp @@ -36,12 +36,14 @@ auto waybar::modules::CpuUsage::update() -> void { fmt::dynamic_format_arg_store store; store.push_back(fmt::arg("usage", total_usage)); store.push_back(fmt::arg("icon", getIcon(total_usage, icons))); + std::vector arg_names; + arg_names.reserve(cpu_usage.size() * 2); for (size_t i = 1; i < cpu_usage.size(); ++i) { auto core_i = i - 1; - auto core_format = fmt::format("usage{}", core_i); - store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i])); - auto icon_format = fmt::format("icon{}", core_i); - store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); + arg_names.push_back(fmt::format("usage{}", core_i)); + store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i])); + arg_names.push_back(fmt::format("icon{}", core_i)); + store.push_back(fmt::arg(arg_names.back().c_str(), getIcon(cpu_usage[i], icons))); } label_.set_markup(fmt::vformat(format, store)); @@ -78,7 +80,7 @@ std::tuple, std::string> waybar::modules::CpuUsage::getCpu auto [prev_idle, prev_total] = prev_times[0]; const float delta_idle = curr_idle - prev_idle; const float delta_total = curr_total - prev_total; - uint16_t tmp = 100 * (1 - delta_idle / delta_total); + uint16_t tmp = (delta_total > 0) ? static_cast(100 * (1 - delta_idle / delta_total)) : 0; tooltip = fmt::format("Total: {}%\nCores: (pending)", tmp); usage.push_back(tmp); } else { @@ -100,7 +102,7 @@ std::tuple, std::string> waybar::modules::CpuUsage::getCpu } const float delta_idle = curr_idle - prev_idle; const float delta_total = curr_total - prev_total; - uint16_t tmp = 100 * (1 - delta_idle / delta_total); + uint16_t tmp = (delta_total > 0) ? static_cast(100 * (1 - delta_idle / delta_total)) : 0; if (i == 0) { tooltip = fmt::format("Total: {}%", tmp); } else { diff --git a/src/modules/disk.cpp b/src/modules/disk.cpp index 39703547..fd7ef817 100644 --- a/src/modules/disk.cpp +++ b/src/modules/disk.cpp @@ -41,7 +41,7 @@ auto waybar::modules::Disk::update() -> void { fs_used - File system used space */ - if (err != 0) { + if (err != 0 || stats.f_blocks == 0) { event_box_.hide(); return; } diff --git a/src/modules/dwl/tags.cpp b/src/modules/dwl/tags.cpp index fb065650..399c7d4b 100644 --- a/src/modules/dwl/tags.cpp +++ b/src/modules/dwl/tags.cpp @@ -179,6 +179,7 @@ bool Tags::handle_button_press(GdkEventButton* event_button, uint32_t tag) { } void Tags::handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint32_t focused) { + if (tag >= buttons_.size()) return; // First clear all occupied state auto& button = buttons_[tag]; if (clients) { diff --git a/src/modules/ext/workspace_manager.cpp b/src/modules/ext/workspace_manager.cpp index 637177d3..b4471c14 100644 --- a/src/modules/ext/workspace_manager.cpp +++ b/src/modules/ext/workspace_manager.cpp @@ -143,7 +143,9 @@ void WorkspaceManager::handle_finished() { ext_manager_ = nullptr; } -void WorkspaceManager::commit() const { ext_workspace_manager_v1_commit(ext_manager_); } +void WorkspaceManager::commit() const { + if (ext_manager_) ext_workspace_manager_v1_commit(ext_manager_); +} void WorkspaceManager::update() { spdlog::debug("[ext/workspaces]: Updating state"); diff --git a/src/modules/gamemode.cpp b/src/modules/gamemode.cpp index 8c33fcf3..1c846434 100644 --- a/src/modules/gamemode.cpp +++ b/src/modules/gamemode.cpp @@ -128,9 +128,9 @@ void Gamemode::getData() { Glib::VariantContainerBase data = gamemode_proxy->call_sync("Get", parameters); if (data && data.is_of_type(Glib::VariantType("(v)"))) { Glib::VariantBase variant; - g_variant_get(data.gobj_copy(), "(v)", &variant); + g_variant_get(data.gobj(), "(v)", &variant); if (variant && variant.is_of_type(Glib::VARIANT_TYPE_INT32)) { - g_variant_get(variant.gobj_copy(), "i", &gameCount); + g_variant_get(variant.gobj(), "i", &gameCount); return; } } @@ -158,7 +158,7 @@ void Gamemode::prepareForSleep_cb(const Glib::RefPtr& con const Glib::VariantContainerBase& parameters) { if (parameters.is_of_type(Glib::VariantType("(b)"))) { gboolean sleeping; - g_variant_get(parameters.gobj_copy(), "(b)", &sleeping); + g_variant_get(parameters.gobj(), "(b)", &sleeping); if (!sleeping) { getData(); dp.emit(); diff --git a/src/modules/jack.cpp b/src/modules/jack.cpp index 8e5a75a8..578fb4e0 100644 --- a/src/modules/jack.cpp +++ b/src/modules/jack.cpp @@ -53,7 +53,7 @@ std::string JACK::JACKState() { auto JACK::update() -> void { std::string format; std::string state = JACKState(); - float latency = 1000 * (float)bufsize_ / (float)samplerate_; + float latency = samplerate_ > 0 ? 1000.0f * (float)bufsize_ / (float)samplerate_ : 0.0f; if (label_.get_style_context()->has_class("xrun")) { label_.get_style_context()->remove_class("xrun"); @@ -91,16 +91,19 @@ auto JACK::update() -> void { } int JACK::bufSize(jack_nframes_t size) { + std::lock_guard lock(mutex_); bufsize_ = size; return 0; } int JACK::sampleRate(jack_nframes_t rate) { + std::lock_guard lock(mutex_); samplerate_ = rate; return 0; } int JACK::xrun() { + std::lock_guard lock(mutex_); xruns_ += 1; state_ = "xrun"; return 0; diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 1be21436..1bdd7df6 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -117,7 +117,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) } GError* error = nullptr; - waybar::util::ScopeGuard error_deleter([error]() { + waybar::util::ScopeGuard error_deleter([&error]() { if (error) { g_error_free(error); } @@ -478,7 +478,7 @@ auto Mpris::getPlayerInfo() -> std::optional { } GError* error = nullptr; - waybar::util::ScopeGuard error_deleter([error]() { + waybar::util::ScopeGuard error_deleter([&error]() { if (error) { g_error_free(error); } @@ -543,6 +543,10 @@ auto Mpris::getPlayerInfo() -> std::optional { g_object_get(last_active_player_, "status", &player_status, "playback-status", &player_playback_status, NULL); + if (!player_status) { + spdlog::error("mpris: failed to get player status"); + return std::nullopt; + } // make status lowercase player_status[0] = std::tolower(player_status[0]); diff --git a/src/modules/network.cpp b/src/modules/network.cpp index c33e750d..0dbdcc57 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -46,6 +46,7 @@ waybar::modules::Network::readBandwidthUsage() { std::string ifacename; iss >> ifacename; // ifacename contains "eth0:" + if (ifacename.empty()) continue; ifacename.pop_back(); // remove trailing ':' if (ifacename != ifname_) { continue; diff --git a/src/modules/power_profiles_daemon.cpp b/src/modules/power_profiles_daemon.cpp index 60569521..566787f3 100644 --- a/src/modules/power_profiles_daemon.cpp +++ b/src/modules/power_profiles_daemon.cpp @@ -176,6 +176,10 @@ auto PowerProfilesDaemon::update() -> void { bool PowerProfilesDaemon::handleToggle(GdkEventButton* const& e) { if (e->type == GdkEventType::GDK_BUTTON_PRESS && connected_) { + if (availableProfiles_.empty()) return true; + if (activeProfile_ == availableProfiles_.end()) { + activeProfile_ = availableProfiles_.begin(); + } if (e->button == 1) /* left click */ { activeProfile_++; if (activeProfile_ == availableProfiles_.end()) { diff --git a/src/modules/river/layout.cpp b/src/modules/river/layout.cpp index 31358f80..3f1745fe 100644 --- a/src/modules/river/layout.cpp +++ b/src/modules/river/layout.cpp @@ -118,6 +118,7 @@ Layout::Layout(const std::string& id, const waybar::Bar& bar, const Json::Value& if (!seat_) { spdlog::error("wl_seat not advertised"); + return; } label_.hide(); diff --git a/src/modules/river/mode.cpp b/src/modules/river/mode.cpp index 10f5089e..13545030 100644 --- a/src/modules/river/mode.cpp +++ b/src/modules/river/mode.cpp @@ -77,6 +77,7 @@ Mode::Mode(const std::string& id, const waybar::Bar& bar, const Json::Value& con if (!seat_) { spdlog::error("wl_seat not advertised"); + return; } label_.hide(); diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index fc4d98f9..f06565f6 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -101,10 +101,12 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con if (!control_) { spdlog::error("river_control_v1 not advertised"); + return; } if (!seat_) { spdlog::error("wl_seat not advertised"); + return; } box_.set_name("tags"); @@ -168,6 +170,7 @@ Tags::~Tags() { } void Tags::handle_show() { + if (!status_manager_) return; 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); diff --git a/src/modules/river/window.cpp b/src/modules/river/window.cpp index 1f41ab4c..2daed002 100644 --- a/src/modules/river/window.cpp +++ b/src/modules/river/window.cpp @@ -78,6 +78,7 @@ Window::Window(const std::string& id, const waybar::Bar& bar, const Json::Value& if (!seat_) { spdlog::error("wl_seat not advertised"); + return; } label_.hide(); // hide the label until populated diff --git a/src/modules/sndio.cpp b/src/modules/sndio.cpp index 9779cd36..46bffdc2 100644 --- a/src/modules/sndio.cpp +++ b/src/modules/sndio.cpp @@ -102,7 +102,9 @@ Sndio::~Sndio() { sioctl_close(hdl_); } auto Sndio::update() -> void { auto format = format_; - unsigned int vol = 100. * static_cast(volume_) / static_cast(maxval_); + unsigned int vol = (maxval_ > 0) + ? static_cast(100. * static_cast(volume_) / static_cast(maxval_)) + : 0; if (volume_ == 0) { label_.get_style_context()->add_class("muted"); diff --git a/src/modules/sni/host.cpp b/src/modules/sni/host.cpp index 54faa16c..75501207 100644 --- a/src/modules/sni/host.cpp +++ b/src/modules/sni/host.cpp @@ -59,7 +59,7 @@ void Host::nameVanished(const Glib::RefPtr& conn, const G void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) { GError* error = nullptr; - waybar::util::ScopeGuard error_deleter([error]() { + waybar::util::ScopeGuard error_deleter([&error]() { if (error != nullptr) { g_error_free(error); } @@ -81,7 +81,7 @@ void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) { void Host::registerHost(GObject* src, GAsyncResult* res, gpointer data) { GError* error = nullptr; - waybar::util::ScopeGuard error_deleter([error]() { + waybar::util::ScopeGuard error_deleter([&error]() { if (error != nullptr) { g_error_free(error); } diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index ef2543b5..d33765d2 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -365,13 +365,14 @@ Glib::RefPtr Item::extractPixBuf(GVariant* variant) { void Item::updateImage() { auto pixbuf = getIconPixbuf(); + if (!pixbuf) return; auto scaled_icon_size = getScaledIconSize(); // If the loaded icon is not square, assume that the icon height should match the // requested icon size, but the width is allowed to be different. As such, if the // height of the image does not match the requested icon size, resize the icon such that // the aspect ratio is maintained, but the height matches the requested icon size. - if (pixbuf->get_height() != scaled_icon_size) { + if (pixbuf->get_height() > 0 && pixbuf->get_height() != scaled_icon_size) { int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height(); pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR); } diff --git a/src/modules/sni/watcher.cpp b/src/modules/sni/watcher.cpp index 324bd9f5..1534d924 100644 --- a/src/modules/sni/watcher.cpp +++ b/src/modules/sni/watcher.cpp @@ -31,7 +31,7 @@ Watcher::~Watcher() { void Watcher::busAcquired(const Glib::RefPtr& conn, Glib::ustring name) { GError* error = nullptr; - waybar::util::ScopeGuard error_deleter([error]() { + waybar::util::ScopeGuard error_deleter([&error]() { if (error) { g_error_free(error); } diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index 90f33be7..d33e9c3c 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -77,9 +77,9 @@ void SystemdFailedUnits::RequestSystemState() { Glib::VariantContainerBase data = proxy->call_sync("Get", parameters); if (data && data.is_of_type(Glib::VariantType("(v)"))) { Glib::VariantBase variant; - g_variant_get(data.gobj_copy(), "(v)", &variant); + g_variant_get(data.gobj(), "(v)", &variant); if (variant && variant.is_of_type(Glib::VARIANT_TYPE_STRING)) { - return g_variant_get_string(variant.gobj_copy(), NULL); + return g_variant_get_string(variant.gobj(), NULL); } } } catch (Glib::Error& e) { @@ -105,9 +105,9 @@ void SystemdFailedUnits::RequestFailedUnits() { Glib::VariantContainerBase data = proxy->call_sync("Get", parameters); if (data && data.is_of_type(Glib::VariantType("(v)"))) { Glib::VariantBase variant; - g_variant_get(data.gobj_copy(), "(v)", &variant); + g_variant_get(data.gobj(), "(v)", &variant); if (variant && variant.is_of_type(Glib::VARIANT_TYPE_UINT32)) { - return g_variant_get_uint32(variant.gobj_copy()); + return g_variant_get_uint32(variant.gobj()); } } } catch (Glib::Error& e) { diff --git a/src/modules/upower.cpp b/src/modules/upower.cpp index 7530890c..3006a34a 100644 --- a/src/modules/upower.cpp +++ b/src/modules/upower.cpp @@ -59,10 +59,12 @@ UPower::UPower(const std::string& id, const Json::Value& config) sigc::mem_fun(*this, &UPower::getConn_cb)); // Make UPower client - GError** gErr = NULL; - upClient_ = up_client_new_full(NULL, gErr); - if (upClient_ == NULL) - spdlog::error("Upower. UPower client connection error. {}", (*gErr)->message); + GError* gErr = NULL; + upClient_ = up_client_new_full(NULL, &gErr); + if (upClient_ == NULL) { + spdlog::error("Upower. UPower client connection error. {}", gErr ? gErr->message : "unknown error"); + if (gErr) g_error_free(gErr); + } // Subscribe UPower events g_signal_connect(upClient_, "device-added", G_CALLBACK(deviceAdded_cb), this); diff --git a/src/modules/wayfire/backend.cpp b/src/modules/wayfire/backend.cpp index 5a9c0c1a..545aaa89 100644 --- a/src/modules/wayfire/backend.cpp +++ b/src/modules/wayfire/backend.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -32,7 +33,13 @@ auto pack_and_write(Sock& sock, std::string&& buf) -> void { auto read_exact(Sock& sock, size_t n) -> std::string { auto buf = std::string(n, 0); - for (size_t i = 0; i < n;) i += read(sock.fd, &buf[i], n - i); + for (size_t i = 0; i < n;) { + auto r = read(sock.fd, &buf[i], n - i); + if (r <= 0) { + throw std::runtime_error("Wayfire IPC: read failed"); + } + i += static_cast(r); + } return buf; } @@ -53,6 +60,9 @@ auto State::Wset::locate_ws(const Json::Value& geo) -> Workspace& { } auto State::Wset::locate_ws(const Json::Value& geo) const -> const Workspace& { + if (!output.has_value()) { + throw std::runtime_error("Wayfire IPC: wset has no output assigned"); + } const auto& out = output.value().get(); auto [qx, rx] = std::div(geo["x"].asInt(), out.w); auto [qy, ry] = std::div(geo["y"].asInt(), out.h); @@ -88,7 +98,10 @@ auto State::update_view(const Json::Value& view) -> void { auto IPC::get_instance() -> std::shared_ptr { auto p = instance.lock(); - if (!p) instance = p = std::shared_ptr(new IPC); + if (!p) { + instance = p = std::shared_ptr(new IPC); + p->start(); + } return p; } @@ -116,7 +129,9 @@ auto IPC::connect() -> Sock { } auto IPC::receive(Sock& sock) -> Json::Value { - auto len = *reinterpret_cast(read_exact(sock, 4).data()); + auto len_buf = read_exact(sock, 4); + uint32_t len; + std::memcpy(&len, len_buf.data(), sizeof(len)); if constexpr (std::endian::native != std::endian::little) len = byteswap(len); auto buf = read_exact(sock, len); @@ -153,15 +168,15 @@ auto IPC::start() -> void { send("window-rules/get-focused-view", {}); send("window-rules/get-focused-output", {}); - std::thread([&] { + std::thread([self = shared_from_this()] { auto sock = connect(); { Json::Value json; json["method"] = "window-rules/events/watch"; - pack_and_write(sock, Json::writeString(writer_builder, json)); - if (receive(sock)["result"] != "ok") { + pack_and_write(sock, Json::writeString(self->writer_builder, json)); + if (self->receive(sock)["result"] != "ok") { spdlog::error( "Wayfire IPC: method \"window-rules/events/watch\"" " have failed"); @@ -169,10 +184,10 @@ auto IPC::start() -> void { } } - while (auto json = receive(sock)) { + while (auto json = self->receive(sock)) { auto ev = json["event"].asString(); spdlog::debug("Wayfire IPC: received event \"{}\"", ev); - root_event_handler(ev, json); + self->root_event_handler(ev, json); } }).detach(); } diff --git a/src/modules/wayfire/window.cpp b/src/modules/wayfire/window.cpp index fbcde6ec..8634f090 100644 --- a/src/modules/wayfire/window.cpp +++ b/src/modules/wayfire/window.cpp @@ -34,8 +34,12 @@ auto Window::update() -> void { auto Window::update_icon_label() -> void { auto _ = ipc->lock_state(); - const auto& output = ipc->get_outputs().at(bar_.output->name); - const auto& wset = ipc->get_wsets().at(output.wset_idx); + auto out_it = ipc->get_outputs().find(bar_.output->name); + if (out_it == ipc->get_outputs().end()) return; + const auto& output = out_it->second; + auto wset_it = ipc->get_wsets().find(output.wset_idx); + if (wset_it == ipc->get_wsets().end()) return; + const auto& wset = wset_it->second; const auto& views = ipc->get_views(); auto ctx = bar_.window.get_style_context(); diff --git a/src/modules/wayfire/workspaces.cpp b/src/modules/wayfire/workspaces.cpp index 4c4cd6c1..724a19f8 100644 --- a/src/modules/wayfire/workspaces.cpp +++ b/src/modules/wayfire/workspaces.cpp @@ -70,8 +70,12 @@ auto Workspaces::handleScroll(GdkEventScroll* e) -> bool { Json::Value data; { auto _ = ipc->lock_state(); - const auto& output = ipc->get_outputs().at(bar_.output->name); - const auto& wset = ipc->get_wsets().at(output.wset_idx); + auto out_it = ipc->get_outputs().find(bar_.output->name); + if (out_it == ipc->get_outputs().end()) return true; + const auto& output = out_it->second; + auto wset_it = ipc->get_wsets().find(output.wset_idx); + if (wset_it == ipc->get_wsets().end()) return true; + const auto& wset = wset_it->second; auto n = wset.ws_w * wset.ws_h; auto i = (wset.ws_idx() + delta + n) % n; data["x"] = Json::Value((uint64_t)i % wset.ws_w); @@ -92,8 +96,12 @@ auto Workspaces::update_box() -> void { auto _ = ipc->lock_state(); const auto& output_name = bar_.output->name; - const auto& output = ipc->get_outputs().at(output_name); - const auto& wset = ipc->get_wsets().at(output.wset_idx); + auto out_it = ipc->get_outputs().find(output_name); + if (out_it == ipc->get_outputs().end()) return; + const auto& output = out_it->second; + auto wset_it = ipc->get_wsets().find(output.wset_idx); + if (wset_it == ipc->get_wsets().end()) return; + const auto& wset = wset_it->second; auto output_focused = ipc->get_focused_output_name() == output_name; auto ws_w = wset.ws_w; diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index 2480a0e7..e9cb7206 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -81,7 +81,7 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* if (proxy == nullptr) { auto err = fmt::format("Object '{}' not found\n", id); spdlog::error("[{}]: {}", self->name_, err); - throw std::runtime_error(err); + return; } g_autoptr(WpProperties) properties = @@ -153,7 +153,7 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se if (variant == nullptr) { auto err = fmt::format("Node {} does not support volume\n", id); spdlog::error("[{}]: {}", self->name_, err); - throw std::runtime_error(err); + return; } g_variant_lookup(variant, "volume", "d", &self->volume_); @@ -287,14 +287,14 @@ void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wir if (self->def_nodes_api_ == nullptr) { spdlog::error("[{}]: default nodes api is not loaded.", self->name_); - throw std::runtime_error("Default nodes API is not loaded\n"); + return; } self->mixer_api_ = wp_plugin_find(self->wp_core_, "mixer-api"); if (self->mixer_api_ == nullptr) { spdlog::error("[{}]: mixer api is not loaded.", self->name_); - throw std::runtime_error("Mixer api is not loaded\n"); + return; } // Get default sink @@ -336,7 +336,7 @@ void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult* if (wp_object_activate_finish(p, res, &error) == 0) { spdlog::error("[{}]: error activating plugin: {}", self->name_, error->message); - throw std::runtime_error(error->message); + return; } if (--self->pending_plugins_ == 0) { @@ -373,7 +373,7 @@ void waybar::modules::Wireplumber::onDefaultNodesApiLoaded(WpObject* p, GAsyncRe if (success == FALSE) { spdlog::error("[{}]: default nodes API load failed", self->name_); - throw std::runtime_error(error->message); + return; } spdlog::debug("[{}]: loaded default nodes api", self->name_); g_ptr_array_add(self->apis_, wp_plugin_find(self->wp_core_, "default-nodes-api")); @@ -392,7 +392,7 @@ void waybar::modules::Wireplumber::onMixerApiLoaded(WpObject* p, GAsyncResult* r if (success == FALSE) { spdlog::error("[{}]: mixer API load failed", self->name_); - throw std::runtime_error(error->message); + return; } spdlog::debug("[{}]: loaded mixer API", self->name_); @@ -524,6 +524,7 @@ bool waybar::modules::Wireplumber::handleScroll(GdkEventScroll* e) { } } if (newVol != volume_) { + if (mixer_api_ == nullptr) return true; GVariant* variant = g_variant_new_double(newVol); gboolean ret; g_signal_emit_by_name(mixer_api_, "set-volume", node_id_, variant, &ret); diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index c6985b94..2aef0ae3 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -437,7 +437,9 @@ void Task::handle_drag_data_get(const Glib::RefPtr& context, void Task::handle_drag_data_received(const Glib::RefPtr& context, int x, int y, Gtk::SelectionData selection_data, guint info, guint time) { spdlog::debug("drag_data_received"); - gpointer handle = *(gpointer*)selection_data.get_data(); + auto* raw = selection_data.get_data(); + if (!raw || selection_data.get_length() < static_cast(sizeof(gpointer))) return; + gpointer handle = *(gpointer*)raw; auto dragged_button = (Gtk::Button*)handle; if (dragged_button == &this->button) return; diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index 342d40b2..f61ee945 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -237,9 +237,10 @@ void AudioBackend::sourceInfoCb(pa_context* /*context*/, const pa_source_info* i */ void AudioBackend::serverInfoCb(pa_context* context, const pa_server_info* i, void* data) { auto* backend = static_cast(data); - backend->current_sink_name_ = i->default_sink_name; - backend->default_sink_name = i->default_sink_name; - backend->default_source_name_ = i->default_source_name; + if (i == nullptr) return; + backend->current_sink_name_ = i->default_sink_name ? i->default_sink_name : ""; + backend->default_sink_name = i->default_sink_name ? i->default_sink_name : ""; + backend->default_source_name_ = i->default_source_name ? i->default_source_name : ""; pa_context_get_sink_info_list(context, sinkInfoCb, data); pa_context_get_source_info_list(context, sourceInfoCb, data); @@ -355,6 +356,7 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma } void AudioBackend::toggleSinkMute() { + if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return; muted_ = !muted_; pa_threaded_mainloop_lock(mainloop_); pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast(muted_), nullptr, @@ -363,6 +365,7 @@ void AudioBackend::toggleSinkMute() { } void AudioBackend::toggleSinkMute(bool mute) { + if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return; muted_ = mute; pa_threaded_mainloop_lock(mainloop_); pa_context_set_sink_mute_by_index(context_, sink_idx_, static_cast(muted_), nullptr, @@ -371,7 +374,8 @@ void AudioBackend::toggleSinkMute(bool mute) { } void AudioBackend::toggleSourceMute() { - source_muted_ = !muted_; + if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return; + source_muted_ = !source_muted_; pa_threaded_mainloop_lock(mainloop_); pa_context_set_source_mute_by_index(context_, source_idx_, static_cast(source_muted_), nullptr, nullptr); @@ -379,6 +383,7 @@ void AudioBackend::toggleSourceMute() { } void AudioBackend::toggleSourceMute(bool mute) { + if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return; source_muted_ = mute; pa_threaded_mainloop_lock(mainloop_); pa_context_set_source_mute_by_index(context_, source_idx_, static_cast(source_muted_), diff --git a/src/util/backlight_backend.cpp b/src/util/backlight_backend.cpp index 48473dd2..c7a2b046 100644 --- a/src/util/backlight_backend.cpp +++ b/src/util/backlight_backend.cpp @@ -79,18 +79,20 @@ static void upsert_device(std::vector& devices, udev_device* de }); if (found != devices.end()) { if (actual != nullptr) { - found->set_actual(std::stoi(actual)); + try { found->set_actual(std::stoi(actual)); } catch (const std::exception&) {} } if (max != nullptr) { - found->set_max(std::stoi(max)); + try { found->set_max(std::stoi(max)); } catch (const std::exception&) {} } if (power != nullptr) { - found->set_powered(std::stoi(power) == 0); + try { found->set_powered(std::stoi(power) == 0); } catch (const std::exception&) {} } } else { - const int actual_int = actual == nullptr ? 0 : std::stoi(actual); - const int max_int = max == nullptr ? 0 : std::stoi(max); - const bool power_bool = power == nullptr ? true : std::stoi(power) == 0; + int actual_int = 0, max_int = 0; + bool power_bool = true; + try { if (actual != nullptr) actual_int = std::stoi(actual); } catch (const std::exception&) {} + try { if (max != nullptr) max_int = std::stoi(max); } catch (const std::exception&) {} + try { if (power != nullptr) power_bool = std::stoi(power) == 0; } catch (const std::exception&) {} devices.emplace_back(name, actual_int, max_int, power_bool); } } @@ -261,6 +263,11 @@ void BacklightBackend::set_brightness(const std::string& preferred_device, Chang void BacklightBackend::set_brightness_internal(const std::string& device_name, int brightness, int max_brightness) { + if (!login_proxy_) { + spdlog::error("Login proxy not available, cannot set brightness"); + return; + } + brightness = std::clamp(brightness, 0, max_brightness); auto call_args = Glib::VariantContainerBase( @@ -273,6 +280,7 @@ int BacklightBackend::get_scaled_brightness(const std::string& preferred_device) GET_BEST_DEVICE(best, (*this), preferred_device); if (best != nullptr) { + if (best->get_max() == 0) return 0; return static_cast(std::round(best->get_actual() * 100.0F / best->get_max())); } diff --git a/src/util/css_reload_helper.cpp b/src/util/css_reload_helper.cpp index 06381d60..be8c0a68 100644 --- a/src/util/css_reload_helper.cpp +++ b/src/util/css_reload_helper.cpp @@ -115,11 +115,15 @@ std::vector waybar::CssReloadHelper::parseImports(const std::string auto maxIterations = 100U; do { previousSize = imports.size(); + std::vector to_parse; for (const auto& [file, parsed] : imports) { if (!parsed) { - parseImports(file, imports); + to_parse.push_back(file); } } + for (const auto& file : to_parse) { + parseImports(file, imports); + } } while (imports.size() > previousSize && maxIterations-- > 0); diff --git a/src/util/icon_loader.cpp b/src/util/icon_loader.cpp index fe534f69..1d0bd067 100644 --- a/src/util/icon_loader.cpp +++ b/src/util/icon_loader.cpp @@ -5,8 +5,11 @@ std::vector IconLoader::search_prefix() { std::vector prefixes = {""}; - std::string home_dir = std::getenv("HOME"); - prefixes.push_back(home_dir + "/.local/share/"); + const char* home_env = std::getenv("HOME"); + std::string home_dir = home_env ? home_env : ""; + if (!home_dir.empty()) { + prefixes.push_back(home_dir + "/.local/share/"); + } auto xdg_data_dirs = std::getenv("XDG_DATA_DIRS"); if (!xdg_data_dirs) { @@ -139,7 +142,7 @@ bool IconLoader::image_load_icon(Gtk::Image& image, const Glib::RefPtrget_width() != scaled_icon_size) { + if (pixbuf->get_width() != scaled_icon_size && pixbuf->get_height() > 0) { int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height(); pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR); } diff --git a/src/util/pipewire/pipewire_backend.cpp b/src/util/pipewire/pipewire_backend.cpp index a88a3fbe..82ec0c0f 100644 --- a/src/util/pipewire/pipewire_backend.cpp +++ b/src/util/pipewire/pipewire_backend.cpp @@ -54,11 +54,17 @@ PipewireBackend::PipewireBackend(PrivateConstructorTag tag) context_ = pw_context_new(pw_thread_loop_get_loop(mainloop_), nullptr, 0); if (context_ == nullptr) { pw_thread_loop_unlock(mainloop_); + pw_thread_loop_destroy(mainloop_); + mainloop_ = nullptr; throw std::runtime_error("pa_context_new() failed."); } core_ = pw_context_connect(context_, nullptr, 0); if (core_ == nullptr) { pw_thread_loop_unlock(mainloop_); + pw_context_destroy(context_); + context_ = nullptr; + pw_thread_loop_destroy(mainloop_); + mainloop_ = nullptr; throw std::runtime_error("pw_context_connect() failed"); } registry_ = pw_core_get_registry(core_, PW_VERSION_REGISTRY, 0); @@ -136,7 +142,10 @@ void PipewireBackend::handleRegistryEventGlobal(uint32_t id, uint32_t permission pw_proxy_add_object_listener(proxy, &pNodeInfo->object_listener, &NODE_EVENTS, pNodeInfo); - privacy_nodes.insert_or_assign(id, pNodeInfo); + { + std::lock_guard lock(mutex_); + privacy_nodes.insert_or_assign(id, pNodeInfo); + } } void PipewireBackend::handleRegistryEventGlobalRemove(uint32_t id) { diff --git a/src/util/portal.cpp b/src/util/portal.cpp index 6df2a6b6..0aa78cb9 100644 --- a/src/util/portal.cpp +++ b/src/util/portal.cpp @@ -58,21 +58,26 @@ void waybar::Portal::refreshAppearance() { // xdg-desktop-portal 1.17 will fix this issue with a new `ReadOne` method, // but this version is not yet released. // TODO(xdg-desktop-portal v1.17): switch to ReadOne - auto container = Glib::VariantBase::cast_dynamic(response); - Glib::VariantBase modev; - container.get_child(modev, 0); - auto mode = - Glib::VariantBase::cast_dynamic>>>(modev) - .get() - .get() - .get(); - auto newMode = Appearance(mode); - if (newMode == currentMode) { + try { + auto container = Glib::VariantBase::cast_dynamic(response); + Glib::VariantBase modev; + container.get_child(modev, 0); + auto mode = + Glib::VariantBase::cast_dynamic>>>(modev) + .get() + .get() + .get(); + auto newMode = Appearance(mode); + if (newMode == currentMode) { + return; + } + spdlog::info("Discovered appearance '{}'", newMode); + currentMode = newMode; + m_signal_appearance_changed.emit(currentMode); + } catch (const std::bad_cast& e) { + spdlog::error("Unexpected appearance variant format: {}", e.what()); return; } - spdlog::info("Discovered appearance '{}'", newMode); - currentMode = newMode; - m_signal_appearance_changed.emit(currentMode); } waybar::Appearance waybar::Portal::getAppearance() { return currentMode; }; From 802bf184fbf54b0e49dcb65edca0a2d410aba03e Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 24 Feb 2026 00:49:23 +0100 Subject: [PATCH 563/780] fix: lint --- src/AAppIconLabel.cpp | 3 ++- src/main.cpp | 2 +- src/modules/cpu_usage/common.cpp | 6 ++++-- src/modules/network.cpp | 2 +- src/modules/sndio.cpp | 6 +++--- src/modules/upower.cpp | 3 ++- src/util/backlight_backend.cpp | 30 ++++++++++++++++++++++++------ src/util/portal.cpp | 3 ++- 8 files changed, 39 insertions(+), 16 deletions(-) diff --git a/src/AAppIconLabel.cpp b/src/AAppIconLabel.cpp index 55de78f5..b72906c3 100644 --- a/src/AAppIconLabel.cpp +++ b/src/AAppIconLabel.cpp @@ -158,7 +158,8 @@ void AAppIconLabel::updateAppIcon() { } else if (app_icon_name_.front() == '/') { try { int scaled_icon_size = app_icon_size_ * image_.get_scale_factor(); - auto pixbuf = Gdk::Pixbuf::create_from_file(app_icon_name_, scaled_icon_size, scaled_icon_size); + auto pixbuf = + Gdk::Pixbuf::create_from_file(app_icon_name_, scaled_icon_size, scaled_icon_size); auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image_.get_scale_factor(), image_.get_window()); diff --git a/src/main.cpp b/src/main.cpp index 166dd2bb..9d7d0ba2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -142,7 +142,7 @@ static void handleSignalMainThread(int signum, bool& reload) { spdlog::debug("Received SIGCHLD in signalThread"); { std::lock_guard lock(reap_mtx); - for (auto it = reap.begin(); it != reap.end(); ) { + for (auto it = reap.begin(); it != reap.end();) { if (waitpid(*it, nullptr, WNOHANG) == *it) { spdlog::debug("Reaped child with PID: {}", *it); it = reap.erase(it); diff --git a/src/modules/cpu_usage/common.cpp b/src/modules/cpu_usage/common.cpp index 82126dac..1571cb74 100644 --- a/src/modules/cpu_usage/common.cpp +++ b/src/modules/cpu_usage/common.cpp @@ -80,7 +80,8 @@ std::tuple, std::string> waybar::modules::CpuUsage::getCpu auto [prev_idle, prev_total] = prev_times[0]; const float delta_idle = curr_idle - prev_idle; const float delta_total = curr_total - prev_total; - uint16_t tmp = (delta_total > 0) ? static_cast(100 * (1 - delta_idle / delta_total)) : 0; + uint16_t tmp = + (delta_total > 0) ? static_cast(100 * (1 - delta_idle / delta_total)) : 0; tooltip = fmt::format("Total: {}%\nCores: (pending)", tmp); usage.push_back(tmp); } else { @@ -102,7 +103,8 @@ std::tuple, std::string> waybar::modules::CpuUsage::getCpu } const float delta_idle = curr_idle - prev_idle; const float delta_total = curr_total - prev_total; - uint16_t tmp = (delta_total > 0) ? static_cast(100 * (1 - delta_idle / delta_total)) : 0; + uint16_t tmp = + (delta_total > 0) ? static_cast(100 * (1 - delta_idle / delta_total)) : 0; if (i == 0) { tooltip = fmt::format("Total: {}%", tmp); } else { diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 0dbdcc57..34dcc03c 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -45,7 +45,7 @@ waybar::modules::Network::readBandwidthUsage() { std::istringstream iss(line); std::string ifacename; - iss >> ifacename; // ifacename contains "eth0:" + iss >> ifacename; // ifacename contains "eth0:" if (ifacename.empty()) continue; ifacename.pop_back(); // remove trailing ':' if (ifacename != ifname_) { diff --git a/src/modules/sndio.cpp b/src/modules/sndio.cpp index 46bffdc2..d878c4a4 100644 --- a/src/modules/sndio.cpp +++ b/src/modules/sndio.cpp @@ -102,9 +102,9 @@ Sndio::~Sndio() { sioctl_close(hdl_); } auto Sndio::update() -> void { auto format = format_; - unsigned int vol = (maxval_ > 0) - ? static_cast(100. * static_cast(volume_) / static_cast(maxval_)) - : 0; + unsigned int vol = (maxval_ > 0) ? static_cast(100. * static_cast(volume_) / + static_cast(maxval_)) + : 0; if (volume_ == 0) { label_.get_style_context()->add_class("muted"); diff --git a/src/modules/upower.cpp b/src/modules/upower.cpp index 3006a34a..8202b718 100644 --- a/src/modules/upower.cpp +++ b/src/modules/upower.cpp @@ -62,7 +62,8 @@ UPower::UPower(const std::string& id, const Json::Value& config) GError* gErr = NULL; upClient_ = up_client_new_full(NULL, &gErr); if (upClient_ == NULL) { - spdlog::error("Upower. UPower client connection error. {}", gErr ? gErr->message : "unknown error"); + spdlog::error("Upower. UPower client connection error. {}", + gErr ? gErr->message : "unknown error"); if (gErr) g_error_free(gErr); } diff --git a/src/util/backlight_backend.cpp b/src/util/backlight_backend.cpp index c7a2b046..61eb9b43 100644 --- a/src/util/backlight_backend.cpp +++ b/src/util/backlight_backend.cpp @@ -79,20 +79,38 @@ static void upsert_device(std::vector& devices, udev_device* de }); if (found != devices.end()) { if (actual != nullptr) { - try { found->set_actual(std::stoi(actual)); } catch (const std::exception&) {} + try { + found->set_actual(std::stoi(actual)); + } catch (const std::exception&) { + } } if (max != nullptr) { - try { found->set_max(std::stoi(max)); } catch (const std::exception&) {} + try { + found->set_max(std::stoi(max)); + } catch (const std::exception&) { + } } if (power != nullptr) { - try { found->set_powered(std::stoi(power) == 0); } catch (const std::exception&) {} + try { + found->set_powered(std::stoi(power) == 0); + } catch (const std::exception&) { + } } } else { int actual_int = 0, max_int = 0; bool power_bool = true; - try { if (actual != nullptr) actual_int = std::stoi(actual); } catch (const std::exception&) {} - try { if (max != nullptr) max_int = std::stoi(max); } catch (const std::exception&) {} - try { if (power != nullptr) power_bool = std::stoi(power) == 0; } catch (const std::exception&) {} + try { + if (actual != nullptr) actual_int = std::stoi(actual); + } catch (const std::exception&) { + } + try { + if (max != nullptr) max_int = std::stoi(max); + } catch (const std::exception&) { + } + try { + if (power != nullptr) power_bool = std::stoi(power) == 0; + } catch (const std::exception&) { + } devices.emplace_back(name, actual_int, max_int, power_bool); } } diff --git a/src/util/portal.cpp b/src/util/portal.cpp index 0aa78cb9..e2be97c7 100644 --- a/src/util/portal.cpp +++ b/src/util/portal.cpp @@ -63,7 +63,8 @@ void waybar::Portal::refreshAppearance() { Glib::VariantBase modev; container.get_child(modev, 0); auto mode = - Glib::VariantBase::cast_dynamic>>>(modev) + Glib::VariantBase::cast_dynamic>>>( + modev) .get() .get() .get(); From 7744320ab2c793d092620c2ec859327280b676a6 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 24 Feb 2026 00:55:27 +0100 Subject: [PATCH 564/780] fix: build --- src/modules/gamemode.cpp | 6 +++--- src/modules/systemd_failed_units.cpp | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/modules/gamemode.cpp b/src/modules/gamemode.cpp index 1c846434..691a2844 100644 --- a/src/modules/gamemode.cpp +++ b/src/modules/gamemode.cpp @@ -128,9 +128,9 @@ void Gamemode::getData() { Glib::VariantContainerBase data = gamemode_proxy->call_sync("Get", parameters); if (data && data.is_of_type(Glib::VariantType("(v)"))) { Glib::VariantBase variant; - g_variant_get(data.gobj(), "(v)", &variant); + g_variant_get(const_cast(data.gobj()), "(v)", &variant); if (variant && variant.is_of_type(Glib::VARIANT_TYPE_INT32)) { - g_variant_get(variant.gobj(), "i", &gameCount); + g_variant_get(const_cast(variant.gobj()), "i", &gameCount); return; } } @@ -158,7 +158,7 @@ void Gamemode::prepareForSleep_cb(const Glib::RefPtr& con const Glib::VariantContainerBase& parameters) { if (parameters.is_of_type(Glib::VariantType("(b)"))) { gboolean sleeping; - g_variant_get(parameters.gobj(), "(b)", &sleeping); + g_variant_get(const_cast(parameters.gobj()), "(b)", &sleeping); if (!sleeping) { getData(); dp.emit(); diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index d33e9c3c..68e61fe9 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -77,9 +77,9 @@ void SystemdFailedUnits::RequestSystemState() { Glib::VariantContainerBase data = proxy->call_sync("Get", parameters); if (data && data.is_of_type(Glib::VariantType("(v)"))) { Glib::VariantBase variant; - g_variant_get(data.gobj(), "(v)", &variant); + g_variant_get(const_cast(data.gobj()), "(v)", &variant); if (variant && variant.is_of_type(Glib::VARIANT_TYPE_STRING)) { - return g_variant_get_string(variant.gobj(), NULL); + return g_variant_get_string(const_cast(variant.gobj()), NULL); } } } catch (Glib::Error& e) { @@ -105,9 +105,9 @@ void SystemdFailedUnits::RequestFailedUnits() { Glib::VariantContainerBase data = proxy->call_sync("Get", parameters); if (data && data.is_of_type(Glib::VariantType("(v)"))) { Glib::VariantBase variant; - g_variant_get(data.gobj(), "(v)", &variant); + g_variant_get(const_cast(data.gobj()), "(v)", &variant); if (variant && variant.is_of_type(Glib::VARIANT_TYPE_UINT32)) { - return g_variant_get_uint32(variant.gobj()); + return g_variant_get_uint32(const_cast(variant.gobj())); } } } catch (Glib::Error& e) { From a6a65cb6b79e8871ae82a7451c1cbc5cdb96774e Mon Sep 17 00:00:00 2001 From: Prakhar Chhalotre Date: Tue, 10 Feb 2026 18:59:56 +0530 Subject: [PATCH 565/780] feat: add support for configurable first day of the week in calendar --- src/modules/clock.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 5fe5407b..b71b2615 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -509,6 +509,18 @@ using deleting_unique_ptr = std::unique_ptr>; // Computations done similarly to Linux cal utility. auto waybar::modules::Clock::first_day_of_week() -> weekday { + const auto firstdow = config_[kCldPlaceholder]["first-day-of-week"]; + if (firstdow.isInt()) { + const int firstDay = firstdow.asInt(); + if (!(firstDay >= 0 && firstDay <= 6)) { + spdlog::warn( + "Clock calender configuration first-day-of-week = {0} must be in range [0, 6]. Default " + "value is used instead", + firstDay); + } else { + return weekday{static_cast(firstDay)}; + } + } if (iso8601Calendar_) { return Monday; } From 15d33f232b9b9707b6c9c4f38e365f9075c19be7 Mon Sep 17 00:00:00 2001 From: Prakhar Chhalotre Date: Tue, 24 Feb 2026 12:29:57 +0530 Subject: [PATCH 566/780] docs: update man page --- man/waybar-clock.5.scd | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/man/waybar-clock.5.scd b/man/waybar-clock.5.scd index b4b5d5b7..15c70b2c 100644 --- a/man/waybar-clock.5.scd +++ b/man/waybar-clock.5.scd @@ -138,6 +138,11 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe :[ 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}'. +|[ *first-day-of-week* +:[ integer +:[ +:[ The first day of the week, where 0 is Sunday and 6 is Saturday. + When not set, the first day of the week is determined by the locale settings. 3. Addressed by *clock: calendar: format* [- *Option* @@ -207,11 +212,12 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe "format-alt": "{:%A, %B %d, %Y (%R)}  ", "tooltip-format": "{calendar}", "calendar": { - "mode" : "year", - "mode-mon-col" : 3, - "weeks-pos" : "right", - "on-scroll" : 1, - "on-click-right": "mode", + "mode" : "year", + "mode-mon-col" : 3, + "weeks-pos" : "right", + "first-day-of-week": 1, + "on-scroll" : 1, + "on-click-right" : "mode", "format": { "months": "{}", "days": "{}", From f2cfcb85dd4b15cf2e3495387465785b4a92a66d Mon Sep 17 00:00:00 2001 From: Prakhar Chhalotre Date: Tue, 24 Feb 2026 19:21:13 +0530 Subject: [PATCH 567/780] feat(keyboard-state): add custom icons in format-icons --- include/modules/keyboard_state.hpp | 3 +- src/modules/keyboard_state.cpp | 62 ++++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/include/modules/keyboard_state.hpp b/include/modules/keyboard_state.hpp index db7f2550..c9f1afac 100644 --- a/include/modules/keyboard_state.hpp +++ b/include/modules/keyboard_state.hpp @@ -36,8 +36,7 @@ class KeyboardState : public AModule { std::string capslock_format_; std::string scrolllock_format_; const std::chrono::seconds interval_; - std::string icon_locked_; - std::string icon_unlocked_; + std::unordered_map> key_icon_states_; std::string devices_path_; struct libinput* libinput_; diff --git a/src/modules/keyboard_state.cpp b/src/modules/keyboard_state.cpp index a2207fdd..577f52bd 100644 --- a/src/modules/keyboard_state.cpp +++ b/src/modules/keyboard_state.cpp @@ -78,6 +78,44 @@ auto supportsLockStates(const libevdev* dev) -> bool { libevdev_has_event_code(dev, EV_LED, LED_SCROLLL); } +auto isCommonFormatIcons(const Json::Value& config) -> bool { + return config["format-icons"].isObject() && (config["format-icons"]["locked"].isString() || config["format-icons"]["unlocked"].isString()); +} + +auto keyStateToIcons(const Json::Value& config) -> std::unordered_map> { + std::unordered_map> key_icon_states; + std::vector default_icons = {"unlocked", "locked"}; + + if (isCommonFormatIcons(config)) { + std::vector icons = { + config["format-icons"]["unlocked"].isString() ? config["format-icons"]["unlocked"].asString() : "unlocked", + config["format-icons"]["locked"].isString() ? config["format-icons"]["locked"].asString() : "locked", + }; + key_icon_states["Lock"] = icons; + return key_icon_states; + } + + bool found_any = false; + for (const auto& key : std::vector{"numlock", "capslock", "scrolllock"}) { + std::string map_key = key.substr(0, key.length() - 4); + map_key[0] = std::toupper(map_key[0]); + if (config["format-icons"].isObject() && config["format-icons"][key].isObject()) { + std::string unlocked = config["format-icons"][key]["unlocked"].isString() ? config["format-icons"][key]["unlocked"].asString() : "unlocked"; + std::string locked = config["format-icons"][key]["locked"].isString() ? config["format-icons"][key]["locked"].asString() : "locked"; + key_icon_states[map_key] = {unlocked, locked}; + found_any = true; + } + } + + if (!found_any) { + key_icon_states["Num"] = default_icons; + key_icon_states["Caps"] = default_icons; + key_icon_states["Scroll"] = default_icons; + } + + return key_icon_states; +} + waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar, const Json::Value& config) : AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()), @@ -98,12 +136,7 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& : "{name} {icon}"), interval_( std::chrono::seconds(config_["interval"].isUInt() ? config_["interval"].asUInt() : 1)), - icon_locked_(config_["format-icons"]["locked"].isString() - ? config_["format-icons"]["locked"].asString() - : "locked"), - icon_unlocked_(config_["format-icons"]["unlocked"].isString() - ? config_["format-icons"]["unlocked"].asString() - : "unlocked"), + key_icon_states_(keyStateToIcons(config_)), devices_path_("/dev/input/"), libinput_(nullptr), libinput_devices_({}) { @@ -290,7 +323,7 @@ auto waybar::modules::KeyboardState::update() -> void { bool state; Gtk::Label& label; const std::string& format; - const char* name; + const std::string name; } label_states[] = { {(bool)numl, numlock_label_, numlock_format_, "Num"}, {(bool)capsl, capslock_label_, capslock_format_, "Caps"}, @@ -298,8 +331,21 @@ auto waybar::modules::KeyboardState::update() -> void { }; for (auto& label_state : label_states) { std::string text; + std::string map_key = isCommonFormatIcons(config_) ? "Lock" : label_state.name; + + if (key_icon_states_.find(map_key) == key_icon_states_.end()) { + spdlog::warn("keyboard-state: Missing icon configuration for '{}'", map_key); + continue; + } + + auto& icons = key_icon_states_[map_key]; + if (icons.size() < 2) { + spdlog::warn("keyboard-state: Invalid icon vector size for '{}'", map_key); + continue; + } + text = fmt::format(fmt::runtime(label_state.format), - fmt::arg("icon", label_state.state ? icon_locked_ : icon_unlocked_), + fmt::arg("icon", label_state.state ? icons[1] : icons[0]), fmt::arg("name", label_state.name)); label_state.label.set_markup(text); if (label_state.state) { From a64876b928b86377c61529113e2a95e6b7c1f60d Mon Sep 17 00:00:00 2001 From: Prakhar Chhalotre Date: Tue, 24 Feb 2026 19:37:01 +0530 Subject: [PATCH 568/780] docs: update man page --- man/waybar-keyboard-state.5.scd | 37 +++++++++++++++++++++++++++++++-- src/modules/keyboard_state.cpp | 21 +++++++++++++------ 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/man/waybar-keyboard-state.5.scd b/man/waybar-keyboard-state.5.scd index 79498414..7babe336 100644 --- a/man/waybar-keyboard-state.5.scd +++ b/man/waybar-keyboard-state.5.scd @@ -26,7 +26,10 @@ You must be a member of the input group to use this module. *format-icons*: ++ typeof: object ++ default: {"locked": "locked", "unlocked": "unlocked"} ++ - Based on the keyboard state, the corresponding icon gets selected. The same set of icons is used for number, caps, and scroll lock, but the icon is selected from the set independently for each. See *icons*. + Based on the keyboard state, the corresponding icon gets selected. Supports two syntaxes: + - Common format-icons: "locked" and "unlocked" keys apply to all lock types. + - Per-lock-type format-icons: per-lock-type objects with "numlock", "capslock", "scrolllock" keys, each containing "locked" and "unlocked" icons. + See *icons*. *numlock*: ++ typeof: bool ++ @@ -68,15 +71,21 @@ You must be a member of the input group to use this module. The following *format-icons* can be set. +## Common format-icons for all lock types: - *locked*: Will be shown when the keyboard state is locked. Default "locked". -- *unlocked*: Will be shown when the keyboard state is not locked. Default "unlocked" +- *unlocked*: Will be shown when the keyboard state is not locked. Default "unlocked". + +## Per-lock-type format-icons: +- *numlock*, *capslock*, *scrolllock*: Object containing "locked" and "unlocked" keys for lock-type-specific icons. Defaults to {"locked": "locked", "unlocked": "unlocked"} for each lock type. # EXAMPLE: +## Common format-icons for all lock types: ``` "keyboard-state": { "numlock": true, "capslock": true, + "scrolllock": true, "format": "{name} {icon}", "format-icons": { "locked": "", @@ -85,6 +94,30 @@ The following *format-icons* can be set. } ``` +## Per-lock-type format-icons: +``` +"keyboard-state": { + "numlock": true, + "capslock": true, + "scrolllock": true, + "format": "{name} {icon}", + "format-icons": { + "numlock": { + "locked": "1", + "unlocked": "0" + }, + "capslock": { + "locked": "A", + "unlocked": "a" + }, + "scrolllock": { + "locked": "S", + "unlocked": "s" + } + } +} +``` + # STYLE - *#keyboard-state* diff --git a/src/modules/keyboard_state.cpp b/src/modules/keyboard_state.cpp index 577f52bd..49cf863f 100644 --- a/src/modules/keyboard_state.cpp +++ b/src/modules/keyboard_state.cpp @@ -79,17 +79,22 @@ auto supportsLockStates(const libevdev* dev) -> bool { } auto isCommonFormatIcons(const Json::Value& config) -> bool { - return config["format-icons"].isObject() && (config["format-icons"]["locked"].isString() || config["format-icons"]["unlocked"].isString()); + return config["format-icons"].isObject() && (config["format-icons"]["locked"].isString() || + config["format-icons"]["unlocked"].isString()); } -auto keyStateToIcons(const Json::Value& config) -> std::unordered_map> { +auto keyStateToIcons(const Json::Value& config) + -> std::unordered_map> { std::unordered_map> key_icon_states; std::vector default_icons = {"unlocked", "locked"}; if (isCommonFormatIcons(config)) { std::vector icons = { - config["format-icons"]["unlocked"].isString() ? config["format-icons"]["unlocked"].asString() : "unlocked", - config["format-icons"]["locked"].isString() ? config["format-icons"]["locked"].asString() : "locked", + config["format-icons"]["unlocked"].isString() + ? config["format-icons"]["unlocked"].asString() + : "unlocked", + config["format-icons"]["locked"].isString() ? config["format-icons"]["locked"].asString() + : "locked", }; key_icon_states["Lock"] = icons; return key_icon_states; @@ -100,8 +105,12 @@ auto keyStateToIcons(const Json::Value& config) -> std::unordered_map Date: Sat, 28 Feb 2026 14:00:25 +0000 Subject: [PATCH 569/780] Initial plan From d5297bc42444f22a7266415d9f3042566c101043 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Feb 2026 14:27:22 +0000 Subject: [PATCH 570/780] fix: resolve PulseAudio/WirePlumber deadlock and freeze issues - Fix AudioBackend destructor: properly lock the PA mainloop before disconnecting the context to prevent race conditions with PA callbacks - Fix context leak on reconnect: call pa_context_unref() when the old context is replaced after PA_CONTEXT_FAILED to avoid resource leaks - Fix PA mainloop killed on reconnect (critical): PA_CONTEXT_TERMINATED was unconditionally calling quit() on the mainloop, even during reconnection when the old context fires TERMINATED after the new one was created. This was killing the new context and preventing successful reconnection, causing Waybar to appear frozen. The fix only quits the mainloop when the terminating context is still the active one. - Fix Wireplumber use-after-free: explicitly disconnect GObject signal handlers for mixer_api_, def_nodes_api_, and om_ before clearing the object references in the destructor to prevent callbacks from firing with a destroyed self pointer. - Fix GVariant memory leak in Wireplumber::handleScroll: unref the GVariant created for the set-volume signal after the emit call. Co-authored-by: Alexays <13947260+Alexays@users.noreply.github.com> --- src/modules/wireplumber.cpp | 10 ++++++++++ src/util/audio_backend.cpp | 25 +++++++++++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index e9cb7206..0c0d1dde 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -54,6 +54,15 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val waybar::modules::Wireplumber::~Wireplumber() { waybar::modules::Wireplumber::modules.remove(this); + if (mixer_api_ != nullptr) { + g_signal_handlers_disconnect_by_data(mixer_api_, this); + } + if (def_nodes_api_ != nullptr) { + g_signal_handlers_disconnect_by_data(def_nodes_api_, this); + } + if (om_ != nullptr) { + g_signal_handlers_disconnect_by_data(om_, this); + } wp_core_disconnect(wp_core_); g_clear_pointer(&apis_, g_ptr_array_unref); g_clear_object(&om_); @@ -528,6 +537,7 @@ bool waybar::modules::Wireplumber::handleScroll(GdkEventScroll* e) { GVariant* variant = g_variant_new_double(newVol); gboolean ret; g_signal_emit_by_name(mixer_api_, "set-volume", node_id_, variant, &ret); + g_variant_unref(variant); } return true; } diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index f61ee945..4087e096 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -40,12 +40,16 @@ AudioBackend::AudioBackend(std::function on_updated_cb, private_construc } AudioBackend::~AudioBackend() { - if (context_ != nullptr) { - pa_context_disconnect(context_); - } - if (mainloop_ != nullptr) { - mainloop_api_->quit(mainloop_api_, 0); + // Lock the mainloop so we can safely disconnect the context. + // This must be done before stopping the thread. + pa_threaded_mainloop_lock(mainloop_); + if (context_ != nullptr) { + pa_context_disconnect(context_); + pa_context_unref(context_); + context_ = nullptr; + } + pa_threaded_mainloop_unlock(mainloop_); pa_threaded_mainloop_stop(mainloop_); pa_threaded_mainloop_free(mainloop_); } @@ -73,7 +77,14 @@ void AudioBackend::contextStateCb(pa_context* c, void* data) { auto* backend = static_cast(data); switch (pa_context_get_state(c)) { case PA_CONTEXT_TERMINATED: - backend->mainloop_api_->quit(backend->mainloop_api_, 0); + // Only quit the mainloop if this is still the active context. + // During reconnection, the old context fires TERMINATED after the new one + // has already been created; quitting in that case would kill the new context. + // Note: context_ is only written from PA callbacks (while the mainloop lock is + // held), so this comparison is safe within any PA callback. + if (backend->context_ == nullptr || backend->context_ == c) { + backend->mainloop_api_->quit(backend->mainloop_api_, 0); + } break; case PA_CONTEXT_READY: pa_context_get_server_info(c, serverInfoCb, data); @@ -93,6 +104,8 @@ void AudioBackend::contextStateCb(pa_context* c, void* data) { // So there is no need to lock it again. if (backend->context_ != nullptr) { pa_context_disconnect(backend->context_); + pa_context_unref(backend->context_); + backend->context_ = nullptr; } backend->connectContext(); break; From dbbad059f7384fdbe88c570014b96f27ab284468 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 9 Feb 2026 13:39:23 -0600 Subject: [PATCH 571/780] fix(sleeper-thread): stop and join before worker reassignment Reassigning SleeperThread could replace a joinable std::thread and trigger std::terminate. I now stop and join any existing worker before reassignment, then reset control state before starting the replacement worker. Signed-off-by: Austin Horstman --- include/util/sleeper_thread.hpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/include/util/sleeper_thread.hpp b/include/util/sleeper_thread.hpp index 62d12931..183083cf 100644 --- a/include/util/sleeper_thread.hpp +++ b/include/util/sleeper_thread.hpp @@ -42,6 +42,15 @@ class SleeperThread { } SleeperThread& operator=(std::function func) { + if (thread_.joinable()) { + stop(); + thread_.join(); + } + { + std::lock_guard lck(mutex_); + do_run_ = true; + signal_ = false; + } thread_ = std::thread([this, func] { while (do_run_) { signal_ = false; @@ -92,7 +101,7 @@ class SleeperThread { condvar_.notify_all(); } - auto stop() { + void stop() { { std::lock_guard lck(mutex_); signal_ = true; From 1c61ecf864ae637f4ed649dd8e61e92c1f6e9095 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 9 Feb 2026 13:39:26 -0600 Subject: [PATCH 572/780] test(utils): add SleeperThread reassignment regression We needed a regression test for reassignment safety after lifecycle fixes. I added a subprocess test that reassigns SleeperThread workers and verifies the process exits normally instead of terminating. Signed-off-by: Austin Horstman --- test/utils/meson.build | 1 + test/utils/sleeper_thread.cpp | 42 +++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 test/utils/sleeper_thread.cpp diff --git a/test/utils/meson.build b/test/utils/meson.build index b7b3665a..050af262 100644 --- a/test/utils/meson.build +++ b/test/utils/meson.build @@ -13,6 +13,7 @@ test_src = files( '../../src/config.cpp', 'JsonParser.cpp', 'SafeSignal.cpp', + 'sleeper_thread.cpp', 'css_reload_helper.cpp', '../../src/util/css_reload_helper.cpp', ) diff --git a/test/utils/sleeper_thread.cpp b/test/utils/sleeper_thread.cpp new file mode 100644 index 00000000..2556a106 --- /dev/null +++ b/test/utils/sleeper_thread.cpp @@ -0,0 +1,42 @@ +#if __has_include() +#include +#else +#include +#endif + +#include +#include +#include +#include + +#include "util/sleeper_thread.hpp" + +namespace waybar::util { +SafeSignal& prepare_for_sleep() { + static SafeSignal signal; + return signal; +} +} // namespace waybar::util + +namespace { +int run_reassignment_regression() { + waybar::util::SleeperThread thread; + thread = [] { std::this_thread::sleep_for(std::chrono::milliseconds(10)); }; + thread = [] { std::this_thread::sleep_for(std::chrono::milliseconds(1)); }; + return 0; +} +} // namespace + +TEST_CASE("SleeperThread reassignment does not terminate process", "[util][sleeper_thread]") { + const auto pid = fork(); + REQUIRE(pid >= 0); + + if (pid == 0) { + _exit(run_reassignment_regression()); + } + + int status = -1; + REQUIRE(waitpid(pid, &status, 0) == pid); + REQUIRE(WIFEXITED(status)); + REQUIRE(WEXITSTATUS(status) == 0); +} From 44eed7afea7ab251a8aee554a360dbb6fbed6c6d Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 9 Feb 2026 13:44:26 -0600 Subject: [PATCH 573/780] fix(sleeper-thread): synchronize control flags with atomics SleeperThread control flags were shared across threads without consistent synchronization. I converted the run/signal flags to atomics and updated wait predicates and lifecycle transitions to use explicit atomic loads/stores. Signed-off-by: Austin Horstman --- include/util/sleeper_thread.hpp | 40 ++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/include/util/sleeper_thread.hpp b/include/util/sleeper_thread.hpp index 183083cf..966772a2 100644 --- a/include/util/sleeper_thread.hpp +++ b/include/util/sleeper_thread.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -31,8 +32,8 @@ class SleeperThread { SleeperThread(std::function func) : thread_{[this, func] { - while (do_run_) { - signal_ = false; + while (do_run_.load(std::memory_order_relaxed)) { + signal_.store(false, std::memory_order_relaxed); func(); } }} { @@ -48,12 +49,12 @@ class SleeperThread { } { std::lock_guard lck(mutex_); - do_run_ = true; - signal_ = false; + do_run_.store(true, std::memory_order_relaxed); + signal_.store(false, std::memory_order_relaxed); } thread_ = std::thread([this, func] { - while (do_run_) { - signal_ = false; + while (do_run_.load(std::memory_order_relaxed)) { + signal_.store(false, std::memory_order_relaxed); func(); } }); @@ -65,12 +66,15 @@ class SleeperThread { return *this; } - bool isRunning() const { return do_run_; } + bool isRunning() const { return do_run_.load(std::memory_order_relaxed); } auto sleep() { std::unique_lock lk(mutex_); CancellationGuard cancel_lock; - return condvar_.wait(lk, [this] { return signal_ || !do_run_; }); + return condvar_.wait(lk, [this] { + return signal_.load(std::memory_order_relaxed) || + !do_run_.load(std::memory_order_relaxed); + }); } auto sleep_for(std::chrono::system_clock::duration dur) { @@ -82,7 +86,10 @@ class SleeperThread { if (now < max_time_point - dur) { wait_end = now + dur; } - return condvar_.wait_until(lk, wait_end, [this] { return signal_ || !do_run_; }); + return condvar_.wait_until(lk, wait_end, [this] { + return signal_.load(std::memory_order_relaxed) || + !do_run_.load(std::memory_order_relaxed); + }); } auto sleep_until( @@ -90,13 +97,16 @@ class SleeperThread { time_point) { std::unique_lock lk(mutex_); CancellationGuard cancel_lock; - return condvar_.wait_until(lk, time_point, [this] { return signal_ || !do_run_; }); + return condvar_.wait_until(lk, time_point, [this] { + return signal_.load(std::memory_order_relaxed) || + !do_run_.load(std::memory_order_relaxed); + }); } void wake_up() { { std::lock_guard lck(mutex_); - signal_ = true; + signal_.store(true, std::memory_order_relaxed); } condvar_.notify_all(); } @@ -104,8 +114,8 @@ class SleeperThread { void stop() { { std::lock_guard lck(mutex_); - signal_ = true; - do_run_ = false; + signal_.store(true, std::memory_order_relaxed); + do_run_.store(false, std::memory_order_relaxed); } condvar_.notify_all(); auto handle = thread_.native_handle(); @@ -127,8 +137,8 @@ class SleeperThread { std::thread thread_; std::condition_variable condvar_; std::mutex mutex_; - bool do_run_ = true; - bool signal_ = false; + std::atomic do_run_ = true; + std::atomic signal_ = false; sigc::connection connection_; }; From 864523772d427fea72f99a6fe9dcc100dc708acc Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 9 Feb 2026 13:44:28 -0600 Subject: [PATCH 574/780] test(utils): stress SleeperThread wake and stop control flow SleeperThread concurrency paths needed stress coverage around wake/stop races. I added a subprocess stress test that repeatedly interleaves wake_up() and stop() and verifies the worker exits cleanly. Signed-off-by: Austin Horstman --- test/utils/sleeper_thread.cpp | 57 +++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/test/utils/sleeper_thread.cpp b/test/utils/sleeper_thread.cpp index 2556a106..7a9a3ca1 100644 --- a/test/utils/sleeper_thread.cpp +++ b/test/utils/sleeper_thread.cpp @@ -19,24 +19,61 @@ SafeSignal& prepare_for_sleep() { } // namespace waybar::util namespace { +int run_in_subprocess(int (*task)()) { + const auto pid = fork(); + if (pid < 0) { + return -1; + } + if (pid == 0) { + alarm(5); + _exit(task()); + } + + int status = -1; + if (waitpid(pid, &status, 0) != pid) { + return -1; + } + if (!WIFEXITED(status)) { + return -1; + } + return WEXITSTATUS(status); +} + int run_reassignment_regression() { waybar::util::SleeperThread thread; thread = [] { std::this_thread::sleep_for(std::chrono::milliseconds(10)); }; thread = [] { std::this_thread::sleep_for(std::chrono::milliseconds(1)); }; return 0; } + +int run_control_flag_stress() { + for (int i = 0; i < 200; ++i) { + waybar::util::SleeperThread thread; + thread = [&thread] { thread.sleep_for(std::chrono::milliseconds(1)); }; + + std::thread waker([&thread] { + for (int j = 0; j < 100; ++j) { + thread.wake_up(); + std::this_thread::yield(); + } + }); + + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + thread.stop(); + waker.join(); + if (thread.isRunning()) { + return 1; + } + } + return 0; +} } // namespace TEST_CASE("SleeperThread reassignment does not terminate process", "[util][sleeper_thread]") { - const auto pid = fork(); - REQUIRE(pid >= 0); + REQUIRE(run_in_subprocess(run_reassignment_regression) == 0); +} - if (pid == 0) { - _exit(run_reassignment_regression()); - } - - int status = -1; - REQUIRE(waitpid(pid, &status, 0) == pid); - REQUIRE(WIFEXITED(status)); - REQUIRE(WEXITSTATUS(status) == 0); +TEST_CASE("SleeperThread control flags are stable under concurrent wake and stop", + "[util][sleeper_thread]") { + REQUIRE(run_in_subprocess(run_control_flag_stress) == 0); } From e4ff024fa87af9ad63b6a9fc9da8f0beb5afa7c6 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 9 Feb 2026 14:06:27 -0600 Subject: [PATCH 575/780] fix(util): bound SafeSignal queue growth under burst load SafeSignal could queue events forever when worker threads emitted faster than the main loop could consume, which risks memory growth and stale updates. I added a queue cap with a drop-oldest policy so growth stays bounded under burst load, plus a regression test that validates bounded delivery. Signed-off-by: Austin Horstman --- include/util/SafeSignal.hpp | 20 ++++++++++++++++++++ test/utils/SafeSignal.cpp | 31 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/include/util/SafeSignal.hpp b/include/util/SafeSignal.hpp index 340f74ee..1e901792 100644 --- a/include/util/SafeSignal.hpp +++ b/include/util/SafeSignal.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,12 @@ struct SafeSignal : sigc::signal...)> { public: SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); } + void set_max_queued_events(std::size_t max_queued_events) { + std::unique_lock lock(mutex_); + max_queued_events_ = max_queued_events; + trim_queue_locked(); + } + template void emit(EmitArgs&&... args) { if (main_tid_ == std::this_thread::get_id()) { @@ -41,6 +48,9 @@ struct SafeSignal : sigc::signal...)> { } else { { std::unique_lock lock(mutex_); + if (max_queued_events_ != 0 && queue_.size() >= max_queued_events_) { + queue_.pop(); + } queue_.emplace(std::forward(args)...); } dp_.emit(); @@ -60,6 +70,15 @@ struct SafeSignal : sigc::signal...)> { using signal_t::emit_reverse; using signal_t::make_slot; + void trim_queue_locked() { + if (max_queued_events_ == 0) { + return; + } + while (queue_.size() > max_queued_events_) { + queue_.pop(); + } + } + void handle_event() { for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) { auto args = queue_.front(); @@ -72,6 +91,7 @@ struct SafeSignal : sigc::signal...)> { Glib::Dispatcher dp_; std::mutex mutex_; std::queue queue_; + std::size_t max_queued_events_ = 4096; const std::thread::id main_tid_ = std::this_thread::get_id(); // cache functor for signal emission to avoid recreating it on each event const slot_t cached_fn_ = make_slot(); diff --git a/test/utils/SafeSignal.cpp b/test/utils/SafeSignal.cpp index e7e096b0..1502b7d9 100644 --- a/test/utils/SafeSignal.cpp +++ b/test/utils/SafeSignal.cpp @@ -9,6 +9,7 @@ #endif #include #include +#include #include "fixtures/GlibTestsFixture.hpp" @@ -141,3 +142,33 @@ TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal copy/move counter", "[signal][thr producer.join(); REQUIRE(count == NUM_EVENTS); } + +TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal queue stays bounded under burst load", + "[signal][thread][util][perf]") { + constexpr int NUM_EVENTS = 200; + constexpr std::size_t MAX_QUEUED_EVENTS = 8; + std::vector received; + + SafeSignal test_signal; + test_signal.set_max_queued_events(MAX_QUEUED_EVENTS); + + setTimeout(500); + + test_signal.connect([&](auto value) { received.push_back(value); }); + + run([&]() { + std::thread producer([&]() { + for (int i = 1; i <= NUM_EVENTS; ++i) { + test_signal.emit(i); + } + }); + producer.join(); + + Glib::signal_timeout().connect_once([this]() { this->quit(); }, 50); + }); + + REQUIRE(received.size() <= MAX_QUEUED_EVENTS); + REQUIRE_FALSE(received.empty()); + REQUIRE(received.back() == NUM_EVENTS); + REQUIRE(received.front() == NUM_EVENTS - static_cast(received.size()) + 1); +} From d0363313b879c7c7fb1a3cf36a10c943ff2b9c5a Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 9 Feb 2026 13:48:28 -0600 Subject: [PATCH 576/780] fix(hyprland-ipc): harden fd lifecycle and listener loop Hyprland IPC had fd lifecycle risks on failure/shutdown paths and used a spin-sleep listener model. I initialized fd state defensively, tightened connect/close/shutdown handling, moved to blocking read with newline framing, and added RAII-style fd cleanup in socket1 reply paths. Signed-off-by: Austin Horstman --- include/modules/hyprland/backend.hpp | 8 +- src/modules/hyprland/backend.cpp | 139 ++++++++++++++++++--------- 2 files changed, 101 insertions(+), 46 deletions(-) diff --git a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp index 2e0ef657..a6ebd191 100644 --- a/include/modules/hyprland/backend.hpp +++ b/include/modules/hyprland/backend.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -43,10 +44,11 @@ class IPC { std::thread ipcThread_; std::mutex callbackMutex_; + std::mutex socketMutex_; util::JsonParser parser_; std::list> callbacks_; - int socketfd_; // the hyprland socket file descriptor - pid_t socketOwnerPid_; - bool running_ = true; // the ipcThread will stop running when this is false + int socketfd_ = -1; // the hyprland socket file descriptor + pid_t socketOwnerPid_ = -1; + std::atomic running_ = true; // the ipcThread will stop running when this is false }; }; // namespace waybar::modules::hyprland diff --git a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp index 7060d304..4022d408 100644 --- a/src/modules/hyprland/backend.cpp +++ b/src/modules/hyprland/backend.cpp @@ -9,11 +9,35 @@ #include #include +#include +#include +#include #include #include namespace waybar::modules::hyprland { +namespace { +class ScopedFd { + public: + explicit ScopedFd(int fd) : fd_(fd) {} + ~ScopedFd() { + if (fd_ != -1) { + close(fd_); + } + } + + // ScopedFd is non-copyable + ScopedFd(const ScopedFd&) = delete; + ScopedFd& operator=(const ScopedFd&) = delete; + + int get() const { return fd_; } + + private: + int fd_; +}; +} // namespace + std::filesystem::path IPC::socketFolder_; std::filesystem::path IPC::getSocketFolder(const char* instanceSig) { @@ -45,8 +69,8 @@ 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(); + ipcThread_ = std::thread([this]() { socketListener(); }); } IPC::~IPC() { @@ -54,19 +78,20 @@ IPC::~IPC() { // failed exec()) exits. if (getpid() != socketOwnerPid_) return; - running_ = false; + running_.store(false, std::memory_order_relaxed); spdlog::info("Hyprland IPC stopping..."); - if (socketfd_ != -1) { - spdlog::trace("Shutting down socket"); - if (shutdown(socketfd_, SHUT_RDWR) == -1) { - spdlog::error("Hyprland IPC: Couldn't shutdown socket"); - } - spdlog::trace("Closing socket"); - if (close(socketfd_) == -1) { - spdlog::error("Hyprland IPC: Couldn't close socket"); + { + std::lock_guard lock(socketMutex_); + if (socketfd_ != -1) { + spdlog::trace("Shutting down socket"); + if (shutdown(socketfd_, SHUT_RDWR) == -1 && errno != ENOTCONN) { + spdlog::error("Hyprland IPC: Couldn't shutdown socket"); + } } } - ipcThread_.join(); + if (ipcThread_.joinable()) { + ipcThread_.join(); + } } IPC& IPC::inst() { @@ -86,9 +111,9 @@ void IPC::socketListener() { spdlog::info("Hyprland IPC starting"); struct sockaddr_un addr; - socketfd_ = socket(AF_UNIX, SOCK_STREAM, 0); + const int socketfd = socket(AF_UNIX, SOCK_STREAM, 0); - if (socketfd_ == -1) { + if (socketfd == -1) { spdlog::error("Hyprland IPC: socketfd failed"); return; } @@ -102,38 +127,67 @@ void IPC::socketListener() { int l = sizeof(struct sockaddr_un); - if (connect(socketfd_, (struct sockaddr*)&addr, l) == -1) { - spdlog::error("Hyprland IPC: Unable to connect?"); + if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) { + spdlog::error("Hyprland IPC: Unable to connect? {}", std::strerror(errno)); + close(socketfd); return; } - auto* file = fdopen(socketfd_, "r"); - if (file == nullptr) { - spdlog::error("Hyprland IPC: Couldn't open file descriptor"); - return; + + { + std::lock_guard lock(socketMutex_); + socketfd_ = socketfd; } - while (running_) { + + std::string pending; + while (running_.load(std::memory_order_relaxed)) { std::array buffer; // Hyprland socket2 events are max 1024 bytes + const ssize_t bytes_read = read(socketfd, buffer.data(), buffer.size()); - auto* receivedCharPtr = fgets(buffer.data(), buffer.size(), file); - - if (receivedCharPtr == nullptr) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - continue; + if (bytes_read == 0) { + if (running_.load(std::memory_order_relaxed)) { + spdlog::warn("Hyprland IPC: Socket closed by peer"); + } + break; } - std::string messageReceived(buffer.data()); - messageReceived = messageReceived.substr(0, messageReceived.find_first_of('\n')); - spdlog::debug("hyprland IPC received {}", messageReceived); - - try { - parseIPC(messageReceived); - } catch (std::exception& e) { - spdlog::warn("Failed to parse IPC message: {}, reason: {}", messageReceived, e.what()); - } catch (...) { - throw; + if (bytes_read < 0) { + if (errno == EINTR) { + continue; + } + if (!running_.load(std::memory_order_relaxed)) { + break; + } + spdlog::error("Hyprland IPC: read failed: {}", std::strerror(errno)); + break; } - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + pending.append(buffer.data(), static_cast(bytes_read)); + for (auto newline_pos = pending.find('\n'); newline_pos != std::string::npos; + newline_pos = pending.find('\n')) { + std::string messageReceived = pending.substr(0, newline_pos); + pending.erase(0, newline_pos + 1); + if (messageReceived.empty()) { + continue; + } + spdlog::debug("hyprland IPC received {}", messageReceived); + + try { + parseIPC(messageReceived); + } catch (std::exception& e) { + spdlog::warn("Failed to parse IPC message: {}, reason: {}", messageReceived, e.what()); + } catch (...) { + throw; + } + } + } + { + std::lock_guard lock(socketMutex_); + if (socketfd_ != -1) { + if (close(socketfd_) == -1) { + spdlog::error("Hyprland IPC: Couldn't close socket"); + } + socketfd_ = -1; + } } spdlog::debug("Hyprland IPC stopped"); } @@ -178,9 +232,9 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) { std::string IPC::getSocket1Reply(const std::string& rq) { // basically hyprctl - const auto serverSocket = socket(AF_UNIX, SOCK_STREAM, 0); + ScopedFd serverSocket(socket(AF_UNIX, SOCK_STREAM, 0)); - if (serverSocket < 0) { + if (serverSocket.get() < 0) { throw std::runtime_error("Hyprland IPC: Couldn't open a socket (1)"); } @@ -203,12 +257,13 @@ std::string IPC::getSocket1Reply(const std::string& rq) { throw std::runtime_error("Hyprland IPC: Couldn't copy socket path (6)"); } - if (connect(serverSocket, reinterpret_cast(&serverAddress), sizeof(serverAddress)) < + if (connect(serverSocket.get(), reinterpret_cast(&serverAddress), + sizeof(serverAddress)) < 0) { throw std::runtime_error("Hyprland IPC: Couldn't connect to " + socketPath + ". (3)"); } - auto sizeWritten = write(serverSocket, rq.c_str(), rq.length()); + auto sizeWritten = write(serverSocket.get(), rq.c_str(), rq.length()); if (sizeWritten < 0) { spdlog::error("Hyprland IPC: Couldn't write (4)"); @@ -219,17 +274,15 @@ std::string IPC::getSocket1Reply(const std::string& rq) { std::string response; do { - sizeWritten = read(serverSocket, buffer.data(), 8192); + sizeWritten = read(serverSocket.get(), buffer.data(), 8192); if (sizeWritten < 0) { spdlog::error("Hyprland IPC: Couldn't read (5)"); - close(serverSocket); return ""; } response.append(buffer.data(), sizeWritten); } while (sizeWritten > 0); - close(serverSocket); return response; } From 87a5b7ed0fd683bc565e89ece3f36412dbd0f648 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 9 Feb 2026 13:48:30 -0600 Subject: [PATCH 577/780] test(hyprland): add failure-path fd-leak coverage Hyprland tests did not explicitly verify descriptor behavior on key failure paths. I added focused tests for missing instance signature and connect-failure paths that assert file descriptor counts stay stable across repeated attempts. Signed-off-by: Austin Horstman --- test/hyprland/backend.cpp | 80 +++++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 11 deletions(-) diff --git a/test/hyprland/backend.cpp b/test/hyprland/backend.cpp index b83b839c..cc7295ec 100644 --- a/test/hyprland/backend.cpp +++ b/test/hyprland/backend.cpp @@ -4,56 +4,114 @@ #include #endif -#include "fixtures/IPCTestFixture.hpp" +#include + +#include "modules/hyprland/backend.hpp" namespace fs = std::filesystem; namespace hyprland = waybar::modules::hyprland; -TEST_CASE_METHOD(IPCTestFixture, "XDGRuntimeDirExists", "[getSocketFolder]") { +namespace { +class IPCTestHelper : public hyprland::IPC { + public: + static void resetSocketFolder() { socketFolder_.clear(); } +}; + +std::size_t countOpenFds() { +#if defined(__linux__) + std::size_t count = 0; + for (const auto& _ : fs::directory_iterator("/proc/self/fd")) { + (void)_; + ++count; + } + return count; +#else + return 0; +#endif +} +} // namespace + +TEST_CASE("XDGRuntimeDirExists", "[getSocketFolder]") { // Test case: XDG_RUNTIME_DIR exists and contains "hypr" directory // Arrange - tempDir = fs::temp_directory_path() / "hypr_test/run/user/1000"; + constexpr auto instanceSig = "instance_sig"; + const fs::path tempDir = fs::temp_directory_path() / "hypr_test/run/user/1000"; + std::error_code ec; + fs::remove_all(tempDir, ec); fs::path expectedPath = tempDir / "hypr" / instanceSig; - fs::create_directories(tempDir / "hypr" / instanceSig); + fs::create_directories(expectedPath); setenv("XDG_RUNTIME_DIR", tempDir.c_str(), 1); + IPCTestHelper::resetSocketFolder(); // Act - fs::path actualPath = getSocketFolder(instanceSig); + fs::path actualPath = hyprland::IPC::getSocketFolder(instanceSig); // Assert expected result REQUIRE(actualPath == expectedPath); + fs::remove_all(tempDir, ec); } -TEST_CASE_METHOD(IPCTestFixture, "XDGRuntimeDirDoesNotExist", "[getSocketFolder]") { +TEST_CASE("XDGRuntimeDirDoesNotExist", "[getSocketFolder]") { // Test case: XDG_RUNTIME_DIR does not exist // Arrange + constexpr auto instanceSig = "instance_sig"; unsetenv("XDG_RUNTIME_DIR"); fs::path expectedPath = fs::path("/tmp") / "hypr" / instanceSig; + IPCTestHelper::resetSocketFolder(); // Act - fs::path actualPath = getSocketFolder(instanceSig); + fs::path actualPath = hyprland::IPC::getSocketFolder(instanceSig); // Assert expected result REQUIRE(actualPath == expectedPath); } -TEST_CASE_METHOD(IPCTestFixture, "XDGRuntimeDirExistsNoHyprDir", "[getSocketFolder]") { +TEST_CASE("XDGRuntimeDirExistsNoHyprDir", "[getSocketFolder]") { // Test case: XDG_RUNTIME_DIR exists but does not contain "hypr" directory // Arrange + constexpr auto instanceSig = "instance_sig"; fs::path tempDir = fs::temp_directory_path() / "hypr_test/run/user/1000"; + std::error_code ec; + fs::remove_all(tempDir, ec); fs::create_directories(tempDir); setenv("XDG_RUNTIME_DIR", tempDir.c_str(), 1); fs::path expectedPath = fs::path("/tmp") / "hypr" / instanceSig; + IPCTestHelper::resetSocketFolder(); // Act - fs::path actualPath = getSocketFolder(instanceSig); + fs::path actualPath = hyprland::IPC::getSocketFolder(instanceSig); // Assert expected result REQUIRE(actualPath == expectedPath); + fs::remove_all(tempDir, ec); } -TEST_CASE_METHOD(IPCTestFixture, "getSocket1Reply throws on no socket", "[getSocket1Reply]") { +TEST_CASE("getSocket1Reply throws on no socket", "[getSocket1Reply]") { + unsetenv("HYPRLAND_INSTANCE_SIGNATURE"); + IPCTestHelper::resetSocketFolder(); std::string request = "test_request"; - CHECK_THROWS(getSocket1Reply(request)); + CHECK_THROWS(hyprland::IPC::getSocket1Reply(request)); } + +#if defined(__linux__) +TEST_CASE("getSocket1Reply failure paths do not leak fds", "[getSocket1Reply][fd-leak]") { + const auto baseline = countOpenFds(); + + unsetenv("HYPRLAND_INSTANCE_SIGNATURE"); + for (int i = 0; i < 16; ++i) { + IPCTestHelper::resetSocketFolder(); + CHECK_THROWS(hyprland::IPC::getSocket1Reply("test_request")); + } + const auto after_missing_signature = countOpenFds(); + REQUIRE(after_missing_signature == baseline); + + setenv("HYPRLAND_INSTANCE_SIGNATURE", "definitely-not-running", 1); + for (int i = 0; i < 16; ++i) { + IPCTestHelper::resetSocketFolder(); + CHECK_THROWS(hyprland::IPC::getSocket1Reply("test_request")); + } + const auto after_connect_failures = countOpenFds(); + REQUIRE(after_connect_failures == baseline); +} +#endif From 04ddc5fd23cfad281f725eda3160f9c5ef4a0ee5 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 9 Feb 2026 14:01:45 -0600 Subject: [PATCH 578/780] chore(test-hyprland): remove unused IPC test fixture The hyprland IPC fixture was no longer used by the current test setup. I removed the dead fixture so the test code reflects the actual execution path and is easier to maintain. Signed-off-by: Austin Horstman --- test/hyprland/fixtures/IPCTestFixture.hpp | 25 ----------------------- 1 file changed, 25 deletions(-) delete mode 100644 test/hyprland/fixtures/IPCTestFixture.hpp diff --git a/test/hyprland/fixtures/IPCTestFixture.hpp b/test/hyprland/fixtures/IPCTestFixture.hpp deleted file mode 100644 index caa92975..00000000 --- a/test/hyprland/fixtures/IPCTestFixture.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "modules/hyprland/backend.hpp" - -namespace fs = std::filesystem; -namespace hyprland = waybar::modules::hyprland; - -class IPCTestFixture : public hyprland::IPC { - public: - IPCTestFixture() : IPC() { IPC::socketFolder_ = ""; } - ~IPCTestFixture() { fs::remove_all(tempDir); } - - protected: - const char* instanceSig = "instance_sig"; - fs::path tempDir = fs::temp_directory_path() / "hypr_test"; - - private: -}; - -class IPCMock : public IPCTestFixture { - public: - // Mock getSocket1Reply to return an empty string - static std::string getSocket1Reply(const std::string& rq) { return ""; } - - protected: - const char* instanceSig = "instance_sig"; -}; From 6dfe1c31111602fe2f92ca81e0907ec1f5903639 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 2 Mar 2026 08:21:12 -0600 Subject: [PATCH 579/780] feat(util): add ScopedFd RAII utility for file descriptors Signed-off-by: Austin Horstman --- include/util/scoped_fd.hpp | 54 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 include/util/scoped_fd.hpp diff --git a/include/util/scoped_fd.hpp b/include/util/scoped_fd.hpp new file mode 100644 index 00000000..e970109e --- /dev/null +++ b/include/util/scoped_fd.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include + +namespace waybar::util { + +class ScopedFd { + public: + explicit ScopedFd(int fd = -1) : fd_(fd) {} + ~ScopedFd() { + if (fd_ != -1) { + close(fd_); + } + } + + // ScopedFd is non-copyable + ScopedFd(const ScopedFd&) = delete; + ScopedFd& operator=(const ScopedFd&) = delete; + + // ScopedFd is moveable + ScopedFd(ScopedFd&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; } + ScopedFd& operator=(ScopedFd&& other) noexcept { + if (this != &other) { + if (fd_ != -1) { + close(fd_); + } + fd_ = other.fd_; + other.fd_ = -1; + } + return *this; + } + + int get() const { return fd_; } + + operator int() const { return fd_; } + + void reset(int fd = -1) { + if (fd_ != -1) { + close(fd_); + } + fd_ = fd; + } + + int release() { + int fd = fd_; + fd_ = -1; + return fd; + } + + private: + int fd_; +}; + +} // namespace waybar::util From e83ab7609c7e6f5eb3eb038c905ba37ea1ed22b8 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 2 Mar 2026 08:21:15 -0600 Subject: [PATCH 580/780] refactor(hyprland): use shared ScopedFd utility Signed-off-by: Austin Horstman --- src/modules/hyprland/backend.cpp | 34 +++++++------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp index 4022d408..0f02b919 100644 --- a/src/modules/hyprland/backend.cpp +++ b/src/modules/hyprland/backend.cpp @@ -15,29 +15,10 @@ #include #include +#include "util/scoped_fd.hpp" + namespace waybar::modules::hyprland { -namespace { -class ScopedFd { - public: - explicit ScopedFd(int fd) : fd_(fd) {} - ~ScopedFd() { - if (fd_ != -1) { - close(fd_); - } - } - - // ScopedFd is non-copyable - ScopedFd(const ScopedFd&) = delete; - ScopedFd& operator=(const ScopedFd&) = delete; - - int get() const { return fd_; } - - private: - int fd_; -}; -} // namespace - std::filesystem::path IPC::socketFolder_; std::filesystem::path IPC::getSocketFolder(const char* instanceSig) { @@ -232,9 +213,9 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) { std::string IPC::getSocket1Reply(const std::string& rq) { // basically hyprctl - ScopedFd serverSocket(socket(AF_UNIX, SOCK_STREAM, 0)); + util::ScopedFd serverSocket(socket(AF_UNIX, SOCK_STREAM, 0)); - if (serverSocket.get() < 0) { + if (serverSocket < 0) { throw std::runtime_error("Hyprland IPC: Couldn't open a socket (1)"); } @@ -257,13 +238,12 @@ std::string IPC::getSocket1Reply(const std::string& rq) { throw std::runtime_error("Hyprland IPC: Couldn't copy socket path (6)"); } - if (connect(serverSocket.get(), reinterpret_cast(&serverAddress), - sizeof(serverAddress)) < + if (connect(serverSocket, reinterpret_cast(&serverAddress), sizeof(serverAddress)) < 0) { throw std::runtime_error("Hyprland IPC: Couldn't connect to " + socketPath + ". (3)"); } - auto sizeWritten = write(serverSocket.get(), rq.c_str(), rq.length()); + auto sizeWritten = write(serverSocket, rq.c_str(), rq.length()); if (sizeWritten < 0) { spdlog::error("Hyprland IPC: Couldn't write (4)"); @@ -274,7 +254,7 @@ std::string IPC::getSocket1Reply(const std::string& rq) { std::string response; do { - sizeWritten = read(serverSocket.get(), buffer.data(), 8192); + sizeWritten = read(serverSocket, buffer.data(), 8192); if (sizeWritten < 0) { spdlog::error("Hyprland IPC: Couldn't read (5)"); From 2ff77fb73dd2e14d399c22fa77242aea1fbf8a8f Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 2 Mar 2026 08:21:17 -0600 Subject: [PATCH 581/780] refactor(niri): use shared ScopedFd utility Signed-off-by: Austin Horstman --- src/modules/niri/backend.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/modules/niri/backend.cpp b/src/modules/niri/backend.cpp index 68bb1724..c23aaa47 100644 --- a/src/modules/niri/backend.cpp +++ b/src/modules/niri/backend.cpp @@ -13,6 +13,7 @@ #include #include +#include "util/scoped_fd.hpp" #include "giomm/datainputstream.h" #include "giomm/dataoutputstream.h" #include "giomm/unixinputstream.h" @@ -30,7 +31,7 @@ int IPC::connectToSocket() { } struct sockaddr_un addr; - int socketfd = socket(AF_UNIX, SOCK_STREAM, 0); + util::ScopedFd socketfd(socket(AF_UNIX, SOCK_STREAM, 0)); if (socketfd == -1) { throw std::runtime_error("socketfd failed"); @@ -45,11 +46,10 @@ int IPC::connectToSocket() { int l = sizeof(struct sockaddr_un); if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) { - close(socketfd); throw std::runtime_error("unable to connect"); } - return socketfd; + return socketfd.release(); } void IPC::startIPC() { @@ -235,7 +235,7 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) { } Json::Value IPC::send(const Json::Value& request) { - int socketfd = connectToSocket(); + util::ScopedFd socketfd(connectToSocket()); auto unix_istream = Gio::UnixInputStream::create(socketfd, true); auto unix_ostream = Gio::UnixOutputStream::create(socketfd, false); From 39e09118f9943c62000542cd35af735b5f9826a1 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 2 Mar 2026 08:21:20 -0600 Subject: [PATCH 582/780] refactor(wayfire): replace custom Sock with shared ScopedFd Signed-off-by: Austin Horstman --- include/modules/wayfire/backend.hpp | 20 +++----------------- src/modules/wayfire/backend.cpp | 11 +++++------ 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/include/modules/wayfire/backend.hpp b/include/modules/wayfire/backend.hpp index d3173269..e1f259cc 100644 --- a/include/modules/wayfire/backend.hpp +++ b/include/modules/wayfire/backend.hpp @@ -12,6 +12,8 @@ #include #include +#include "util/scoped_fd.hpp" + namespace waybar::modules::wayfire { using EventHandler = std::function; @@ -71,23 +73,7 @@ struct State { auto update_view(const Json::Value& view) -> void; }; -struct Sock { - int fd; - - Sock(int fd) : fd{fd} {} - ~Sock() { close(fd); } - Sock(const Sock&) = delete; - auto operator=(const Sock&) = delete; - Sock(Sock&& rhs) noexcept { - fd = rhs.fd; - rhs.fd = -1; - } - auto& operator=(Sock&& rhs) noexcept { - fd = rhs.fd; - rhs.fd = -1; - return *this; - } -}; +using Sock = util::ScopedFd; class IPC : public std::enable_shared_from_this { static std::weak_ptr instance; diff --git a/src/modules/wayfire/backend.cpp b/src/modules/wayfire/backend.cpp index 545aaa89..42976d20 100644 --- a/src/modules/wayfire/backend.cpp +++ b/src/modules/wayfire/backend.cpp @@ -27,14 +27,14 @@ inline auto byteswap(uint32_t x) -> uint32_t { auto pack_and_write(Sock& sock, std::string&& buf) -> void { uint32_t len = buf.size(); if constexpr (std::endian::native != std::endian::little) len = byteswap(len); - (void)write(sock.fd, &len, 4); - (void)write(sock.fd, buf.data(), buf.size()); + (void)write(sock, &len, 4); + (void)write(sock, buf.data(), buf.size()); } auto read_exact(Sock& sock, size_t n) -> std::string { auto buf = std::string(n, 0); for (size_t i = 0; i < n;) { - auto r = read(sock.fd, &buf[i], n - i); + auto r = read(sock, &buf[i], n - i); if (r <= 0) { throw std::runtime_error("Wayfire IPC: read failed"); } @@ -111,7 +111,7 @@ auto IPC::connect() -> Sock { throw std::runtime_error{"Wayfire IPC: ipc not available"}; } - auto sock = socket(AF_UNIX, SOCK_STREAM, 0); + util::ScopedFd sock(socket(AF_UNIX, SOCK_STREAM, 0)); if (sock == -1) { throw std::runtime_error{"Wayfire IPC: socket() failed"}; } @@ -121,11 +121,10 @@ auto IPC::connect() -> Sock { addr.sun_path[sizeof(addr.sun_path) - 1] = 0; if (::connect(sock, (const sockaddr*)&addr, sizeof(addr)) == -1) { - close(sock); throw std::runtime_error{"Wayfire IPC: connect() failed"}; } - return {sock}; + return sock; } auto IPC::receive(Sock& sock) -> Json::Value { From 8d22d3e07a629470114edcebabe6d6fb6afe85cf Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 2 Mar 2026 08:23:06 -0600 Subject: [PATCH 583/780] refactor(sway): use shared ScopedFd for IPC sockets Signed-off-by: Austin Horstman --- include/modules/sway/ipc/client.hpp | 5 +++-- src/modules/sway/ipc/client.cpp | 12 ++++-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/include/modules/sway/ipc/client.hpp b/include/modules/sway/ipc/client.hpp index 281df7ab..eb0f32f9 100644 --- a/include/modules/sway/ipc/client.hpp +++ b/include/modules/sway/ipc/client.hpp @@ -14,6 +14,7 @@ #include "ipc.hpp" #include "util/SafeSignal.hpp" #include "util/sleeper_thread.hpp" +#include "util/scoped_fd.hpp" namespace waybar::modules::sway { @@ -45,8 +46,8 @@ class Ipc { struct ipc_response send(int fd, uint32_t type, const std::string& payload = ""); struct ipc_response recv(int fd); - int fd_; - int fd_event_; + util::ScopedFd fd_; + util::ScopedFd fd_event_; std::mutex mutex_; util::SleeperThread thread_; }; diff --git a/src/modules/sway/ipc/client.cpp b/src/modules/sway/ipc/client.cpp index 4139a53b..3ebccccd 100644 --- a/src/modules/sway/ipc/client.cpp +++ b/src/modules/sway/ipc/client.cpp @@ -9,8 +9,8 @@ namespace waybar::modules::sway { Ipc::Ipc() { const std::string& socketPath = getSocketPath(); - fd_ = open(socketPath); - fd_event_ = open(socketPath); + fd_ = util::ScopedFd(open(socketPath)); + fd_event_ = util::ScopedFd(open(socketPath)); } Ipc::~Ipc() { @@ -21,15 +21,11 @@ Ipc::~Ipc() { if (write(fd_, "close-sway-ipc", 14) == -1) { spdlog::error("Failed to close sway IPC"); } - close(fd_); - fd_ = -1; } if (fd_event_ > 0) { if (write(fd_event_, "close-sway-ipc", 14) == -1) { spdlog::error("Failed to close sway IPC event handler"); } - close(fd_event_); - fd_event_ = -1; } } @@ -64,7 +60,7 @@ const std::string Ipc::getSocketPath() const { } int Ipc::open(const std::string& socketPath) const { - int32_t fd = socket(AF_UNIX, SOCK_STREAM, 0); + util::ScopedFd fd(socket(AF_UNIX, SOCK_STREAM, 0)); if (fd == -1) { throw std::runtime_error("Unable to open Unix socket"); } @@ -78,7 +74,7 @@ int Ipc::open(const std::string& socketPath) const { if (::connect(fd, reinterpret_cast(&addr), l) == -1) { throw std::runtime_error("Unable to connect to Sway"); } - return fd; + return fd.release(); } struct Ipc::ipc_response Ipc::recv(int fd) { From 5e7dbf171542a3e1a0457488a23c7562cbec0fd1 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 9 Feb 2026 13:12:42 -0600 Subject: [PATCH 584/780] fix(command): return non-zero when child exec fails Child exec failure paths were returning success, which masked command launch errors from callers. I switched the child-side failure exits to _exit(127) and added errno-specific logging so failures propagate with actionable diagnostics. Signed-off-by: Austin Horstman --- include/util/command.hpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/include/util/command.hpp b/include/util/command.hpp index b1adcd7c..f6d2cabf 100644 --- a/include/util/command.hpp +++ b/include/util/command.hpp @@ -20,6 +20,8 @@ extern std::list reap; namespace waybar::util::command { +constexpr int kExecFailureExitCode = 127; + struct res { int exit_code; std::string out; @@ -114,7 +116,9 @@ inline FILE* open(const std::string& cmd, int& pid, const std::string& output_na setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1); } execlp("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); - exit(0); + const int saved_errno = errno; + spdlog::error("execlp(/bin/sh) failed in open: {}", strerror(saved_errno)); + _exit(kExecFailureExitCode); } else { ::close(fd[1]); } @@ -162,7 +166,9 @@ inline int32_t forkExec(const std::string& cmd, const std::string& output_name) setenv("WAYBAR_OUTPUT_NAME", output_name.c_str(), 1); } execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0); - exit(0); + const int saved_errno = errno; + spdlog::error("execl(/bin/sh) failed in forkExec: {}", strerror(saved_errno)); + _exit(kExecFailureExitCode); } else { reap_mtx.lock(); reap.push_back(pid); From 79fb1d9f58d15cb3704a62359a7457451cbc0164 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 9 Feb 2026 13:12:45 -0600 Subject: [PATCH 585/780] test(command): cover exec failure paths Command tests did not assert behavior when exec fails in child processes. I added deterministic regression coverage that forces execl/execlp failure and verifies non-zero exit status propagation for both open() and forkExec paths. Signed-off-by: Austin Horstman --- test/utils/command.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++ test/utils/meson.build | 1 + 2 files changed, 57 insertions(+) create mode 100644 test/utils/command.cpp diff --git a/test/utils/command.cpp b/test/utils/command.cpp new file mode 100644 index 00000000..2ccb3383 --- /dev/null +++ b/test/utils/command.cpp @@ -0,0 +1,56 @@ +#if __has_include() +#include +#else +#include +#endif + +#include +#include +#include +#include +#include + +std::mutex reap_mtx; +std::list reap; + +extern "C" int waybar_test_execl(const char* path, const char* arg, ...); +extern "C" int waybar_test_execlp(const char* file, const char* arg, ...); + +#define execl waybar_test_execl +#define execlp waybar_test_execlp +#include "util/command.hpp" +#undef execl +#undef execlp + +extern "C" int waybar_test_execl(const char* path, const char* arg, ...) { + (void)path; + (void)arg; + errno = ENOENT; + return -1; +} + +extern "C" int waybar_test_execlp(const char* file, const char* arg, ...) { + (void)file; + (void)arg; + errno = ENOENT; + return -1; +} + +TEST_CASE("command::execNoRead returns 127 when shell exec fails", "[util][command]") { + const auto result = waybar::util::command::execNoRead("echo should-not-run"); + REQUIRE(result.exit_code == waybar::util::command::kExecFailureExitCode); + REQUIRE(result.out.empty()); +} + +TEST_CASE("command::forkExec child exits 127 when shell exec fails", "[util][command]") { + const auto pid = waybar::util::command::forkExec("echo should-not-run", "test-output"); + REQUIRE(pid > 0); + + int status = -1; + REQUIRE(waitpid(pid, &status, 0) == pid); + REQUIRE(WIFEXITED(status)); + REQUIRE(WEXITSTATUS(status) == waybar::util::command::kExecFailureExitCode); + + std::scoped_lock lock(reap_mtx); + reap.remove(pid); +} diff --git a/test/utils/meson.build b/test/utils/meson.build index 050af262..e8dd37fa 100644 --- a/test/utils/meson.build +++ b/test/utils/meson.build @@ -14,6 +14,7 @@ test_src = files( 'JsonParser.cpp', 'SafeSignal.cpp', 'sleeper_thread.cpp', + 'command.cpp', 'css_reload_helper.cpp', '../../src/util/css_reload_helper.cpp', ) From d929f1a62c764a4cff0897540bc00bf24beb0d5e Mon Sep 17 00:00:00 2001 From: Bryce Gust Date: Mon, 2 Mar 2026 17:58:00 -0600 Subject: [PATCH 586/780] Fix 4894 - float interval cast --- src/ALabel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ALabel.cpp b/src/ALabel.cpp index 0d92c372..fe87e098 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -26,7 +26,7 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st : std::chrono::milliseconds( (config_["interval"].isNumeric() ? std::max(1L, // Minimum 1ms due to millisecond precision - static_cast(config_["interval"].asDouble()) * 1000) + static_cast(config_["interval"].asDouble() * 1000)) : 1000 * (long)interval))), default_format_(format_) { label_.set_name(name); From 0c8acbf39e4ce88adea5ff9f2eebaab7c6bb9cf0 Mon Sep 17 00:00:00 2001 From: Nick Janetakis Date: Mon, 2 Mar 2026 19:10:55 -0500 Subject: [PATCH 587/780] Add support for optional CSS classes --- src/modules/clock.cpp | 46 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 5fe5407b..88f2ec69 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -187,6 +187,52 @@ auto waybar::modules::Clock::update() -> void { } m_tlpText_ = fmt_lib::vformat(m_locale_, m_tlpText_, fmt_lib::make_format_args(now)); + + // Pango doesn't support CSS classes but to continue using it while staying + // backwards compatible this approach uses post-posting to replace fake + // classes with attributes Pango does understand. + // + // The benefit of this approach is anyone using the original styling choices + // can continue doing that and folks can optionally opt into using classes. + // + // It's also forwards compatible to where if this implemention ever changes + // to support proper classes anyone using them will continue to work. + auto context = label_.get_style_context(); + + const std::vector> calendar_class_map = { + {"calendar-today", "today"}, + {"calendar-days", "days"}, + {"calendar-weeks", "weeks"}, + {"calendar-weekdays", "weekdays"}, + {"calendar-months", "months"} + }; + + for (const auto& [css_class, json_key] : calendar_class_map) { + try { + context->add_class(css_class); + Gdk::RGBA color = context->get_color(); + context->remove_class(css_class); + + std::string hex = fmt::format("#{:02x}{:02x}{:02x}", + static_cast(color.get_red() * 255), + static_cast(color.get_green() * 255), + static_cast(color.get_blue() * 255)); + + std::string search = "class='" + json_key + "'"; + std::string replace = "color='" + hex + "'"; + + for (size_t pos = 0; (pos = m_tlpText_.find(search, pos)) != std::string::npos; pos += replace.length()) { + m_tlpText_.replace(pos, search.length(), replace); + } + } catch (const Glib::Error& e) { + spdlog::warn("Clock: Failed to fetch CSS color for {}: {}", css_class, e.what().raw()); + continue; + } catch (...) { + // Catch-all for any other weirdness. + continue; + } + } + m_tooltip_->set_markup(m_tlpText_); label_.trigger_tooltip_query(); } From c85f2a23de79198fd67b46b09d0fe9a90cfb4797 Mon Sep 17 00:00:00 2001 From: Nick Janetakis Date: Mon, 2 Mar 2026 21:29:15 -0500 Subject: [PATCH 588/780] Add readability improvements and optimizations Replace the inner loop with regex_replace. We're already using this elsewhere and the performance difference isn't worth the loss of human readability. Use static const to pre-calc the search string. --- src/modules/clock.cpp | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 88f2ec69..ec8b71e7 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -199,31 +199,26 @@ auto waybar::modules::Clock::update() -> void { // to support proper classes anyone using them will continue to work. auto context = label_.get_style_context(); - const std::vector> calendar_class_map = { - {"calendar-today", "today"}, - {"calendar-days", "days"}, - {"calendar-weeks", "weeks"}, - {"calendar-weekdays", "weekdays"}, - {"calendar-months", "months"} + static const std::vector> calendar_class_map = { + {"calendar-today", "class='today'"}, + {"calendar-days", "class='days'"}, + {"calendar-weeks", "class='weeks'"}, + {"calendar-weekdays", "class='weekdays'"}, + {"calendar-months", "class='months'"} }; - for (const auto& [css_class, json_key] : calendar_class_map) { + for (const auto& [css_class, search_str] : calendar_class_map) { try { context->add_class(css_class); - Gdk::RGBA color = context->get_color(); + const Gdk::RGBA color = context->get_color(); context->remove_class(css_class); - std::string hex = fmt::format("#{:02x}{:02x}{:02x}", - static_cast(color.get_red() * 255), + const std::string replace_str = fmt::format("color='#{:02x}{:02x}{:02x}'", + static_cast(color.get_red() * 255, static_cast(color.get_green() * 255), static_cast(color.get_blue() * 255)); - std::string search = "class='" + json_key + "'"; - std::string replace = "color='" + hex + "'"; - - for (size_t pos = 0; (pos = m_tlpText_.find(search, pos)) != std::string::npos; pos += replace.length()) { - m_tlpText_.replace(pos, search.length(), replace); - } + m_tlpText_ = std::regex_replace(m_tlpText_, std::regex(search_str), replace_str); } catch (const Glib::Error& e) { spdlog::warn("Clock: Failed to fetch CSS color for {}: {}", css_class, e.what().raw()); continue; From 25089b24567c853ad79d09175f2de9dcfe4127f9 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 2 Mar 2026 21:22:53 -0600 Subject: [PATCH 589/780] fix(network): align tooltip and tooltip text Closes https://github.com/Alexays/Waybar/issues/4867 Signed-off-by: Austin Horstman --- src/modules/network.cpp | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 34dcc03c..e0866011 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -373,18 +373,26 @@ auto waybar::modules::Network::update() -> void { 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")), + 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(), "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("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")), - fmt::arg("bandwidthTotalBytes", - pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s"))); + 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() / 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() / 1000.0), "B/s"))); if (label_.get_tooltip_text() != tooltip_text) { label_.set_tooltip_markup(tooltip_text); } From 4c71b2bf9f2636d2ff3c5702eadcc0c02aba3b3d Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 2 Mar 2026 21:51:32 -0600 Subject: [PATCH 590/780] perf(memory): optimize C++ string operations to reduce heap fragmentation - Replaced pass-by-value std::string parameters with const std::string& or std::string_view to prevent SSO overallocations. - Refactored static mapping functions in UPower to return std::string_view instead of constructing std::string literals, enabling perfect cache locality. - Optimized string concatenation in hot loops (network IPs, inhibitor lists, sway window marks) by using std::string::append() and pre-reserving capacity instead of overloaded operator+ which produces temporary heap instances. These optimizations reduce high-frequency memory churn and overall heap fragmentation within the main rendering loops. Signed-off-by: Austin Horstman --- heaptrack.waybar.2711357.zst | Bin 0 -> 12780 bytes include/modules/disk.hpp | 2 +- .../modules/hyprland/windowcreationpayload.hpp | 7 ++++--- include/modules/hyprland/workspaces.hpp | 2 +- include/modules/mpd/mpd.hpp | 2 +- include/modules/sni/host.hpp | 2 +- include/modules/sway/bar.hpp | 2 +- include/modules/sway/language.hpp | 2 +- include/modules/sway/workspaces.hpp | 4 ++-- include/util/SafeSignal.hpp | 2 +- include/util/sleeper_thread.hpp | 9 +++------ src/modules/disk.cpp | 2 +- src/modules/hyprland/windowcreationpayload.cpp | 9 +++++---- src/modules/hyprland/workspaces.cpp | 3 ++- src/modules/inhibitor.cpp | 3 ++- src/modules/mpd/mpd.cpp | 3 ++- src/modules/network.cpp | 1 + src/modules/sni/host.cpp | 2 +- src/modules/sway/bar.cpp | 2 +- src/modules/sway/language.cpp | 2 +- src/modules/sway/window.cpp | 4 ++-- src/modules/sway/workspaces.cpp | 4 ++-- src/modules/upower.cpp | 7 ++++--- test/utils/sleeper_thread.cpp | 5 +++-- 24 files changed, 43 insertions(+), 38 deletions(-) create mode 100644 heaptrack.waybar.2711357.zst diff --git a/heaptrack.waybar.2711357.zst b/heaptrack.waybar.2711357.zst new file mode 100644 index 0000000000000000000000000000000000000000..88921cba1ce1ab95825bb77f62a6f1a72e958a87 GIT binary patch literal 12780 zcmVfcXI_LP$zl z>ZVNYEd9Ux!wYi<9S1%JPU1-XD9tlzkvz}NLuTScW@et4NgkZnN5!c2D5eEdvoMiG zjHVx?L?sJ!!Xl(4SRvUE*HjXjkmpwz6b++zNn_}y6ceIB+EY*qL~)p=@(xnrNs=#A zj{M9tgYz7m%p@jGJ>n`ziMT9LlL{}SS0uy3c=6STx1_`5f#X3RNKzSugcw9USW*i^ z;wj8!Y98Vi&M6pK3P)9$vsvV8s4^c>0(xD@)+<@Kk{A^8DYIFKTFVnfhP@A(kHumj zR_6+-n8@K%G%<<7;LJ>x6IIQADNI_r&%rlE?TsK>ygF59%P~ z(Mj_h&>wP2D^f|M77D`U(6BbVN&ONf2RR)KjB`VnZ;nNgxM3U#9{SJ(Pv^j# znTgmTsg(`3gsahd$0X z36mTVsYy*BhQxGG4$pDeqfnRxN<3NiyR+KS-gL}2Ks;XqGVTy@ztQH#^BEur~;v|+s?L}G^A{oO-O-mRzKs*EHIhIyPn2CsbsnOD$BEj8i;` ziHY+~5uF2%_3=x>B+D@u3O$PFXo;d|GkI8fB7;N@b-uy?q5qWh!Ii=BfDP2)(6|gJ z4RsRfK$t_m7rD7$s+2I9s1^Cv z%rkIY93O~e4m^F7fu?p7TQVsQo5Fepr8wL~3%D>a&lHy`6-YW%@k(%P0sObySk_uOl-l9qpBQc9bH!Fq66j41uJk0|?6ecFh zF}tzEl+?#8m`p5>3{N3~uN0~fId3wt2e1cxKm!ev#6&p?&%z~|Yb>9IMbDE+2Ss6$ z1>YykEMEh}Gf;!ZhaVv17+~n~48$1|<&Y9*K2t%8i|DxDBR(wrNaaXG5lKkHqzw4`pbQU!KSfyJ<;gaMsrd!xm(=gsIIGLnMd~+8gxIFp2mal(h~n zCSH}{u~z&LqeW4g1yX19gP98s5OPRpSTOD=_(7V*f+@)`Ne0DK9L@RGT+$UxAf7=RzyTjkBN@bF zI2MCju-MB=L{UYDMWi@SVMx|=STLd%PSv0fi6lH|9`qp)NF;e;q8!SCG3rI6P*4g3 z62YXPNSuV@z9e;6mWkOuM&m)_0UroLnay*!TP?v7r$t=w>3JoIx(zI0R8tm`;i+pM zqj{-3Xg;8&9t(UvBq|pc(StGA4=w57BtIf#QKWK6PU@FN#RA8_R7`}=%#Tx|= z*D{_gFy7j?Z@9J5$!1=G(H2{4ZDW`x1!b3k*oi9JWLw~-J zX^n9YqprYk-hAI%fx&#a`#Rs|%XsqRHRlQp<@?yaQE9JazFNV&_r?WXzcnYIJjBrD zCo@n9v{67Cf4P?*S76B4JL3wBZ`iN*e+7o?)~r`Uw_bic47dWrd%O27^j_Q}$yQ)AX1sVG3Jm9FX2rgBAw$Y$>^TU>p#o#=8+0vQ$Y3nF#a@9i z<<5nSIMu(G3mFhH2YCfXT-E!=Z(PXu=AJqP1nw_nz_lEt$U6w=Av6OJeV*Y$Mx)C8 zQ!6musDGTz5n%N8=2irnJEgYyFk0dJ{;m~J*067mm1Z5JIM$7VwH;dwd zA9Q);fG%k;l2-Mfa;k;G(_q0xhkas@^F8VVKD-%emgFg9X^b95vfB&s?%JH z4q3$XONR23LDsjQ-6ET_zT#0h&75tDS5XDRmN_1ueKPZ$aiRVy>#-Ly(87(Aw;-D~v!F6

8c_Xq;3TpIT#h#-esC;Z$T%B*j`tQ(N=6i5_*i$qnWLQHSqkmALPeChM2W)bM1pWK)ZsV0xfH94rYa)AB|+r?5niy>TMCQ0DU=kN zcaURwr986r;Wf>i4v9**QXlmc9`bOusGSFW&_)4K>eM4)rz*PrR8o{w;h+#_6(N6E z&78rosN@Tf4|t%C|CKkx-i-#@mX&g(Q z#q@MRLf<^eT;SP4VaH-R6s1}tsi+kzY&yk*6H^*S#TOND77nbHWjb0}rWbug6e$ae zMY)=g25@U8g&X#6O#!SgnVgXd}8f!4AK*>DH_76QwKHKfC{K!&r5wP z%VRuQB;=sp!07_UZ&#(nAT{KmAjq@~l5_scy_8r5W&pAHfY+dv1R6LNYqABxi{za( zRycxh!k@HXEN^~Q9jeS6A69=1H!#vD#MM5s$z1rHBbkIk_XW&2&sso z#_*rli#-7FtD7)7}q=gYStJkKzTK z1XJTLh>VY1NKcA>y~qltzay6@PC*l0zz2Ll6P+H(O>(WpbQX_n)Wb~9YfUN2Kc=x) z5b7WvXJ7#}C3Q+>VKxT{6|0{+NYc3iQm72$(mQo{s7g5BJPIa(4%mjM6v#w}B=L^} zbIurJj5Edv*{vDl&;Os_`|y2llzSL!uE22L-#dM-!0_+yef=D3OCCnghrO@Y^{zI1 zRqyQW=IHb$=X0>PnXIihMk>2^_C93Ts}$^wat}kw?w!4@ds$B=FyiZdulKd-@i1P# z*ZX=Ml-;kH>*Yz%zX_b`}TfdO6b>z(f!^uutuo*zcbUez0W zo$GDh%ejIH&)UdLju>UIY^GR9bBj77#6i>y~I#v)^k zt+-b2>y@nQ>n8s&TI^Na`+8mPY94zhyM+w)m79#++e~`%>W#4lhFZ1vVGE4uExk9o zJ`5GRclNgKWj$H%>)zKZnR1c3!=Ufp`+D8{Ue!B$yH& z_cHEX+`D`(ZZfVtj0pc#t5&UgS9{f}Rm&w+d)2B{SJ)e&YSpSMth}#lg|OCItH6Nu zRaawF4MJFJtrgZAB1zx3FqXUyxrb| zl}X5}81puhEil$(-uPBv^km*Cd0{`^$Gp)UhMZDL-uW^@`J|Js{d|?l!}up9OJ2vj znzzTRn73PdGx9c*Eo0=3FTTk8uw&$n?l9J4j=ZgTSx*)tKd)r0Eo3mJDm#pBI%V`G zF#dTJ{BDP#-aqf-b-b%ZCZC}Vbs7Tn)P0=M>il^pZ#U&e-HdJp20!n6$apWOnKTdGTQg#C0Fu3Q98<`TY)hj?~D6}e7uwQ7gHuBZJr08 zqx|N06>sEy?_Q^?i)B0v=6Dz6oxIJ~^2Zxv^D1V6Q5|*DN%xMov4xD~&eBaS-)k-hsCAJ{~7)c^JyAz-W$l!F_|d<#?M>_-uajcD%8zz+k6b zZLPJ|8ZWbjjQ(dWmv_EV9OXCOXZwb0ypy-K=afyh8gG1W-FRnPfuR~R-YFR`Wu=&! zbV9@#Rh0i4W2RW8lCEd5t-zRzcfEZ>F5bx-*Fr{igTnpwF*<9b+u_Fs6hN?vxyva`mPSSHhj`yc}-)h75Op!(M?=AALI|N1iU26DeK2iy-MXjZyJ}A(QP|FbQ`-@U_9qsbNkJ`0)x5b+_hsDnW^5xP`v^} zxvj44Q^{hb6VmzMVNlsty?rCTjt{=y!J9zZJRG-P}&jFMeRQG723>RQKjZfxuT;XE4`G8d?qM)qfgx91zxK7Se*p$ty##ogWC1{cX8~Xs zaXwxjh1V>uH{*HvZW0i4+dc-1Yb$jLp+ro={yxTv zYw2TewOPNRa*AswoxN|eHtQE8^pc=VLR>3bLk8i1qf7$FwUJILA(U@XJjx^x;@V%_ zq;&K#009t~k6^$)x_n=){OD7Zxo#f=KK}JQ$00USB5CT9O zMFb>300P8BQNS*TGE&+oAc*ad@<-;&*H%V(H3kpzqoVu);s68zhdyALuccA7g|Ib-09Dz_ zwRl1WArKbsV?0~iajmS+VS_+09oOt<1?g+b5jK{dHg|RYy&C%!SN12^uZpyW; zR&Lp*Wg&3yV|=e|lxLHyx%Q^YOg?PztzGje7ocwl(DT)K?Qg3&#%mS7wJn$9UdypP zTdO&TG87?D=&2(KtoJdz*Q)toTg@?u1R~G}fhZ7m{e(g7=XoK<%M8sSR*@SLE387Sn zPG*2Wm823JumH#VyytlBb8Fp_OS&iDk|=aS6d@1@3s3-ou(dH(;iOQLR;L zBRlu;)!L^%&L|_Vop144)obJ0hrCv`wQsFk&}WnpK7%9=0U@--Yn7suIx{xobj54^ zla0DBt{&k+Mul!Fx)%E?|EcI<@Zws1qwizB5JD-{gY__=FG_u%uk&5a8>PO}LAatf z%IKor)Hgc&Dt6y@_I=Sex^{No>C^X3-{){4gHex79>()IeP2)C)x7bXzSFlGgYe&d zn@JX5`o`GotJr-X`l{#~+u8Rq-|5?YSx?5&qwj;?q_eMYd$aq-X7~MO-{%{h$Bw?! zRdip63mFvhbc$#Q$Wq>?Wt~pxl+pJ+Uxo`Ab55rS0a38uH@o{f-_^YJ`zrRn^L@Kj zSKt2-HiWPtgs|E7y>E04A%w6QLf_}~LFR7w-h2Jto2|iq&v*JZll|Ufe(yPEU-iDx zv2)J(vYyPj^L;Wh*d+TllaTqw$n3t;_hI`!=o=lgyKie>){`~g*S^o=qpwNk<|}>c zlFjZLWBV$0--qq{e4}gjeZG_VHs!rP$rwm$hPdclY%qb@eb>v9Ds^H`{kLZ|&{v{><=wbfcHrL|UCYo)aIDpx6` zlu}A5rIdD~lu}A5rIgYcA)}PeN#~q&&hC9WdtaM#(mCgxbj~RiWRub2v(Af4`@(-F;*UQNV zOaq7Rj(?8_c(mXT`=s4C`;!7p*v(HSfpQdVX@Fz7S=>$gmiA=T`LTUg)|IQk{KhA7 z1!?mD9q7A6*a3Y{oA`olw= zvMxRlla@#X{tRW{Cjwbp-C)E}T|+5?F?<|i*IY&utUIG_bl01{4Hx99Bwn+X2B|I4 z#AEjj0Segxf#u>-Cv4(@iBe$*Q2=0}Ei?thDNG|&ZNqgb8N^S@LXP4noWs53)uqc*-=PaNyyYCa?Aae=B?R$n|&%3c*k6imA zwx4tzQ(eh35%*6mkd~W+a~(dR=rqwo!1!o}64JZHLMsv?bCO3_n&K^YH+JcOBhP}G zmzgiHyrkwtXGZl=zEhd@s$8WEEyqSGJjz*D57qmStj z!u7`F`kz@t-uQ?_d2@6c(cTPr-7LHwq@`qO9%oLdbLpm&`9AyDuR+XvY&rxy zSIN0UlgP$hgAU@xC5yNV(P74l3!xI)>Atp6BS}i|za11KT3rjl3PcqZ%Nw*~M-q_4 z`3c)?SpG|GjG%lGteV7R$S5%yL=PB2@cMgoaDd10(_7OqN-8w`7iY{NvyaInr6EC>A%^y&zd z1tb8T`(0Hogaqi(cT|Y?KbKv4rX-XSn7|m;LnhnkCcwge!?D;g-;RY$C9tq@NM2|@ zOD=hb&w2WHhGIwJvw9mMZ9mV@0EvCwVV zBt39vIfOE6?6G{WQo3G>)RldbB(X;}BTeT(j%Et?2-iIXl0{t>ceLl~2LA4fL1$!0 z3byqn!Ky>-8>y+)sSdR0nv|%@)Q?sC1o?j!+hgLoujW`{te|P3(hw;q(V%yX6j?dz zZRLE7sn6n^P;o{s#BrzFejXe5z*<2|_drh;k!w4Oi+;5W4LNGpFoTWRLqIr)t3qUS z75WG!WzRZxM#>B!h{tD<-?KqJVOs6z5lBg+!t<^Yh)kGZAn#MlKsDh7me*}m?qLa^ zUc5$(Yl^4@dI{T@Ri--OzaZ6fEk6STG)DwQOB_h-;fJId#L4WidXXj8xon&hF)B=+ z?29|z!W-H%d|5(s$fBW!9FTm0`yYT903!ma09%vI2xyGhJ|DVJj!-)=6eWOQ83|x; zPM^?J3wSx`kF)aj_;V@Hrd(F$e|b;K&3hmLmReWK0(to;(m{|n(+)PZU4faQz88ou z_htn#kPc%T3~7WH&fPaUI$^PrFpt2o&LMIo;4d-? z*#w5S3If^CKzTz)w_d%)NQ~t&p`SN;xIrHRFOtQBD};Ic++Bk zPpTyDG(9GJ&+4r}zhrS@w8nF#NnN+l#j+pIMzcU}wSdtf=qQHeib#|*oCuK)*hO(@ zvz2>Xlt#Y#bP3?b1QG0#6%tQ%!G54f2YO2H4^9Ew@-{HfLNBYC_j_6mHut}pER+X* z(6e+J$Fvb?h#B30no&KGjs#^;{zC2t8`TqfkNyS4WG2Fz&*%*bXF$P7x?@ePE%zPKeVr$bm%$G|e4=k5JiPcJ zMmkyKdO$@N1836kfmIk~Ci_Vcc#vV=Yn*VXAb_Kg_SK#JTo8R)&^2YBtVn3$oS$Uf zu+E?_T^^BM?1YPalY}UB_8c;MHc>EpQ+|2?C1R$%G;t9@cMj#Y{*j2VBVxx8ahS__ zK3N30iJu&Tak9gc1hdnh6Ti%GG*>NByRNyUJ}%$}*qFpMbqzv1+Q>+N!PGzPAIv=0 z#s;-3R;krecRFVYB7XHEgv>yTL;=2KbaU5~+z&OCZeA?asX9{PS(Q+ig&(l`&w9Aaygtn?SH`{$x1|s!ho_W{VT$Ah;@i#P@(uMi~ZzOLRHKU z9Bh;O-l4RD=!+|JM^__usi+n3H5uk%V`dytkwAiL?6aZm-h4gZVP7A9hpP4Q(II_Y zDn)86)TTUe2PIa^-ZZxOg=zeSsEXMtabJWXJ3GPTXSxP?of0KZ>G>qfMa`o5TK5iS zDnua0R}&diA0Hl z=Qs85DR{F^KKZl+KH036k$vh+*y1q;MT* zsGHYA=lsWqd01#OYy0LYsQlQFL{DH5?H)hGYzU?^jGWPUy-5^Kl^6%A*OcPt749)Q zK-V9cPY4Cv77cAe!=!{Z8Ptdf!LSGi0NU1}u#lLr7Yn>6q{T61aCBGF0fs?AqR~2b_3KA+g5m)EKv25Fn;eJ6 z#pL3m0lyugB-l0p&{o<=0q^BDkBn#n{E-?vSIQpoCUS{qoHlo(}9)D*_jYqE#>rd0vNg3)IQwin~69KBBL2Ti@GXCf> zBsvNyD+T8L7f_qoI>Lvqaj@ZP2rTxtkjJj_Hk{>JB$^;i>pU-$zX<8mM{%7t2(YJ9 zT-ATT#;K|LSpf-OhU7Uq5{U`ns$!mrbVxxEBy4Y*3O%WyH7aOUCV=I8It3oVa1Lkk zQbfDNz$6bLA;m^VQSJ!L^$Kfy9)mC<1%!Lm7~FxqqD5fz)M}8)Jk4w^zx*LEErd%c z@qrRzI*EhjGcpqP`%|I0In@ zV#MM+YHVJ-(5;qGy7L(cO=3n6E&tc?#F+g7GpPWyoiK4fzdkfGg+av0%2vzbTMf0q={i57{x30=rvkJ}d|0fp zt`f?m&A~%i6x(wdD4@6q5O|19I_B0UX@M|e&H1Y+IVeV+51oHbq5_n|ei_T5kxM*i zD)vIIU%9eBS!-jlY=7}4-+HH z|F~aq^G!A{TBaKAJ`JOb=OxzhCl2PWmm}qJ{DQWvN8cuWjVF8F%}i;Kqm#b`s-lCT z2n-XD{mVp-G>nH4F>oZcgyT@`ehjGXmuV~TPak~>?NHoGX~)(g#702DMx_Kdnp@;%y=p zZ2luEckxp)DJ62(v(=e<3&Je~WUkn-aI+L5%%9^VKV6PfCOnCG=EVXNNm=ivE0^<9 z3dVQH!WC95v#tRFlH?5dsd(=@y^6FlB??-8Ah5wX{2h*#a7dcch{YwhJl<(j|#(k zT{13%7*ud`x?auv(&A#h#7`pvnCXZP|qr%wXbR@r66)AzoDpYMFL9 z3EiBx#rT=_WmD8gTrp!A#Q;S2-kYV4!GKS(l_g7JWfAZwy`v&8H6kQ1jfB&{xV8h1 zyr^IcynU<*_}r%5U6gG>ZK>0D*j732iwfh5Q+9)n1OW4u+i4eaN8JTv!~oAj>$Io$ z`q&t(Ym$O@@@X0fIQ&jsH4XkCj;ZODEiNF{B_s0X#jT+bx*Gh1#;u}o!0#;KxX@IY zTf1U6*snS=J?EJg_wz@=zIN@cbdbo4@=pXhf2J8a=Ouu6(BIFqqkc%QH4(e^o(|b7{VWI8A&;rrTqwu@@ z0p3^Bnwzy^+i_j87%&S%!ipDzUx{(DpJyF|gazFvyLxe1YcC7KpmZirm#@-#357bs zVdh7aMcQ}d5IrE$W8XK`^G%XoRF9M{K9ZuqPXNCPtq*P)qF{nh9|XF5#zM1N0^prc zm{}pDw~2GP7$*5TGf}45C&z>`o&R%B0H{Yn>xdNZ8~nh+>=FhQGcf@85IKH9z6JkSMgl>JSq6WN zS1HETo(o2(DQ+~H!P49%1)ByvD9v%9H<@p5>~of%ap09^cn&)ZTTI0&& zM5`nqRBj;Tw5x9}465ibmpZ}(^y zvtZCjEL*GRQ?__Q@3^tmCdhtvj>VZub61Jk=ZCh+5EAwb7O7q~ww1SaDM_ktQZBnqYWWrSx{8uDbrq*x?R(1#^nTxZq&+pSxt( z*mDMTrBczt0QBoVAvK^Q&8PYl2%qYCkRCo8tHLvVYJ z6k*<_>wABLub$G~_P~b+RWmWvV3HtpVT%_@F!{irY*2#hExX>8;+R-2!cB!@UIY)@ zRj;I8i=|+zj6v;GfI1e(5eekQV$Iie5I|*)NoE8diy$MxD!2rO+CJj~4&QpI5_&@A ziH5R3j{E^EoRmBOMCO{4!jfgiM|l6MWoS{KS4rb>^bZli(az5ttf>|I;nS5oHvH?A zz)1|nsdZl41a<|ALFAt_{F0`r3_g*qtE&%R>V{+YoWv=s0xAHCB?!|{cKXv&H7;r3 z{L^9G3>Y~T$hgFw$@XLLHq5LIj)N^i1d|+f0T=THU{9BDpO!9Epm$BRy|@T;%<*m8 zbM=S>*l%Lun&}0u&=WM%1&D*L#r{3{>%$yBMxLoum- z{N+1V_lncOTu^zFSxzkT)z}d@Xp3ee`FUv~eW$3fjyj@791x+G#48US%A>}8>V+h$ zNYpn;cBPmj4n8OU7lvg*STc*u+f^@sPneAiQx026FzHCuoVR(z5U*vkAb$525~jC3 z5giW)ExGCI;VUEjMv0MEtX^f)hX;Xf|FyuIU>n?*3fbY3R9tX$i-SY}+y5&-xY1ay zU#xmbibl{vFW_;mI%u&04g_}tRRJ(~oD;;dzCg<+u;{gs$XL4H)8A#uMsq!#&X-Z- zF+%aOyb|K3Kqk!r8bOh%`;0iwT;el@aF&w}J~^zzvic9A!@+uxrbVS%(y^Xy?)VZuKeq z{Z3cR&`ZUM(4bV`nB)l(y$e~8%k`8y;G!qat}Yh7OZ5<2ksMo@75%UG8KDc%^^PHSocn}}#}r(b{%El1NyLX4Q{GQyTraLZVUpH}Xf+I>&Z`6EVeJ8Zlj zxlCwZSPI(-EPR>R$zVgY412Gy-$ zM1_5Z53 yyK-veLj0bnNHqW4@W?1j_=`ecORo6k4w==YDq)pzyCRH95P3fE`uzc9Y|*2|=`Vf& literal 0 HcmV?d00001 diff --git a/include/modules/disk.hpp b/include/modules/disk.hpp index 1b4f3176..b576e19f 100644 --- a/include/modules/disk.hpp +++ b/include/modules/disk.hpp @@ -22,7 +22,7 @@ class Disk : public ALabel { std::string path_; std::string unit_; - float calc_specific_divisor(const std::string divisor); + float calc_specific_divisor(const std::string& divisor); }; } // namespace waybar::modules diff --git a/include/modules/hyprland/windowcreationpayload.hpp b/include/modules/hyprland/windowcreationpayload.hpp index 226a7c41..906be805 100644 --- a/include/modules/hyprland/windowcreationpayload.hpp +++ b/include/modules/hyprland/windowcreationpayload.hpp @@ -40,10 +40,11 @@ struct WindowRepr { class WindowCreationPayload { public: - WindowCreationPayload(std::string workspace_name, WindowAddress window_address, + WindowCreationPayload(const std::string& workspace_name, WindowAddress window_address, WindowRepr window_repr); - WindowCreationPayload(std::string workspace_name, WindowAddress window_address, - std::string window_class, std::string window_title, bool is_active); + WindowCreationPayload(const std::string& workspace_name, WindowAddress window_address, + const std::string& window_class, const std::string& window_title, + bool is_active); WindowCreationPayload(Json::Value const& client_data); int incrementTimeSpentUncreated(); diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 8bf88888..0cec3c1d 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -59,7 +59,7 @@ class Workspaces : public AModule, public EventHandler { 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 getRewrite(const std::string& window_class, const std::string& window_title); std::string& getWindowSeparator() { return m_formatWindowSeparator; } bool isWorkspaceIgnored(std::string const& workspace_name); diff --git a/include/modules/mpd/mpd.hpp b/include/modules/mpd/mpd.hpp index 32d526e9..9d81638e 100644 --- a/include/modules/mpd/mpd.hpp +++ b/include/modules/mpd/mpd.hpp @@ -44,7 +44,7 @@ class MPD : public ALabel { std::string getFilename() const; void setLabel(); std::string getStateIcon() const; - std::string getOptionIcon(std::string optionName, bool activated) const; + std::string getOptionIcon(const std::string& optionName, bool activated) const; // GUI-side methods bool handlePlayPause(GdkEventButton* const&); diff --git a/include/modules/sni/host.hpp b/include/modules/sni/host.hpp index 6c62ac31..7248ad2f 100644 --- a/include/modules/sni/host.hpp +++ b/include/modules/sni/host.hpp @@ -30,7 +30,7 @@ class Host { static void itemUnregistered(SnWatcher*, const gchar*, gpointer); std::tuple getBusNameAndObjectPath(const std::string); - void addRegisteredItem(std::string service); + void addRegisteredItem(const std::string& service); std::vector> items_; const std::string bus_name_; diff --git a/include/modules/sway/bar.hpp b/include/modules/sway/bar.hpp index fd48e5a3..381c6c37 100644 --- a/include/modules/sway/bar.hpp +++ b/include/modules/sway/bar.hpp @@ -37,7 +37,7 @@ class BarIpcClient { void onModeUpdate(bool visible_by_modifier); void onUrgencyUpdate(bool visible_by_urgency); void update(); - bool isModuleEnabled(std::string name); + bool isModuleEnabled(const std::string& name); Bar& bar_; util::JsonParser parser_; diff --git a/include/modules/sway/language.hpp b/include/modules/sway/language.hpp index 91aa181d..470b9879 100644 --- a/include/modules/sway/language.hpp +++ b/include/modules/sway/language.hpp @@ -47,7 +47,7 @@ class Language : public ALabel, public sigc::trackable { void onEvent(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&); - auto set_current_layout(std::string current_layout) -> void; + auto set_current_layout(const std::string& current_layout) -> void; auto init_layouts_map(const std::vector& used_layouts) -> void; const static std::string XKB_LAYOUT_NAMES_KEY; diff --git a/include/modules/sway/workspaces.hpp b/include/modules/sway/workspaces.hpp index d8a9e18a..14528841 100644 --- a/include/modules/sway/workspaces.hpp +++ b/include/modules/sway/workspaces.hpp @@ -27,7 +27,7 @@ class Workspaces : public AModule, public sigc::trackable { static constexpr std::string_view persistent_workspace_switch_cmd_ = R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")"; - static int convertWorkspaceNameToNum(std::string name); + static int convertWorkspaceNameToNum(const std::string& name); static int windowRewritePriorityFunction(std::string const& window_rule); void onCmd(const struct Ipc::ipc_response&); @@ -40,7 +40,7 @@ class Workspaces : public AModule, public sigc::trackable { std::string getIcon(const std::string&, const Json::Value&); std::string getCycleWorkspace(std::vector::iterator, bool prev) const; uint16_t getWorkspaceIndex(const std::string& name) const; - static std::string trimWorkspaceName(std::string); + static std::string trimWorkspaceName(const std::string&); bool handleScroll(GdkEventScroll* /*unused*/) override; const Bar& bar_; diff --git a/include/util/SafeSignal.hpp b/include/util/SafeSignal.hpp index 1e901792..afb0893e 100644 --- a/include/util/SafeSignal.hpp +++ b/include/util/SafeSignal.hpp @@ -3,10 +3,10 @@ #include #include +#include #include #include #include -#include #include #include #include diff --git a/include/util/sleeper_thread.hpp b/include/util/sleeper_thread.hpp index 966772a2..1f3a372d 100644 --- a/include/util/sleeper_thread.hpp +++ b/include/util/sleeper_thread.hpp @@ -72,8 +72,7 @@ class SleeperThread { std::unique_lock lk(mutex_); CancellationGuard cancel_lock; return condvar_.wait(lk, [this] { - return signal_.load(std::memory_order_relaxed) || - !do_run_.load(std::memory_order_relaxed); + return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed); }); } @@ -87,8 +86,7 @@ class SleeperThread { wait_end = now + dur; } return condvar_.wait_until(lk, wait_end, [this] { - return signal_.load(std::memory_order_relaxed) || - !do_run_.load(std::memory_order_relaxed); + return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed); }); } @@ -98,8 +96,7 @@ class SleeperThread { std::unique_lock lk(mutex_); CancellationGuard cancel_lock; return condvar_.wait_until(lk, time_point, [this] { - return signal_.load(std::memory_order_relaxed) || - !do_run_.load(std::memory_order_relaxed); + return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed); }); } diff --git a/src/modules/disk.cpp b/src/modules/disk.cpp index fd7ef817..462940b2 100644 --- a/src/modules/disk.cpp +++ b/src/modules/disk.cpp @@ -92,7 +92,7 @@ auto waybar::modules::Disk::update() -> void { ALabel::update(); } -float waybar::modules::Disk::calc_specific_divisor(std::string divisor) { +float waybar::modules::Disk::calc_specific_divisor(const std::string& divisor) { if (divisor == "kB") { return 1000.0; } else if (divisor == "kiB") { diff --git a/src/modules/hyprland/windowcreationpayload.cpp b/src/modules/hyprland/windowcreationpayload.cpp index 61ea606f..5ace5b32 100644 --- a/src/modules/hyprland/windowcreationpayload.cpp +++ b/src/modules/hyprland/windowcreationpayload.cpp @@ -19,7 +19,7 @@ WindowCreationPayload::WindowCreationPayload(Json::Value const& client_data) clearWorkspaceName(); } -WindowCreationPayload::WindowCreationPayload(std::string workspace_name, +WindowCreationPayload::WindowCreationPayload(const std::string& workspace_name, WindowAddress window_address, WindowRepr window_repr) : m_window(std::move(window_repr)), m_windowAddress(std::move(window_address)), @@ -28,9 +28,10 @@ WindowCreationPayload::WindowCreationPayload(std::string workspace_name, clearWorkspaceName(); } -WindowCreationPayload::WindowCreationPayload(std::string workspace_name, - WindowAddress window_address, std::string window_class, - std::string window_title, bool is_active) +WindowCreationPayload::WindowCreationPayload(const std::string& workspace_name, + WindowAddress window_address, + const std::string& window_class, + const std::string& window_title, bool is_active) : m_window(std::make_pair(std::move(window_class), std::move(window_title))), m_windowAddress(std::move(window_address)), m_workspaceName(std::move(workspace_name)), diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 88b01223..dd891673 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -155,7 +155,8 @@ void Workspaces::extendOrphans(int workspaceId, Json::Value const& clientsJson) } } -std::string Workspaces::getRewrite(std::string window_class, std::string window_title) { +std::string Workspaces::getRewrite(const std::string& window_class, + const std::string& window_title) { std::string windowReprKey; if (windowRewriteConfigUsesTitle()) { windowReprKey = fmt::format("class<{}> title<{}>", window_class, window_title); diff --git a/src/modules/inhibitor.cpp b/src/modules/inhibitor.cpp index 170d0508..43c1f83e 100644 --- a/src/modules/inhibitor.cpp +++ b/src/modules/inhibitor.cpp @@ -85,7 +85,8 @@ auto getInhibitors(const Json::Value& config) -> std::string { if (config["what"].isArray()) { inhibitors = checkInhibitor(config["what"][0].asString()); for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) { - inhibitors += ":" + checkInhibitor(config["what"][i].asString()); + inhibitors.append(":"); + inhibitors.append(checkInhibitor(config["what"][i].asString())); } return inhibitors; } diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 1e28a58a..9874f458 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -239,7 +239,8 @@ std::string waybar::modules::MPD::getStateIcon() const { } } -std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) const { +std::string waybar::modules::MPD::getOptionIcon(const std::string& optionName, + bool activated) const { if (!config_[optionName + "-icons"].isObject()) { return ""; } diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 34dcc03c..868a06f2 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -321,6 +321,7 @@ auto waybar::modules::Network::update() -> void { } else if (addr_pref_ == ip_addr_pref::IPV6) { final_ipaddr_ = ipaddr6_; } else if (addr_pref_ == ip_addr_pref::IPV4_6) { + final_ipaddr_.reserve(ipaddr_.length() + ipaddr6_.length() + 1); final_ipaddr_ = ipaddr_; final_ipaddr_ += '\n'; final_ipaddr_ += ipaddr6_; diff --git a/src/modules/sni/host.cpp b/src/modules/sni/host.cpp index 75501207..6bd1154a 100644 --- a/src/modules/sni/host.cpp +++ b/src/modules/sni/host.cpp @@ -132,7 +132,7 @@ std::tuple Host::getBusNameAndObjectPath(const std::st return {service, "/StatusNotifierItem"}; } -void Host::addRegisteredItem(std::string service) { +void Host::addRegisteredItem(const std::string& service) { std::string bus_name, object_path; std::tie(bus_name, object_path) = getBusNameAndObjectPath(service); auto it = std::find_if(items_.begin(), items_.end(), [&bus_name, &object_path](const auto& item) { diff --git a/src/modules/sway/bar.cpp b/src/modules/sway/bar.cpp index f28b0502..f374f1c1 100644 --- a/src/modules/sway/bar.cpp +++ b/src/modules/sway/bar.cpp @@ -60,7 +60,7 @@ BarIpcClient::BarIpcClient(waybar::Bar& bar) : bar_{bar} { }); } -bool BarIpcClient::isModuleEnabled(std::string name) { +bool BarIpcClient::isModuleEnabled(const std::string& name) { for (const auto& section : {"modules-left", "modules-center", "modules-right"}) { if (const auto& modules = bar_.config.get(section, {}); modules.isArray()) { for (const auto& module : modules) { diff --git a/src/modules/sway/language.cpp b/src/modules/sway/language.cpp index f4cfa6c3..2e1103d3 100644 --- a/src/modules/sway/language.cpp +++ b/src/modules/sway/language.cpp @@ -124,7 +124,7 @@ auto Language::update() -> void { ALabel::update(); } -auto Language::set_current_layout(std::string current_layout) -> void { +auto Language::set_current_layout(const std::string& current_layout) -> void { label_.get_style_context()->remove_class(layout_.short_name); layout_ = layouts_map_[current_layout]; label_.get_style_context()->add_class(layout_.short_name); diff --git a/src/modules/sway/window.cpp b/src/modules/sway/window.cpp index 830a4120..2908b85c 100644 --- a/src/modules/sway/window.cpp +++ b/src/modules/sway/window.cpp @@ -184,9 +184,9 @@ std::tuple getWindowInfo( continue; } if (!marks.empty()) { - marks += ','; + marks.append(","); } - marks += m.asString(); + marks.append(m.asString()); } } return {app_id, app_class, shell, marks}; diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index e0255893..6abe5383 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -10,7 +10,7 @@ namespace waybar::modules::sway { // Helper function to assign a number to a workspace, just like sway. In fact // this is taken quite verbatim from `sway/ipc-json.c`. -int Workspaces::convertWorkspaceNameToNum(std::string name) { +int Workspaces::convertWorkspaceNameToNum(const std::string& name) { if (isdigit(name[0]) != 0) { errno = 0; char* endptr = nullptr; @@ -487,7 +487,7 @@ std::string Workspaces::getCycleWorkspace(std::vector::iterator it, return (*it)["name"].asString(); } -std::string Workspaces::trimWorkspaceName(std::string name) { +std::string Workspaces::trimWorkspaceName(const std::string& name) { std::size_t found = name.find(':'); if (found != std::string::npos) { return name.substr(found + 1); diff --git a/src/modules/upower.cpp b/src/modules/upower.cpp index 8202b718..d260ba83 100644 --- a/src/modules/upower.cpp +++ b/src/modules/upower.cpp @@ -95,7 +95,7 @@ UPower::~UPower() { removeDevices(); } -static const std::string getDeviceStatus(UpDeviceState& state) { +static std::string_view getDeviceStatus(UpDeviceState& state) { switch (state) { case UP_DEVICE_STATE_CHARGING: case UP_DEVICE_STATE_PENDING_CHARGE: @@ -112,7 +112,7 @@ static const std::string getDeviceStatus(UpDeviceState& state) { } } -static const std::string getDeviceIcon(UpDeviceKind& kind) { +static std::string_view getDeviceIcon(UpDeviceKind& kind) { switch (kind) { case UP_DEVICE_KIND_LINE_POWER: return "ac-adapter-symbolic"; @@ -212,7 +212,8 @@ auto UPower::update() -> void { // Remove last status if it exists if (!lastStatus_.empty() && box_.get_style_context()->has_class(lastStatus_)) box_.get_style_context()->remove_class(lastStatus_); - if (!box_.get_style_context()->has_class(status)) box_.get_style_context()->add_class(status); + if (!box_.get_style_context()->has_class(std::string(status))) + box_.get_style_context()->add_class(std::string(status)); lastStatus_ = status; if (devices_.size() == 0 && !upDeviceValid && hideIfEmpty_) { diff --git a/test/utils/sleeper_thread.cpp b/test/utils/sleeper_thread.cpp index 7a9a3ca1..9e1a1ef3 100644 --- a/test/utils/sleeper_thread.cpp +++ b/test/utils/sleeper_thread.cpp @@ -4,11 +4,12 @@ #include #endif -#include #include -#include #include +#include +#include + #include "util/sleeper_thread.hpp" namespace waybar::util { From e684e701df0515efd207b2f70ca766acc9770075 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 2 Mar 2026 22:27:41 -0600 Subject: [PATCH 591/780] perf(sni): eliminate redundant g_memdup2 allocation for D-Bus pixbufs Memory profiling via Valgrind Massif indicated that 10-20% of peak memory allocations within the SNI loop resulted from copying DBus image data payloads via g_memdup2 before modifying them from ARGB to RGBA. This commit optimizes the pixel conversion by directly allocating the final array via g_malloc and running the ARGB->RGBA transposition in a single pass while copying from the read-only GVariant buffer, entirely eliminating the intermediate g_memdup stage. Signed-off-by: Austin Horstman --- src/modules/sni/item.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index d33765d2..7ce95faf 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -336,11 +336,20 @@ Glib::RefPtr Item::extractPixBuf(GVariant* variant) { if (array != nullptr) { g_free(array); } -#if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 68 - array = static_cast(g_memdup2(data, size)); -#else - array = static_cast(g_memdup(data, size)); -#endif + // We must allocate our own array because the data from GVariant is read-only + // and we need to modify it to convert ARGB to RGBA. + array = static_cast(g_malloc(size)); + + // Copy and convert ARGB to RGBA in one pass to avoid g_memdup2 overhead + const guchar* src = static_cast(data); + for (gsize i = 0; i < size; i += 4) { + guchar alpha = src[i]; + array[i] = src[i + 1]; + array[i + 1] = src[i + 2]; + array[i + 2] = src[i + 3]; + array[i + 3] = alpha; + } + lwidth = width; lheight = height; } @@ -349,14 +358,6 @@ Glib::RefPtr Item::extractPixBuf(GVariant* variant) { } g_variant_iter_free(it); if (array != nullptr) { - /* argb to rgba */ - for (uint32_t i = 0; i < 4U * lwidth * lheight; i += 4) { - guchar alpha = array[i]; - array[i] = array[i + 1]; - array[i + 1] = array[i + 2]; - array[i + 2] = array[i + 3]; - array[i + 3] = alpha; - } return Gdk::Pixbuf::create_from_data(array, Gdk::Colorspace::COLORSPACE_RGB, true, 8, lwidth, lheight, 4 * lwidth, &pixbuf_data_deleter); } From 7d8be29f97b17061404c1ba01938b9481499e2be Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 2 Mar 2026 22:36:55 -0600 Subject: [PATCH 592/780] perf(sni): eliminate icon theme rescanning from system tray hotpath Valgrind Massif profiling revealed that invoking Gtk::IconTheme::rescan_if_needed() inside SNI updateImage() and getIconByName() loops caused considerable memory churn and potential filesystem stat overhead whenever a system tray app pushed a metadata update. This commit removes the rescan polling from the SNI proxy callback pipeline and the DefaultGtkIconThemeWrapper, restricting icon theme caching to load boundaries. Signed-off-by: Austin Horstman --- src/modules/sni/item.cpp | 2 -- src/util/gtk_icon.cpp | 1 - 2 files changed, 3 deletions(-) diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 7ce95faf..1428bd8e 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -423,8 +423,6 @@ Glib::RefPtr Item::getIconPixbuf() { } Glib::RefPtr Item::getIconByName(const std::string& name, int request_size) { - icon_theme->rescan_if_needed(); - if (!icon_theme_path.empty()) { auto icon_info = icon_theme->lookup_icon(name.c_str(), request_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE); diff --git a/src/util/gtk_icon.cpp b/src/util/gtk_icon.cpp index 73f77284..1b2fca57 100644 --- a/src/util/gtk_icon.cpp +++ b/src/util/gtk_icon.cpp @@ -21,7 +21,6 @@ Glib::RefPtr DefaultGtkIconThemeWrapper::load_icon( const std::lock_guard lock(default_theme_mutex); auto default_theme = Gtk::IconTheme::get_default(); - default_theme->rescan_if_needed(); auto icon_info = default_theme->lookup_icon(name, tmp_size, flags); From fe03dfaa3bb32622139a1a3b124d31af631e7837 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 2 Mar 2026 22:40:14 -0600 Subject: [PATCH 593/780] perf(memory): eliminate deep copies in range-based for loops and lambdas This commit addresses memory churn caused by implicit deep copies during traversal and allocation of complex structures: - Replaced pass-by-value 'Json::Value' in std::ranges and range-based for loops with 'const auto&' or 'const Json::Value&' in Hyprland modules, preventing large JSON tree duplications on every update. - Fixed implicit string and pair copies in UPower and CPU Frequency loops by converting 'auto' to 'const auto&' where possible. - Added 'std::vector::reserve' calls before 'push_back' loops in MPRIS, Niri, and CFFI modules to prevent exponential vector reallocation during initialization. Signed-off-by: Austin Horstman --- src/modules/cffi.cpp | 1 + src/modules/cpu_frequency/linux.cpp | 2 +- src/modules/hyprland/window.cpp | 16 ++++++++-------- src/modules/hyprland/windowcount.cpp | 4 ++-- src/modules/hyprland/workspace.cpp | 2 +- src/modules/hyprland/workspaces.cpp | 6 +++--- src/modules/mpris/mpris.cpp | 1 + src/modules/niri/language.cpp | 1 + 8 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/modules/cffi.cpp b/src/modules/cffi.cpp index 930c4d47..c3f1398f 100644 --- a/src/modules/cffi.cpp +++ b/src/modules/cffi.cpp @@ -71,6 +71,7 @@ CFFI::CFFI(const std::string& name, const std::string& id, const Json::Value& co // Prepare config_entries array std::vector config_entries; + config_entries.reserve(keys.size()); for (size_t i = 0; i < keys.size(); i++) { config_entries.push_back({keys[i].c_str(), config_entries_stringstor[i].c_str()}); } diff --git a/src/modules/cpu_frequency/linux.cpp b/src/modules/cpu_frequency/linux.cpp index 1f368789..83f06aa5 100644 --- a/src/modules/cpu_frequency/linux.cpp +++ b/src/modules/cpu_frequency/linux.cpp @@ -26,7 +26,7 @@ std::vector waybar::modules::CpuFrequency::parseCpuFrequencies() { if (std::filesystem::exists(cpufreq_dir)) { std::vector frequency_files = {"/cpuinfo_min_freq", "/cpuinfo_max_freq"}; for (auto& p : std::filesystem::directory_iterator(cpufreq_dir)) { - for (auto freq_file : frequency_files) { + for (const auto& freq_file : frequency_files) { std::string freq_file_path = p.path().string() + freq_file; if (std::filesystem::exists(freq_file_path)) { std::string freq_value; diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index 1fddb45b..97d49eaf 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -124,7 +124,7 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace { const auto monitors = IPC::inst().getSocket1JsonReply("monitors"); if (monitors.isArray()) { auto monitor = std::ranges::find_if( - monitors, [&](Json::Value monitor) { return monitor["name"] == monitorName; }); + monitors, [&](const Json::Value& monitor) { return monitor["name"] == monitorName; }); if (monitor == std::end(monitors)) { spdlog::warn("Monitor not found: {}", monitorName); return Workspace{ @@ -139,7 +139,7 @@ auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace { const auto workspaces = IPC::inst().getSocket1JsonReply("workspaces"); if (workspaces.isArray()) { auto workspace = std::ranges::find_if( - workspaces, [&](Json::Value workspace) { return workspace["id"] == id; }); + workspaces, [&](const Json::Value& workspace) { return workspace["id"] == id; }); if (workspace == std::end(workspaces)) { spdlog::warn("No workspace with id {}", id); return Workspace{ @@ -190,7 +190,7 @@ void Window::queryActiveWorkspace() { const auto clients = m_ipc.getSocket1JsonReply("clients"); if (clients.isArray()) { auto activeWindow = std::ranges::find_if( - clients, [&](Json::Value window) { return window["address"] == workspace_.last_window; }); + clients, [&](const Json::Value& window) { return window["address"] == workspace_.last_window; }); if (activeWindow == std::end(clients)) { focused_ = false; @@ -200,19 +200,19 @@ void Window::queryActiveWorkspace() { windowData_ = WindowData::parse(*activeWindow); updateAppIconName(windowData_.class_name, windowData_.initial_class_name); std::vector workspaceWindows; - std::ranges::copy_if(clients, std::back_inserter(workspaceWindows), [&](Json::Value window) { + std::ranges::copy_if(clients, std::back_inserter(workspaceWindows), [&](const Json::Value& window) { return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool(); }); - swallowing_ = std::ranges::any_of(workspaceWindows, [&](Json::Value window) { + swallowing_ = std::ranges::any_of(workspaceWindows, [&](const Json::Value& window) { return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0"; }); std::vector visibleWindows; std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows), - [&](Json::Value window) { return !window["hidden"].asBool(); }); + [&](const Json::Value& window) { return !window["hidden"].asBool(); }); solo_ = 1 == std::count_if(visibleWindows.begin(), visibleWindows.end(), - [&](Json::Value window) { return !window["floating"].asBool(); }); + [&](const Json::Value& window) { return !window["floating"].asBool(); }); allFloating_ = std::ranges::all_of( - visibleWindows, [&](Json::Value window) { return window["floating"].asBool(); }); + visibleWindows, [&](const Json::Value& window) { return window["floating"].asBool(); }); fullscreen_ = windowData_.fullscreen; // Fullscreen windows look like they are solo diff --git a/src/modules/hyprland/windowcount.cpp b/src/modules/hyprland/windowcount.cpp index ab573cca..5f5db80c 100644 --- a/src/modules/hyprland/windowcount.cpp +++ b/src/modules/hyprland/windowcount.cpp @@ -79,7 +79,7 @@ auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspac const auto monitors = m_ipc.getSocket1JsonReply("monitors"); if (monitors.isArray()) { auto monitor = std::ranges::find_if( - monitors, [&](Json::Value monitor) { return monitor["name"] == monitorName; }); + monitors, [&](const Json::Value& monitor) { return monitor["name"] == monitorName; }); if (monitor == std::end(monitors)) { spdlog::warn("Monitor not found: {}", monitorName); return Workspace{ @@ -93,7 +93,7 @@ auto WindowCount::getActiveWorkspace(const std::string& monitorName) -> Workspac const auto workspaces = m_ipc.getSocket1JsonReply("workspaces"); if (workspaces.isArray()) { auto workspace = std::ranges::find_if( - workspaces, [&](Json::Value workspace) { return workspace["id"] == id; }); + workspaces, [&](const Json::Value& workspace) { return workspace["id"] == id; }); if (workspace == std::end(workspaces)) { spdlog::warn("No workspace with id {}", id); return Workspace{ diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 87933ac0..21e7ef9b 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -96,7 +96,7 @@ bool Workspace::handleClicked(GdkEventButton* bt) const { void Workspace::initializeWindowMap(const Json::Value& clients_data) { m_windowMap.clear(); - for (auto client : clients_data) { + for (const auto& client : clients_data) { if (client["workspace"]["id"].asInt() == id()) { insertWindow({client}); } diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index dd891673..cbd14f90 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -197,7 +197,7 @@ void Workspaces::initializeWorkspaces() { auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); auto const clientsJson = m_ipc.getSocket1JsonReply("clients"); - for (Json::Value workspaceJson : workspacesJson) { + for (const auto& workspaceJson : workspacesJson) { std::string workspaceName = workspaceJson["name"].asString(); if ((allOutputs() || m_bar.output->name == workspaceJson["monitor"].asString()) && (!workspaceName.starts_with("special") || showSpecial()) && @@ -401,7 +401,7 @@ void Workspaces::onWorkspaceCreated(std::string const& payload, Json::Value cons auto const workspaceRules = m_ipc.getSocket1JsonReply("workspacerules"); auto const workspacesJson = m_ipc.getSocket1JsonReply("workspaces"); - for (Json::Value workspaceJson : workspacesJson) { + for (auto workspaceJson : workspacesJson) { const auto currentId = workspaceJson["id"].asInt(); if (currentId == *workspaceId) { std::string workspaceName = workspaceJson["name"].asString(); @@ -1004,7 +1004,7 @@ void Workspaces::setUrgentWorkspace(std::string const& windowaddress) { const Json::Value clientsJson = m_ipc.getSocket1JsonReply("clients"); int workspaceId = -1; - for (Json::Value clientJson : clientsJson) { + for (const auto& clientJson : clientsJson) { if (clientJson["address"].asString().ends_with(windowaddress)) { workspaceId = clientJson["workspace"]["id"].asInt(); break; diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 1bdd7df6..93f73690 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -109,6 +109,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) player_ = config_["player"].asString(); } if (config_["ignored-players"].isArray()) { + ignored_players_.reserve(config_["ignored-players"].size()); for (const auto& item : config_["ignored-players"]) { if (item.isString()) { ignored_players_.push_back(item.asString()); diff --git a/src/modules/niri/language.cpp b/src/modules/niri/language.cpp index 3359ef4c..14ca555f 100644 --- a/src/modules/niri/language.cpp +++ b/src/modules/niri/language.cpp @@ -32,6 +32,7 @@ void Language::updateFromIPC() { auto ipcLock = gIPC->lockData(); layouts_.clear(); + layouts_.reserve(gIPC->keyboardLayoutNames().size()); for (const auto& fullName : gIPC->keyboardLayoutNames()) layouts_.push_back(getLayout(fullName)); current_idx_ = gIPC->keyboardLayoutCurrent(); From c5449bd361bed8be9875c1322fd0d693ba975cdd Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Mon, 2 Mar 2026 23:12:54 -0600 Subject: [PATCH 594/780] fix(network): log new address only when state changes Signed-off-by: Austin Horstman --- src/modules/network.cpp | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index e0866011..a25024e8 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -634,18 +634,31 @@ int waybar::modules::Network::handleEvents(struct nl_msg* msg, void* data) { case IFA_LOCAL: char ipaddr[INET6_ADDRSTRLEN]; if (!is_del_event) { + bool addr_changed = false; + std::string changed_ipaddr; + int changed_cidr = 0; if ((net->addr_pref_ == ip_addr_pref::IPV4 || net->addr_pref_ == ip_addr_pref::IPV4_6) && net->cidr_ == 0 && ifa->ifa_family == AF_INET) { - net->ipaddr_ = - inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)); - net->cidr_ = ifa->ifa_prefixlen; + if (inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)) != + nullptr) { + net->ipaddr_ = ipaddr; + net->cidr_ = ifa->ifa_prefixlen; + addr_changed = true; + changed_ipaddr = net->ipaddr_; + changed_cidr = net->cidr_; + } } else if ((net->addr_pref_ == ip_addr_pref::IPV6 || net->addr_pref_ == ip_addr_pref::IPV4_6) && net->cidr6_ == 0 && ifa->ifa_family == AF_INET6) { - net->ipaddr6_ = - inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)); - net->cidr6_ = ifa->ifa_prefixlen; + if (inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)) != + nullptr) { + net->ipaddr6_ = ipaddr; + net->cidr6_ = ifa->ifa_prefixlen; + addr_changed = true; + changed_ipaddr = net->ipaddr6_; + changed_cidr = net->cidr6_; + } } switch (ifa->ifa_family) { @@ -665,7 +678,10 @@ int waybar::modules::Network::handleEvents(struct nl_msg* msg, void* data) { net->netmask6_ = inet_ntop(ifa->ifa_family, &netmask6, ipaddr, sizeof(ipaddr)); } } - spdlog::debug("network: {}, new addr {}/{}", net->ifname_, net->ipaddr_, net->cidr_); + if (addr_changed) { + spdlog::debug("network: {}, new addr {}/{}", net->ifname_, changed_ipaddr, + changed_cidr); + } } else { net->ipaddr_.clear(); net->ipaddr6_.clear(); From a97e8dad7cc739abf27a1562e9270000ac4124f1 Mon Sep 17 00:00:00 2001 From: Alison Date: Mon, 2 Mar 2026 23:40:15 -0800 Subject: [PATCH 595/780] Revert "Fix tooltip sync issue by removing conditional checks" This reverts commit 2b29c9a5d6e353ce6d6686be2d51a6240c0778bf. --- src/modules/custom.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index e2b705da..ff897fef 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -188,9 +188,13 @@ auto waybar::modules::Custom::update() -> void { fmt::arg("percentage", percentage_)); label_.set_tooltip_markup(tooltip); } else if (text_ == tooltip_) { - label_.set_tooltip_markup(str); + if (label_.get_tooltip_markup() != str) { + label_.set_tooltip_markup(str); + } } else { - label_.set_tooltip_markup(tooltip_); + if (label_.get_tooltip_markup() != tooltip_) { + label_.set_tooltip_markup(tooltip_); + } } } auto style = label_.get_style_context(); From 3eb2c7e8f43b25387e68b9e320321edf274840ee Mon Sep 17 00:00:00 2001 From: Alison Date: Mon, 2 Mar 2026 23:49:22 -0800 Subject: [PATCH 596/780] sync tooltip updates without resetting hover state --- include/modules/custom.hpp | 1 + src/modules/custom.cpp | 24 +++++++++++++----------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/include/modules/custom.hpp b/include/modules/custom.hpp index 6c17c6e4..a345a33b 100644 --- a/include/modules/custom.hpp +++ b/include/modules/custom.hpp @@ -35,6 +35,7 @@ class Custom : public ALabel { std::string id_; std::string alt_; std::string tooltip_; + std::string last_tooltip_markup_; const bool tooltip_format_enabled_; std::vector class_; int percentage_; diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index ff897fef..93956614 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -1,6 +1,7 @@ #include "modules/custom.hpp" #include +#include #include "util/scope_guard.hpp" @@ -180,21 +181,22 @@ auto waybar::modules::Custom::update() -> void { } else { label_.set_markup(str); if (tooltipEnabled()) { + std::string tooltip_markup; if (tooltip_format_enabled_) { auto tooltip = config_["tooltip-format"].asString(); - tooltip = fmt::format(fmt::runtime(tooltip), fmt::arg("text", text_), - fmt::arg("tooltip", tooltip_), fmt::arg("alt", alt_), - fmt::arg("icon", getIcon(percentage_, alt_)), - fmt::arg("percentage", percentage_)); - label_.set_tooltip_markup(tooltip); + tooltip_markup = fmt::format(fmt::runtime(tooltip), fmt::arg("text", text_), + fmt::arg("tooltip", tooltip_), fmt::arg("alt", alt_), + fmt::arg("icon", getIcon(percentage_, alt_)), + fmt::arg("percentage", percentage_)); } else if (text_ == tooltip_) { - if (label_.get_tooltip_markup() != str) { - label_.set_tooltip_markup(str); - } + tooltip_markup = str; } else { - if (label_.get_tooltip_markup() != tooltip_) { - label_.set_tooltip_markup(tooltip_); - } + tooltip_markup = tooltip_; + } + + if (last_tooltip_markup_ != tooltip_markup) { + label_.set_tooltip_markup(tooltip_markup); + last_tooltip_markup_ = std::move(tooltip_markup); } } auto style = label_.get_style_context(); From a81621863790ce42cff243b215a01c229cef9142 Mon Sep 17 00:00:00 2001 From: Neale Swinnerton Date: Tue, 3 Mar 2026 16:02:04 +0000 Subject: [PATCH 597/780] fix(mpris): disconnect GLib signals before destroying objects Waybar SEGVs in Glib::DispatchNotifier::pipe_io_handler when the MPRIS module is enabled. The crash is intermittent because it requires a race between signal emission and object destruction: a playerctl GLib signal callback (e.g. onPlayerPlay) calls dp.emit(), which writes a pointer to the Dispatcher into an internal pipe. If the Mpris object is destroyed before the GLib main loop reads that pipe entry, pipe_io_handler dereferences a dangling pointer. This typically occurs when a media player appears or vanishes on D-Bus (browser closing, player quitting) or during waybar shutdown/config reload. The root cause is that ~Mpris() calls g_object_unref() on the manager and player GObjects without first disconnecting the signal handlers that hold raw `this` pointers. If playerctl holds additional references to these GObjects, they survive the unref and can still fire signals targeting the already-destroyed Mpris instance. Adopt the same cleanup pattern used by the Wireplumber module: call g_signal_handlers_disconnect_by_data() to sever all signal connections referencing `this` before releasing the GObjects with g_clear_object(). This guarantees no callbacks can enqueue stale Dispatcher notifications after teardown begins. Additionally: - Clean up old player in onPlayerNameAppeared before replacing it, fixing a GObject leak and accumulation of dangling signal connections - Remove duplicate onPlayerStop signal registration (copy-paste bug) --- src/modules/mpris/mpris.cpp | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 1bdd7df6..5771a5ba 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -161,8 +161,7 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) if (player) { g_object_connect(player, "signal::play", G_CALLBACK(onPlayerPlay), this, "signal::pause", G_CALLBACK(onPlayerPause), this, "signal::stop", G_CALLBACK(onPlayerStop), - this, "signal::stop", G_CALLBACK(onPlayerStop), this, "signal::metadata", - G_CALLBACK(onPlayerMetadata), this, NULL); + this, "signal::metadata", G_CALLBACK(onPlayerMetadata), this, NULL); } // allow setting an interval count that triggers periodic refreshes @@ -178,9 +177,17 @@ Mpris::Mpris(const std::string& id, const Json::Value& config) } Mpris::~Mpris() { - if (last_active_player_ && last_active_player_ != player) g_object_unref(last_active_player_); - if (manager != nullptr) g_object_unref(manager); - if (player != nullptr) g_object_unref(player); + if (manager != nullptr) { + g_signal_handlers_disconnect_by_data(manager, this); + } + if (player != nullptr) { + g_signal_handlers_disconnect_by_data(player, this); + } + if (last_active_player_ != nullptr && last_active_player_ != player) { + g_object_unref(last_active_player_); + } + g_clear_object(&manager); + g_clear_object(&player); } auto Mpris::getIconFromJson(const Json::Value& icons, const std::string& key) -> std::string { @@ -411,11 +418,14 @@ auto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlaye return; } + if (mpris->player != nullptr) { + g_signal_handlers_disconnect_by_data(mpris->player, mpris); + g_clear_object(&mpris->player); + } mpris->player = playerctl_player_new_from_name(player_name, nullptr); g_object_connect(mpris->player, "signal::play", G_CALLBACK(onPlayerPlay), mpris, "signal::pause", G_CALLBACK(onPlayerPause), mpris, "signal::stop", G_CALLBACK(onPlayerStop), - mpris, "signal::stop", G_CALLBACK(onPlayerStop), mpris, "signal::metadata", - G_CALLBACK(onPlayerMetadata), mpris, NULL); + mpris, "signal::metadata", G_CALLBACK(onPlayerMetadata), mpris, NULL); mpris->dp.emit(); } From 5de21f4a078ebc251605b35ab430bb59bb1d356f Mon Sep 17 00:00:00 2001 From: jpk Date: Thu, 5 Mar 2026 09:04:21 +0100 Subject: [PATCH 598/780] Added support for hostname based module addition --- include/util/hosts_check.hpp | 8 ++++++++ meson.build | 1 + src/bar.cpp | 14 ++++++++++++++ src/util/hosts_check.cpp | 19 +++++++++++++++++++ 4 files changed, 42 insertions(+) create mode 100644 include/util/hosts_check.hpp create mode 100644 src/util/hosts_check.cpp diff --git a/include/util/hosts_check.hpp b/include/util/hosts_check.hpp new file mode 100644 index 00000000..a927f535 --- /dev/null +++ b/include/util/hosts_check.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +namespace waybar::util { +bool valid_host(const Json::Value& config); +} // namespace waybar::util diff --git a/meson.build b/meson.build index db9407eb..fdab7932 100644 --- a/meson.build +++ b/meson.build @@ -181,6 +181,7 @@ src_files = files( 'src/util/prepare_for_sleep.cpp', 'src/util/ustring_clen.cpp', 'src/util/sanitize_str.cpp', + 'src/util/hosts_check.cpp', 'src/util/rewrite_string.cpp', 'src/util/gtk_icon.cpp', 'src/util/icon_loader.cpp', diff --git a/src/bar.cpp b/src/bar.cpp index 6a78707e..6f2f36a6 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -10,6 +10,7 @@ #include "group.hpp" #include "util/enum.hpp" #include "util/kill_signal.hpp" +#include "util/hosts_check.hpp" #ifdef HAVE_SWAY #include "modules/sway/bar.hpp" @@ -531,6 +532,19 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, for (const auto& name : module_list) { try { auto ref = name.asString(); + const Json::Value* module_config = nullptr; + if (config.isMember(ref)) { + module_config = &config[ref]; + } else { + auto hash_pos = ref.find('#'); + if (hash_pos != std::string::npos) { + std::string ref_base = ref.substr(0, hash_pos); + if (config.isMember(ref_base)) { + module_config = &config[ref_base]; + } + } + } + if (module_config && !waybar::util::valid_host(*module_config)) continue; AModule* module; if (ref.compare(0, 6, "group/") == 0 && ref.size() > 6) { diff --git a/src/util/hosts_check.cpp b/src/util/hosts_check.cpp new file mode 100644 index 00000000..27260cd3 --- /dev/null +++ b/src/util/hosts_check.cpp @@ -0,0 +1,19 @@ +#include + +#include "util/hosts_check.hpp" + +namespace waybar::util { +// replaces ``<>&"'`` with their encoded counterparts +bool valid_host(const Json::Value& config) { + if (config.isMember("hosts") && config["hosts"].isArray()) { + auto hostname = Glib::get_host_name(); + for (const auto& host : config["hosts"]) { + if (host.asString() == hostname) { + return true; + } + } + return false; + } + return true; +} +} // namespace waybar::util From 49db7adc0c782a59a65ec99212590472ade16587 Mon Sep 17 00:00:00 2001 From: jpk Date: Thu, 5 Mar 2026 12:28:48 +0100 Subject: [PATCH 599/780] Per host checks --- meson.build | 1 + src/bar.cpp | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index fdab7932..58b49b34 100644 --- a/meson.build +++ b/meson.build @@ -183,6 +183,7 @@ src_files = files( 'src/util/sanitize_str.cpp', 'src/util/hosts_check.cpp', 'src/util/rewrite_string.cpp', + 'src/util/hosts_check.cpp', 'src/util/gtk_icon.cpp', 'src/util/icon_loader.cpp', 'src/util/regex_collection.cpp', diff --git a/src/bar.cpp b/src/bar.cpp index 6f2f36a6..04ad8362 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -9,6 +9,7 @@ #include "factory.hpp" #include "group.hpp" #include "util/enum.hpp" +#include "util/hosts_check.hpp" #include "util/kill_signal.hpp" #include "util/hosts_check.hpp" @@ -532,6 +533,7 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, for (const auto& name : module_list) { try { auto ref = name.asString(); + const Json::Value* module_config = nullptr; if (config.isMember(ref)) { module_config = &config[ref]; @@ -544,7 +546,8 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, } } } - if (module_config && !waybar::util::valid_host(*module_config)) continue; + if ((module_config != nullptr) && !waybar::util::valid_host(*module_config)) continue; + AModule* module; if (ref.compare(0, 6, "group/") == 0 && ref.size() > 6) { From 88e1f85341f2a5c26ab963ab0fad0ed44076e859 Mon Sep 17 00:00:00 2001 From: jpk Date: Thu, 5 Mar 2026 13:29:14 +0100 Subject: [PATCH 600/780] Simplified logic --- include/util/hosts_check.hpp | 1 - src/bar.cpp | 16 +++------------- src/util/hosts_check.cpp | 18 ++++++++---------- 3 files changed, 11 insertions(+), 24 deletions(-) diff --git a/include/util/hosts_check.hpp b/include/util/hosts_check.hpp index a927f535..4179f633 100644 --- a/include/util/hosts_check.hpp +++ b/include/util/hosts_check.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include namespace waybar::util { bool valid_host(const Json::Value& config); diff --git a/src/bar.cpp b/src/bar.cpp index 04ad8362..4b619de2 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include "client.hpp" @@ -11,7 +12,6 @@ #include "util/enum.hpp" #include "util/hosts_check.hpp" #include "util/kill_signal.hpp" -#include "util/hosts_check.hpp" #ifdef HAVE_SWAY #include "modules/sway/bar.hpp" @@ -534,19 +534,9 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, try { auto ref = name.asString(); - const Json::Value* module_config = nullptr; - if (config.isMember(ref)) { - module_config = &config[ref]; - } else { - auto hash_pos = ref.find('#'); - if (hash_pos != std::string::npos) { - std::string ref_base = ref.substr(0, hash_pos); - if (config.isMember(ref_base)) { - module_config = &config[ref_base]; - } - } + if (config[ref].isMember("hosts") && !waybar::util::valid_host(config[ref])) { + continue; } - if ((module_config != nullptr) && !waybar::util::valid_host(*module_config)) continue; AModule* module; diff --git a/src/util/hosts_check.cpp b/src/util/hosts_check.cpp index 27260cd3..1769dd78 100644 --- a/src/util/hosts_check.cpp +++ b/src/util/hosts_check.cpp @@ -1,18 +1,16 @@ -#include - #include "util/hosts_check.hpp" +#include +#include + namespace waybar::util { -// replaces ``<>&"'`` with their encoded counterparts bool valid_host(const Json::Value& config) { if (config.isMember("hosts") && config["hosts"].isArray()) { - auto hostname = Glib::get_host_name(); - for (const auto& host : config["hosts"]) { - if (host.asString() == hostname) { - return true; - } - } - return false; + const auto hostname = Glib::get_host_name(); + + if (!std::ranges::any_of(config["hosts"].begin(), config["hosts"].end(), + [&](const auto& h) { return h.asString() == hostname; })) + return false; } return true; } From 78bd6c8b87cd5c6bd987d1fa7563be48bfbc2b7a Mon Sep 17 00:00:00 2001 From: jpk Date: Thu, 5 Mar 2026 14:15:29 +0100 Subject: [PATCH 601/780] Remove duplicate from source include --- meson.build | 1 - 1 file changed, 1 deletion(-) diff --git a/meson.build b/meson.build index 58b49b34..9f533aa8 100644 --- a/meson.build +++ b/meson.build @@ -181,7 +181,6 @@ src_files = files( 'src/util/prepare_for_sleep.cpp', 'src/util/ustring_clen.cpp', 'src/util/sanitize_str.cpp', - 'src/util/hosts_check.cpp', 'src/util/rewrite_string.cpp', 'src/util/hosts_check.cpp', 'src/util/gtk_icon.cpp', From ab5b1a6cd41d10da238e6ab5da32d798aa6ba04c Mon Sep 17 00:00:00 2001 From: 7FM <41307817+7FM@users.noreply.github.com> Date: Fri, 6 Mar 2026 00:37:42 +0100 Subject: [PATCH 602/780] pulseaudio: add target option to control source (microphone) volume The pulseaudio/slider module had partial support for a "target" config option but changeVolume() in the audio backend was hardcoded to only modify sink volume. This adds full source volume control: - Add AudioTarget enum (Sink/Source) to audio_backend - Extend changeVolume() overloads with AudioTarget parameter - Store and use pa_cvolume for source (with mono channel default) - Add sourceVolumeModifyCb for source volume change confirmation - Add "target" config option to the pulseaudio module for scroll control - Document "target" in both pulseaudio and pulseaudio-slider man pages Usage: "pulseaudio/slider#in": { "target": "source" } "pulseaudio#in": { "target": "source" } --- include/modules/pulseaudio.hpp | 1 + include/modules/pulseaudio_slider.hpp | 7 +- include/util/audio_backend.hpp | 13 +++- man/waybar-pulseaudio-slider.5.scd | 16 ++++- man/waybar-pulseaudio.5.scd | 5 ++ src/modules/pulseaudio.cpp | 6 +- src/modules/pulseaudio_slider.cpp | 18 +++--- src/util/audio_backend.cpp | 93 ++++++++++++++++++++------- 8 files changed, 116 insertions(+), 43 deletions(-) diff --git a/include/modules/pulseaudio.hpp b/include/modules/pulseaudio.hpp index eead664f..80b2059b 100644 --- a/include/modules/pulseaudio.hpp +++ b/include/modules/pulseaudio.hpp @@ -22,6 +22,7 @@ class Pulseaudio : public ALabel { const std::vector getPulseIcon() const; std::shared_ptr backend = nullptr; + util::AudioTarget target = util::AudioTarget::Sink; }; } // namespace waybar::modules diff --git a/include/modules/pulseaudio_slider.hpp b/include/modules/pulseaudio_slider.hpp index 3ef44684..c6c06567 100644 --- a/include/modules/pulseaudio_slider.hpp +++ b/include/modules/pulseaudio_slider.hpp @@ -6,11 +6,6 @@ #include "util/audio_backend.hpp" namespace waybar::modules { -enum class PulseaudioSliderTarget { - Sink, - Source, -}; - class PulseaudioSlider : public ASlider { public: PulseaudioSlider(const std::string&, const Json::Value&); @@ -21,7 +16,7 @@ class PulseaudioSlider : public ASlider { private: std::shared_ptr backend = nullptr; - PulseaudioSliderTarget target = PulseaudioSliderTarget::Sink; + util::AudioTarget target = util::AudioTarget::Sink; }; } // namespace waybar::modules \ No newline at end of file diff --git a/include/util/audio_backend.hpp b/include/util/audio_backend.hpp index 3737ae26..3e8e0403 100644 --- a/include/util/audio_backend.hpp +++ b/include/util/audio_backend.hpp @@ -14,6 +14,11 @@ namespace waybar::util { +enum class AudioTarget { + Sink, + Source, +}; + class AudioBackend { private: static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*); @@ -22,12 +27,14 @@ class AudioBackend { static void sourceInfoCb(pa_context*, const pa_source_info* i, int, void* data); static void serverInfoCb(pa_context*, const pa_server_info*, void*); static void volumeModifyCb(pa_context*, int, void*); + static void sourceVolumeModifyCb(pa_context*, int, void*); void connectContext(); pa_threaded_mainloop* mainloop_; pa_mainloop_api* mainloop_api_; pa_context* context_; pa_cvolume pa_volume_; + pa_cvolume pa_source_volume_; // SINK uint32_t sink_idx_{0}; @@ -67,8 +74,10 @@ class AudioBackend { AudioBackend(std::function on_updated_cb, private_constructor_tag tag); ~AudioBackend(); - void changeVolume(uint16_t volume, uint16_t min_volume = 0, uint16_t max_volume = 100); - void changeVolume(ChangeType change_type, double step = 1, uint16_t max_volume = 100); + void changeVolume(uint16_t volume, uint16_t min_volume = 0, uint16_t max_volume = 100, + AudioTarget target = AudioTarget::Sink); + void changeVolume(ChangeType change_type, double step = 1, uint16_t max_volume = 100, + AudioTarget target = AudioTarget::Sink); void setIgnoredSinks(const Json::Value& config); diff --git a/man/waybar-pulseaudio-slider.5.scd b/man/waybar-pulseaudio-slider.5.scd index 0271e7c5..6001d929 100644 --- a/man/waybar-pulseaudio-slider.5.scd +++ b/man/waybar-pulseaudio-slider.5.scd @@ -32,16 +32,28 @@ The volume can be controlled by dragging the slider across the bar or clicking o default: false ++ Enables this module to consume all left over space dynamically. +*target*: ++ + typeof: string ++ + default: sink ++ + The audio target to control. Can be either `sink` (output/speakers) or `source` (input/microphone). + # EXAMPLES ``` "modules-right": [ - "pulseaudio/slider", + "pulseaudio/slider#out", + "pulseaudio/slider#in", ], -"pulseaudio/slider": { +"pulseaudio/slider#out": { "min": 0, "max": 100, "orientation": "horizontal" +}, +"pulseaudio/slider#in": { + "min": 0, + "max": 100, + "orientation": "horizontal", + "target": "source" } ``` diff --git a/man/waybar-pulseaudio.5.scd b/man/waybar-pulseaudio.5.scd index f555fd4d..6347291b 100644 --- a/man/waybar-pulseaudio.5.scd +++ b/man/waybar-pulseaudio.5.scd @@ -135,6 +135,11 @@ Additionally, you can control the volume by scrolling *up* or *down* while the c default: false ++ Enables this module to consume all left over space dynamically. +*target*: ++ + typeof: string ++ + default: sink ++ + The audio target to control when scrolling. Can be either `sink` (output/speakers) or `source` (input/microphone). + # FORMAT REPLACEMENTS *{desc}*: Pulseaudio port's description, for bluetooth it'll be the device name. diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index 4d63ff3c..5b4c0be6 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -7,6 +7,10 @@ waybar::modules::Pulseaudio::Pulseaudio(const std::string& id, const Json::Value backend = util::AudioBackend::getInstance([this] { this->dp.emit(); }); backend->setIgnoredSinks(config_["ignored-sinks"]); + + if (config_["target"].isString() && config_["target"].asString() == "source") { + target = util::AudioTarget::Source; + } } bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll* e) { @@ -33,7 +37,7 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll* e) { ? util::ChangeType::Increase : util::ChangeType::Decrease; - backend->changeVolume(change_type, step, max_volume); + backend->changeVolume(change_type, step, max_volume, target); return true; } diff --git a/src/modules/pulseaudio_slider.cpp b/src/modules/pulseaudio_slider.cpp index bf85584e..c789cea0 100644 --- a/src/modules/pulseaudio_slider.cpp +++ b/src/modules/pulseaudio_slider.cpp @@ -10,16 +10,16 @@ PulseaudioSlider::PulseaudioSlider(const std::string& id, const Json::Value& con if (config_["target"].isString()) { std::string target = config_["target"].asString(); if (target == "sink") { - this->target = PulseaudioSliderTarget::Sink; + this->target = util::AudioTarget::Sink; } else if (target == "source") { - this->target = PulseaudioSliderTarget::Source; + this->target = util::AudioTarget::Source; } } } void PulseaudioSlider::update() { switch (target) { - case PulseaudioSliderTarget::Sink: + case util::AudioTarget::Sink: if (backend->getSinkMuted()) { scale_.set_value(min_); } else { @@ -27,7 +27,7 @@ void PulseaudioSlider::update() { } break; - case PulseaudioSliderTarget::Source: + case util::AudioTarget::Source: if (backend->getSourceMuted()) { scale_.set_value(min_); } else { @@ -41,13 +41,13 @@ void PulseaudioSlider::onValueChanged() { bool is_mute = false; switch (target) { - case PulseaudioSliderTarget::Sink: + case util::AudioTarget::Sink: if (backend->getSinkMuted()) { is_mute = true; } break; - case PulseaudioSliderTarget::Source: + case util::AudioTarget::Source: if (backend->getSourceMuted()) { is_mute = true; } @@ -65,18 +65,18 @@ void PulseaudioSlider::onValueChanged() { // If the sink/source is mute, but the user clicked the slider, unmute it! else { switch (target) { - case PulseaudioSliderTarget::Sink: + case util::AudioTarget::Sink: backend->toggleSinkMute(false); break; - case PulseaudioSliderTarget::Source: + case util::AudioTarget::Source: backend->toggleSourceMute(false); break; } } } - backend->changeVolume(volume, min_, max_); + backend->changeVolume(volume, min_, max_, target); } } // namespace waybar::modules \ No newline at end of file diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index 4087e096..041e3d5e 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -24,8 +24,9 @@ AudioBackend::AudioBackend(std::function on_updated_cb, private_construc source_volume_(0), source_muted_(false), on_updated_cb_(std::move(on_updated_cb)) { - // Initialize pa_volume_ with safe defaults + // Initialize pa_volume_ and pa_source_volume_ with safe defaults pa_cvolume_init(&pa_volume_); + pa_cvolume_init(&pa_source_volume_); mainloop_ = pa_threaded_mainloop_new(); if (mainloop_ == nullptr) { throw std::runtime_error("pa_mainloop_new() failed."); @@ -155,6 +156,19 @@ void AudioBackend::volumeModifyCb(pa_context* c, int success, void* data) { } } +void AudioBackend::sourceVolumeModifyCb(pa_context* c, int success, void* data) { + auto* backend = static_cast(data); + if (success != 0) { + if ((backend->context_ != nullptr) && + pa_context_get_state(backend->context_) == PA_CONTEXT_READY) { + pa_context_get_source_info_by_index(backend->context_, backend->source_idx_, sourceInfoCb, + data); + } + } else { + spdlog::debug("Source volume modification failed"); + } +} + /* * Called when the requested sink information is ready. */ @@ -234,6 +248,11 @@ void AudioBackend::sourceInfoCb(pa_context* /*context*/, const pa_source_info* i void* data) { auto* backend = static_cast(data); if (i != nullptr && backend->default_source_name_ == i->name) { + if (pa_cvolume_valid(&i->volume) != 0) { + backend->pa_source_volume_ = i->volume; + } else { + pa_cvolume_init(&backend->pa_source_volume_); + } auto source_volume = static_cast(pa_cvolume_avg(&(i->volume))) / float{PA_VOLUME_NORM}; backend->source_volume_ = std::round(source_volume * 100.0F); backend->source_idx_ = i->index; @@ -259,25 +278,31 @@ void AudioBackend::serverInfoCb(pa_context* context, const pa_server_info* i, vo pa_context_get_source_info_list(context, sourceInfoCb, data); } -void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t max_volume) { +void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t max_volume, + AudioTarget target) { // Early return if context is not ready if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) { spdlog::error("PulseAudio context not ready"); return; } + bool is_source = target == AudioTarget::Source; + + // Select the appropriate stored volume structure + auto& ref_volume = is_source ? pa_source_volume_ : pa_volume_; + // Prepare volume structure pa_cvolume pa_volume; pa_cvolume_init(&pa_volume); // Use existing volume structure if valid, otherwise create a safe default - if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) { - pa_volume = pa_volume_; + if ((pa_cvolume_valid(&ref_volume) != 0) && (pa_channels_valid(ref_volume.channels) != 0)) { + pa_volume = ref_volume; } else { - // Set stereo as a safe default - pa_volume.channels = 2; - spdlog::debug("Using default stereo volume structure"); + // Mono for sources (microphones), stereo for sinks + pa_volume.channels = is_source ? 1 : 2; + spdlog::debug("Using default volume structure ({} channels)", pa_volume.channels); } // Set the volume safely @@ -291,39 +316,56 @@ void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t m // Apply the volume change pa_threaded_mainloop_lock(mainloop_); - pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); + if (is_source) { + pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume, sourceVolumeModifyCb, + this); + } else { + pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); + } pa_threaded_mainloop_unlock(mainloop_); } -void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t max_volume) { +void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t max_volume, + AudioTarget target) { // Early return if context is not ready if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) { spdlog::error("PulseAudio context not ready"); return; } + bool is_source = target == AudioTarget::Source; + + // Select the appropriate stored volume structure and current volume + auto& ref_volume = is_source ? pa_source_volume_ : pa_volume_; + auto current_volume = is_source ? source_volume_ : volume_; + // Prepare volume structure pa_cvolume pa_volume; pa_cvolume_init(&pa_volume); // Use existing volume structure if valid, otherwise create a safe default - if ((pa_cvolume_valid(&pa_volume_) != 0) && (pa_channels_valid(pa_volume_.channels) != 0)) { - pa_volume = pa_volume_; + if ((pa_cvolume_valid(&ref_volume) != 0) && (pa_channels_valid(ref_volume.channels) != 0)) { + pa_volume = ref_volume; } else { - // Set stereo as a safe default - pa_volume.channels = 2; - spdlog::debug("Using default stereo volume structure"); + // Mono for sources (microphones), stereo for sinks + pa_volume.channels = is_source ? 1 : 2; + spdlog::debug("Using default volume structure ({} channels)", pa_volume.channels); // Initialize all channels to current volume level double volume_tick = static_cast(PA_VOLUME_NORM) / 100; - pa_volume_t vol = volume_ * volume_tick; + pa_volume_t vol = current_volume * volume_tick; for (uint8_t i = 0; i < pa_volume.channels; i++) { pa_volume.values[i] = vol; } // No need to continue with volume change if we had to create a new structure pa_threaded_mainloop_lock(mainloop_); - pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); + if (is_source) { + pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume, + sourceVolumeModifyCb, this); + } else { + pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); + } pa_threaded_mainloop_unlock(mainloop_); return; } @@ -333,10 +375,10 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma pa_volume_t change; max_volume = std::min(max_volume, static_cast(PA_VOLUME_UI_MAX)); - if (change_type == ChangeType::Increase && volume_ < max_volume) { + if (change_type == ChangeType::Increase && current_volume < max_volume) { // Calculate how much to increase - if (volume_ + step > max_volume) { - change = round((max_volume - volume_) * volume_tick); + if (current_volume + step > max_volume) { + change = round((max_volume - current_volume) * volume_tick); } else { change = round(step * volume_tick); } @@ -345,10 +387,10 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma for (uint8_t i = 0; i < pa_volume.channels; i++) { pa_volume.values[i] = std::min(pa_volume.values[i] + change, PA_VOLUME_MAX); } - } else if (change_type == ChangeType::Decrease && volume_ > 0) { + } else if (change_type == ChangeType::Decrease && current_volume > 0) { // Calculate how much to decrease - if (volume_ - step < 0) { - change = round(volume_ * volume_tick); + if (current_volume - step < 0) { + change = round(current_volume * volume_tick); } else { change = round(step * volume_tick); } @@ -364,7 +406,12 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma // Apply the volume change pa_threaded_mainloop_lock(mainloop_); - pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); + if (is_source) { + pa_context_set_source_volume_by_index(context_, source_idx_, &pa_volume, sourceVolumeModifyCb, + this); + } else { + pa_context_set_sink_volume_by_index(context_, sink_idx_, &pa_volume, volumeModifyCb, this); + } pa_threaded_mainloop_unlock(mainloop_); } From f48fce57dc6f40d8b3992041a715776fe6072dd1 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Fri, 6 Mar 2026 18:38:21 -0600 Subject: [PATCH 603/780] fix(menu): keep popup menus alive after builder teardown The popup menu was retrieved from GtkBuilder and stored in menu_, but the builder was unref'd immediately after construction. That left the later popup path operating on a builder-owned GtkMenu whose lifetime was no longer guaranteed, which matches the GTK_IS_WIDGET and GTK_IS_MENU assertions from the regression report. Take an owned reference to the built menu and release it in AModule teardown so popup menus stay valid without extending the lifetime of the whole builder. Signed-off-by: Austin Horstman --- src/ALabel.cpp | 2 ++ src/AModule.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/ALabel.cpp b/src/ALabel.cpp index fe87e098..795f87f1 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -100,6 +100,8 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st g_object_unref(builder); throw std::runtime_error("Failed to get 'menu' object from GtkBuilder"); } + // Keep the menu alive after dropping the transient GtkBuilder. + g_object_ref(menu_); submenus_ = std::map(); menuActionsMap_ = std::map(); diff --git a/src/AModule.cpp b/src/AModule.cpp index 5f3a187a..a5ba69d3 100644 --- a/src/AModule.cpp +++ b/src/AModule.cpp @@ -88,6 +88,10 @@ AModule::~AModule() { killpg(pid, SIGTERM); } } + if (menu_ != nullptr) { + g_object_unref(menu_); + menu_ = nullptr; + } } auto AModule::update() -> void { From 790101f824513a6b48396fd956dd3a9dfae5ad4b Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Fri, 6 Mar 2026 18:49:02 -0600 Subject: [PATCH 604/780] chore: format Signed-off-by: Austin Horstman --- include/modules/sway/ipc/client.hpp | 2 +- src/modules/custom.cpp | 1 + src/modules/hyprland/window.cpp | 17 ++++++++++------- src/modules/niri/backend.cpp | 2 +- test/utils/command.cpp | 5 +++-- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/include/modules/sway/ipc/client.hpp b/include/modules/sway/ipc/client.hpp index eb0f32f9..f6eb7c40 100644 --- a/include/modules/sway/ipc/client.hpp +++ b/include/modules/sway/ipc/client.hpp @@ -13,8 +13,8 @@ #include "ipc.hpp" #include "util/SafeSignal.hpp" -#include "util/sleeper_thread.hpp" #include "util/scoped_fd.hpp" +#include "util/sleeper_thread.hpp" namespace waybar::modules::sway { diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 93956614..28def8c9 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -1,6 +1,7 @@ #include "modules/custom.hpp" #include + #include #include "util/scope_guard.hpp" diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index 97d49eaf..a67e81ac 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -189,8 +189,9 @@ void Window::queryActiveWorkspace() { if (workspace_.windows > 0) { const auto clients = m_ipc.getSocket1JsonReply("clients"); if (clients.isArray()) { - auto activeWindow = std::ranges::find_if( - clients, [&](const Json::Value& window) { return window["address"] == workspace_.last_window; }); + auto activeWindow = std::ranges::find_if(clients, [&](const Json::Value& window) { + return window["address"] == workspace_.last_window; + }); if (activeWindow == std::end(clients)) { focused_ = false; @@ -200,17 +201,19 @@ void Window::queryActiveWorkspace() { windowData_ = WindowData::parse(*activeWindow); updateAppIconName(windowData_.class_name, windowData_.initial_class_name); std::vector workspaceWindows; - std::ranges::copy_if(clients, std::back_inserter(workspaceWindows), [&](const Json::Value& window) { - return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool(); - }); + std::ranges::copy_if( + clients, std::back_inserter(workspaceWindows), [&](const Json::Value& window) { + return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool(); + }); swallowing_ = std::ranges::any_of(workspaceWindows, [&](const Json::Value& window) { return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0"; }); std::vector visibleWindows; std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows), [&](const Json::Value& window) { return !window["hidden"].asBool(); }); - solo_ = 1 == std::count_if(visibleWindows.begin(), visibleWindows.end(), - [&](const Json::Value& window) { return !window["floating"].asBool(); }); + solo_ = 1 == std::count_if( + visibleWindows.begin(), visibleWindows.end(), + [&](const Json::Value& window) { return !window["floating"].asBool(); }); allFloating_ = std::ranges::all_of( visibleWindows, [&](const Json::Value& window) { return window["floating"].asBool(); }); fullscreen_ = windowData_.fullscreen; diff --git a/src/modules/niri/backend.cpp b/src/modules/niri/backend.cpp index c23aaa47..de3f8a9a 100644 --- a/src/modules/niri/backend.cpp +++ b/src/modules/niri/backend.cpp @@ -13,11 +13,11 @@ #include #include -#include "util/scoped_fd.hpp" #include "giomm/datainputstream.h" #include "giomm/dataoutputstream.h" #include "giomm/unixinputstream.h" #include "giomm/unixoutputstream.h" +#include "util/scoped_fd.hpp" namespace waybar::modules::niri { diff --git a/test/utils/command.cpp b/test/utils/command.cpp index 2ccb3383..053a2b77 100644 --- a/test/utils/command.cpp +++ b/test/utils/command.cpp @@ -4,11 +4,12 @@ #include #endif +#include +#include + #include #include #include -#include -#include std::mutex reap_mtx; std::list reap; From 0a35b86e2088360d3c27e46a4e61e97ef428eb51 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Fri, 6 Mar 2026 18:33:12 -0600 Subject: [PATCH 605/780] fix(hyprland/ipc): honor the requested instance signature The Hyprland IPC helper cached the socket folder with the first instance signature already appended, so later calls ignored their instanceSig argument and always reused the first path. That made the helper violate its own API even though most real Waybar sessions only talk to a single Hyprland instance. Cache only the base socket directory and append the requested signature per lookup. This fixes correctness for tests, nested or debug multi-instance setups, and future code that needs to resolve a different signature, without claiming support for one Waybar process managing multiple Hyprland sessions. Signed-off-by: Austin Horstman --- src/modules/hyprland/backend.cpp | 70 +++++++++++++++++++------------- test/hyprland/backend.cpp | 18 ++++++++ 2 files changed, 60 insertions(+), 28 deletions(-) diff --git a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp index 0f02b919..d0371202 100644 --- a/src/modules/hyprland/backend.cpp +++ b/src/modules/hyprland/backend.cpp @@ -25,27 +25,23 @@ std::filesystem::path IPC::getSocketFolder(const char* instanceSig) { static std::mutex folderMutex; std::unique_lock lock(folderMutex); - // socket path, specified by EventManager of Hyprland - if (!socketFolder_.empty()) { - return socketFolder_; + if (socketFolder_.empty()) { + const char* xdgRuntimeDirEnv = std::getenv("XDG_RUNTIME_DIR"); + std::filesystem::path xdgRuntimeDir; + // Only set path if env variable is set + if (xdgRuntimeDirEnv != nullptr) { + xdgRuntimeDir = std::filesystem::path(xdgRuntimeDirEnv); + } + + if (!xdgRuntimeDir.empty() && std::filesystem::exists(xdgRuntimeDir / "hypr")) { + socketFolder_ = xdgRuntimeDir / "hypr"; + } else { + spdlog::warn("$XDG_RUNTIME_DIR/hypr does not exist, falling back to /tmp/hypr"); + socketFolder_ = std::filesystem::path("/tmp") / "hypr"; + } } - const char* xdgRuntimeDirEnv = std::getenv("XDG_RUNTIME_DIR"); - std::filesystem::path xdgRuntimeDir; - // Only set path if env variable is set - if (xdgRuntimeDirEnv != nullptr) { - xdgRuntimeDir = std::filesystem::path(xdgRuntimeDirEnv); - } - - if (!xdgRuntimeDir.empty() && std::filesystem::exists(xdgRuntimeDir / "hypr")) { - socketFolder_ = xdgRuntimeDir / "hypr"; - } else { - spdlog::warn("$XDG_RUNTIME_DIR/hypr does not exist, falling back to /tmp/hypr"); - socketFolder_ = std::filesystem::path("/tmp") / "hypr"; - } - - socketFolder_ = socketFolder_ / instanceSig; - return socketFolder_; + return socketFolder_ / instanceSig; } IPC::IPC() { @@ -91,7 +87,7 @@ void IPC::socketListener() { spdlog::info("Hyprland IPC starting"); - struct sockaddr_un addr; + struct sockaddr_un addr = {}; const int socketfd = socket(AF_UNIX, SOCK_STREAM, 0); if (socketfd == -1) { @@ -102,10 +98,13 @@ void IPC::socketListener() { addr.sun_family = AF_UNIX; auto socketPath = IPC::getSocketFolder(his) / ".socket2.sock"; + if (socketPath.native().size() >= sizeof(addr.sun_path)) { + spdlog::error("Hyprland IPC: Socket path is too long: {}", socketPath.string()); + close(socketfd); + return; + } strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1); - addr.sun_path[sizeof(addr.sun_path) - 1] = 0; - int l = sizeof(struct sockaddr_un); if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) { @@ -233,8 +232,10 @@ std::string IPC::getSocket1Reply(const std::string& rq) { std::string socketPath = IPC::getSocketFolder(instanceSig) / ".socket.sock"; // Use snprintf to copy the socketPath string into serverAddress.sun_path - if (snprintf(serverAddress.sun_path, sizeof(serverAddress.sun_path), "%s", socketPath.c_str()) < - 0) { + const auto socketPathLength = + snprintf(serverAddress.sun_path, sizeof(serverAddress.sun_path), "%s", socketPath.c_str()); + if (socketPathLength < 0 || + socketPathLength >= static_cast(sizeof(serverAddress.sun_path))) { throw std::runtime_error("Hyprland IPC: Couldn't copy socket path (6)"); } @@ -243,15 +244,28 @@ std::string IPC::getSocket1Reply(const std::string& rq) { throw std::runtime_error("Hyprland IPC: Couldn't connect to " + socketPath + ". (3)"); } - auto sizeWritten = write(serverSocket, rq.c_str(), rq.length()); + std::size_t totalWritten = 0; + while (totalWritten < rq.length()) { + const auto sizeWritten = + write(serverSocket, rq.c_str() + totalWritten, rq.length() - totalWritten); - if (sizeWritten < 0) { - spdlog::error("Hyprland IPC: Couldn't write (4)"); - return ""; + if (sizeWritten < 0) { + if (errno == EINTR) { + continue; + } + spdlog::error("Hyprland IPC: Couldn't write (4)"); + return ""; + } + if (sizeWritten == 0) { + spdlog::error("Hyprland IPC: Socket write made no progress"); + return ""; + } + totalWritten += static_cast(sizeWritten); } std::array buffer = {0}; std::string response; + ssize_t sizeWritten = 0; do { sizeWritten = read(serverSocket, buffer.data(), 8192); diff --git a/test/hyprland/backend.cpp b/test/hyprland/backend.cpp index cc7295ec..ccc2da65 100644 --- a/test/hyprland/backend.cpp +++ b/test/hyprland/backend.cpp @@ -86,6 +86,24 @@ TEST_CASE("XDGRuntimeDirExistsNoHyprDir", "[getSocketFolder]") { fs::remove_all(tempDir, ec); } +TEST_CASE("Socket folder is resolved per instance signature", "[getSocketFolder]") { + const fs::path tempDir = fs::temp_directory_path() / "hypr_test/run/user/1000"; + std::error_code ec; + fs::remove_all(tempDir, ec); + fs::create_directories(tempDir / "hypr"); + setenv("XDG_RUNTIME_DIR", tempDir.c_str(), 1); + IPCTestHelper::resetSocketFolder(); + + const auto firstPath = hyprland::IPC::getSocketFolder("instance_a"); + const auto secondPath = hyprland::IPC::getSocketFolder("instance_b"); + + REQUIRE(firstPath == tempDir / "hypr" / "instance_a"); + REQUIRE(secondPath == tempDir / "hypr" / "instance_b"); + REQUIRE(firstPath != secondPath); + + fs::remove_all(tempDir, ec); +} + TEST_CASE("getSocket1Reply throws on no socket", "[getSocket1Reply]") { unsetenv("HYPRLAND_INSTANCE_SIGNATURE"); IPCTestHelper::resetSocketFolder(); From b1a87f943c4da2cc6ab2fef700f64497db7fc205 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Fri, 6 Mar 2026 18:33:14 -0600 Subject: [PATCH 606/780] fix(hyprland/window): avoid stale state during IPC refresh The window module re-entered the same shared_mutex while refreshing IPC state: update() took the lock and then called queryActiveWorkspace(), which tried to lock it again. That is undefined behavior for std::shared_mutex and could manifest as a deadlock. Remove the recursive lock path and reset the derived window state before each IPC refresh. That keeps solo/floating/swallowing/fullscreen classes from sticking around when the client lookup fails or a workspace becomes empty. Signed-off-by: Austin Horstman --- include/modules/hyprland/window.hpp | 22 +++--- src/modules/hyprland/window.cpp | 100 ++++++++++++++-------------- 2 files changed, 60 insertions(+), 62 deletions(-) diff --git a/include/modules/hyprland/window.hpp b/include/modules/hyprland/window.hpp index 2be64594..9725d33a 100644 --- a/include/modules/hyprland/window.hpp +++ b/include/modules/hyprland/window.hpp @@ -20,8 +20,8 @@ class Window : public waybar::AAppIconLabel, public EventHandler { private: struct Workspace { - int id; - int windows; + int id = 0; + int windows = 0; std::string last_window; std::string last_window_title; @@ -29,14 +29,14 @@ class Window : public waybar::AAppIconLabel, public EventHandler { }; struct WindowData { - bool floating; + bool floating = false; int monitor = -1; std::string class_name; std::string initial_class_name; std::string title; std::string initial_title; - bool fullscreen; - bool grouped; + bool fullscreen = false; + bool grouped = false; static auto parse(const Json::Value&) -> WindowData; }; @@ -47,7 +47,7 @@ class Window : public waybar::AAppIconLabel, public EventHandler { void queryActiveWorkspace(); void setClass(const std::string&, bool enable); - bool separateOutputs_; + bool separateOutputs_ = false; std::mutex mutex_; const Bar& bar_; util::JsonParser parser_; @@ -55,11 +55,11 @@ class Window : public waybar::AAppIconLabel, public EventHandler { Workspace workspace_; std::string soloClass_; std::string lastSoloClass_; - bool solo_; - bool allFloating_; - bool swallowing_; - bool fullscreen_; - bool focused_; + bool solo_ = false; + bool allFloating_ = false; + bool swallowing_ = false; + bool fullscreen_ = false; + bool focused_ = false; IPC& m_ipc; }; diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index a67e81ac..d3fe6edf 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -32,7 +32,6 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) windowIpcUniqueLock.unlock(); - queryActiveWorkspace(); update(); dp.emit(); } @@ -177,66 +176,65 @@ auto Window::WindowData::parse(const Json::Value& value) -> Window::WindowData { } void Window::queryActiveWorkspace() { - std::shared_lock windowIpcShareLock(windowIpcSmtx); - if (separateOutputs_) { workspace_ = getActiveWorkspace(this->bar_.output->name); } else { workspace_ = getActiveWorkspace(); } + focused_ = false; + windowData_ = WindowData{}; + allFloating_ = false; + swallowing_ = false; + fullscreen_ = false; + solo_ = false; + soloClass_.clear(); + + if (workspace_.windows <= 0) { + return; + } + + const auto clients = m_ipc.getSocket1JsonReply("clients"); + if (!clients.isArray()) { + return; + } + + auto activeWindow = std::ranges::find_if(clients, [&](const Json::Value& window) { + return window["address"] == workspace_.last_window; + }); + + if (activeWindow == std::end(clients)) { + return; + } + focused_ = true; - if (workspace_.windows > 0) { - const auto clients = m_ipc.getSocket1JsonReply("clients"); - if (clients.isArray()) { - auto activeWindow = std::ranges::find_if(clients, [&](const Json::Value& window) { - return window["address"] == workspace_.last_window; + windowData_ = WindowData::parse(*activeWindow); + updateAppIconName(windowData_.class_name, windowData_.initial_class_name); + std::vector workspaceWindows; + std::ranges::copy_if( + clients, std::back_inserter(workspaceWindows), [&](const Json::Value& window) { + return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool(); }); + swallowing_ = std::ranges::any_of(workspaceWindows, [&](const Json::Value& window) { + return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0"; + }); + std::vector visibleWindows; + std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows), + [&](const Json::Value& window) { return !window["hidden"].asBool(); }); + solo_ = 1 == std::count_if( + visibleWindows.begin(), visibleWindows.end(), + [&](const Json::Value& window) { return !window["floating"].asBool(); }); + allFloating_ = std::ranges::all_of( + visibleWindows, [&](const Json::Value& window) { return window["floating"].asBool(); }); + fullscreen_ = windowData_.fullscreen; - if (activeWindow == std::end(clients)) { - focused_ = false; - return; - } + // Fullscreen windows look like they are solo + if (fullscreen_) { + solo_ = true; + } - windowData_ = WindowData::parse(*activeWindow); - updateAppIconName(windowData_.class_name, windowData_.initial_class_name); - std::vector workspaceWindows; - std::ranges::copy_if( - clients, std::back_inserter(workspaceWindows), [&](const Json::Value& window) { - return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool(); - }); - swallowing_ = std::ranges::any_of(workspaceWindows, [&](const Json::Value& window) { - return !window["swallowing"].isNull() && window["swallowing"].asString() != "0x0"; - }); - std::vector visibleWindows; - std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows), - [&](const Json::Value& window) { return !window["hidden"].asBool(); }); - solo_ = 1 == std::count_if( - visibleWindows.begin(), visibleWindows.end(), - [&](const Json::Value& window) { return !window["floating"].asBool(); }); - allFloating_ = std::ranges::all_of( - visibleWindows, [&](const Json::Value& window) { return window["floating"].asBool(); }); - fullscreen_ = windowData_.fullscreen; - - // Fullscreen windows look like they are solo - if (fullscreen_) { - solo_ = true; - } - - if (solo_) { - soloClass_ = windowData_.class_name; - } else { - soloClass_ = ""; - } - } - } else { - focused_ = false; - windowData_ = WindowData{}; - allFloating_ = false; - swallowing_ = false; - fullscreen_ = false; - solo_ = false; - soloClass_ = ""; + if (solo_) { + soloClass_ = windowData_.class_name; } } From dd47a2b826eecf31a66ed1c6a2caf769507cdca5 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Fri, 6 Mar 2026 18:33:17 -0600 Subject: [PATCH 607/780] fix(hyprland/workspaces): stabilize reload and event handling Hyprland workspace reloads could stack duplicate scroll-event connections, causing a single wheel gesture to switch multiple workspaces after repeated config reloads. The persistent-workspaces monitor-array form also created the monitor name instead of the configured workspace name. Disconnect and replace the scroll handler on reinit, fix the persistent workspace name selection, normalize urgent-window address matching, and reject malformed workspace payloads before they corrupt the local state machine. Signed-off-by: Austin Horstman --- include/modules/hyprland/workspaces.hpp | 2 + src/modules/hyprland/workspaces.cpp | 59 +++++++++++++++++-------- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 0cec3c1d..03548ccb 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -208,6 +209,7 @@ class Workspaces : public AModule, public EventHandler { std::mutex m_mutex; const Bar& m_bar; Gtk::Box m_box; + sigc::connection m_scrollEventConnection_; IPC& m_ipc; }; diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index cbd14f90..f794249b 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -34,6 +34,9 @@ Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& } Workspaces::~Workspaces() { + if (m_scrollEventConnection_.connected()) { + m_scrollEventConnection_.disconnect(); + } m_ipc.unregisterForIPC(this); // wait for possible event handler to finish std::lock_guard lg(m_mutex); @@ -44,10 +47,14 @@ void Workspaces::init() { initializeWorkspaces(); + if (m_scrollEventConnection_.connected()) { + m_scrollEventConnection_.disconnect(); + } if (barScroll()) { auto& window = const_cast(m_bar).window; window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); - window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll)); + m_scrollEventConnection_ = + window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll)); } dp.emit(); @@ -271,7 +278,7 @@ void Workspaces::loadPersistentWorkspacesFromConfig(Json::Value const& clientsJs // key is the workspace and value is array of monitors to create on for (const Json::Value& monitor : value) { if (monitor.isString() && monitor.asString() == currentMonitor) { - persistentWorkspacesToCreate.emplace_back(currentMonitor); + persistentWorkspacesToCreate.emplace_back(key); break; } } @@ -332,8 +339,13 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value& c void Workspaces::onEvent(const std::string& ev) { std::lock_guard lock(m_mutex); - std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>')); - std::string payload = ev.substr(eventName.size() + 2); + const auto separator = ev.find(">>"); + if (separator == std::string::npos) { + spdlog::warn("Malformed Hyprland workspace event: {}", ev); + return; + } + std::string eventName = ev.substr(0, separator); + std::string payload = ev.substr(separator + 2); if (eventName == "workspacev2") { onWorkspaceActivated(payload); @@ -496,19 +508,21 @@ void Workspaces::onMonitorFocused(std::string const& payload) { void Workspaces::onWindowOpened(std::string const& payload) { spdlog::trace("Window opened: {}", payload); updateWindowCount(); - size_t lastCommaIdx = 0; - size_t nextCommaIdx = payload.find(','); - std::string windowAddress = payload.substr(lastCommaIdx, nextCommaIdx - lastCommaIdx); + const auto firstComma = payload.find(','); + const auto secondComma = + firstComma == std::string::npos ? std::string::npos : payload.find(',', firstComma + 1); + const auto thirdComma = + secondComma == std::string::npos ? std::string::npos : payload.find(',', secondComma + 1); + if (firstComma == std::string::npos || secondComma == std::string::npos || + thirdComma == std::string::npos) { + spdlog::warn("Malformed Hyprland openwindow payload: {}", payload); + return; + } - lastCommaIdx = nextCommaIdx; - nextCommaIdx = payload.find(',', nextCommaIdx + 1); - std::string workspaceName = payload.substr(lastCommaIdx + 1, nextCommaIdx - lastCommaIdx - 1); - - lastCommaIdx = nextCommaIdx; - nextCommaIdx = payload.find(',', nextCommaIdx + 1); - std::string windowClass = payload.substr(lastCommaIdx + 1, nextCommaIdx - lastCommaIdx - 1); - - std::string windowTitle = payload.substr(nextCommaIdx + 1, payload.length() - nextCommaIdx); + std::string windowAddress = payload.substr(0, firstComma); + std::string workspaceName = payload.substr(firstComma + 1, secondComma - firstComma - 1); + std::string windowClass = payload.substr(secondComma + 1, thirdComma - secondComma - 1); + std::string windowTitle = payload.substr(thirdComma + 1); bool isActive = m_currentActiveWindowAddress == windowAddress; m_windowsToCreate.emplace_back(workspaceName, windowAddress, windowClass, windowTitle, isActive); @@ -1002,10 +1016,12 @@ void Workspaces::sortWorkspaces() { void Workspaces::setUrgentWorkspace(std::string const& windowaddress) { const Json::Value clientsJson = m_ipc.getSocket1JsonReply("clients"); + const std::string normalizedAddress = + windowaddress.starts_with("0x") ? windowaddress : fmt::format("0x{}", windowaddress); int workspaceId = -1; for (const auto& clientJson : clientsJson) { - if (clientJson["address"].asString().ends_with(windowaddress)) { + if (clientJson["address"].asString() == normalizedAddress) { workspaceId = clientJson["workspace"]["id"].asInt(); break; } @@ -1134,7 +1150,11 @@ std::string Workspaces::makePayload(Args const&... args) { } std::pair Workspaces::splitDoublePayload(std::string const& payload) { - const std::string part1 = payload.substr(0, payload.find(',')); + const auto separator = payload.find(','); + if (separator == std::string::npos) { + throw std::invalid_argument("Expected a two-part Hyprland payload"); + } + const std::string part1 = payload.substr(0, separator); const std::string part2 = payload.substr(part1.size() + 1); return {part1, part2}; } @@ -1143,6 +1163,9 @@ std::tuple Workspaces::splitTriplePayload std::string const& payload) { const size_t firstComma = payload.find(','); const size_t secondComma = payload.find(',', firstComma + 1); + if (firstComma == std::string::npos || secondComma == std::string::npos) { + throw std::invalid_argument("Expected a three-part Hyprland payload"); + } const std::string part1 = payload.substr(0, firstComma); const std::string part2 = payload.substr(firstComma + 1, secondComma - (firstComma + 1)); From 6317022304a49c0033c52f0c6cdfe038e88dd314 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Fri, 6 Mar 2026 18:33:19 -0600 Subject: [PATCH 608/780] fix(hyprland): guard malformed module events The language and submap modules assumed their Hyprland payload delimiters were always present. When that assumption is violated, the old code could perform invalid iterator math or throw while slicing the event string. Validate the expected separators up front and bail out with a warning when the event is malformed so the modules degrade safely instead of crashing the update path. Signed-off-by: Austin Horstman --- src/modules/hyprland/language.cpp | 26 +++++++++++++++++++++----- src/modules/hyprland/submap.cpp | 7 ++++++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index 25f6789d..6e0fe23d 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -63,19 +63,35 @@ auto Language::update() -> void { void Language::onEvent(const std::string& ev) { std::lock_guard lg(mutex_); - std::string kbName(begin(ev) + ev.find_last_of('>') + 1, begin(ev) + ev.find_first_of(',')); + const auto payloadStart = ev.find(">>"); + if (payloadStart == std::string::npos) { + spdlog::warn("hyprland language received malformed event: {}", ev); + return; + } + const auto payload = ev.substr(payloadStart + 2); + const auto kbSeparator = payload.find(','); + if (kbSeparator == std::string::npos) { + spdlog::warn("hyprland language received malformed event payload: {}", ev); + return; + } + std::string kbName = payload.substr(0, kbSeparator); // Last comma before variants parenthesis, eg: // activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl., // with dead keys) std::string beforeParenthesis; - auto parenthesisPos = ev.find_last_of('('); + auto parenthesisPos = payload.find_last_of('('); if (parenthesisPos == std::string::npos) { - beforeParenthesis = ev; + beforeParenthesis = payload; } else { - beforeParenthesis = std::string(begin(ev), begin(ev) + parenthesisPos); + beforeParenthesis = payload.substr(0, parenthesisPos); } - auto layoutName = ev.substr(beforeParenthesis.find_last_of(',') + 1); + const auto layoutSeparator = beforeParenthesis.find_last_of(','); + if (layoutSeparator == std::string::npos) { + spdlog::warn("hyprland language received malformed layout payload: {}", ev); + return; + } + auto layoutName = payload.substr(layoutSeparator + 1); if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString()) return; // ignore diff --git a/src/modules/hyprland/submap.cpp b/src/modules/hyprland/submap.cpp index ff18e7f3..36b9bf79 100644 --- a/src/modules/hyprland/submap.cpp +++ b/src/modules/hyprland/submap.cpp @@ -75,7 +75,12 @@ void Submap::onEvent(const std::string& ev) { return; } - auto submapName = ev.substr(ev.find_first_of('>') + 2); + const auto separator = ev.find(">>"); + if (separator == std::string::npos) { + spdlog::warn("hyprland submap received malformed event: {}", ev); + return; + } + auto submapName = ev.substr(separator + 2); submap_ = submapName; From 2a748f1a56ef9c49d3022657f7265ae4d3f65b46 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Sun, 8 Mar 2026 01:08:39 -0600 Subject: [PATCH 609/780] fix(sni): delay tray item insertion until proxies are ready Only add tray widgets after the SNI proxy has finished initializing and the item has a valid id/category pair. This also removes invalid items through the host teardown path, refreshes the tray when item status changes, and avoids calling DBus methods through a null proxy during early clicks or scroll events. Signed-off-by: Austin Horstman --- include/modules/sni/host.hpp | 7 ++++- include/modules/sni/item.hpp | 14 +++++++++- include/modules/sni/tray.hpp | 1 + src/modules/sni/host.cpp | 54 +++++++++++++++++++++++++++++++----- src/modules/sni/item.cpp | 48 +++++++++++++++++++++++++++----- src/modules/sni/tray.cpp | 5 +++- 6 files changed, 112 insertions(+), 17 deletions(-) diff --git a/include/modules/sni/host.hpp b/include/modules/sni/host.hpp index 7248ad2f..d76ec74a 100644 --- a/include/modules/sni/host.hpp +++ b/include/modules/sni/host.hpp @@ -16,7 +16,7 @@ class Host { public: Host(const std::size_t id, const Json::Value&, const Bar&, const std::function&)>&, - const std::function&)>&); + const std::function&)>&, const std::function&); ~Host(); private: @@ -28,6 +28,10 @@ class Host { static void registerHost(GObject*, GAsyncResult*, gpointer); static void itemRegistered(SnWatcher*, const gchar*, gpointer); static void itemUnregistered(SnWatcher*, const gchar*, gpointer); + void itemReady(Item&); + void itemInvalidated(Item&); + void removeItem(std::vector>::iterator); + void clearItems(); std::tuple getBusNameAndObjectPath(const std::string); void addRegisteredItem(const std::string& service); @@ -43,6 +47,7 @@ class Host { const Bar& bar_; const std::function&)> on_add_; const std::function&)> on_remove_; + const std::function on_update_; }; } // namespace waybar::modules::SNI diff --git a/include/modules/sni/item.hpp b/include/modules/sni/item.hpp index 503ab637..43200fdb 100644 --- a/include/modules/sni/item.hpp +++ b/include/modules/sni/item.hpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -25,9 +26,13 @@ struct ToolTip { class Item : public sigc::trackable { public: - Item(const std::string&, const std::string&, const Json::Value&, const Bar&); + Item(const std::string&, const std::string&, const Json::Value&, const Bar&, + const std::function&, const std::function&, + const std::function&); ~Item(); + bool isReady() const; + std::string bus_name; std::string object_path; @@ -62,6 +67,8 @@ class Item : public sigc::trackable { void proxyReady(Glib::RefPtr& result); void setProperty(const Glib::ustring& name, Glib::VariantBase& value); void setStatus(const Glib::ustring& value); + void setReady(); + void invalidate(); void setCustomIcon(const std::string& id); void getUpdatedProperties(); void processUpdatedProperties(Glib::RefPtr& result); @@ -86,8 +93,13 @@ class Item : public sigc::trackable { gdouble distance_scrolled_y_ = 0; // visibility of items with Status == Passive bool show_passive_ = false; + bool ready_ = false; + Glib::ustring status_ = "active"; const Bar& bar_; + const std::function on_ready_; + const std::function on_invalidate_; + const std::function on_updated_; Glib::RefPtr proxy_; Glib::RefPtr cancellable_; diff --git a/include/modules/sni/tray.hpp b/include/modules/sni/tray.hpp index 5f12d7f2..3d90b3fd 100644 --- a/include/modules/sni/tray.hpp +++ b/include/modules/sni/tray.hpp @@ -19,6 +19,7 @@ class Tray : public AModule { private: void onAdd(std::unique_ptr& item); void onRemove(std::unique_ptr& item); + void queueUpdate(); static inline std::size_t nb_hosts_ = 0; bool show_passive_ = false; diff --git a/src/modules/sni/host.cpp b/src/modules/sni/host.cpp index 6bd1154a..18eac643 100644 --- a/src/modules/sni/host.cpp +++ b/src/modules/sni/host.cpp @@ -8,7 +8,8 @@ namespace waybar::modules::SNI { Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar, const std::function&)>& on_add, - const std::function&)>& on_remove) + const std::function&)>& on_remove, + const std::function& on_update) : bus_name_("org.kde.StatusNotifierHost-" + std::to_string(getpid()) + "-" + std::to_string(id)), object_path_("/StatusNotifierHost/" + std::to_string(id)), @@ -17,7 +18,8 @@ Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar, config_(config), bar_(bar), on_add_(on_add), - on_remove_(on_remove) {} + on_remove_(on_remove), + on_update_(on_update) {} Host::~Host() { if (bus_name_id_ > 0) { @@ -54,7 +56,7 @@ void Host::nameVanished(const Glib::RefPtr& conn, const G g_cancellable_cancel(cancellable_); g_clear_object(&cancellable_); g_clear_object(&watcher_); - items_.clear(); + clearItems(); } void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) { @@ -117,13 +119,50 @@ void Host::itemUnregistered(SnWatcher* watcher, const gchar* service, gpointer d auto [bus_name, object_path] = host->getBusNameAndObjectPath(service); for (auto it = host->items_.begin(); it != host->items_.end(); ++it) { if ((*it)->bus_name == bus_name && (*it)->object_path == object_path) { - host->on_remove_(*it); - host->items_.erase(it); + host->removeItem(it); break; } } } +void Host::itemReady(Item& item) { + auto it = std::find_if(items_.begin(), items_.end(), + [&item](const auto& candidate) { return candidate.get() == &item; }); + if (it != items_.end() && (*it)->isReady()) { + on_add_(*it); + } +} + +void Host::itemInvalidated(Item& item) { + auto it = std::find_if(items_.begin(), items_.end(), + [&item](const auto& candidate) { return candidate.get() == &item; }); + if (it != items_.end()) { + removeItem(it); + } +} + +void Host::removeItem(std::vector>::iterator it) { + if ((*it)->isReady()) { + on_remove_(*it); + } + items_.erase(it); +} + +void Host::clearItems() { + bool removed_ready_item = false; + for (auto& item : items_) { + if (item->isReady()) { + on_remove_(item); + removed_ready_item = true; + } + } + bool had_items = !items_.empty(); + items_.clear(); + if (had_items && !removed_ready_item) { + on_update_(); + } +} + std::tuple Host::getBusNameAndObjectPath(const std::string service) { auto it = service.find('/'); if (it != std::string::npos) { @@ -139,8 +178,9 @@ void Host::addRegisteredItem(const std::string& service) { return bus_name == item->bus_name && object_path == item->object_path; }); if (it == items_.end()) { - items_.emplace_back(new Item(bus_name, object_path, config_, bar_)); - on_add_(items_.back()); + items_.emplace_back(new Item( + bus_name, object_path, config_, bar_, [this](Item& item) { itemReady(item); }, + [this](Item& item) { itemInvalidated(item); }, on_update_)); } } diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 1428bd8e..9820cc62 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -37,13 +37,18 @@ namespace waybar::modules::SNI { static const Glib::ustring SNI_INTERFACE_NAME = sn_item_interface_info()->name; static const unsigned UPDATE_DEBOUNCE_TIME = 10; -Item::Item(const std::string& bn, const std::string& op, const Json::Value& config, const Bar& bar) +Item::Item(const std::string& bn, const std::string& op, const Json::Value& config, const Bar& bar, + const std::function& on_ready, + const std::function& on_invalidate, const std::function& on_updated) : bus_name(bn), object_path(op), icon_size(16), effective_icon_size(0), icon_theme(Gtk::IconTheme::create()), - bar_(bar) { + bar_(bar), + on_ready_(on_ready), + on_invalidate_(on_invalidate), + on_updated_(on_updated) { if (config["icon-size"].isUInt()) { icon_size = config["icon-size"].asUInt(); } @@ -85,6 +90,8 @@ Item::~Item() { } } +bool Item::isReady() const { return ready_; } + bool Item::handleMouseEnter(GdkEventCrossing* const& e) { event_box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT); return false; @@ -112,14 +119,18 @@ void Item::proxyReady(Glib::RefPtr& result) { if (this->id.empty() || this->category.empty()) { spdlog::error("Invalid Status Notifier Item: {}, {}", bus_name, object_path); + invalidate(); return; } this->updateImage(); + setReady(); } catch (const Glib::Error& err) { spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); + invalidate(); } catch (const std::exception& err) { spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); + invalidate(); } } @@ -217,18 +228,35 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { } void Item::setStatus(const Glib::ustring& value) { - Glib::ustring lower = value.lowercase(); - event_box.set_visible(show_passive_ || lower.compare("passive") != 0); + status_ = value.lowercase(); + event_box.set_visible(show_passive_ || status_.compare("passive") != 0); auto style = event_box.get_style_context(); for (const auto& class_name : style->list_classes()) { style->remove_class(class_name); } - if (lower.compare("needsattention") == 0) { + auto css_class = status_; + if (css_class.compare("needsattention") == 0) { // convert status to dash-case for CSS - lower = "needs-attention"; + css_class = "needs-attention"; } - style->add_class(lower); + style->add_class(css_class); + on_updated_(); +} + +void Item::setReady() { + if (ready_) { + return; + } + ready_ = true; + on_ready_(*this); +} + +void Item::invalidate() { + if (ready_) { + ready_ = false; + } + on_invalidate_(*this); } void Item::setCustomIcon(const std::string& id) { @@ -464,6 +492,9 @@ void Item::makeMenu() { } bool Item::handleClick(GdkEventButton* const& ev) { + if (!proxy_) { + return false; + } auto parameters = Glib::VariantContainerBase::create_tuple( {Glib::Variant::create(ev->x_root + bar_.x_global), Glib::Variant::create(ev->y_root + bar_.y_global)}); @@ -491,6 +522,9 @@ bool Item::handleClick(GdkEventButton* const& ev) { } bool Item::handleScroll(GdkEventScroll* const& ev) { + if (!proxy_) { + return false; + } int dx = 0, dy = 0; switch (ev->direction) { case GDK_SCROLL_UP: diff --git a/src/modules/sni/tray.cpp b/src/modules/sni/tray.cpp index 34a3c05f..114aba78 100644 --- a/src/modules/sni/tray.cpp +++ b/src/modules/sni/tray.cpp @@ -13,7 +13,8 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) box_(bar.orientation, 0), watcher_(SNI::Watcher::getInstance()), host_(nb_hosts_, config, bar, std::bind(&Tray::onAdd, this, std::placeholders::_1), - std::bind(&Tray::onRemove, this, std::placeholders::_1)) { + std::bind(&Tray::onRemove, this, std::placeholders::_1), + std::bind(&Tray::queueUpdate, this)) { box_.set_name("tray"); event_box_.add(box_); if (!id.empty()) { @@ -33,6 +34,8 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) dp.emit(); } +void Tray::queueUpdate() { dp.emit(); } + void Tray::onAdd(std::unique_ptr& item) { if (config_["reverse-direction"].isBool() && config_["reverse-direction"].asBool()) { box_.pack_end(item->event_box); From 78f6cde232147ee98986edd3cacfe9c6570bd7ec Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Sun, 8 Mar 2026 01:08:43 -0600 Subject: [PATCH 610/780] fix(sni): correct watcher host teardown signaling Return the host registration method correctly on duplicate host registration and emit HostUnregistered instead of HostRegistered when the last host vanishes. Also free the corresponding name watch once the tracked host/item disappears so the watcher does not leak stale watch records. Signed-off-by: Austin Horstman --- src/modules/sni/watcher.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/sni/watcher.cpp b/src/modules/sni/watcher.cpp index 1534d924..66083c99 100644 --- a/src/modules/sni/watcher.cpp +++ b/src/modules/sni/watcher.cpp @@ -69,7 +69,7 @@ gboolean Watcher::handleRegisterHost(Watcher* obj, GDBusMethodInvocation* invoca if (watch != nullptr) { g_warning("Status Notifier Host with bus name '%s' and object path '%s' is already registered", bus_name, object_path); - sn_watcher_complete_register_item(obj->watcher_, invocation); + sn_watcher_complete_register_host(obj->watcher_, invocation); return TRUE; } watch = gfWatchNew(GF_WATCH_TYPE_HOST, service, bus_name, object_path, obj); @@ -158,7 +158,7 @@ void Watcher::nameVanished(GDBusConnection* connection, const char* name, gpoint watch->watcher->hosts_ = g_slist_remove(watch->watcher->hosts_, watch); if (watch->watcher->hosts_ == nullptr) { sn_watcher_set_is_host_registered(watch->watcher->watcher_, FALSE); - sn_watcher_emit_host_registered(watch->watcher->watcher_); + sn_watcher_emit_host_unregistered(watch->watcher->watcher_); } } else if (watch->type == GF_WATCH_TYPE_ITEM) { watch->watcher->items_ = g_slist_remove(watch->watcher->items_, watch); @@ -167,6 +167,7 @@ void Watcher::nameVanished(GDBusConnection* connection, const char* name, gpoint sn_watcher_emit_item_unregistered(watch->watcher->watcher_, tmp); g_free(tmp); } + gfWatchFree(watch); } void Watcher::updateRegisteredItems(SnWatcher* obj) { From f6d92fd708d0fd87b008e3ed8005bc6b65fb3e05 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Sun, 8 Mar 2026 01:08:53 -0600 Subject: [PATCH 611/780] fix(sni): render attention and overlay tray icon assets Load attention and overlay pixmaps from item properties, watch the corresponding update signals, and prefer attention artwork while an item is in NeedsAttention state. When an item only exports an attention movie asset, fall back to loading that asset as a static pixbuf so the tray still shows the alert state. Signed-off-by: Austin Horstman --- include/modules/sni/item.hpp | 9 ++- src/modules/sni/item.cpp | 110 +++++++++++++++++++++++++++-------- 2 files changed, 93 insertions(+), 26 deletions(-) diff --git a/include/modules/sni/item.hpp b/include/modules/sni/item.hpp index 43200fdb..74d54f4c 100644 --- a/include/modules/sni/item.hpp +++ b/include/modules/sni/item.hpp @@ -48,7 +48,9 @@ class Item : public sigc::trackable { Glib::RefPtr icon_pixmap; Glib::RefPtr icon_theme; std::string overlay_icon_name; + Glib::RefPtr overlay_icon_pixmap; std::string attention_icon_name; + Glib::RefPtr attention_icon_pixmap; std::string attention_movie_name; std::string icon_theme_path; std::string menu; @@ -76,8 +78,13 @@ class Item : public sigc::trackable { const Glib::VariantContainerBase& arguments); void updateImage(); - Glib::RefPtr extractPixBuf(GVariant* variant); + static Glib::RefPtr extractPixBuf(GVariant* variant); Glib::RefPtr getIconPixbuf(); + Glib::RefPtr getAttentionIconPixbuf(); + Glib::RefPtr getOverlayIconPixbuf(); + Glib::RefPtr loadIconFromNameOrFile(const std::string& name, bool log_failure); + static Glib::RefPtr overlayPixbufs(const Glib::RefPtr&, + const Glib::RefPtr&); Glib::RefPtr getIconByName(const std::string& name, int size); double getScaledIconSize(); static void onMenuDestroyed(Item* self, GObject* old_menu_pointer); diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 9820cc62..2f368083 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -195,11 +196,11 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { } else if (name == "OverlayIconName") { overlay_icon_name = get_variant(value); } else if (name == "OverlayIconPixmap") { - // TODO: overlay_icon_pixmap + overlay_icon_pixmap = extractPixBuf(value.gobj()); } else if (name == "AttentionIconName") { attention_icon_name = get_variant(value); } else if (name == "AttentionIconPixmap") { - // TODO: attention_icon_pixmap + attention_icon_pixmap = extractPixBuf(value.gobj()); } else if (name == "AttentionMovieName") { attention_movie_name = get_variant(value); } else if (name == "ToolTip") { @@ -315,8 +316,8 @@ void Item::processUpdatedProperties(Glib::RefPtr& _result) { static const std::map> signal2props = { {"NewTitle", {"Title"}}, {"NewIcon", {"IconName", "IconPixmap"}}, - // {"NewAttentionIcon", {"AttentionIconName", "AttentionIconPixmap", "AttentionMovieName"}}, - // {"NewOverlayIcon", {"OverlayIconName", "OverlayIconPixmap"}}, + {"NewAttentionIcon", {"AttentionIconName", "AttentionIconPixmap", "AttentionMovieName"}}, + {"NewOverlayIcon", {"OverlayIconName", "OverlayIconPixmap"}}, {"NewIconThemePath", {"IconThemePath"}}, {"NewToolTip", {"ToolTip"}}, {"NewStatus", {"Status"}}, @@ -406,36 +407,24 @@ void Item::updateImage() { pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR); } + pixbuf = overlayPixbufs(pixbuf, getOverlayIconPixbuf()); + auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image.get_scale_factor(), image.get_window()); image.set(surface); } Glib::RefPtr Item::getIconPixbuf() { - if (!icon_name.empty()) { - try { - std::ifstream temp(icon_name); - if (temp.is_open()) { - return Gdk::Pixbuf::create_from_file(icon_name); - } - } catch (Glib::Error& e) { - // Ignore because we want to also try different methods of getting an icon. - // - // But a warning is logged, as the file apparently exists, but there was - // a failure in creating a pixbuf out of it. - - spdlog::warn("Item '{}': {}", id, static_cast(e.what())); - } - - try { - // Will throw if it can not find an icon. - return getIconByName(icon_name, getScaledIconSize()); - } catch (Glib::Error& e) { - spdlog::trace("Item '{}': {}", id, static_cast(e.what())); + if (status_ == "needsattention") { + if (auto attention_pixbuf = getAttentionIconPixbuf()) { + return attention_pixbuf; } } - // Return the pixmap only if an icon for the given name could not be found. + if (auto pixbuf = loadIconFromNameOrFile(icon_name, true)) { + return pixbuf; + } + if (icon_pixmap) { return icon_pixmap; } @@ -450,6 +439,77 @@ Glib::RefPtr Item::getIconPixbuf() { return getIconByName("image-missing", getScaledIconSize()); } +Glib::RefPtr Item::getAttentionIconPixbuf() { + if (auto pixbuf = loadIconFromNameOrFile(attention_icon_name, false)) { + return pixbuf; + } + if (auto pixbuf = loadIconFromNameOrFile(attention_movie_name, false)) { + return pixbuf; + } + return attention_icon_pixmap; +} + +Glib::RefPtr Item::getOverlayIconPixbuf() { + if (auto pixbuf = loadIconFromNameOrFile(overlay_icon_name, false)) { + return pixbuf; + } + return overlay_icon_pixmap; +} + +Glib::RefPtr Item::loadIconFromNameOrFile(const std::string& name, bool log_failure) { + if (name.empty()) { + return {}; + } + + try { + std::ifstream temp(name); + if (temp.is_open()) { + return Gdk::Pixbuf::create_from_file(name); + } + } catch (const Glib::Error& e) { + if (log_failure) { + spdlog::warn("Item '{}': {}", id, static_cast(e.what())); + } + } + + try { + return getIconByName(name, getScaledIconSize()); + } catch (const Glib::Error& e) { + if (log_failure) { + spdlog::trace("Item '{}': {}", id, static_cast(e.what())); + } + } + + return {}; +} + +Glib::RefPtr Item::overlayPixbufs(const Glib::RefPtr& base, + const Glib::RefPtr& overlay) { + if (!base || !overlay) { + return base; + } + + auto composed = base->copy(); + if (!composed) { + return base; + } + + int overlay_target_size = + std::max(1, std::min(composed->get_width(), composed->get_height()) / 2); + auto scaled_overlay = overlay->scale_simple(overlay_target_size, overlay_target_size, + Gdk::InterpType::INTERP_BILINEAR); + if (!scaled_overlay) { + return composed; + } + + int dest_x = std::max(0, composed->get_width() - scaled_overlay->get_width()); + int dest_y = std::max(0, composed->get_height() - scaled_overlay->get_height()); + scaled_overlay->composite(composed, dest_x, dest_y, scaled_overlay->get_width(), + scaled_overlay->get_height(), dest_x, dest_y, 1.0, 1.0, + Gdk::InterpType::INTERP_BILINEAR, 255); + return composed; +} + Glib::RefPtr Item::getIconByName(const std::string& name, int request_size) { if (!icon_theme_path.empty()) { auto icon_info = icon_theme->lookup_icon(name.c_str(), request_size, From 8e2e437ec665af43a7d16d80b53934ea3fefe733 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Sun, 8 Mar 2026 01:16:25 -0600 Subject: [PATCH 612/780] fix(sni): silence duplicate item registration warnings Some tray items re-register the same bus name and object path during normal operation. Treat that path as an idempotent registration instead of logging a warning, while still completing the DBus method successfully. Signed-off-by: Austin Horstman --- src/modules/sni/watcher.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/sni/watcher.cpp b/src/modules/sni/watcher.cpp index 66083c99..969806cf 100644 --- a/src/modules/sni/watcher.cpp +++ b/src/modules/sni/watcher.cpp @@ -98,8 +98,8 @@ gboolean Watcher::handleRegisterItem(Watcher* obj, GDBusMethodInvocation* invoca } auto watch = gfWatchFind(obj->items_, bus_name, object_path); if (watch != nullptr) { - g_warning("Status Notifier Item with bus name '%s' and object path '%s' is already registered", - bus_name, object_path); + spdlog::debug("Ignoring duplicate Status Notifier Item registration for '{}' at '{}'", bus_name, + object_path); sn_watcher_complete_register_item(obj->watcher_, invocation); return TRUE; } From 8a3a78676f9fb46889acc7fe379a4a596b55f529 Mon Sep 17 00:00:00 2001 From: eonphi <259135569+eonphi@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:01:47 +0100 Subject: [PATCH 613/780] docs(hyprland/workspaces): icon grouping man page entries --- man/waybar-hyprland-workspaces.5.scd | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index 5284ce99..b495052d 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -41,6 +41,19 @@ This setting is ignored if *workspace-taskbar.enable* is set to true. The separator to be used between windows in a workspace. ++ This setting is ignored if *workspace-taskbar.enable* is set to true. +*window-rewrite-group-threshold*: ++ + typeof: int ++ + default: 0 ++ + When a workspace contains at least this many windows with the same rewrite result, they are collapsed into a single one using *window-rewrite-group-format*. ++ + Set to 0 to disable grouping. ++ + This setting is ignored if *workspace-taskbar.enable* is set to true. + +*window-rewrite-group-format*: ++ + typeof: string ++ + default: "{icon}×{count}" ++ + The format used to represent a group of collapsed windows. Available placeholders are {icon} (the icon being grouped) and {count} (how many windows share it). ++ + This setting is ignored if *workspace-taskbar.enable* is set to true. + *workspace-taskbar*: ++ typeof: object ++ Contains settings for the workspace taskbar, an alternative mode for the workspaces module which displays the window icons as images instead of text. From 558c2753d79bcbaa87ef4c3c2cf4f63578bd6484 Mon Sep 17 00:00:00 2001 From: Visal Vijay Date: Mon, 16 Mar 2026 21:04:59 +0530 Subject: [PATCH 614/780] Add simple calendar tooltip example for clock module --- man/waybar-clock.5.scd | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/man/waybar-clock.5.scd b/man/waybar-clock.5.scd index b4b5d5b7..5c5509f6 100644 --- a/man/waybar-clock.5.scd +++ b/man/waybar-clock.5.scd @@ -259,7 +259,19 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe "tooltip-format": "{tz_list}" } ``` +5. Simple calendar tooltip +``` +"clock": { +"format": "{:%H:%M}", +"tooltip-format": "{calendar}", +"calendar": { +"format": { +"today": "{}" +} +} +} +``` # STYLE - *#clock* From 3cfb62266033f8266f09d861e88845c55ac2777c Mon Sep 17 00:00:00 2001 From: Visal Vijay Date: Mon, 16 Mar 2026 23:45:28 +0530 Subject: [PATCH 615/780] Fix menu-actions GTK callback pointer handling --- src/ALabel.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ALabel.cpp b/src/ALabel.cpp index 795f87f1..e6f46d94 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -116,8 +116,9 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st } submenus_[key] = GTK_MENU_ITEM(item); menuActionsMap_[key] = it->asString(); - g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent), - (gpointer)menuActionsMap_[key].c_str()); + g_signal_connect(submenus_[key], "activate", + G_CALLBACK(handleGtkMenuEvent), + (gpointer)menuActionsMap_[key].c_str()); } g_object_unref(builder); } catch (std::runtime_error& e) { From acf6f117eabb92b0e3cba35d7499635ccd36c8a9 Mon Sep 17 00:00:00 2001 From: Visal Vijay <150381094+B2krobbery@users.noreply.github.com> Date: Tue, 17 Mar 2026 01:13:14 +0530 Subject: [PATCH 616/780] Update ALabel.cpp --- src/ALabel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ALabel.cpp b/src/ALabel.cpp index e6f46d94..7f34bca3 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -118,7 +118,7 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st menuActionsMap_[key] = it->asString(); g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent), - (gpointer)menuActionsMap_[key].c_str()); + (gpointer)g_strdup(menuActionsMap_[key].c_str())); } g_object_unref(builder); } catch (std::runtime_error& e) { From 196589cf321ef4fdf848d18f1e249199bc7821e9 Mon Sep 17 00:00:00 2001 From: Visal Vijay <150381094+B2krobbery@users.noreply.github.com> Date: Tue, 17 Mar 2026 01:17:08 +0530 Subject: [PATCH 617/780] Update ALabel.cpp --- src/ALabel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ALabel.cpp b/src/ALabel.cpp index 7f34bca3..b23926d8 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -118,7 +118,7 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st menuActionsMap_[key] = it->asString(); g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent), - (gpointer)g_strdup(menuActionsMap_[key].c_str())); + (gpointer)g_strdup(menuActionsMap_[key].c_str())); } g_object_unref(builder); } catch (std::runtime_error& e) { From d046c19b855853dc08ef1bb7abdb6c16f9a420b4 Mon Sep 17 00:00:00 2001 From: Carlo Teubner Date: Tue, 17 Mar 2026 21:33:21 +0000 Subject: [PATCH 618/780] systemd-failed-units: small tweaks - Remove unneeded destructor impl - Rename member variables for consistench with other files - manpage wording fixes - updateData(): small logic tweak --- include/modules/systemd_failed_units.hpp | 16 ++--- man/waybar-systemd-failed-units.5.scd | 16 ++--- src/modules/systemd_failed_units.cpp | 91 +++++++++++++----------- 3 files changed, 64 insertions(+), 59 deletions(-) diff --git a/include/modules/systemd_failed_units.hpp b/include/modules/systemd_failed_units.hpp index ffb25082..fdc6eb8e 100644 --- a/include/modules/systemd_failed_units.hpp +++ b/include/modules/systemd_failed_units.hpp @@ -11,18 +11,18 @@ namespace waybar::modules { class SystemdFailedUnits : public ALabel { public: SystemdFailedUnits(const std::string&, const Json::Value&); - virtual ~SystemdFailedUnits(); + virtual ~SystemdFailedUnits() = default; auto update() -> void override; private: - bool hide_on_ok; - std::string format_ok; + bool hide_on_ok_; + std::string format_ok_; - bool update_pending; - std::string system_state, user_state, overall_state; - uint32_t nr_failed_system, nr_failed_user, nr_failed; - std::string last_status; - Glib::RefPtr system_proxy, user_proxy; + bool update_pending_; + std::string system_state_, user_state_, overall_state_; + uint32_t nr_failed_system_, nr_failed_user_, nr_failed_; + std::string last_status_; + Glib::RefPtr system_props_proxy_, user_props_proxy_; void notify_cb(const Glib::ustring& sender_name, const Glib::ustring& signal_name, const Glib::VariantContainerBase& arguments); diff --git a/man/waybar-systemd-failed-units.5.scd b/man/waybar-systemd-failed-units.5.scd index 8d7c980a..f99283f2 100644 --- a/man/waybar-systemd-failed-units.5.scd +++ b/man/waybar-systemd-failed-units.5.scd @@ -19,7 +19,7 @@ Addressed by *systemd-failed-units* *format-ok*: ++ typeof: string ++ - This format is used when there is no failing units. + This format is used when there are no failing units. *user*: ++ typeof: bool ++ @@ -34,15 +34,15 @@ Addressed by *systemd-failed-units* *hide-on-ok*: ++ typeof: bool ++ default: *true* ++ - Option to hide this module when there is no failing units. + Option to hide this module when there are no failed units. *menu*: ++ typeof: string ++ - Action that popups the menu. + Action that pops up the menu. *menu-file*: ++ typeof: string ++ - Location of the menu descriptor file. There need to be an element of type + Location of the menu descriptor file. There needs to be an element of type GtkMenu with id *menu* *menu-actions*: ++ @@ -52,7 +52,7 @@ Addressed by *systemd-failed-units* *expand*: ++ typeof: bool ++ default: false ++ - Enables this module to consume all left over space dynamically. + Enables this module to consume all leftover space dynamically. # FORMAT REPLACEMENTS @@ -62,11 +62,11 @@ Addressed by *systemd-failed-units* *{nr_failed}*: Number of total failed units. -*{systemd_state}:* State of the systemd system session +*{system_state}:* State of the systemd system session. -*{user_state}:* State of the systemd user session +*{user_state}:* State of the systemd user session. -*{overall_state}:* Overall state of the systemd and user session. ("Ok" or "Degraded") +*{overall_state}:* Overall state of the systemd and user session. ("ok" or "degraded") # EXAMPLES diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index 68e61fe9..44e66393 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -1,10 +1,12 @@ #include "modules/systemd_failed_units.hpp" +#include #include #include #include #include +#include static const unsigned UPDATE_DEBOUNCE_TIME_MS = 1000; @@ -12,39 +14,41 @@ namespace waybar::modules { SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value& config) : ALabel(config, "systemd-failed-units", id, "{nr_failed} failed", 1), - hide_on_ok(true), - update_pending(false), - nr_failed_system(0), - nr_failed_user(0), - nr_failed(0), - last_status() { + hide_on_ok_(true), + update_pending_(false), + nr_failed_system_(0), + nr_failed_user_(0), + nr_failed_(0), + last_status_() { if (config["hide-on-ok"].isBool()) { - hide_on_ok = config["hide-on-ok"].asBool(); + hide_on_ok_ = config["hide-on-ok"].asBool(); } if (config["format-ok"].isString()) { - format_ok = config["format-ok"].asString(); + format_ok_ = config["format-ok"].asString(); } else { - format_ok = format_; + format_ok_ = format_; } /* Default to enable both "system" and "user". */ if (!config["system"].isBool() || config["system"].asBool()) { - system_proxy = Gio::DBus::Proxy::create_for_bus_sync( + system_props_proxy_ = Gio::DBus::Proxy::create_for_bus_sync( Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.DBus.Properties"); - if (!system_proxy) { + if (!system_props_proxy_) { throw std::runtime_error("Unable to connect to systemwide systemd DBus!"); } - system_proxy->signal_signal().connect(sigc::mem_fun(*this, &SystemdFailedUnits::notify_cb)); + system_props_proxy_->signal_signal().connect( + sigc::mem_fun(*this, &SystemdFailedUnits::notify_cb)); } if (!config["user"].isBool() || config["user"].asBool()) { - user_proxy = Gio::DBus::Proxy::create_for_bus_sync( + user_props_proxy_ = Gio::DBus::Proxy::create_for_bus_sync( Gio::DBus::BusType::BUS_TYPE_SESSION, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.DBus.Properties"); - if (!user_proxy) { + if (!user_props_proxy_) { throw std::runtime_error("Unable to connect to user systemd DBus!"); } - user_proxy->signal_signal().connect(sigc::mem_fun(*this, &SystemdFailedUnits::notify_cb)); + user_props_proxy_->signal_signal().connect( + sigc::mem_fun(*this, &SystemdFailedUnits::notify_cb)); } updateData(); @@ -52,16 +56,11 @@ SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value& dp.emit(); } -SystemdFailedUnits::~SystemdFailedUnits() { - if (system_proxy) system_proxy.reset(); - if (user_proxy) user_proxy.reset(); -} - auto SystemdFailedUnits::notify_cb(const Glib::ustring& sender_name, const Glib::ustring& signal_name, const Glib::VariantContainerBase& arguments) -> void { - if (signal_name == "PropertiesChanged" && !update_pending) { - update_pending = true; + if (signal_name == "PropertiesChanged" && !update_pending_) { + update_pending_ = true; /* The fail count may fluctuate due to restarting. */ Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &SystemdFailedUnits::updateData), UPDATE_DEBOUNCE_TIME_MS); @@ -88,12 +87,12 @@ void SystemdFailedUnits::RequestSystemState() { return "unknown"; }; - system_state = load("systemwide", system_proxy); - user_state = load("user", user_proxy); - if (system_state == "running" && user_state == "running") - overall_state = "ok"; + system_state_ = load("systemwide", system_props_proxy_); + user_state_ = load("user", user_props_proxy_); + if (system_state_ == "running" && user_state_ == "running") + overall_state_ = "ok"; else - overall_state = "degraded"; + overall_state_ = "degraded"; } void SystemdFailedUnits::RequestFailedUnits() { @@ -116,25 +115,31 @@ void SystemdFailedUnits::RequestFailedUnits() { return 0; }; - nr_failed_system = load("systemwide", system_proxy); - nr_failed_user = load("user", user_proxy); - nr_failed = nr_failed_system + nr_failed_user; + nr_failed_system_ = load("systemwide", system_props_proxy_); + nr_failed_user_ = load("user", user_props_proxy_); + nr_failed_ = nr_failed_system_ + nr_failed_user_; } void SystemdFailedUnits::updateData() { - update_pending = false; + update_pending_ = false; RequestSystemState(); - if (overall_state == "degraded") RequestFailedUnits(); + if (overall_state_ == "degraded") { + RequestFailedUnits(); + } else { + nr_failed_system_ = 0; + nr_failed_user_ = 0; + nr_failed_ = 0; + } dp.emit(); } auto SystemdFailedUnits::update() -> void { - if (last_status == overall_state) return; + if (last_status_ == overall_state_) return; // Hide if needed. - if (overall_state == "ok" && hide_on_ok) { + if (overall_state_ == "ok" && hide_on_ok_) { event_box_.set_visible(false); return; } @@ -142,20 +147,20 @@ auto SystemdFailedUnits::update() -> void { event_box_.set_visible(true); // Set state class. - if (!last_status.empty() && label_.get_style_context()->has_class(last_status)) { - label_.get_style_context()->remove_class(last_status); + if (!last_status_.empty() && label_.get_style_context()->has_class(last_status_)) { + label_.get_style_context()->remove_class(last_status_); } - if (!label_.get_style_context()->has_class(overall_state)) { - label_.get_style_context()->add_class(overall_state); + if (!label_.get_style_context()->has_class(overall_state_)) { + label_.get_style_context()->add_class(overall_state_); } - last_status = overall_state; + last_status_ = overall_state_; label_.set_markup(fmt::format( - fmt::runtime(nr_failed == 0 ? format_ok : format_), fmt::arg("nr_failed", nr_failed), - fmt::arg("nr_failed_system", nr_failed_system), fmt::arg("nr_failed_user", nr_failed_user), - fmt::arg("system_state", system_state), fmt::arg("user_state", user_state), - fmt::arg("overall_state", overall_state))); + fmt::runtime(nr_failed_ == 0 ? format_ok_ : format_), fmt::arg("nr_failed", nr_failed_), + fmt::arg("nr_failed_system", nr_failed_system_), + fmt::arg("nr_failed_user", nr_failed_user_), fmt::arg("system_state", system_state_), + fmt::arg("user_state", user_state_), fmt::arg("overall_state", overall_state_))); ALabel::update(); } From 3b1262061da2276c208c7241205d6326deda8c58 Mon Sep 17 00:00:00 2001 From: Carlo Teubner Date: Tue, 17 Mar 2026 21:33:41 +0000 Subject: [PATCH 619/780] systemd-failed-units: tooltip w/ failed units list --- include/modules/systemd_failed_units.hpp | 20 +++ man/waybar-systemd-failed-units.5.scd | 29 +++++ src/modules/systemd_failed_units.cpp | 148 ++++++++++++++++++++++- 3 files changed, 192 insertions(+), 5 deletions(-) diff --git a/include/modules/systemd_failed_units.hpp b/include/modules/systemd_failed_units.hpp index fdc6eb8e..3eda799a 100644 --- a/include/modules/systemd_failed_units.hpp +++ b/include/modules/systemd_failed_units.hpp @@ -3,6 +3,7 @@ #include #include +#include #include "ALabel.hpp" @@ -15,19 +16,38 @@ class SystemdFailedUnits : public ALabel { auto update() -> void override; private: + struct FailedUnit { + std::string name; + std::string description; + std::string load_state; + std::string active_state; + std::string sub_state; + std::string scope; + }; + bool hide_on_ok_; std::string format_ok_; + std::string tooltip_format_; + std::string tooltip_format_ok_; + std::string tooltip_unit_format_; bool update_pending_; std::string system_state_, user_state_, overall_state_; uint32_t nr_failed_system_, nr_failed_user_, nr_failed_; std::string last_status_; Glib::RefPtr system_props_proxy_, user_props_proxy_; + Glib::RefPtr system_manager_proxy_, user_manager_proxy_; + std::vector failed_units_; void notify_cb(const Glib::ustring& sender_name, const Glib::ustring& signal_name, const Glib::VariantContainerBase& arguments); void RequestFailedUnits(); + void RequestFailedUnitsList(); void RequestSystemState(); + std::vector LoadFailedUnitsList(const char* kind, + Glib::RefPtr& proxy, + const std::string& scope); + std::string BuildTooltipFailedList() const; void updateData(); }; diff --git a/man/waybar-systemd-failed-units.5.scd b/man/waybar-systemd-failed-units.5.scd index f99283f2..aaeee2d1 100644 --- a/man/waybar-systemd-failed-units.5.scd +++ b/man/waybar-systemd-failed-units.5.scd @@ -36,6 +36,21 @@ Addressed by *systemd-failed-units* default: *true* ++ Option to hide this module when there are no failed units. +*tooltip-format*: ++ + typeof: string ++ + default: *System: {system_state}\nUser: {user_state}\nFailed units ({nr_failed}):\n{failed_units_list}* ++ + Tooltip format shown when there are failed units. + +*tooltip-format-ok*: ++ + typeof: string ++ + default: *System: {system_state}\nUser: {user_state}* ++ + Tooltip format used when there are no failed units. + +*tooltip-unit-format*: ++ + typeof: string ++ + default: *{name}: {description}* ++ + Format used to render each failed unit inside the tooltip. Each item is prefixed with a bullet. + *menu*: ++ typeof: string ++ Action that pops up the menu. @@ -68,6 +83,18 @@ Addressed by *systemd-failed-units* *{overall_state}:* Overall state of the systemd and user session. ("ok" or "degraded") +*{failed_units_list}:* Bulleted list of failed units using *tooltip-unit-format*. Empty when +there are no failed units. + +The *tooltip-unit-format* string supports the following replacements: + +*{name}*: Unit name ++ +*{description}*: Unit description ++ +*{load_state}*: Unit load state ++ +*{active_state}*: Unit active state ++ +*{sub_state}*: Unit sub state ++ +*{scope}*: Either *system* or *user* depending on where the unit originated + # EXAMPLES ``` @@ -77,6 +104,8 @@ Addressed by *systemd-failed-units* "format-ok": "✓", "system": true, "user": false, + "tooltip-format": "{nr_failed} failed units:\n{failed_units_list}", + "tooltip-unit-format": "{scope}: {name} ({active_state})", } ``` diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index 44e66393..600d0fb1 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -2,11 +2,14 @@ #include #include +#include #include #include #include +#include #include +#include static const unsigned UPDATE_DEBOUNCE_TIME_MS = 1000; @@ -15,6 +18,10 @@ namespace waybar::modules { SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value& config) : ALabel(config, "systemd-failed-units", id, "{nr_failed} failed", 1), hide_on_ok_(true), + tooltip_format_("System: {system_state}\nUser: {user_state}\nFailed units ({nr_failed}):\n" + "{failed_units_list}"), + tooltip_format_ok_("System: {system_state}\nUser: {user_state}"), + tooltip_unit_format_("{name}: {description}"), update_pending_(false), nr_failed_system_(0), nr_failed_user_(0), @@ -29,6 +36,16 @@ SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value& format_ok_ = format_; } + if (config["tooltip-format"].isString()) { + tooltip_format_ = config["tooltip-format"].asString(); + } + if (config["tooltip-format-ok"].isString()) { + tooltip_format_ok_ = config["tooltip-format-ok"].asString(); + } + if (config["tooltip-unit-format"].isString()) { + tooltip_unit_format_ = config["tooltip-unit-format"].asString(); + } + /* Default to enable both "system" and "user". */ if (!config["system"].isBool() || config["system"].asBool()) { system_props_proxy_ = Gio::DBus::Proxy::create_for_bus_sync( @@ -39,6 +56,14 @@ SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value& } system_props_proxy_->signal_signal().connect( sigc::mem_fun(*this, &SystemdFailedUnits::notify_cb)); + try { + system_manager_proxy_ = Gio::DBus::Proxy::create_for_bus_sync( + Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager"); + } catch (const Glib::Error& e) { + spdlog::warn("Unable to connect to systemwide systemd Manager interface: {}", + e.what().c_str()); + } } if (!config["user"].isBool() || config["user"].asBool()) { user_props_proxy_ = Gio::DBus::Proxy::create_for_bus_sync( @@ -49,6 +74,13 @@ SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value& } user_props_proxy_->signal_signal().connect( sigc::mem_fun(*this, &SystemdFailedUnits::notify_cb)); + try { + user_manager_proxy_ = Gio::DBus::Proxy::create_for_bus_sync( + Gio::DBus::BusType::BUS_TYPE_SESSION, "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager"); + } catch (const Glib::Error& e) { + spdlog::warn("Unable to connect to user systemd Manager interface: {}", e.what().c_str()); + } } updateData(); @@ -120,27 +152,117 @@ void SystemdFailedUnits::RequestFailedUnits() { nr_failed_ = nr_failed_system_ + nr_failed_user_; } +void SystemdFailedUnits::RequestFailedUnitsList() { + failed_units_.clear(); + if (!tooltipEnabled() || nr_failed_ == 0) { + return; + } + + if (system_manager_proxy_) { + auto units = LoadFailedUnitsList("systemwide", system_manager_proxy_, "system"); + failed_units_.insert(failed_units_.end(), units.begin(), units.end()); + } + if (user_manager_proxy_) { + auto units = LoadFailedUnitsList("user", user_manager_proxy_, "user"); + failed_units_.insert(failed_units_.end(), units.begin(), units.end()); + } +} + +auto SystemdFailedUnits::LoadFailedUnitsList(const char* kind, + Glib::RefPtr& proxy, + const std::string& scope) + -> std::vector { + // org.freedesktop.systemd1.Manager.ListUnits returns + // (name, description, load_state, active_state, sub_state, followed, unit_path, job_id, + // job_type, job_path). + using UnitRow = std::tuple; + using ListUnitsReply = Glib::Variant>>; + + std::vector units; + if (!proxy) { + return units; + } + + try { + auto data = proxy->call_sync("ListUnits"); + if (!data) return units; + if (!data.is_of_type(ListUnitsReply::variant_type())) { + spdlog::warn("Unexpected DBus signature for ListUnits: {}", data.get_type_string()); + return units; + } + + auto [rows] = Glib::VariantBase::cast_dynamic(data).get(); + for (const auto& row : rows) { + const auto& name = std::get<0>(row); + const auto& description = std::get<1>(row); + const auto& load_state = std::get<2>(row); + const auto& active_state = std::get<3>(row); + const auto& sub_state = std::get<4>(row); + if (active_state == "failed" || sub_state == "failed") { + units.push_back({name, description, load_state, active_state, sub_state, scope}); + } + } + } catch (const Glib::Error& e) { + spdlog::error("Failed to list {} units: {}", kind, e.what().c_str()); + } + + return units; +} + +std::string SystemdFailedUnits::BuildTooltipFailedList() const { + if (failed_units_.empty()) { + return ""; + } + + std::string list; + list.reserve(failed_units_.size() * 16); + bool first = true; + for (const auto& unit : failed_units_) { + try { + auto line = fmt::format( + fmt::runtime(tooltip_unit_format_), + fmt::arg("name", Glib::Markup::escape_text(unit.name).raw()), + fmt::arg("description", Glib::Markup::escape_text(unit.description).raw()), + fmt::arg("load_state", unit.load_state), + fmt::arg("active_state", unit.active_state), + fmt::arg("sub_state", unit.sub_state), + fmt::arg("scope", unit.scope)); + if (!first) { + list += "\n"; + } + first = false; + list += "- "; + list += line; + } catch (const std::exception& e) { + spdlog::warn("Failed to format tooltip for unit {}: {}", unit.name, e.what()); + } + } + + return list; +} + void SystemdFailedUnits::updateData() { update_pending_ = false; RequestSystemState(); if (overall_state_ == "degraded") { RequestFailedUnits(); + RequestFailedUnitsList(); } else { - nr_failed_system_ = 0; - nr_failed_user_ = 0; - nr_failed_ = 0; + nr_failed_system_ = nr_failed_user_ = nr_failed_ = 0; + failed_units_.clear(); } dp.emit(); } auto SystemdFailedUnits::update() -> void { - if (last_status_ == overall_state_) return; - // Hide if needed. if (overall_state_ == "ok" && hide_on_ok_) { event_box_.set_visible(false); + last_status_ = overall_state_; return; } @@ -161,6 +283,22 @@ auto SystemdFailedUnits::update() -> void { fmt::arg("nr_failed_system", nr_failed_system_), fmt::arg("nr_failed_user", nr_failed_user_), fmt::arg("system_state", system_state_), fmt::arg("user_state", user_state_), fmt::arg("overall_state", overall_state_))); + if (tooltipEnabled()) { + std::string failed_list = BuildTooltipFailedList(); + auto tooltip_template = overall_state_ == "ok" ? tooltip_format_ok_ : tooltip_format_; + if (!tooltip_template.empty()) { + label_.set_tooltip_markup( + fmt::format(fmt::runtime(tooltip_template), fmt::arg("nr_failed", nr_failed_), + fmt::arg("nr_failed_system", nr_failed_system_), + fmt::arg("nr_failed_user", nr_failed_user_), + fmt::arg("system_state", system_state_), + fmt::arg("user_state", user_state_), + fmt::arg("overall_state", overall_state_), + fmt::arg("failed_units_list", failed_list))); + } else { + label_.set_tooltip_text(""); + } + } ALabel::update(); } From e7c077ab9a5cd959bd412d18df84462cbd97e3b1 Mon Sep 17 00:00:00 2001 From: Carlo Teubner Date: Tue, 17 Mar 2026 21:39:55 +0000 Subject: [PATCH 620/780] clang-format --- src/modules/systemd_failed_units.cpp | 34 ++++++++++++---------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index 600d0fb1..8d7e866f 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -18,8 +18,9 @@ namespace waybar::modules { SystemdFailedUnits::SystemdFailedUnits(const std::string& id, const Json::Value& config) : ALabel(config, "systemd-failed-units", id, "{nr_failed} failed", 1), hide_on_ok_(true), - tooltip_format_("System: {system_state}\nUser: {user_state}\nFailed units ({nr_failed}):\n" - "{failed_units_list}"), + tooltip_format_( + "System: {system_state}\nUser: {user_state}\nFailed units ({nr_failed}):\n" + "{failed_units_list}"), tooltip_format_ok_("System: {system_state}\nUser: {user_state}"), tooltip_unit_format_("{name}: {description}"), update_pending_(false), @@ -170,8 +171,7 @@ void SystemdFailedUnits::RequestFailedUnitsList() { auto SystemdFailedUnits::LoadFailedUnitsList(const char* kind, Glib::RefPtr& proxy, - const std::string& scope) - -> std::vector { + const std::string& scope) -> std::vector { // org.freedesktop.systemd1.Manager.ListUnits returns // (name, description, load_state, active_state, sub_state, followed, unit_path, job_id, // job_type, job_path). @@ -225,10 +225,8 @@ std::string SystemdFailedUnits::BuildTooltipFailedList() const { fmt::runtime(tooltip_unit_format_), fmt::arg("name", Glib::Markup::escape_text(unit.name).raw()), fmt::arg("description", Glib::Markup::escape_text(unit.description).raw()), - fmt::arg("load_state", unit.load_state), - fmt::arg("active_state", unit.active_state), - fmt::arg("sub_state", unit.sub_state), - fmt::arg("scope", unit.scope)); + fmt::arg("load_state", unit.load_state), fmt::arg("active_state", unit.active_state), + fmt::arg("sub_state", unit.sub_state), fmt::arg("scope", unit.scope)); if (!first) { list += "\n"; } @@ -280,21 +278,19 @@ auto SystemdFailedUnits::update() -> void { label_.set_markup(fmt::format( fmt::runtime(nr_failed_ == 0 ? format_ok_ : format_), fmt::arg("nr_failed", nr_failed_), - fmt::arg("nr_failed_system", nr_failed_system_), - fmt::arg("nr_failed_user", nr_failed_user_), fmt::arg("system_state", system_state_), - fmt::arg("user_state", user_state_), fmt::arg("overall_state", overall_state_))); + fmt::arg("nr_failed_system", nr_failed_system_), fmt::arg("nr_failed_user", nr_failed_user_), + fmt::arg("system_state", system_state_), fmt::arg("user_state", user_state_), + fmt::arg("overall_state", overall_state_))); if (tooltipEnabled()) { std::string failed_list = BuildTooltipFailedList(); auto tooltip_template = overall_state_ == "ok" ? tooltip_format_ok_ : tooltip_format_; if (!tooltip_template.empty()) { - label_.set_tooltip_markup( - fmt::format(fmt::runtime(tooltip_template), fmt::arg("nr_failed", nr_failed_), - fmt::arg("nr_failed_system", nr_failed_system_), - fmt::arg("nr_failed_user", nr_failed_user_), - fmt::arg("system_state", system_state_), - fmt::arg("user_state", user_state_), - fmt::arg("overall_state", overall_state_), - fmt::arg("failed_units_list", failed_list))); + label_.set_tooltip_markup(fmt::format( + fmt::runtime(tooltip_template), fmt::arg("nr_failed", nr_failed_), + fmt::arg("nr_failed_system", nr_failed_system_), + fmt::arg("nr_failed_user", nr_failed_user_), fmt::arg("system_state", system_state_), + fmt::arg("user_state", user_state_), fmt::arg("overall_state", overall_state_), + fmt::arg("failed_units_list", failed_list))); } else { label_.set_tooltip_text(""); } From c62e987dbd4f8e402f625cdd61cfc2c9dd3c376f Mon Sep 17 00:00:00 2001 From: Visal Vijay <150381094+B2krobbery@users.noreply.github.com> Date: Thu, 19 Mar 2026 01:22:34 +0530 Subject: [PATCH 621/780] Update waybar.5.scd.in --- man/waybar.5.scd.in | 1 + 1 file changed, 1 insertion(+) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 624798ed..7223daec 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -168,6 +168,7 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles( # MODULE FORMAT You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat). +Note: Tooltip appearance in Waybar is controlled globally via CSS (e.g., `tooltip { ... }`). It is currently not possible to apply tooltip styles per module. e.g. From 215c952137f614e71b1485dfde2c3968477bef3d Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Thu, 19 Mar 2026 00:28:50 -0500 Subject: [PATCH 622/780] chore: remove heaptrack dump Accidentally committed... Signed-off-by: Austin Horstman --- heaptrack.waybar.2711357.zst | Bin 12780 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 heaptrack.waybar.2711357.zst diff --git a/heaptrack.waybar.2711357.zst b/heaptrack.waybar.2711357.zst deleted file mode 100644 index 88921cba1ce1ab95825bb77f62a6f1a72e958a87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12780 zcmVfcXI_LP$zl z>ZVNYEd9Ux!wYi<9S1%JPU1-XD9tlzkvz}NLuTScW@et4NgkZnN5!c2D5eEdvoMiG zjHVx?L?sJ!!Xl(4SRvUE*HjXjkmpwz6b++zNn_}y6ceIB+EY*qL~)p=@(xnrNs=#A zj{M9tgYz7m%p@jGJ>n`ziMT9LlL{}SS0uy3c=6STx1_`5f#X3RNKzSugcw9USW*i^ z;wj8!Y98Vi&M6pK3P)9$vsvV8s4^c>0(xD@)+<@Kk{A^8DYIFKTFVnfhP@A(kHumj zR_6+-n8@K%G%<<7;LJ>x6IIQADNI_r&%rlE?TsK>ygF59%P~ z(Mj_h&>wP2D^f|M77D`U(6BbVN&ONf2RR)KjB`VnZ;nNgxM3U#9{SJ(Pv^j# znTgmTsg(`3gsahd$0X z36mTVsYy*BhQxGG4$pDeqfnRxN<3NiyR+KS-gL}2Ks;XqGVTy@ztQH#^BEur~;v|+s?L}G^A{oO-O-mRzKs*EHIhIyPn2CsbsnOD$BEj8i;` ziHY+~5uF2%_3=x>B+D@u3O$PFXo;d|GkI8fB7;N@b-uy?q5qWh!Ii=BfDP2)(6|gJ z4RsRfK$t_m7rD7$s+2I9s1^Cv z%rkIY93O~e4m^F7fu?p7TQVsQo5Fepr8wL~3%D>a&lHy`6-YW%@k(%P0sObySk_uOl-l9qpBQc9bH!Fq66j41uJk0|?6ecFh zF}tzEl+?#8m`p5>3{N3~uN0~fId3wt2e1cxKm!ev#6&p?&%z~|Yb>9IMbDE+2Ss6$ z1>YykEMEh}Gf;!ZhaVv17+~n~48$1|<&Y9*K2t%8i|DxDBR(wrNaaXG5lKkHqzw4`pbQU!KSfyJ<;gaMsrd!xm(=gsIIGLnMd~+8gxIFp2mal(h~n zCSH}{u~z&LqeW4g1yX19gP98s5OPRpSTOD=_(7V*f+@)`Ne0DK9L@RGT+$UxAf7=RzyTjkBN@bF zI2MCju-MB=L{UYDMWi@SVMx|=STLd%PSv0fi6lH|9`qp)NF;e;q8!SCG3rI6P*4g3 z62YXPNSuV@z9e;6mWkOuM&m)_0UroLnay*!TP?v7r$t=w>3JoIx(zI0R8tm`;i+pM zqj{-3Xg;8&9t(UvBq|pc(StGA4=w57BtIf#QKWK6PU@FN#RA8_R7`}=%#Tx|= z*D{_gFy7j?Z@9J5$!1=G(H2{4ZDW`x1!b3k*oi9JWLw~-J zX^n9YqprYk-hAI%fx&#a`#Rs|%XsqRHRlQp<@?yaQE9JazFNV&_r?WXzcnYIJjBrD zCo@n9v{67Cf4P?*S76B4JL3wBZ`iN*e+7o?)~r`Uw_bic47dWrd%O27^j_Q}$yQ)AX1sVG3Jm9FX2rgBAw$Y$>^TU>p#o#=8+0vQ$Y3nF#a@9i z<<5nSIMu(G3mFhH2YCfXT-E!=Z(PXu=AJqP1nw_nz_lEt$U6w=Av6OJeV*Y$Mx)C8 zQ!6musDGTz5n%N8=2irnJEgYyFk0dJ{;m~J*067mm1Z5JIM$7VwH;dwd zA9Q);fG%k;l2-Mfa;k;G(_q0xhkas@^F8VVKD-%emgFg9X^b95vfB&s?%JH z4q3$XONR23LDsjQ-6ET_zT#0h&75tDS5XDRmN_1ueKPZ$aiRVy>#-Ly(87(Aw;-D~v!F6

8c_Xq;3TpIT#h#-esC;Z$T%B*j`tQ(N=6i5_*i$qnWLQHSqkmALPeChM2W)bM1pWK)ZsV0xfH94rYa)AB|+r?5niy>TMCQ0DU=kN zcaURwr986r;Wf>i4v9**QXlmc9`bOusGSFW&_)4K>eM4)rz*PrR8o{w;h+#_6(N6E z&78rosN@Tf4|t%C|CKkx-i-#@mX&g(Q z#q@MRLf<^eT;SP4VaH-R6s1}tsi+kzY&yk*6H^*S#TOND77nbHWjb0}rWbug6e$ae zMY)=g25@U8g&X#6O#!SgnVgXd}8f!4AK*>DH_76QwKHKfC{K!&r5wP z%VRuQB;=sp!07_UZ&#(nAT{KmAjq@~l5_scy_8r5W&pAHfY+dv1R6LNYqABxi{za( zRycxh!k@HXEN^~Q9jeS6A69=1H!#vD#MM5s$z1rHBbkIk_XW&2&sso z#_*rli#-7FtD7)7}q=gYStJkKzTK z1XJTLh>VY1NKcA>y~qltzay6@PC*l0zz2Ll6P+H(O>(WpbQX_n)Wb~9YfUN2Kc=x) z5b7WvXJ7#}C3Q+>VKxT{6|0{+NYc3iQm72$(mQo{s7g5BJPIa(4%mjM6v#w}B=L^} zbIurJj5Edv*{vDl&;Os_`|y2llzSL!uE22L-#dM-!0_+yef=D3OCCnghrO@Y^{zI1 zRqyQW=IHb$=X0>PnXIihMk>2^_C93Ts}$^wat}kw?w!4@ds$B=FyiZdulKd-@i1P# z*ZX=Ml-;kH>*Yz%zX_b`}TfdO6b>z(f!^uutuo*zcbUez0W zo$GDh%ejIH&)UdLju>UIY^GR9bBj77#6i>y~I#v)^k zt+-b2>y@nQ>n8s&TI^Na`+8mPY94zhyM+w)m79#++e~`%>W#4lhFZ1vVGE4uExk9o zJ`5GRclNgKWj$H%>)zKZnR1c3!=Ufp`+D8{Ue!B$yH& z_cHEX+`D`(ZZfVtj0pc#t5&UgS9{f}Rm&w+d)2B{SJ)e&YSpSMth}#lg|OCItH6Nu zRaawF4MJFJtrgZAB1zx3FqXUyxrb| zl}X5}81puhEil$(-uPBv^km*Cd0{`^$Gp)UhMZDL-uW^@`J|Js{d|?l!}up9OJ2vj znzzTRn73PdGx9c*Eo0=3FTTk8uw&$n?l9J4j=ZgTSx*)tKd)r0Eo3mJDm#pBI%V`G zF#dTJ{BDP#-aqf-b-b%ZCZC}Vbs7Tn)P0=M>il^pZ#U&e-HdJp20!n6$apWOnKTdGTQg#C0Fu3Q98<`TY)hj?~D6}e7uwQ7gHuBZJr08 zqx|N06>sEy?_Q^?i)B0v=6Dz6oxIJ~^2Zxv^D1V6Q5|*DN%xMov4xD~&eBaS-)k-hsCAJ{~7)c^JyAz-W$l!F_|d<#?M>_-uajcD%8zz+k6b zZLPJ|8ZWbjjQ(dWmv_EV9OXCOXZwb0ypy-K=afyh8gG1W-FRnPfuR~R-YFR`Wu=&! zbV9@#Rh0i4W2RW8lCEd5t-zRzcfEZ>F5bx-*Fr{igTnpwF*<9b+u_Fs6hN?vxyva`mPSSHhj`yc}-)h75Op!(M?=AALI|N1iU26DeK2iy-MXjZyJ}A(QP|FbQ`-@U_9qsbNkJ`0)x5b+_hsDnW^5xP`v^} zxvj44Q^{hb6VmzMVNlsty?rCTjt{=y!J9zZJRG-P}&jFMeRQG723>RQKjZfxuT;XE4`G8d?qM)qfgx91zxK7Se*p$ty##ogWC1{cX8~Xs zaXwxjh1V>uH{*HvZW0i4+dc-1Yb$jLp+ro={yxTv zYw2TewOPNRa*AswoxN|eHtQE8^pc=VLR>3bLk8i1qf7$FwUJILA(U@XJjx^x;@V%_ zq;&K#009t~k6^$)x_n=){OD7Zxo#f=KK}JQ$00USB5CT9O zMFb>300P8BQNS*TGE&+oAc*ad@<-;&*H%V(H3kpzqoVu);s68zhdyALuccA7g|Ib-09Dz_ zwRl1WArKbsV?0~iajmS+VS_+09oOt<1?g+b5jK{dHg|RYy&C%!SN12^uZpyW; zR&Lp*Wg&3yV|=e|lxLHyx%Q^YOg?PztzGje7ocwl(DT)K?Qg3&#%mS7wJn$9UdypP zTdO&TG87?D=&2(KtoJdz*Q)toTg@?u1R~G}fhZ7m{e(g7=XoK<%M8sSR*@SLE387Sn zPG*2Wm823JumH#VyytlBb8Fp_OS&iDk|=aS6d@1@3s3-ou(dH(;iOQLR;L zBRlu;)!L^%&L|_Vop144)obJ0hrCv`wQsFk&}WnpK7%9=0U@--Yn7suIx{xobj54^ zla0DBt{&k+Mul!Fx)%E?|EcI<@Zws1qwizB5JD-{gY__=FG_u%uk&5a8>PO}LAatf z%IKor)Hgc&Dt6y@_I=Sex^{No>C^X3-{){4gHex79>()IeP2)C)x7bXzSFlGgYe&d zn@JX5`o`GotJr-X`l{#~+u8Rq-|5?YSx?5&qwj;?q_eMYd$aq-X7~MO-{%{h$Bw?! zRdip63mFvhbc$#Q$Wq>?Wt~pxl+pJ+Uxo`Ab55rS0a38uH@o{f-_^YJ`zrRn^L@Kj zSKt2-HiWPtgs|E7y>E04A%w6QLf_}~LFR7w-h2Jto2|iq&v*JZll|Ufe(yPEU-iDx zv2)J(vYyPj^L;Wh*d+TllaTqw$n3t;_hI`!=o=lgyKie>){`~g*S^o=qpwNk<|}>c zlFjZLWBV$0--qq{e4}gjeZG_VHs!rP$rwm$hPdclY%qb@eb>v9Ds^H`{kLZ|&{v{><=wbfcHrL|UCYo)aIDpx6` zlu}A5rIdD~lu}A5rIgYcA)}PeN#~q&&hC9WdtaM#(mCgxbj~RiWRub2v(Af4`@(-F;*UQNV zOaq7Rj(?8_c(mXT`=s4C`;!7p*v(HSfpQdVX@Fz7S=>$gmiA=T`LTUg)|IQk{KhA7 z1!?mD9q7A6*a3Y{oA`olw= zvMxRlla@#X{tRW{Cjwbp-C)E}T|+5?F?<|i*IY&utUIG_bl01{4Hx99Bwn+X2B|I4 z#AEjj0Segxf#u>-Cv4(@iBe$*Q2=0}Ei?thDNG|&ZNqgb8N^S@LXP4noWs53)uqc*-=PaNyyYCa?Aae=B?R$n|&%3c*k6imA zwx4tzQ(eh35%*6mkd~W+a~(dR=rqwo!1!o}64JZHLMsv?bCO3_n&K^YH+JcOBhP}G zmzgiHyrkwtXGZl=zEhd@s$8WEEyqSGJjz*D57qmStj z!u7`F`kz@t-uQ?_d2@6c(cTPr-7LHwq@`qO9%oLdbLpm&`9AyDuR+XvY&rxy zSIN0UlgP$hgAU@xC5yNV(P74l3!xI)>Atp6BS}i|za11KT3rjl3PcqZ%Nw*~M-q_4 z`3c)?SpG|GjG%lGteV7R$S5%yL=PB2@cMgoaDd10(_7OqN-8w`7iY{NvyaInr6EC>A%^y&zd z1tb8T`(0Hogaqi(cT|Y?KbKv4rX-XSn7|m;LnhnkCcwge!?D;g-;RY$C9tq@NM2|@ zOD=hb&w2WHhGIwJvw9mMZ9mV@0EvCwVV zBt39vIfOE6?6G{WQo3G>)RldbB(X;}BTeT(j%Et?2-iIXl0{t>ceLl~2LA4fL1$!0 z3byqn!Ky>-8>y+)sSdR0nv|%@)Q?sC1o?j!+hgLoujW`{te|P3(hw;q(V%yX6j?dz zZRLE7sn6n^P;o{s#BrzFejXe5z*<2|_drh;k!w4Oi+;5W4LNGpFoTWRLqIr)t3qUS z75WG!WzRZxM#>B!h{tD<-?KqJVOs6z5lBg+!t<^Yh)kGZAn#MlKsDh7me*}m?qLa^ zUc5$(Yl^4@dI{T@Ri--OzaZ6fEk6STG)DwQOB_h-;fJId#L4WidXXj8xon&hF)B=+ z?29|z!W-H%d|5(s$fBW!9FTm0`yYT903!ma09%vI2xyGhJ|DVJj!-)=6eWOQ83|x; zPM^?J3wSx`kF)aj_;V@Hrd(F$e|b;K&3hmLmReWK0(to;(m{|n(+)PZU4faQz88ou z_htn#kPc%T3~7WH&fPaUI$^PrFpt2o&LMIo;4d-? z*#w5S3If^CKzTz)w_d%)NQ~t&p`SN;xIrHRFOtQBD};Ic++Bk zPpTyDG(9GJ&+4r}zhrS@w8nF#NnN+l#j+pIMzcU}wSdtf=qQHeib#|*oCuK)*hO(@ zvz2>Xlt#Y#bP3?b1QG0#6%tQ%!G54f2YO2H4^9Ew@-{HfLNBYC_j_6mHut}pER+X* z(6e+J$Fvb?h#B30no&KGjs#^;{zC2t8`TqfkNyS4WG2Fz&*%*bXF$P7x?@ePE%zPKeVr$bm%$G|e4=k5JiPcJ zMmkyKdO$@N1836kfmIk~Ci_Vcc#vV=Yn*VXAb_Kg_SK#JTo8R)&^2YBtVn3$oS$Uf zu+E?_T^^BM?1YPalY}UB_8c;MHc>EpQ+|2?C1R$%G;t9@cMj#Y{*j2VBVxx8ahS__ zK3N30iJu&Tak9gc1hdnh6Ti%GG*>NByRNyUJ}%$}*qFpMbqzv1+Q>+N!PGzPAIv=0 z#s;-3R;krecRFVYB7XHEgv>yTL;=2KbaU5~+z&OCZeA?asX9{PS(Q+ig&(l`&w9Aaygtn?SH`{$x1|s!ho_W{VT$Ah;@i#P@(uMi~ZzOLRHKU z9Bh;O-l4RD=!+|JM^__usi+n3H5uk%V`dytkwAiL?6aZm-h4gZVP7A9hpP4Q(II_Y zDn)86)TTUe2PIa^-ZZxOg=zeSsEXMtabJWXJ3GPTXSxP?of0KZ>G>qfMa`o5TK5iS zDnua0R}&diA0Hl z=Qs85DR{F^KKZl+KH036k$vh+*y1q;MT* zsGHYA=lsWqd01#OYy0LYsQlQFL{DH5?H)hGYzU?^jGWPUy-5^Kl^6%A*OcPt749)Q zK-V9cPY4Cv77cAe!=!{Z8Ptdf!LSGi0NU1}u#lLr7Yn>6q{T61aCBGF0fs?AqR~2b_3KA+g5m)EKv25Fn;eJ6 z#pL3m0lyugB-l0p&{o<=0q^BDkBn#n{E-?vSIQpoCUS{qoHlo(}9)D*_jYqE#>rd0vNg3)IQwin~69KBBL2Ti@GXCf> zBsvNyD+T8L7f_qoI>Lvqaj@ZP2rTxtkjJj_Hk{>JB$^;i>pU-$zX<8mM{%7t2(YJ9 zT-ATT#;K|LSpf-OhU7Uq5{U`ns$!mrbVxxEBy4Y*3O%WyH7aOUCV=I8It3oVa1Lkk zQbfDNz$6bLA;m^VQSJ!L^$Kfy9)mC<1%!Lm7~FxqqD5fz)M}8)Jk4w^zx*LEErd%c z@qrRzI*EhjGcpqP`%|I0In@ zV#MM+YHVJ-(5;qGy7L(cO=3n6E&tc?#F+g7GpPWyoiK4fzdkfGg+av0%2vzbTMf0q={i57{x30=rvkJ}d|0fp zt`f?m&A~%i6x(wdD4@6q5O|19I_B0UX@M|e&H1Y+IVeV+51oHbq5_n|ei_T5kxM*i zD)vIIU%9eBS!-jlY=7}4-+HH z|F~aq^G!A{TBaKAJ`JOb=OxzhCl2PWmm}qJ{DQWvN8cuWjVF8F%}i;Kqm#b`s-lCT z2n-XD{mVp-G>nH4F>oZcgyT@`ehjGXmuV~TPak~>?NHoGX~)(g#702DMx_Kdnp@;%y=p zZ2luEckxp)DJ62(v(=e<3&Je~WUkn-aI+L5%%9^VKV6PfCOnCG=EVXNNm=ivE0^<9 z3dVQH!WC95v#tRFlH?5dsd(=@y^6FlB??-8Ah5wX{2h*#a7dcch{YwhJl<(j|#(k zT{13%7*ud`x?auv(&A#h#7`pvnCXZP|qr%wXbR@r66)AzoDpYMFL9 z3EiBx#rT=_WmD8gTrp!A#Q;S2-kYV4!GKS(l_g7JWfAZwy`v&8H6kQ1jfB&{xV8h1 zyr^IcynU<*_}r%5U6gG>ZK>0D*j732iwfh5Q+9)n1OW4u+i4eaN8JTv!~oAj>$Io$ z`q&t(Ym$O@@@X0fIQ&jsH4XkCj;ZODEiNF{B_s0X#jT+bx*Gh1#;u}o!0#;KxX@IY zTf1U6*snS=J?EJg_wz@=zIN@cbdbo4@=pXhf2J8a=Ouu6(BIFqqkc%QH4(e^o(|b7{VWI8A&;rrTqwu@@ z0p3^Bnwzy^+i_j87%&S%!ipDzUx{(DpJyF|gazFvyLxe1YcC7KpmZirm#@-#357bs zVdh7aMcQ}d5IrE$W8XK`^G%XoRF9M{K9ZuqPXNCPtq*P)qF{nh9|XF5#zM1N0^prc zm{}pDw~2GP7$*5TGf}45C&z>`o&R%B0H{Yn>xdNZ8~nh+>=FhQGcf@85IKH9z6JkSMgl>JSq6WN zS1HETo(o2(DQ+~H!P49%1)ByvD9v%9H<@p5>~of%ap09^cn&)ZTTI0&& zM5`nqRBj;Tw5x9}465ibmpZ}(^y zvtZCjEL*GRQ?__Q@3^tmCdhtvj>VZub61Jk=ZCh+5EAwb7O7q~ww1SaDM_ktQZBnqYWWrSx{8uDbrq*x?R(1#^nTxZq&+pSxt( z*mDMTrBczt0QBoVAvK^Q&8PYl2%qYCkRCo8tHLvVYJ z6k*<_>wABLub$G~_P~b+RWmWvV3HtpVT%_@F!{irY*2#hExX>8;+R-2!cB!@UIY)@ zRj;I8i=|+zj6v;GfI1e(5eekQV$Iie5I|*)NoE8diy$MxD!2rO+CJj~4&QpI5_&@A ziH5R3j{E^EoRmBOMCO{4!jfgiM|l6MWoS{KS4rb>^bZli(az5ttf>|I;nS5oHvH?A zz)1|nsdZl41a<|ALFAt_{F0`r3_g*qtE&%R>V{+YoWv=s0xAHCB?!|{cKXv&H7;r3 z{L^9G3>Y~T$hgFw$@XLLHq5LIj)N^i1d|+f0T=THU{9BDpO!9Epm$BRy|@T;%<*m8 zbM=S>*l%Lun&}0u&=WM%1&D*L#r{3{>%$yBMxLoum- z{N+1V_lncOTu^zFSxzkT)z}d@Xp3ee`FUv~eW$3fjyj@791x+G#48US%A>}8>V+h$ zNYpn;cBPmj4n8OU7lvg*STc*u+f^@sPneAiQx026FzHCuoVR(z5U*vkAb$525~jC3 z5giW)ExGCI;VUEjMv0MEtX^f)hX;Xf|FyuIU>n?*3fbY3R9tX$i-SY}+y5&-xY1ay zU#xmbibl{vFW_;mI%u&04g_}tRRJ(~oD;;dzCg<+u;{gs$XL4H)8A#uMsq!#&X-Z- zF+%aOyb|K3Kqk!r8bOh%`;0iwT;el@aF&w}J~^zzvic9A!@+uxrbVS%(y^Xy?)VZuKeq z{Z3cR&`ZUM(4bV`nB)l(y$e~8%k`8y;G!qat}Yh7OZ5<2ksMo@75%UG8KDc%^^PHSocn}}#}r(b{%El1NyLX4Q{GQyTraLZVUpH}Xf+I>&Z`6EVeJ8Zlj zxlCwZSPI(-EPR>R$zVgY412Gy-$ zM1_5Z53 yyK-veLj0bnNHqW4@W?1j_=`ecORo6k4w==YDq)pzyCRH95P3fE`uzc9Y|*2|=`Vf& From 86234a8946787798ba39530ea3c74b017efe87d2 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Thu, 19 Mar 2026 00:29:01 -0500 Subject: [PATCH 623/780] feat: gitignore heaptrack dumps Signed-off-by: Austin Horstman --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f42bf85e..f21700d6 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ result-* .ccls-cache _codeql_detected_source_root +heaptrack* From 95f9922ccc6e931d161b6d846f83b747f63b01af Mon Sep 17 00:00:00 2001 From: Viktar Lukashonak Date: Thu, 19 Mar 2026 11:03:08 +0300 Subject: [PATCH 624/780] cava bump --- nix/default.nix | 6 +++--- subprojects/libcava.wrap | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nix/default.nix b/nix/default.nix index 43b6e097..1850a414 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -6,13 +6,13 @@ }: let libcava = rec { - version = "0.10.7-beta"; + version = "0.10.7"; src = pkgs.fetchFromGitHub { owner = "LukashonakV"; repo = "cava"; # NOTE: Needs to match the cava.wrap - tag = "v${version}"; - hash = "sha256-IX1B375gTwVDRjpRfwKGuzTAZOV2pgDWzUd4bW2cTDU="; + tag = "${version}"; + hash = "sha256-zkyj1vBzHtoypX4Bxdh1Vmwh967DKKxN751v79hzmgQ="; }; }; in diff --git a/subprojects/libcava.wrap b/subprojects/libcava.wrap index 466e2262..d66d657b 100644 --- a/subprojects/libcava.wrap +++ b/subprojects/libcava.wrap @@ -4,9 +4,9 @@ #depth = 1 [wrap-file] -directory = cava-0.10.7-beta -source_url = https://github.com/LukashonakV/cava/archive/v0.10.7-beta.tar.gz +directory = cava-0.10.7 +source_url = https://github.com/LukashonakV/cava/archive/0.10.7.tar.gz source_filename = cava-0.10.7.tar.gz -source_hash = 8915d7214f2046554c158fe6f2ae518881dfb573e421ea848727be11a5dfa8c4 +source_hash = 50cc6413e9c96c503657f814744a2baf429a24ff9fed31a8343e0ed285269eff [provide] libcava = cava_dep From 50c1431348ca544e47b6397e9891cd3bc7b33c7d Mon Sep 17 00:00:00 2001 From: Jakob Sjudin Date: Thu, 19 Mar 2026 15:36:32 +0100 Subject: [PATCH 625/780] Add start-expanded option to group --- src/group.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/group.cpp b/src/group.cpp index 51caf0e2..acd7bd42 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -64,11 +64,19 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value& : true); click_to_reveal = drawer_config["click-to-reveal"].asBool(); + const bool start_expanded = + (drawer_config["start-expanded"].isBool() ? drawer_config["start-expanded"].asBool() + : false); + auto transition_type = getPreferredTransitionType(vertical); revealer.set_transition_type(transition_type); revealer.set_transition_duration(transition_duration); - revealer.set_reveal_child(false); + revealer.set_reveal_child(start_expanded); + + if (start_expanded) { + box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT); + } revealer.get_style_context()->add_class("drawer"); From 83e1949dd8435d5f8991de366a8327a2a6e58786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Baha=20Y=C4=B1ld=C4=B1r=C4=B1m?= Date: Thu, 19 Mar 2026 19:32:21 +0300 Subject: [PATCH 626/780] fix(hyprland/window): Fix segfault caused by use-after-free MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The window module registers itself with the Hyprland IPC singleton at the start of its constructor, before calling update(). If update() throws an exception (e.g. from an invalid format string), the object is destroyed without the destructor running, leaving a dangling pointer in the IPC callback list. When the IPC thread receives an event, it attempts to call onEvent() on this invalid memory, causing a crash. Moving the update() call before IPC registration ensures that any initialization errors occur before the pointer is shared. If the configuration is invalid, the module fails to construct and is gracefully disabled by the factory without leaving a "landmine" in the background IPC thread. Fixes: #4923 Signed-off-by: Emir Baha Yıldırım --- src/modules/hyprland/window.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index d3fe6edf..2e3e0bb9 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -19,20 +19,19 @@ std::shared_mutex windowIpcSmtx; Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), m_ipc(IPC::inst()) { - std::unique_lock windowIpcUniqueLock(windowIpcSmtx); - separateOutputs_ = config["separate-outputs"].asBool(); + update(); + // register for hyprland ipc + std::unique_lock windowIpcUniqueLock(windowIpcSmtx); m_ipc.registerForIPC("activewindow", this); m_ipc.registerForIPC("closewindow", this); m_ipc.registerForIPC("movewindow", this); m_ipc.registerForIPC("changefloatingmode", this); m_ipc.registerForIPC("fullscreen", this); - windowIpcUniqueLock.unlock(); - update(); dp.emit(); } From a05c7bc28ffaf889826102cb8e9ac75e9577b4ce Mon Sep 17 00:00:00 2001 From: ignormies Date: Thu, 19 Mar 2026 17:39:57 -0700 Subject: [PATCH 627/780] chore: Stop using deprecated/removed Nerdfonts codepoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These Nerdfonts codepoints were removed in [3.0.0](https://www.nerdfonts.com/releases#v3.0.0): > **Breaking 2: Material Design Icons Codepoints** > > The old Material Design Icon codepoints are finally dropped. Due to an historic mistake we placed them in between some asiatic glyphs, breaking that script. Since v2.3.0 the (updated and expanded) Material Design Icons have new codepoints in the 5 digit region. > > - Dropped codepoints `F500`… and class names `nf-mdi-*` > - New codepoints `F0001`… and class names `nf-md-*` > - The whole discussions are here: https://github.com/ryanoasis/nerd-fonts/issues/365 > - A translation table is available here: https://github.com/ryanoasis/nerd-fonts/issues/1059#issuecomment-1404891287 > - There are tools out there that probably can update your configuration. For the majority of the removed symbols, it was as easy as finding the replacement with the exact same name. For example, `f76b` (`nf-mdi-format_header_2`) became `f026c` (`nf-md-format_header_2`). There was one symbol that was completely removed (it was removed from Material Design): `f5fc` (`nf-mdi-camcorder_box`). I have substituted it with `f03d` (`nf-fa-video_camera`) which is not Material Design, but the closest icon I could find. Here's some example output from [nerdfix](https://github.com/loichyan/nerdfix) ``` ╭─(ignormies)(~/g/fork-waybar)  removed-nerdfonts-codepoints ╰──▪ nix-shell -p nerdfix --run "nerdfix check resources/config.jsonc" INFO Check input from 'resources/config.jsonc' ☞ Found obsolete icon U+F76B ╭─[resources/config.jsonc:131:27] 130 │ "format": "{temperatureC}°C {icon}", 131 │ "format-icons": ["", "", ""] · ┬ · ╰── Icon 'mdi-format_header_2' is marked as obsolete 132 │ }, ╰──── help: You could replace it with: 1. 󰉬 U+F026C md-format_header_2 2. 󰉫 U+F026B md-format_header_1 3. 󰉭 U+F026D md-format_header_3 4. 󰉮 U+F026E md-format_header_4 ☞ Found obsolete icon U+F769 ╭─[resources/config.jsonc:131:41] 130 │ "format": "{temperatureC}°C {icon}", 131 │ "format-icons": ["", "", ""] · ┬ · ╰── Icon 'mdi-format_float_right' is marked as obsolete 132 │ }, ╰──── help: You could replace it with: 1. 󰉪 U+F026A md-format_float_right 2. 󰉨 U+F0268 md-format_float_left 3. 󰉩 U+F0269 md-format_float_none 4. 󰉣 U+F0263 md-format_align_right ☞ Found obsolete icon U+F5E7 ╭─[resources/config.jsonc:146:41] 145 │ "format-full": "{capacity}% {icon}", 146 │ "format-charging": "{capacity}% ", · ┬ · ╰── Icon 'mdi-cached' is marked as obsolete 147 │ "format-plugged": "{capacity}% ", ╰──── help: You could replace it with: 1. 󰃨 U+F00E8 md-cached ☞ Found obsolete icon U+F796 ╭─[resources/config.jsonc:170:45] 169 │ "format-wifi": "{essid} ({signalStrength}%) ", 170 │ "format-ethernet": "{ipaddr}/{cidr} ", · ┬ · ╰── Icon 'mdi-gamepad_variant' is marked as obsolete 171 │ "tooltip-format": "{ifname} via {gwaddr} ", ╰──── help: You could replace it with: 1. 󰊗 U+F0297 md-gamepad_variant 2. 󰺷 U+F0EB7 md-gamepad_variant_outline 3. 󰑢 U+F0462 md-road_variant 4. 󰉜 U+F025C md-food_variant ☞ Found obsolete icon U+F796 ╭─[resources/config.jsonc:171:50] 170 │ "format-ethernet": "{ipaddr}/{cidr} ", 171 │ "tooltip-format": "{ifname} via {gwaddr} ", · ┬ · ╰── Icon 'mdi-gamepad_variant' is marked as obsolete 172 │ "format-linked": "{ifname} (No IP) ", ╰──── help: You could replace it with: 1. 󰊗 U+F0297 md-gamepad_variant 2. 󰺷 U+F0EB7 md-gamepad_variant_outline 3. 󰑢 U+F0462 md-road_variant 4. 󰉜 U+F025C md-food_variant ☞ Found obsolete icon U+F796 ╭─[resources/config.jsonc:172:44] 171 │ "tooltip-format": "{ifname} via {gwaddr} ", 172 │ "format-linked": "{ifname} (No IP) ", · ┬ · ╰── Icon 'mdi-gamepad_variant' is marked as obsolete 173 │ "format-disconnected": "Disconnected ⚠", ╰──── help: You could replace it with: 1. 󰊗 U+F0297 md-gamepad_variant 2. 󰺷 U+F0EB7 md-gamepad_variant_outline 3. 󰑢 U+F0462 md-road_variant 4. 󰉜 U+F025C md-food_variant ☞ Found obsolete icon U+F6A9 ╭─[resources/config.jsonc:180:36] 179 │ "format-bluetooth": "{volume}% {icon} {format_source}", 180 │ "format-bluetooth-muted": " {icon} {format_source}", · ┬ · ╰── Icon 'mdi-cup' is marked as obsolete 181 │ "format-muted": " {format_source}", ╰──── help: You could replace it with: 1. 󰆪 U+F01AA md-cup ☞ Found obsolete icon U+F6A9 ╭─[resources/config.jsonc:181:26] 180 │ "format-bluetooth-muted": " {icon} {format_source}", 181 │ "format-muted": " {format_source}", · ┬ · ╰── Icon 'mdi-cup' is marked as obsolete 182 │ "format-source": "{volume}% ", ╰──── help: You could replace it with: 1. 󰆪 U+F01AA md-cup ☞ Found obsolete icon U+F590 ╭─[resources/config.jsonc:186:28] 185 │ "headphone": "", 186 │ "hands-free": "", · ┬ · ╰── Icon 'mdi-battery_unknown' is marked as obsolete 187 │ "headset": "", ╰──── help: You could replace it with: 1. 󰂑 U+F0091 md-battery_unknown 2. 󰥊 U+F094A md-battery_unknown_bluetooth 3. 󱟞 U+F17DE md-battery_arrow_down 4. 󰝐 U+F0750 md-microsoft_xbox_controller_battery_unknown ☞ Found obsolete icon U+F590 ╭─[resources/config.jsonc:187:25] 186 │ "hands-free": "", 187 │ "headset": "", · ┬ · ╰── Icon 'mdi-battery_unknown' is marked as obsolete 188 │ "phone": "", ╰──── help: You could replace it with: 1. 󰂑 U+F0091 md-battery_unknown 2. 󰥊 U+F094A md-battery_unknown_bluetooth 3. 󱟞 U+F17DE md-battery_arrow_down 4. 󰝐 U+F0750 md-microsoft_xbox_controller_battery_unknown ``` --- include/modules/gamemode.hpp | 2 +- man/waybar-clock.5.scd | 4 ++-- man/waybar-gamemode.5.scd | 4 ++-- man/waybar-pulseaudio.5.scd | 4 ++-- man/waybar-river-mode.5.scd | 2 +- man/waybar-sway-mode.5.scd | 2 +- man/waybar-wireplumber.5.scd | 2 +- resources/config.jsonc | 18 +++++++++--------- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/include/modules/gamemode.hpp b/include/modules/gamemode.hpp index f63861a3..89f66272 100644 --- a/include/modules/gamemode.hpp +++ b/include/modules/gamemode.hpp @@ -26,7 +26,7 @@ class Gamemode : public AModule { const std::string DEFAULT_FORMAT = "{glyph}"; const std::string DEFAULT_FORMAT_ALT = "{glyph} {count}"; const std::string DEFAULT_TOOLTIP_FORMAT = "Games running: {count}"; - const std::string DEFAULT_GLYPH = ""; + const std::string DEFAULT_GLYPH = "󰊴"; void appear(const Glib::RefPtr& connection, const Glib::ustring& name, const Glib::ustring& name_owner); diff --git a/man/waybar-clock.5.scd b/man/waybar-clock.5.scd index 5c5509f6..1b13dcb7 100644 --- a/man/waybar-clock.5.scd +++ b/man/waybar-clock.5.scd @@ -204,7 +204,7 @@ View all valid format options in *strftime(3)* or have a look https://en.cpprefe ``` "clock": { "format": "{:%H:%M}  ", - "format-alt": "{:%A, %B %d, %Y (%R)}  ", + "format-alt": "{:%A, %B %d, %Y (%R)} 󰃰 ", "tooltip-format": "{calendar}", "calendar": { "mode" : "year", @@ -299,7 +299,7 @@ Example of working config ``` "clock": { "format": "{:%H:%M}  ", - "format-alt": "{:%A, %B %d, %Y (%R)}  ", + "format-alt": "{:%A, %B %d, %Y (%R)} 󰃰 ", "tooltip-format": "\n{calendar}", "calendar": { "mode" : "year", diff --git a/man/waybar-gamemode.5.scd b/man/waybar-gamemode.5.scd index a6ca9af0..bb82e0aa 100644 --- a/man/waybar-gamemode.5.scd +++ b/man/waybar-gamemode.5.scd @@ -43,7 +43,7 @@ Feral Gamemode optimizations. *glyph*: ++ typeof: string ++ - default:  ++ + default: 󰊴 ++ The string icon to display. Only visible if *use-icon* is set to false. *icon-name*: ++ @@ -82,7 +82,7 @@ Feral Gamemode optimizations. "gamemode": { "format": "{glyph}", "format-alt": "{glyph} {count}", - "glyph": "", + "glyph": "󰊴", "hide-not-running": true, "use-icon": true, "icon-name": "input-gaming-symbolic", diff --git a/man/waybar-pulseaudio.5.scd b/man/waybar-pulseaudio.5.scd index f555fd4d..a52d5b3e 100644 --- a/man/waybar-pulseaudio.5.scd +++ b/man/waybar-pulseaudio.5.scd @@ -178,8 +178,8 @@ to be selected when the corresponding audio device is muted. This applies to *de "alsa_output.pci-0000_00_1f.3.analog-stereo": "", "alsa_output.pci-0000_00_1f.3.analog-stereo-muted": "", "headphone": "", - "hands-free": "", - "headset": "", + "hands-free": "󰂑", + "headset": "󰂑", "phone": "", "phone-muted": "", "portable": "", diff --git a/man/waybar-river-mode.5.scd b/man/waybar-river-mode.5.scd index 5837411d..ac209f2c 100644 --- a/man/waybar-river-mode.5.scd +++ b/man/waybar-river-mode.5.scd @@ -87,7 +87,7 @@ Addressed by *river/mode* ``` "river/mode": { - "format": " {}" + "format": " {}" } ``` diff --git a/man/waybar-sway-mode.5.scd b/man/waybar-sway-mode.5.scd index 52827376..2c5ba7e6 100644 --- a/man/waybar-sway-mode.5.scd +++ b/man/waybar-sway-mode.5.scd @@ -92,7 +92,7 @@ Addressed by *sway/mode* ``` "sway/mode": { - "format": " {}", + "format": " {}", "max-length": 50 } ``` diff --git a/man/waybar-wireplumber.5.scd b/man/waybar-wireplumber.5.scd index ae78f184..8d97d7af 100644 --- a/man/waybar-wireplumber.5.scd +++ b/man/waybar-wireplumber.5.scd @@ -128,7 +128,7 @@ The *wireplumber* module displays the current volume reported by WirePlumber. ``` "wireplumber#sink": { "format": "{volume}% {icon}", - "format-muted": "", + "format-muted": "󰅶", "format-icons": ["", "", ""], "on-click": "helvum", "on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle", diff --git a/resources/config.jsonc b/resources/config.jsonc index 67d5ff5b..9ba8b97a 100644 --- a/resources/config.jsonc +++ b/resources/config.jsonc @@ -128,7 +128,7 @@ "critical-threshold": 80, // "format-critical": "{temperatureC}°C {icon}", "format": "{temperatureC}°C {icon}", - "format-icons": ["", "", ""] + "format-icons": ["󰉬", "", "󰉪"] }, "backlight": { // "device": "acpi_video1", @@ -143,7 +143,7 @@ }, "format": "{capacity}% {icon}", "format-full": "{capacity}% {icon}", - "format-charging": "{capacity}% ", + "format-charging": "{capacity}% 󰃨", "format-plugged": "{capacity}% ", "format-alt": "{time} {icon}", // "format-good": "", // An empty format will hide the module @@ -167,9 +167,9 @@ "network": { // "interface": "wlp2*", // (Optional) To force the use of this interface "format-wifi": "{essid} ({signalStrength}%) ", - "format-ethernet": "{ipaddr}/{cidr} ", - "tooltip-format": "{ifname} via {gwaddr} ", - "format-linked": "{ifname} (No IP) ", + "format-ethernet": "{ipaddr}/{cidr} 󰊗", + "tooltip-format": "{ifname} via {gwaddr} 󰊗", + "format-linked": "{ifname} (No IP) 󰊗", "format-disconnected": "Disconnected ⚠", "format-alt": "{ifname}: {ipaddr}/{cidr}" }, @@ -177,14 +177,14 @@ // "scroll-step": 1, // %, can be a float "format": "{volume}% {icon} {format_source}", "format-bluetooth": "{volume}% {icon} {format_source}", - "format-bluetooth-muted": " {icon} {format_source}", - "format-muted": " {format_source}", + "format-bluetooth-muted": "󰅶 {icon} {format_source}", + "format-muted": "󰅶 {format_source}", "format-source": "{volume}% ", "format-source-muted": "", "format-icons": { "headphone": "", - "hands-free": "", - "headset": "", + "hands-free": "󰂑", + "headset": "󰂑", "phone": "", "portable": "", "car": "", From 6afe10864279dd09a66c13d222f61e9d1bcaa260 Mon Sep 17 00:00:00 2001 From: Jakob Sjudin Date: Fri, 20 Mar 2026 08:15:23 +0100 Subject: [PATCH 628/780] Update manpages --- man/waybar.5.scd.in | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 624798ed..8b923eef 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -363,6 +363,11 @@ A group may hide all but one element, showing them only on mouse hover. In order default: false ++ Whether left click should reveal the content rather than mouse over. Note that grouped modules may still process their own on-click events. +*start-expanded*: ++ + typeof: bool ++ + default: false ++ + Defines whether the drawer should initialize in an expanded state. + *transition-left-to-right*: ++ typeof: bool ++ default: true ++ From c0c1a4223ad06dc7690e15825aab56a409e37498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=BCndel?= Date: Sat, 21 Mar 2026 13:17:06 +0100 Subject: [PATCH 629/780] Fix network bandwidth rate calculation for early updates --- include/modules/network.hpp | 1 + src/modules/network.cpp | 43 +++++++++++++++++++++---------------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index 4abe26bd..3bc43b23 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -70,6 +70,7 @@ class Network : public ALabel { unsigned long long bandwidth_down_total_{0}; unsigned long long bandwidth_up_total_{0}; + std::chrono::steady_clock::time_point bandwidth_last_sample_time_; std::string state_; std::string essid_; diff --git a/src/modules/network.cpp b/src/modules/network.cpp index a4f2bcc2..ede4b988 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -105,6 +105,7 @@ waybar::modules::Network::Network(const std::string& id, const Json::Value& conf bandwidth_down_total_ = 0; bandwidth_up_total_ = 0; } + bandwidth_last_sample_time_ = std::chrono::steady_clock::now(); if (!config_["interface"].isString()) { // "interface" isn't configured, then try to guess the external @@ -277,6 +278,12 @@ const std::string waybar::modules::Network::getNetworkState() const { auto waybar::modules::Network::update() -> void { std::lock_guard lock(mutex_); std::string tooltip_format; + auto now = std::chrono::steady_clock::now(); + auto elapsed_seconds = std::chrono::duration(now - bandwidth_last_sample_time_).count(); + if (elapsed_seconds <= 0.0) { + elapsed_seconds = std::chrono::duration(interval_).count(); + } + bandwidth_last_sample_time_ = now; auto bandwidth = readBandwidthUsage(); auto bandwidth_down = 0ull; @@ -336,22 +343,22 @@ auto waybar::modules::Network::update() -> void { 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() / 1000.0), "b/s")), + pow_format(bandwidth_down * 8ull / elapsed_seconds, "b/s")), fmt::arg("bandwidthUpBits", - pow_format(bandwidth_up * 8ull / (interval_.count() / 1000.0), "b/s")), + pow_format(bandwidth_up * 8ull / elapsed_seconds, "b/s")), fmt::arg( "bandwidthTotalBits", - pow_format((bandwidth_up + bandwidth_down) * 8ull / (interval_.count() / 1000.0), "b/s")), + pow_format((bandwidth_up + bandwidth_down) * 8ull / elapsed_seconds, "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")), + pow_format(bandwidth_down / elapsed_seconds, "o/s")), + fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / elapsed_seconds, "o/s")), fmt::arg("bandwidthTotalOctets", - pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "o/s")), + pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "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")), + pow_format(bandwidth_down / elapsed_seconds, "B/s")), + fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")), fmt::arg("bandwidthTotalBytes", - pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "B/s"))); + pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s"))); if (text.compare(label_.get_label()) != 0) { label_.set_markup(text); if (text.empty()) { @@ -374,26 +381,26 @@ auto waybar::modules::Network::update() -> void { 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() / 1000.0), "b/s")), + pow_format(bandwidth_down * 8ull / elapsed_seconds, "b/s")), fmt::arg("bandwidthUpBits", - pow_format(bandwidth_up * 8ull / (interval_.count() / 1000.0), "b/s")), + pow_format(bandwidth_up * 8ull / elapsed_seconds, "b/s")), fmt::arg("bandwidthTotalBits", - pow_format((bandwidth_up + bandwidth_down) * 8ull / (interval_.count() / 1000.0), + pow_format((bandwidth_up + bandwidth_down) * 8ull / elapsed_seconds, "b/s")), fmt::arg("bandwidthDownOctets", - pow_format(bandwidth_down / (interval_.count() / 1000.0), "o/s")), + pow_format(bandwidth_down / elapsed_seconds, "o/s")), fmt::arg("bandwidthUpOctets", - pow_format(bandwidth_up / (interval_.count() / 1000.0), "o/s")), + pow_format(bandwidth_up / elapsed_seconds, "o/s")), fmt::arg( "bandwidthTotalOctets", - pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "o/s")), + pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")), fmt::arg("bandwidthDownBytes", - pow_format(bandwidth_down / (interval_.count() / 1000.0), "B/s")), + pow_format(bandwidth_down / elapsed_seconds, "B/s")), fmt::arg("bandwidthUpBytes", - pow_format(bandwidth_up / (interval_.count() / 1000.0), "B/s")), + pow_format(bandwidth_up / elapsed_seconds, "B/s")), fmt::arg( "bandwidthTotalBytes", - pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "B/s"))); + pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s"))); if (label_.get_tooltip_text() != tooltip_text) { label_.set_tooltip_markup(tooltip_text); } From dc31db6d0c79e30fe5c5f219d8c53f1d4bfdb74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=BCndel?= Date: Sat, 21 Mar 2026 18:38:33 +0100 Subject: [PATCH 630/780] fix: linting error --- src/modules/network.cpp | 56 ++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index ede4b988..31092133 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -342,23 +342,18 @@ 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 / elapsed_seconds, "b/s")), - fmt::arg("bandwidthUpBits", - pow_format(bandwidth_up * 8ull / elapsed_seconds, "b/s")), - fmt::arg( - "bandwidthTotalBits", - pow_format((bandwidth_up + bandwidth_down) * 8ull / elapsed_seconds, "b/s")), - fmt::arg("bandwidthDownOctets", - pow_format(bandwidth_down / elapsed_seconds, "o/s")), - fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / elapsed_seconds, "o/s")), + fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / elapsed_seconds, "b/s")), + fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / elapsed_seconds, "b/s")), + fmt::arg("bandwidthTotalBits", + pow_format((bandwidth_up + bandwidth_down) * 8ull / elapsed_seconds, "b/s")), + fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / elapsed_seconds, "o/s")), + fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / elapsed_seconds, "o/s")), fmt::arg("bandwidthTotalOctets", - pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")), - fmt::arg("bandwidthDownBytes", - pow_format(bandwidth_down / elapsed_seconds, "B/s")), - fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")), + pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")), + fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")), + fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")), fmt::arg("bandwidthTotalBytes", - pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s"))); + pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s"))); if (text.compare(label_.get_label()) != 0) { label_.set_markup(text); if (text.empty()) { @@ -380,27 +375,18 @@ 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 / elapsed_seconds, "b/s")), - fmt::arg("bandwidthUpBits", - pow_format(bandwidth_up * 8ull / elapsed_seconds, "b/s")), + fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / elapsed_seconds, "b/s")), + fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / elapsed_seconds, "b/s")), fmt::arg("bandwidthTotalBits", - pow_format((bandwidth_up + bandwidth_down) * 8ull / elapsed_seconds, - "b/s")), - fmt::arg("bandwidthDownOctets", - pow_format(bandwidth_down / elapsed_seconds, "o/s")), - fmt::arg("bandwidthUpOctets", - pow_format(bandwidth_up / elapsed_seconds, "o/s")), - fmt::arg( - "bandwidthTotalOctets", - pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")), - fmt::arg("bandwidthDownBytes", - pow_format(bandwidth_down / elapsed_seconds, "B/s")), - fmt::arg("bandwidthUpBytes", - pow_format(bandwidth_up / elapsed_seconds, "B/s")), - fmt::arg( - "bandwidthTotalBytes", - pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s"))); + pow_format((bandwidth_up + bandwidth_down) * 8ull / elapsed_seconds, "b/s")), + fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / elapsed_seconds, "o/s")), + fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / elapsed_seconds, "o/s")), + fmt::arg("bandwidthTotalOctets", + pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")), + fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")), + fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")), + fmt::arg("bandwidthTotalBytes", + pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s"))); if (label_.get_tooltip_text() != tooltip_text) { label_.set_tooltip_markup(tooltip_text); } From d2aad467a7b888e3b0e65be6d44a258433822407 Mon Sep 17 00:00:00 2001 From: Visal Vijay <150381094+B2krobbery@users.noreply.github.com> Date: Sat, 21 Mar 2026 23:12:36 +0530 Subject: [PATCH 631/780] Update waybar.5.scd.in --- 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 7223daec..80628285 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -168,7 +168,7 @@ The visual display elements for waybar use a CSS stylesheet, see *waybar-styles( # MODULE FORMAT You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat). -Note: Tooltip appearance in Waybar is controlled globally via CSS (e.g., `tooltip { ... }`). It is currently not possible to apply tooltip styles per module. +Tooltip appearance is generally controlled globally via CSS and cannot be truly scoped per module. Some modules (such as the clock) provide limited workarounds for customization. e.g. From 60c57b7195dabc750dfe1faca01ad936961b120b Mon Sep 17 00:00:00 2001 From: Visal Vijay Date: Sun, 22 Mar 2026 11:40:44 +0530 Subject: [PATCH 632/780] clarify logical condition in wlr taskbar module --- src/modules/wlr/taskbar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 2aef0ae3..bde90910 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -198,7 +198,7 @@ void Task::handle_title(const char* title) { title_ = title; hide_if_ignored(); - if (!with_icon_ && !with_name_ || app_info_) { + if ((!with_icon_ && !with_name_) || app_info_) { return; } From 1aa32def3bff7a1af47f20440f443d53a60a9c46 Mon Sep 17 00:00:00 2001 From: Visal Vijay Date: Sun, 22 Mar 2026 15:41:50 +0530 Subject: [PATCH 633/780] fix: prevent resource leak when rebinding Wayland globals --- src/client.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index 103b093f..1da3ffcb 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -19,11 +19,26 @@ waybar::Client* waybar::Client::inst() { void waybar::Client::handleGlobal(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { auto* client = static_cast(data); + if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 && version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) { - client->xdg_output_manager = static_cast(wl_registry_bind( - registry, name, &zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION)); + + if (client->xdg_output_manager != nullptr) { + zxdg_output_manager_v1_destroy(client->xdg_output_manager); + client->xdg_output_manager = nullptr; + } + + client->xdg_output_manager = static_cast( + wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, + ZXDG_OUTPUT_V1_NAME_SINCE_VERSION)); + } else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) { + + if (client->idle_inhibit_manager != nullptr) { + zwp_idle_inhibit_manager_v1_destroy(client->idle_inhibit_manager); + client->idle_inhibit_manager = nullptr; + } + client->idle_inhibit_manager = static_cast( wl_registry_bind(registry, name, &zwp_idle_inhibit_manager_v1_interface, 1)); } From b64265bdf71eba390b0dbebcea674bfa5434c231 Mon Sep 17 00:00:00 2001 From: Greg Darke Date: Mon, 23 Mar 2026 14:33:51 +1100 Subject: [PATCH 634/780] Network: Fix default interface selection. When an interface is not specified for the network module, we parse the routing table to look for default routes. We have defined a default route to: - have a gateway specified, and - have no destination specified, or have an all-zero destination. Previous versions of Waybar had the second condition inverted, causing it to incorrectly pick interfaces are used to route a subnet/single host. For example, with the following routing table, we should pick `eth0` to show information about, not `wg0`. ``` ip -4 route default via 192.168.252.1 dev eth0 proto dhcp src 192.168.252.200 metric 100 192.168.252.0/24 dev eth0 proto kernel scope link src 192.168.252.200 metric 100 192.168.2.254 via 192.168.1.1 dev wg0 proto static metric 50 192.168.1.0/24 dev wg0 proto static scope link metric 50 192.168.1.0/24 dev wg0 proto kernel scope link src 192.168.1.254 metric 50 ``` --- src/modules/network.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index a4f2bcc2..6c31324a 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -744,16 +744,20 @@ int waybar::modules::Network::handleEvents(struct nl_msg* msg, void* data) { /* The destination address. * Should be either missing, or maybe all 0s. Accept both. */ - const uint32_t nr_zeroes = (family == AF_INET) ? 4 : 16; - unsigned char c = 0; - size_t dstlen = RTA_PAYLOAD(attr); - if (dstlen != nr_zeroes) { - break; + auto* dest = (const unsigned char*)RTA_DATA(attr); + size_t dest_size = RTA_PAYLOAD(attr); + for (size_t i = 0; i < dest_size; ++i) { + if (dest[i] != 0) { + has_destination = true; + break; + } } - for (uint32_t i = 0; i < dstlen; i += 1) { - c |= *((unsigned char*)RTA_DATA(attr) + i); + + if (rtm->rtm_dst_len != 0) { + // We have found a destination like 0.0.0.0/24, this is not a + // default gateway route. + has_destination = true; } - has_destination = (c == 0); break; } case RTA_OIF: From fff1495efb1ea4360e88ef89a70ffd47111d3f69 Mon Sep 17 00:00:00 2001 From: Skyfrei Date: Tue, 24 Mar 2026 20:12:30 +0100 Subject: [PATCH 635/780] adding a new keyword 'on-click-copy' that accepts a bool and copies to clipboard on true' --- include/ALabel.hpp | 1 + include/AModule.hpp | 3 ++- src/ALabel.cpp | 13 ++++++++++--- src/AModule.cpp | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/include/ALabel.hpp b/include/ALabel.hpp index b3852877..4b5ade12 100644 --- a/include/ALabel.hpp +++ b/include/ALabel.hpp @@ -26,6 +26,7 @@ class ALabel : public AModule { std::string default_format_; bool handleToggle(GdkEventButton* const& e) override; + void copyToClipboard(const std::string&); virtual std::string getState(uint8_t value, bool lesser = false); std::map submenus_; diff --git a/include/AModule.hpp b/include/AModule.hpp index a338ffe3..2961d512 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -78,7 +78,8 @@ class AModule : public IModule { {std::make_pair(9, GdkEventType::GDK_BUTTON_PRESS), "on-click-forward"}, {std::make_pair(9, GdkEventType::GDK_BUTTON_RELEASE), "on-click-forward-release"}, {std::make_pair(9, GdkEventType::GDK_2BUTTON_PRESS), "on-double-click-forward"}, - {std::make_pair(9, GdkEventType::GDK_3BUTTON_PRESS), "on-triple-click-forward"}}; + {std::make_pair(9, GdkEventType::GDK_3BUTTON_PRESS), "on-triple-click-forward"}, + {std::make_pair(10, GdkEventType::GDK_BUTTON_PRESS), "on-click-copy"}}; }; } // namespace waybar diff --git a/src/ALabel.cpp b/src/ALabel.cpp index b23926d8..f71526f9 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -116,9 +116,8 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st } submenus_[key] = GTK_MENU_ITEM(item); menuActionsMap_[key] = it->asString(); - g_signal_connect(submenus_[key], "activate", - G_CALLBACK(handleGtkMenuEvent), - (gpointer)g_strdup(menuActionsMap_[key].c_str())); + g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent), + (gpointer)g_strdup(menuActionsMap_[key].c_str())); } g_object_unref(builder); } catch (std::runtime_error& e) { @@ -190,6 +189,10 @@ std::string ALabel::getIcon(uint16_t percentage, const std::vector& return ""; } +void ALabel::copyToClipboard(const std::string& literal) { + Gtk::Clipboard::get()->set_text(literal); +} + bool waybar::ALabel::handleToggle(GdkEventButton* const& e) { if (config_["format-alt-click"].isUInt() && e->button == config_["format-alt-click"].asUInt()) { alt_ = !alt_; @@ -199,6 +202,10 @@ bool waybar::ALabel::handleToggle(GdkEventButton* const& e) { format_ = default_format_; } } + + if (config_["on-click-copy"].isBool() && config_["on-click-copy"].asBool()) { + copyToClipboard(label_.get_text()); + } return AModule::handleToggle(e); } diff --git a/src/AModule.cpp b/src/AModule.cpp index a5ba69d3..862aa38e 100644 --- a/src/AModule.cpp +++ b/src/AModule.cpp @@ -42,7 +42,7 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std:: std::find_if(eventMap_.cbegin(), eventMap_.cend(), [&config](const auto& eventEntry) { // True if there is any non-release type event return eventEntry.first.second != GdkEventType::GDK_BUTTON_RELEASE && - config[eventEntry.second].isString(); + (config[eventEntry.second].isString() || config[eventEntry.second].isBool()); }) != eventMap_.cend(); if (enable_click || hasUserEvents) { From 0332da0cef5080f31ca6a901a9ca3ea8e11da59f Mon Sep 17 00:00:00 2001 From: Visal Vijay Date: Wed, 25 Mar 2026 15:18:00 +0530 Subject: [PATCH 636/780] bar: warn on invalid height type in configuration --- src/bar.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bar.cpp b/src/bar.cpp index 6a78707e..fe66b806 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -172,6 +172,10 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) right_.set_spacing(spacing); } + if (config.isMember("height") && !config["height"].isUInt()) { + spdlog::warn("Invalid type for 'height', expected unsigned integer"); + } + height_ = config["height"].isUInt() ? config["height"].asUInt() : 0; width_ = config["width"].isUInt() ? config["width"].asUInt() : 0; From e55bff662e18cc853d6df5c820d05e3823f00ef1 Mon Sep 17 00:00:00 2001 From: Visal Vijay Date: Wed, 25 Mar 2026 21:45:14 +0530 Subject: [PATCH 637/780] Remove unnecessary g_strdup in GTK callback --- src/ALabel.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ALabel.cpp b/src/ALabel.cpp index b23926d8..795f87f1 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -116,9 +116,8 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st } submenus_[key] = GTK_MENU_ITEM(item); menuActionsMap_[key] = it->asString(); - g_signal_connect(submenus_[key], "activate", - G_CALLBACK(handleGtkMenuEvent), - (gpointer)g_strdup(menuActionsMap_[key].c_str())); + g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent), + (gpointer)menuActionsMap_[key].c_str()); } g_object_unref(builder); } catch (std::runtime_error& e) { From a49990c2667a3d6ba7c1fa4c4607b47d4ce0491a Mon Sep 17 00:00:00 2001 From: Jolan Oubrier Date: Fri, 27 Mar 2026 18:31:05 +0100 Subject: [PATCH 638/780] fix: issue #4955 wrong behaviour of regex for sway/window 'rewrite' --- src/util/rewrite_string.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/rewrite_string.cpp b/src/util/rewrite_string.cpp index 3f6ae4ca..8c377d3f 100644 --- a/src/util/rewrite_string.cpp +++ b/src/util/rewrite_string.cpp @@ -18,7 +18,7 @@ std::string rewriteString(const std::string& value, const Json::Value& rules) { // malformated regexes will cause an exception. // in this case, log error and try the next rule. const std::regex rule{it.key().asString(), std::regex_constants::icase}; - if (std::regex_match(value, rule)) { + if (std::regex_match(res, rule)) { res = std::regex_replace(res, rule, it->asString()); } } catch (const std::regex_error& e) { From 3b512d1a2cfcfcb454616e3e3988fab34b1ac3c4 Mon Sep 17 00:00:00 2001 From: Keepo Date: Sat, 28 Mar 2026 21:10:15 -0400 Subject: [PATCH 639/780] feat(client): add support for 8-bit hex color codes in CSS This allows users to use #RRGGBBAA format in their style.css. The client now detects 8-bit hex codes, transforms them into GTK-compatible rgba() syntax, and loads the modified data into the CSS provider. - Added utility to detect 8-bit hex patterns. - Added transformation logic to convert hex alpha to decimal. - Intercepted CSS loading in Client::setupCss to handle the conversion. --- include/util/hex_checker.hpp | 17 ++++++++++++ meson.build | 4 ++- src/client.cpp | 22 +++++++++------- src/util/has_8bit_hex.cpp | 15 +++++++++++ src/util/transform_8bit_to_rgba.cpp | 41 +++++++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 11 deletions(-) create mode 100644 include/util/hex_checker.hpp create mode 100644 src/util/has_8bit_hex.cpp create mode 100644 src/util/transform_8bit_to_rgba.cpp diff --git a/include/util/hex_checker.hpp b/include/util/hex_checker.hpp new file mode 100644 index 00000000..5e742436 --- /dev/null +++ b/include/util/hex_checker.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +/** + * Reads a CSS file, searches for 8-bit hex codes (#RRGGBBAA), + * and returns true if found, or false if not found. + */ + +bool has_8bit_hex(std::string file_path); + +/** + * Reads a CSS file, searches for 8-bit hex codes (#RRGGBBAA), + * and transforms them into GTK-compatible rgba() syntax. + */ + +std::string transform_8bit_to_hex(std::string file_path); diff --git a/meson.build b/meson.build index db9407eb..b9df3f80 100644 --- a/meson.build +++ b/meson.build @@ -185,7 +185,9 @@ src_files = files( 'src/util/gtk_icon.cpp', 'src/util/icon_loader.cpp', 'src/util/regex_collection.cpp', - 'src/util/css_reload_helper.cpp' + 'src/util/css_reload_helper.cpp', + 'src/util/has_8bit_hex.cpp', + 'src/util/transform_8bit_to_rgba.cpp' ) man_files = files( diff --git a/src/client.cpp b/src/client.cpp index 1da3ffcb..178552a4 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -10,6 +10,7 @@ #include "idle-inhibit-unstable-v1-client-protocol.h" #include "util/clara.hpp" #include "util/format.hpp" +#include "util/hex_checker.hpp" waybar::Client* waybar::Client::inst() { static auto* c = new Client(); @@ -22,21 +23,18 @@ void waybar::Client::handleGlobal(void* data, struct wl_registry* registry, uint if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 && version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) { - if (client->xdg_output_manager != nullptr) { zxdg_output_manager_v1_destroy(client->xdg_output_manager); client->xdg_output_manager = nullptr; } - client->xdg_output_manager = static_cast( - wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, - ZXDG_OUTPUT_V1_NAME_SINCE_VERSION)); + client->xdg_output_manager = static_cast(wl_registry_bind( + registry, name, &zxdg_output_manager_v1_interface, ZXDG_OUTPUT_V1_NAME_SINCE_VERSION)); } else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) { - if (client->idle_inhibit_manager != nullptr) { zwp_idle_inhibit_manager_v1_destroy(client->idle_inhibit_manager); - client->idle_inhibit_manager = nullptr; + client->idle_inhibit_manager = nullptr; } client->idle_inhibit_manager = static_cast( @@ -209,11 +207,15 @@ auto waybar::Client::setupCss(const std::string& css_file) -> void { } css_provider_ = Gtk::CssProvider::create(); - if (!css_provider_->load_from_path(css_file)) { - css_provider_.reset(); - throw std::runtime_error("Can't open style file"); + if (has_8bit_hex(css_file)) { + std::string modified_css = transform_8bit_to_hex(css_file); + css_provider_->load_from_data(modified_css); + } else { + if (!css_provider_->load_from_path(css_file)) { + css_provider_.reset(); + throw std::runtime_error("Can't open style file"); + } } - Gtk::StyleContext::add_provider_for_screen(screen, css_provider_, GTK_STYLE_PROVIDER_PRIORITY_USER); } diff --git a/src/util/has_8bit_hex.cpp b/src/util/has_8bit_hex.cpp new file mode 100644 index 00000000..db594980 --- /dev/null +++ b/src/util/has_8bit_hex.cpp @@ -0,0 +1,15 @@ +#include +#include +#include + +namespace fs = std::filesystem; + +bool has_8bit_hex(std::string file_path) { + std::ifstream f(file_path, std::ios::in | std::ios::binary); + const auto size = fs::file_size(file_path); + std::string result(size, '\0'); + f.read(result.data(), size); + std::regex pattern( + R"((?:\#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})))"); + return std::regex_search(result, pattern); +} diff --git a/src/util/transform_8bit_to_rgba.cpp b/src/util/transform_8bit_to_rgba.cpp new file mode 100644 index 00000000..4e0cd3a8 --- /dev/null +++ b/src/util/transform_8bit_to_rgba.cpp @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include +namespace fs = std::filesystem; +std::string transform_8bit_to_hex(std::string file_path) { + std::ifstream f(file_path, std::ios::in | std::ios::binary); + const auto size = fs::file_size(file_path); + std::string result(size, '\0'); + f.read(result.data(), size); + + std::regex pattern( + R"((?:\#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})))"); + std::string final_output; + + auto it = std::sregex_iterator(result.begin(), result.end(), pattern); + auto eof = std::sregex_iterator(); + std::smatch match = *it; + + while (it != eof) { + + final_output += match.prefix().str(); + + int r = stoi(match[1].str(), nullptr, 16); + int g = stoi(match[2].str(), nullptr, 16); + int b = stoi(match[3].str(), nullptr, 16); + double a = (stoi(match[4].str(), nullptr, 16) / 255.0); + + std::stringstream ss; + ss << "rgba(" << r << "," << g << "," << b << "," << std::fixed + << std::setprecision(2) << a << ")"; + final_output += ss.str(); + + ++it; + } + + final_output += match.suffix().str(); + + return final_output; +} From 937ef176ffc5af51e243e744adc3d965ec8dd006 Mon Sep 17 00:00:00 2001 From: Keepo Date: Sat, 28 Mar 2026 22:45:42 -0400 Subject: [PATCH 640/780] fix: memory issues & duplicate file loads. --- src/client.cpp | 4 ++-- src/util/transform_8bit_to_rgba.cpp | 33 +++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index 178552a4..d1489186 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -207,8 +207,8 @@ auto waybar::Client::setupCss(const std::string& css_file) -> void { } css_provider_ = Gtk::CssProvider::create(); - if (has_8bit_hex(css_file)) { - std::string modified_css = transform_8bit_to_hex(css_file); + auto [modified_css, was_transformed] = transform_8bit_to_hex(css_file); + if (was_transformed) { css_provider_->load_from_data(modified_css); } else { if (!css_provider_->load_from_path(css_file)) { diff --git a/src/util/transform_8bit_to_rgba.cpp b/src/util/transform_8bit_to_rgba.cpp index 4e0cd3a8..ca95784d 100644 --- a/src/util/transform_8bit_to_rgba.cpp +++ b/src/util/transform_8bit_to_rgba.cpp @@ -4,21 +4,40 @@ #include #include namespace fs = std::filesystem; -std::string transform_8bit_to_hex(std::string file_path) { + +struct TransformResult { + std::string css; + bool was_transformed; +}; + +TransformResult transform_8bit_to_hex(const std::string& file_path) { std::ifstream f(file_path, std::ios::in | std::ios::binary); const auto size = fs::file_size(file_path); std::string result(size, '\0'); + if (!f.is_open() || !f.good()) { + throw std::runtime_error("Cannot open file: " + file_path); + } + + if (size == 0) { + return {.css = result, .was_transformed = false}; + } + f.read(result.data(), size); - std::regex pattern( - R"((?:\#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})))"); + static std::regex pattern( + R"((\#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})))"); std::string final_output; auto it = std::sregex_iterator(result.begin(), result.end(), pattern); auto eof = std::sregex_iterator(); - std::smatch match = *it; + if (it == eof) { + return {.css = result, .was_transformed = false}; + } + + std::smatch match; while (it != eof) { + match = *it; final_output += match.prefix().str(); @@ -28,8 +47,8 @@ std::string transform_8bit_to_hex(std::string file_path) { double a = (stoi(match[4].str(), nullptr, 16) / 255.0); std::stringstream ss; - ss << "rgba(" << r << "," << g << "," << b << "," << std::fixed - << std::setprecision(2) << a << ")"; + ss << "rgba(" << r << "," << g << "," << b << "," << std::fixed << std::setprecision(2) << a + << ")"; final_output += ss.str(); ++it; @@ -37,5 +56,5 @@ std::string transform_8bit_to_hex(std::string file_path) { final_output += match.suffix().str(); - return final_output; + return {.css = final_output, .was_transformed = true}; } From 3533265675084b07ff5fb51f9b1ec8570080e954 Mon Sep 17 00:00:00 2001 From: Keepo Date: Sat, 28 Mar 2026 23:02:16 -0400 Subject: [PATCH 641/780] fix: removed unnecessary function, update header file for signature change. --- include/util/hex_checker.hpp | 9 +-------- src/util/has_8bit_hex.cpp | 15 --------------- 2 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 src/util/has_8bit_hex.cpp diff --git a/include/util/hex_checker.hpp b/include/util/hex_checker.hpp index 5e742436..e896031b 100644 --- a/include/util/hex_checker.hpp +++ b/include/util/hex_checker.hpp @@ -2,16 +2,9 @@ #include -/** - * Reads a CSS file, searches for 8-bit hex codes (#RRGGBBAA), - * and returns true if found, or false if not found. - */ - -bool has_8bit_hex(std::string file_path); - /** * Reads a CSS file, searches for 8-bit hex codes (#RRGGBBAA), * and transforms them into GTK-compatible rgba() syntax. */ -std::string transform_8bit_to_hex(std::string file_path); +std::string transform_8bit_to_hex(const std::string& file_path); diff --git a/src/util/has_8bit_hex.cpp b/src/util/has_8bit_hex.cpp deleted file mode 100644 index db594980..00000000 --- a/src/util/has_8bit_hex.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include -#include - -namespace fs = std::filesystem; - -bool has_8bit_hex(std::string file_path) { - std::ifstream f(file_path, std::ios::in | std::ios::binary); - const auto size = fs::file_size(file_path); - std::string result(size, '\0'); - f.read(result.data(), size); - std::regex pattern( - R"((?:\#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})))"); - return std::regex_search(result, pattern); -} From 72d6a51fb7e2fe04aa36515b8db8107c03d3372e Mon Sep 17 00:00:00 2001 From: Keepo Date: Sat, 28 Mar 2026 23:26:34 -0400 Subject: [PATCH 642/780] fix: fix compile/run bugs from trying to be safer. rip --- include/util/hex_checker.hpp | 2 +- meson.build | 1 - src/util/transform_8bit_to_rgba.cpp | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/include/util/hex_checker.hpp b/include/util/hex_checker.hpp index e896031b..9855c2de 100644 --- a/include/util/hex_checker.hpp +++ b/include/util/hex_checker.hpp @@ -7,4 +7,4 @@ * and transforms them into GTK-compatible rgba() syntax. */ -std::string transform_8bit_to_hex(const std::string& file_path); +std::pair transform_8bit_to_hex(const std::string& file_path); diff --git a/meson.build b/meson.build index b9df3f80..0c494eb2 100644 --- a/meson.build +++ b/meson.build @@ -186,7 +186,6 @@ src_files = files( 'src/util/icon_loader.cpp', 'src/util/regex_collection.cpp', 'src/util/css_reload_helper.cpp', - 'src/util/has_8bit_hex.cpp', 'src/util/transform_8bit_to_rgba.cpp' ) diff --git a/src/util/transform_8bit_to_rgba.cpp b/src/util/transform_8bit_to_rgba.cpp index ca95784d..dc87a88e 100644 --- a/src/util/transform_8bit_to_rgba.cpp +++ b/src/util/transform_8bit_to_rgba.cpp @@ -25,7 +25,7 @@ TransformResult transform_8bit_to_hex(const std::string& file_path) { f.read(result.data(), size); static std::regex pattern( - R"((\#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})))"); + R"(\#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2}))"); std::string final_output; auto it = std::sregex_iterator(result.begin(), result.end(), pattern); From a9aab4e3569a0c771a873e2a4b1c8695f70430e9 Mon Sep 17 00:00:00 2001 From: Keepo Date: Sat, 28 Mar 2026 23:44:41 -0400 Subject: [PATCH 643/780] fix: type consistency --- include/util/hex_checker.hpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/include/util/hex_checker.hpp b/include/util/hex_checker.hpp index 9855c2de..27a84d1f 100644 --- a/include/util/hex_checker.hpp +++ b/include/util/hex_checker.hpp @@ -2,9 +2,16 @@ #include +/** + * Result of transforming 8-bit hex codes to rgba(). + */ +struct TransformResult { + std::string css; + bool was_transformed; +}; + /** * Reads a CSS file, searches for 8-bit hex codes (#RRGGBBAA), * and transforms them into GTK-compatible rgba() syntax. */ - -std::pair transform_8bit_to_hex(const std::string& file_path); +TransformResult transform_8bit_to_hex(const std::string& file_path); From 3f69bacff018371507c9a8f8dbd78aa558a02cd1 Mon Sep 17 00:00:00 2001 From: Visal Vijay Date: Sun, 29 Mar 2026 22:12:16 +0530 Subject: [PATCH 644/780] fix: prevent resource leak when rebinding dwl globals --- src/modules/dwl/tags.cpp | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/modules/dwl/tags.cpp b/src/modules/dwl/tags.cpp index 399c7d4b..243aaa92 100644 --- a/src/modules/dwl/tags.cpp +++ b/src/modules/dwl/tags.cpp @@ -70,17 +70,33 @@ static const zdwl_ipc_output_v2_listener output_status_listener_impl{ static void handle_global(void* data, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { - if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) { - static_cast(data)->status_manager_ = static_cast( - (zdwl_ipc_manager_v2*)wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1)); - } - if (std::strcmp(interface, wl_seat_interface.name) == 0) { - version = std::min(version, 1); - static_cast(data)->seat_ = - static_cast(wl_registry_bind(registry, name, &wl_seat_interface, version)); - } + +if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) { + auto* self = static_cast(data); + + if (self->status_manager_) { + zdwl_ipc_manager_v2_destroy(self->status_manager_); + self->status_manager_ = nullptr; } + self->status_manager_ = static_cast( + wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1)); +} + +if (std::strcmp(interface, wl_seat_interface.name) == 0) { + auto* self = static_cast(data); + + if (self->seat_) { + wl_seat_destroy(self->seat_); + self->seat_ = nullptr; + } + + version = std::min(version, 1); + + self->seat_ = static_cast( + wl_registry_bind(registry, name, &wl_seat_interface, version)); +} +} static void handle_global_remove(void* data, struct wl_registry* registry, uint32_t name) { /* Ignore event */ } From 753294dbf4da6a7276e072c314fc7c078a8404c4 Mon Sep 17 00:00:00 2001 From: Adrian Lopez Date: Mon, 30 Mar 2026 19:38:24 +0200 Subject: [PATCH 645/780] fix(network): prevent near-zero bandwidth on rapid event-driven updates When netlink events (link/addr/route changes) fire between timer intervals, dp.emit() triggers update() which consumes the byte delta and resets bandwidth_down_total_. A subsequent timer update sees near-zero delta, displaying very small bandwidth. Cache the last computed bandwidth values and skip recalculation when update() is called within half the interval. Event-driven updates reuse the cached values instead. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- include/modules/network.hpp | 2 ++ src/modules/network.cpp | 40 ++++++++++++++++++++++++++----------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/include/modules/network.hpp b/include/modules/network.hpp index 3bc43b23..66fc6d04 100644 --- a/include/modules/network.hpp +++ b/include/modules/network.hpp @@ -70,6 +70,8 @@ class Network : public ALabel { unsigned long long bandwidth_down_total_{0}; unsigned long long bandwidth_up_total_{0}; + unsigned long long bandwidth_down_prev_{0}; + unsigned long long bandwidth_up_prev_{0}; std::chrono::steady_clock::time_point bandwidth_last_sample_time_; std::string state_; diff --git a/src/modules/network.cpp b/src/modules/network.cpp index a39a5ed3..572b5fcc 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -280,23 +280,39 @@ auto waybar::modules::Network::update() -> void { std::string tooltip_format; auto now = std::chrono::steady_clock::now(); auto elapsed_seconds = std::chrono::duration(now - bandwidth_last_sample_time_).count(); - if (elapsed_seconds <= 0.0) { - elapsed_seconds = std::chrono::duration(interval_).count(); - } - bandwidth_last_sample_time_ = now; - auto bandwidth = readBandwidthUsage(); auto bandwidth_down = 0ull; auto bandwidth_up = 0ull; - if (bandwidth.has_value()) { - auto down_octets = (*bandwidth).first; - auto up_octets = (*bandwidth).second; - bandwidth_down = down_octets - bandwidth_down_total_; - bandwidth_down_total_ = down_octets; + // Only recalculate bandwidth when enough time has elapsed since the last + // sample. Event-driven dp.emit() calls (link/addr/route changes) can + // trigger update() between timer intervals, which would consume the byte + // delta prematurely and show near-zero bandwidth. + auto min_elapsed = std::chrono::duration(interval_).count() * 0.5; + if (elapsed_seconds >= min_elapsed) { + if (elapsed_seconds <= 0.0) { + elapsed_seconds = std::chrono::duration(interval_).count(); + } + bandwidth_last_sample_time_ = now; - bandwidth_up = up_octets - bandwidth_up_total_; - bandwidth_up_total_ = up_octets; + auto bandwidth = readBandwidthUsage(); + if (bandwidth.has_value()) { + auto down_octets = (*bandwidth).first; + auto up_octets = (*bandwidth).second; + + bandwidth_down = down_octets - bandwidth_down_total_; + bandwidth_down_total_ = down_octets; + + bandwidth_up = up_octets - bandwidth_up_total_; + bandwidth_up_total_ = up_octets; + + bandwidth_down_prev_ = bandwidth_down; + bandwidth_up_prev_ = bandwidth_up; + } + } else { + bandwidth_down = bandwidth_down_prev_; + bandwidth_up = bandwidth_up_prev_; + elapsed_seconds = std::chrono::duration(interval_).count(); } if (!alt_) { From 8b1e5740634093b89582d9708981aeb37b8f09ab Mon Sep 17 00:00:00 2001 From: cebem1nt Date: Tue, 31 Mar 2026 17:49:30 -0300 Subject: [PATCH 646/780] niri/workspaces: feature - add "hide-empty" config option (#4965) --- src/modules/niri/workspaces.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/modules/niri/workspaces.cpp b/src/modules/niri/workspaces.cpp index 3e8a432e..97d15215 100644 --- a/src/modules/niri/workspaces.cpp +++ b/src/modules/niri/workspaces.cpp @@ -114,6 +114,11 @@ void Workspaces::doUpdate() { button.show(); else button.hide(); + } else if (config_["hide-empty"].asBool()) { + if (ws["active_window_id"].isNull() && !ws["is_focused"].asBool()) + button.hide(); + else + button.show(); } else { button.show(); } From 16886117b3645db1cc32055a9732b4b9cf1562d9 Mon Sep 17 00:00:00 2001 From: Anubhab Ghosh Date: Thu, 2 Apr 2026 19:54:19 +0530 Subject: [PATCH 647/780] Network: fix: delete correct address type Only delete the corresponding address type (IPv4 or IPv6) when an event about a specific type (AF_INET or AF_INET6) is received This fixes situations where only one type of the address is deleted (and possibly added again) but Waybar still thinks the interface is in "linked" (no IP) state. --- src/modules/network.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index a39a5ed3..6dbb8bf0 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -677,12 +677,15 @@ int waybar::modules::Network::handleEvents(struct nl_msg* msg, void* data) { changed_cidr); } } else { - net->ipaddr_.clear(); - net->ipaddr6_.clear(); - net->cidr_ = 0; - net->cidr6_ = 0; - net->netmask_.clear(); - net->netmask6_.clear(); + if (ifa->ifa_family == AF_INET) { + net->ipaddr_.clear(); + net->cidr_ = 0; + net->netmask_.clear(); + } else if (ifa->ifa_family == AF_INET6) { + net->ipaddr6_.clear(); + net->cidr6_ = 0; + net->netmask6_.clear(); + } spdlog::debug("network: {} addr deleted {}/{}", net->ifname_, inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr)), ifa->ifa_prefixlen); From 36518e4eca9ccfc09403b80466621fd58a1fabfb Mon Sep 17 00:00:00 2001 From: Visal Vijay Date: Fri, 3 Apr 2026 19:47:17 +0530 Subject: [PATCH 648/780] fix(bar): ensure exception safety when creating group modules using std::unique_ptr --- src/bar.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/bar.cpp b/src/bar.cpp index 6a78707e..6e81332c 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -545,9 +545,11 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, if (group_config["modules"].isNull()) { spdlog::warn("Group definition '{}' has not been found, group will be hidden", ref); } - auto* group_module = new waybar::Group(id_name, class_name, group_config, vertical); - getModules(factory, ref, group_module); - module = group_module; + auto group_module = std::make_unique( + id_name, class_name, group_config, vertical); + + getModules(factory, ref, group_module.get()); + module = group_module.release(); } else { module = factory.makeModule(ref, pos); } From ae11954398e5d9692805d46f8d52b6ffbf7e3597 Mon Sep 17 00:00:00 2001 From: cebem1nt Date: Sat, 4 Apr 2026 22:11:19 -0300 Subject: [PATCH 649/780] fix: ensure passing group_config as reference --- src/bar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bar.cpp b/src/bar.cpp index 6a78707e..f42f31a3 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -541,7 +541,7 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, auto vertical = (group != nullptr ? group->getBox().get_orientation() : box_.get_orientation()) == Gtk::ORIENTATION_VERTICAL; - auto group_config = config[ref]; + const Json::Value& group_config = config[ref]; if (group_config["modules"].isNull()) { spdlog::warn("Group definition '{}' has not been found, group will be hidden", ref); } From 2c2901e8f70a8391399c2f5b4d35aaaddb1566a7 Mon Sep 17 00:00:00 2001 From: vdermichev Date: Fri, 27 Mar 2026 23:34:34 +0100 Subject: [PATCH 650/780] hyprland/workspaces: expose workspace name as CSS class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable per-workspace styling by adding the workspace name as a CSS class to each workspace button. The name is sanitized to a valid CSS identifier: lowercased, non-alphanumeric characters collapsed to hyphens, digit-leading names prefixed with "ws-". Examples: "IDE" → .ide, "special:telegram" → .telegram, "1" → .ws-1 Allows users to style individual workspaces: #workspaces button.telegram { color: #54a0e0; } --- include/modules/hyprland/workspace.hpp | 1 + src/modules/hyprland/workspace.cpp | 40 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/include/modules/hyprland/workspace.hpp b/include/modules/hyprland/workspace.hpp index 1d80d331..fb06fc72 100644 --- a/include/modules/hyprland/workspace.hpp +++ b/include/modules/hyprland/workspace.hpp @@ -71,6 +71,7 @@ class Workspace { int m_id; std::string m_name; + std::string m_prevNameClass; std::string m_output; uint m_windows; bool m_isActive = false; diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 21e7ef9b..02f03008 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -9,6 +10,33 @@ #include "util/command.hpp" #include "util/icon_loader.hpp" +namespace { +constexpr std::string_view kCssClassPrefix = "ws-"; + +// Convert a workspace name to a valid CSS class name. +// Lowercases, replaces non-alphanumeric runs with single hyphens, +// and prefixes digit-leading names (CSS classes can't start with a digit). +std::string sanitizeCssClass(const std::string& name) { + std::string result; + result.reserve(name.size() + kCssClassPrefix.size()); + for (auto c : name) { + auto uc = static_cast(c); + if (std::isalnum(uc)) { + result += static_cast(std::tolower(uc)); + } else if (!result.empty() && result.back() != '-') { + result += '-'; + } + } + if (!result.empty() && result.back() == '-') { + result.pop_back(); + } + if (!result.empty() && std::isdigit(static_cast(result.front()))) { + result.insert(0, kCssClassPrefix); + } + return result; +} +} // namespace + namespace waybar::modules::hyprland { Workspace::Workspace(const Json::Value& workspace_data, Workspaces& workspace_manager, @@ -209,6 +237,7 @@ std::string& Workspace::selectIcon(std::map& icons_map return m_name; } + void Workspace::update(const std::string& workspace_icon) { if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) { m_button.hide(); @@ -240,6 +269,17 @@ void Workspace::update(const std::string& workspace_icon) { addOrRemoveClass(styleContext, isVisible(), "visible"); addOrRemoveClass(styleContext, m_workspaceManager.getBarOutput() == output(), "hosting-monitor"); + // Add workspace name as CSS class for per-workspace styling + if (!m_prevNameClass.empty()) { + styleContext->remove_class(m_prevNameClass); + m_prevNameClass.clear(); + } + auto nameClass = sanitizeCssClass(name()); + if (!nameClass.empty()) { + styleContext->add_class(nameClass); + m_prevNameClass = nameClass; + } + std::string windows; // Optimization: The {windows} substitution string is only possible if the taskbar is disabled, no // need to compute this if enableTaskbar() is true From fc11789a4f6d437fd329cf3dada6ffb4b9aaa9e0 Mon Sep 17 00:00:00 2001 From: Duke B Date: Sun, 5 Apr 2026 19:01:36 -0400 Subject: [PATCH 651/780] add unit config option to memory module --- include/modules/memory.hpp | 4 +++ man/waybar-memory.5.scd | 18 ++++++---- src/modules/memory/common.cpp | 66 ++++++++++++++++++++++++----------- 3 files changed, 62 insertions(+), 26 deletions(-) diff --git a/include/modules/memory.hpp b/include/modules/memory.hpp index 3b6342b3..c73ece23 100644 --- a/include/modules/memory.hpp +++ b/include/modules/memory.hpp @@ -19,9 +19,13 @@ class Memory : public ALabel { private: void parseMeminfo(); + static float calc_divisor(const std::string& divisor); + std::unordered_map meminfo_; util::SleeperThread thread_; + + std::string unit_; }; } // namespace waybar::modules diff --git a/man/waybar-memory.5.scd b/man/waybar-memory.5.scd index 567c2c72..99f57299 100644 --- a/man/waybar-memory.5.scd +++ b/man/waybar-memory.5.scd @@ -102,23 +102,29 @@ Addressed by *memory* default: false ++ Enables this module to consume all left over space dynamically. +*unit*: ++ + typeof: string ++ + default: GiB ++ + Used to specify unit for total, swapTotal, used, swapUsed, avail, swapAvail, + and swapState. Accepts B, kB, kiB, MB, MiB, GB, GiB, TB, and TiB. + # FORMAT REPLACEMENTS *{percentage}*: Percentage of memory in use. *{swapPercentage}*: Percentage of swap in use. -*{total}*: Amount of total memory available in GiB. +*{total}*: Amount of total memory available. Defaults to GiB. -*{swapTotal}*: Amount of total swap available in GiB. +*{swapTotal}*: Amount of total swap available. Defaults to GiB. -*{used}*: Amount of used memory in GiB. +*{used}*: Amount of used memory. Defaults to GiB. -*{swapUsed}*: Amount of used swap in GiB. +*{swapUsed}*: Amount of used swap. Defaults to GiB. -*{avail}*: Amount of available memory in GiB. +*{avail}*: Amount of available memory. Defaults to GiB. -*{swapAvail}*: Amount of available swap in GiB. +*{swapAvail}*: Amount of available swap. Defaults to GiB. *{swapState}*: Signals if swap is activated or not diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp index 3c486a33..731f0b08 100644 --- a/src/modules/memory/common.cpp +++ b/src/modules/memory/common.cpp @@ -6,6 +6,9 @@ waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config dp.emit(); thread_.sleep_for(interval_); }; + if (config["unit"].isString()) { + unit_ = config["unit"].asString(); + } } auto waybar::modules::Memory::update() -> void { @@ -13,15 +16,15 @@ auto waybar::modules::Memory::update() -> void { unsigned long memtotal = meminfo_["MemTotal"]; unsigned long swaptotal = 0; - if (meminfo_.count("SwapTotal")) { + if (meminfo_.contains("SwapTotal")) { swaptotal = meminfo_["SwapTotal"]; } unsigned long memfree; unsigned long swapfree = 0; - if (meminfo_.count("SwapFree")) { + if (meminfo_.contains("SwapFree")) { swapfree = meminfo_["SwapFree"]; } - if (meminfo_.count("MemAvailable")) { + if (meminfo_.contains("MemAvailable")) { // New kernels (3.4+) have an accurate available memory field. memfree = meminfo_["MemAvailable"] + meminfo_["zfs_size"]; } else { @@ -31,18 +34,19 @@ auto waybar::modules::Memory::update() -> void { } if (memtotal > 0 && memfree >= 0) { - float total_ram_gigabytes = - 0.01 * round(memtotal / 10485.76); // 100*10485.76 = 2^20 = 1024^2 = GiB/KiB - float total_swap_gigabytes = 0.01 * round(swaptotal / 10485.76); int used_ram_percentage = 100 * (memtotal - memfree) / memtotal; int used_swap_percentage = 0; - if (swaptotal) { + if ((bool) swaptotal) { used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal; } - float used_ram_gigabytes = 0.01 * round((memtotal - memfree) / 10485.76); - float used_swap_gigabytes = 0.01 * round((swaptotal - swapfree) / 10485.76); - float available_ram_gigabytes = 0.01 * round(memfree / 10485.76); - float available_swap_gigabytes = 0.01 * round(swapfree / 10485.76); + + float divisor = calc_divisor(unit_); + float total_ram = memtotal / divisor; + float total_swap = swaptotal / divisor; + float used_ram = (memtotal - memfree) / divisor; + float used_swap = (swaptotal - swapfree) / divisor; + float available_ram = memfree / divisor; + float available_swap = swapfree / divisor; auto format = format_; auto state = getState(used_ram_percentage); @@ -58,12 +62,12 @@ auto waybar::modules::Memory::update() -> void { label_.set_markup(fmt::format( fmt::runtime(format), used_ram_percentage, fmt::arg("icon", getIcon(used_ram_percentage, icons)), - fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), + fmt::arg("total", total_ram), fmt::arg("swapTotal", total_swap), fmt::arg("percentage", used_ram_percentage), fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"), - fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes), - fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes), - fmt::arg("swapAvail", available_swap_gigabytes))); + fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram), + fmt::arg("swapUsed", used_swap), fmt::arg("avail", available_ram), + fmt::arg("swapAvail", available_swap))); } if (tooltipEnabled()) { @@ -71,14 +75,14 @@ auto waybar::modules::Memory::update() -> void { auto tooltip_format = config_["tooltip-format"].asString(); label_.set_tooltip_markup(fmt::format( fmt::runtime(tooltip_format), used_ram_percentage, - fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes), + fmt::arg("total", total_ram), fmt::arg("swapTotal", total_swap), fmt::arg("percentage", used_ram_percentage), fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"), - fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes), - fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes), - fmt::arg("swapAvail", available_swap_gigabytes))); + fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram), + fmt::arg("swapUsed", used_swap), fmt::arg("avail", available_ram), + fmt::arg("swapAvail", available_swap))); } else { - label_.set_tooltip_markup(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1)); + label_.set_tooltip_markup(fmt::format("{:.{}f}GiB used", used_ram, 1)); } } } else { @@ -87,3 +91,25 @@ auto waybar::modules::Memory::update() -> void { // Call parent update ALabel::update(); } + +float waybar::modules::Memory::calc_divisor(const std::string& divisor) { + if (divisor == "kB") { + return 1.0; + } else if (divisor == "kiB") { + return 1.024; + } else if (divisor == "MB") { + return 1.000 * 1000.0; + } else if (divisor == "MiB") { + return 1.024 * 1024.0; + } else if (divisor == "GB") { + return 1.000 * 1000.0 * 1000.0; + } else if (divisor == "GiB") { + return 1.024 * 1024.0 * 1024.0; + } else if (divisor == "TB") { + return 1.000 * 1000.0 * 1000.0 * 1000.0; + } else if (divisor == "TiB") { + return 1.024 * 1024.0 * 1024.0 * 1024.0; + } else { // default to GiB if it is anything that we don't recongnise + return 1.024 * 1024.0 * 1024.0; + } +} From 66cb828bf8379c52ca17a1ece7ac472197575bab Mon Sep 17 00:00:00 2001 From: OliMoli Date: Tue, 7 Apr 2026 14:02:10 +0200 Subject: [PATCH 652/780] Add CSS selector for expanded drawers --- src/group.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/group.cpp b/src/group.cpp index acd7bd42..be0fd0f1 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -95,11 +95,13 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value& void Group::show_group() { box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT); revealer.set_reveal_child(true); + box.get_style_context()->add_class("expanded"); } void Group::hide_group() { box.unset_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT); revealer.set_reveal_child(false); + box.get_style_context()->remove_class("expanded"); } bool Group::handleMouseEnter(GdkEventCrossing* const& e) { From 7feb23c3dddbfc91b89b38643c2ab250a973c6bb Mon Sep 17 00:00:00 2001 From: OliMoli Date: Tue, 7 Apr 2026 16:29:59 +0200 Subject: [PATCH 653/780] Update manpage to mention the .expanded CSS class --- man/waybar.5.scd.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 8b923eef..c76755f4 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -374,6 +374,8 @@ A group may hide all but one element, showing them only on mouse hover. In order Defines the direction of the transition animation. If true, the hidden elements will slide from left to right. If false, they will slide from right to left. When the bar is vertical, it reads as top-to-bottom. +Group drawers are also given the `.expanded` CSS class when they are expanded. + ``` "group/power": { "orientation": "inherit", From efadb82c56b16699c01b029913af7474b61464f3 Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Sat, 13 Sep 2025 00:00:20 +0200 Subject: [PATCH 654/780] feat: basic line graph component and cpu graph module --- include/AGraph.hpp | 50 +++++++++ include/modules/cpu_graph.hpp | 32 ++++++ man/waybar-cpu-graph.5.scd | 85 +++++++++++++++ meson.build | 3 + src/AGraph.cpp | 197 ++++++++++++++++++++++++++++++++++ src/factory.cpp | 4 + src/modules/cpu_graph.cpp | 47 ++++++++ 7 files changed, 418 insertions(+) create mode 100644 include/AGraph.hpp create mode 100644 include/modules/cpu_graph.hpp create mode 100644 man/waybar-cpu-graph.5.scd create mode 100644 src/AGraph.cpp create mode 100644 src/modules/cpu_graph.cpp diff --git a/include/AGraph.hpp b/include/AGraph.hpp new file mode 100644 index 00000000..3466e577 --- /dev/null +++ b/include/AGraph.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "AModule.hpp" + +namespace waybar { + +class AGraph : public AModule { + public: + AGraph(const Json::Value &, const std::string &, const std::string &, + uint16_t interval = 0, bool enable_click = false, + bool enable_scroll = false); + virtual ~AGraph() = default; + auto update() -> void override; + + protected: + Gtk::DrawingArea graph_; + std::deque values_; + uint16_t datapoints_ = 20; + uint16_t y_offset_ = 0; + + void addValue(const int n); + + const std::chrono::seconds interval_; + + bool onDraw(const Cairo::RefPtr &cr); + + std::map submenus_; + std::map menuActionsMap_; + static void handleGtkMenuEvent(GtkMenuItem *menuitem, gpointer data); + + private: + void drawFilledArea(const Cairo::RefPtr &cr, + const std::vector> &points, + double height, const Gdk::RGBA &bg_color); + + void drawLine(const Cairo::RefPtr &cr, + const std::vector> &points, const Gdk::RGBA &fg_color); + + void drawPath(const Cairo::RefPtr &cr, + const std::vector> &points); +}; + +} // namespace waybar diff --git a/include/modules/cpu_graph.hpp b/include/modules/cpu_graph.hpp new file mode 100644 index 00000000..df5f6ea3 --- /dev/null +++ b/include/modules/cpu_graph.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include "AGraph.hpp" +#include "util/sleeper_thread.hpp" + +namespace waybar::modules { + +class CpuGraph : public AGraph { + public: + CpuGraph(const std::string&, const Json::Value&); + virtual ~CpuGraph() = default; + auto update() -> void override; + + private: + static constexpr const char *MODERATE_CLASS = "cpu-moderate"; + static constexpr const char *HIGH_CLASS = "cpu-high"; + static constexpr const char *INTENSIVE_CLASS = "cpu-intensive"; + + std::vector> prev_times_; + util::SleeperThread thread_; +}; + +} // namespace waybar::modules diff --git a/man/waybar-cpu-graph.5.scd b/man/waybar-cpu-graph.5.scd new file mode 100644 index 00000000..bfe9de42 --- /dev/null +++ b/man/waybar-cpu-graph.5.scd @@ -0,0 +1,85 @@ +waybar-cpu(5) + +# NAME + +waybar - cpu graph module + +# DESCRIPTION + +The *cpu graph* module displays a line graph with the CPU utilization. + +# CONFIGURATION + +*interval*: ++ + typeof: integer ++ + default: 10 ++ + The interval in which the information gets polled. + +*width*: ++ + typeof: integer ++ + The length in pixels the module should display. + +*y_offset*: ++ + typeof: integer ++ + The margin in pixels at the bottom of the module. + +*datapoints*: ++ + typeof: integer ++ + How many data points to show. + +*on-click*: ++ + typeof: string ++ + Command to execute when clicked on the module. + +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + +*on-click-right*: ++ + typeof: string ++ + Command to execute when you right-click on the module. + +*on-update*: ++ + typeof: string ++ + Command to execute when the module is updated. + +*on-scroll-up*: ++ + typeof: string ++ + Command to execute when scrolling up on the module. + +*on-scroll-down*: ++ + typeof: string ++ + Command to execute when scrolling down on the module. + +*smooth-scrolling-threshold*: ++ + typeof: double ++ + Threshold to be used when scrolling. + +*tooltip*: ++ + typeof: bool ++ + default: true ++ + Option to disable tooltip on hover. + +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + +# EXAMPLES + +Basic configuration: + +``` +"cpu_graph": { + "interval": 2, + "width": 10, + "y_offset": 4 +} +``` + +# STYLE + +- *#cpu_graph* +- *.cpu-intensive* +- *.cpu-high* +- *.cpu-moderate* diff --git a/meson.build b/meson.build index 0c494eb2..713a723f 100644 --- a/meson.build +++ b/meson.build @@ -159,6 +159,7 @@ endif src_files = files( 'src/factory.cpp', + 'src/AGraph.cpp', 'src/AModule.cpp', 'src/ALabel.cpp', 'src/AIconLabel.cpp', @@ -210,6 +211,7 @@ if is_linux 'src/modules/bluetooth.cpp', 'src/modules/cffi.cpp', 'src/modules/cpu.cpp', + 'src/modules/cpu_graph.cpp', 'src/modules/cpu_frequency/common.cpp', 'src/modules/cpu_frequency/linux.cpp', 'src/modules/cpu_usage/common.cpp', @@ -234,6 +236,7 @@ elif is_dragonfly or is_freebsd or is_netbsd or is_openbsd src_files += files( 'src/modules/cffi.cpp', 'src/modules/cpu.cpp', + 'src/modules/cpu_graph.cpp', 'src/modules/cpu_frequency/bsd.cpp', 'src/modules/cpu_frequency/common.cpp', 'src/modules/cpu_usage/bsd.cpp', diff --git a/src/AGraph.cpp b/src/AGraph.cpp new file mode 100644 index 00000000..529cdb66 --- /dev/null +++ b/src/AGraph.cpp @@ -0,0 +1,197 @@ +#include "AGraph.hpp" + +#include +#include + +#include +#include +#include + +#include "config.hpp" + +namespace waybar { + +AGraph::AGraph(const Json::Value& config, const std::string& name, const std::string& id, + uint16_t interval, bool enable_click, bool enable_scroll) + : AModule(config, name, id, + config["format-alt"].isString() || config["menu"].isString() || enable_click, + enable_scroll), + interval_(config_["interval"] == "once" + ? std::chrono::seconds::max() + : std::chrono::seconds( + config_["interval"].isUInt() ? config_["interval"].asUInt() : interval)) { + graph_.signal_draw().connect(sigc::mem_fun(*this, &AGraph::onDraw)); + graph_.set_name(name); + if (!id.empty()) { + graph_.get_style_context()->add_class(id); + } + graph_.get_style_context()->add_class(MODULE_CLASS); + if (config_["width"].isUInt()) { + graph_.set_size_request(config_["width"].asUInt(), -1); + } else { + graph_.set_size_request(100, -1); + } + + event_box_.add(graph_); + + if (config_["datapoints"].isUInt()) { + datapoints_ = config_["datapoints_"].asUInt(); + } + + if (config_["y_offset"].isUInt()) { + y_offset_ = config_["y_offset"].asUInt(); + } + + // If a GTKMenu is requested in the config + if (config_["menu"].isString()) { + // Create the GTKMenu widget + try { + // Check that the file exists + std::string menuFile = config_["menu-file"].asString(); + + // there might be "~" or "$HOME" in original path, try to expand it. + auto result = Config::tryExpandPath(menuFile, ""); + if (result.empty()) { + throw std::runtime_error("Failed to expand file: " + menuFile); + } + + menuFile = result.front(); + // Read the menu descriptor file + std::ifstream file(menuFile); + if (!file.is_open()) { + throw std::runtime_error("Failed to open file: " + menuFile); + } + std::stringstream fileContent; + fileContent << file.rdbuf(); + GtkBuilder* builder = gtk_builder_new(); + + // Make the GtkBuilder and check for errors in his parsing + if (gtk_builder_add_from_string(builder, fileContent.str().c_str(), -1, nullptr) == 0U) { + throw std::runtime_error("Error found in the file " + menuFile); + } + + menu_ = gtk_builder_get_object(builder, "menu"); + if (menu_ == nullptr) { + throw std::runtime_error("Failed to get 'menu' object from GtkBuilder"); + } + submenus_ = std::map(); + menuActionsMap_ = std::map(); + + // Linking actions to the GTKMenu based on + for (Json::Value::const_iterator it = config_["menu-actions"].begin(); + it != config_["menu-actions"].end(); ++it) { + std::string key = it.key().asString(); + submenus_[key] = GTK_MENU_ITEM(gtk_builder_get_object(builder, key.c_str())); + menuActionsMap_[key] = it->asString(); + g_signal_connect(submenus_[key], "activate", G_CALLBACK(handleGtkMenuEvent), + (gpointer)menuActionsMap_[key].c_str()); + } + } catch (std::runtime_error& e) { + spdlog::warn("Error while creating the menu : {}. Menu popup not activated.", e.what()); + } + } +} + +auto AGraph::update() -> void { + graph_.queue_draw(); + AModule::update(); +} + +void AGraph::handleGtkMenuEvent(GtkMenuItem* /*menuitem*/, gpointer data) { + waybar::util::command::res res = waybar::util::command::exec((char*)data, "GtkMenu"); +} + +void AGraph::addValue(const int n) { + if (values_.size() >= datapoints_) { + values_.pop_front(); + } + values_.push_back(n); +} + +bool AGraph::onDraw(const Cairo::RefPtr& cr) { + const int width = graph_.get_allocated_width(); + const int height = graph_.get_allocated_height() - 1 - y_offset_; + + if (values_.empty() || width <= 0 || height <= 0) { + return false; + } + + auto style_context = graph_.get_style_context(); + Gdk::RGBA fg_color = style_context->get_color(Gtk::STATE_FLAG_NORMAL); + Gdk::RGBA bg_color = fg_color; + bg_color.set_alpha(0.3); + + cr->set_line_width(1.0); + + const double step_width = static_cast(width) / datapoints_; + const int values_count = values_.size(); + const int empty_space = datapoints_ - values_count; + + std::vector> points; + points.reserve(values_count); + + for (int i = empty_space; i < datapoints_; ++i) { + double x = i * step_width; + int value_index = i - empty_space; + int value = values_[value_index]; + double y = height - (static_cast(value) / 100.0 * height); + points.emplace_back(x, y); + } + + if (!points.empty()) { + + drawFilledArea(cr, points, height, bg_color); + + drawLine(cr, points, fg_color); + } + + return false; +} +void AGraph::drawFilledArea(const Cairo::RefPtr& cr, + const std::vector>& points, + double height, const Gdk::RGBA& bg_color) { + if (points.empty()) return; + + double first_x = points.front().first; + double last_x = points.back().first; + + drawPath(cr, points); + + cr->line_to(last_x, height); + cr->line_to(first_x, height); + cr->close_path(); + + cr->set_source_rgba(bg_color.get_red(), bg_color.get_green(), bg_color.get_blue(), + bg_color.get_alpha()); + cr->fill(); +} + +void AGraph::drawLine(const Cairo::RefPtr& cr, + const std::vector>& points, + const Gdk::RGBA& fg_color) { + if (points.empty()) return; + + cr->begin_new_path(); + drawPath(cr, points); + + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), + fg_color.get_alpha()); + cr->stroke(); +} + +void AGraph::drawPath(const Cairo::RefPtr& cr, + const std::vector>& points) { + if (points.empty()) return; + + bool first_point = true; + for (const auto& point : points) { + if (first_point) { + cr->move_to(point.first, point.second); + first_point = false; + } else { + cr->line_to(point.first, point.second); + } + } +} + +} // namespace waybar diff --git a/src/factory.cpp b/src/factory.cpp index 2fd3e3b8..e82711b5 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -51,6 +51,7 @@ #endif #if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD) #include "modules/cpu.hpp" +#include "modules/cpu_graph.hpp" #include "modules/cpu_frequency.hpp" #include "modules/cpu_usage.hpp" #include "modules/load.hpp" @@ -251,6 +252,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, if (ref == "cpu") { return new waybar::modules::Cpu(id, config_[name]); } + if (ref == "cpu_graph") { + return new waybar::modules::CpuGraph(id, config_[name]); + } #if defined(HAVE_CPU_LINUX) if (ref == "cpu_frequency") { return new waybar::modules::CpuFrequency(id, config_[name]); diff --git a/src/modules/cpu_graph.cpp b/src/modules/cpu_graph.cpp new file mode 100644 index 00000000..a9a29dd2 --- /dev/null +++ b/src/modules/cpu_graph.cpp @@ -0,0 +1,47 @@ +#include "modules/cpu_graph.hpp" + +#include "modules/cpu_frequency.hpp" +#include "modules/cpu_usage.hpp" +#include "modules/load.hpp" + +// In the 80000 version of fmt library authors decided to optimize imports +// and moved declarations required for fmt::dynamic_format_arg_store in new +// header fmt/args.h +#if (FMT_VERSION >= 80000) +#include +#else +#include +#endif + +waybar::modules::CpuGraph::CpuGraph(const std::string& id, const Json::Value& config) + : AGraph(config, "cpu_graph", id, 5) { + thread_ = [this] { + dp.emit(); + thread_.sleep_for(interval_); + }; +} + +auto waybar::modules::CpuGraph::update() -> void { + // TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both + auto [cpu_usage, tooltip] = CpuUsage::getCpuUsage(prev_times_); + if (tooltipEnabled()) { + graph_.set_tooltip_text(tooltip); + } + auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; + addValue(total_usage); + + graph_.get_style_context()->remove_class(MODERATE_CLASS); + graph_.get_style_context()->remove_class(HIGH_CLASS); + graph_.get_style_context()->remove_class(INTENSIVE_CLASS); + + if (total_usage > 90) { + graph_.get_style_context()->add_class(INTENSIVE_CLASS); + } else if (total_usage > 70) { + graph_.get_style_context()->add_class(HIGH_CLASS); + } else if (total_usage > 30) { + graph_.get_style_context()->add_class(MODERATE_CLASS); + } + + // Call parent update + AGraph::update(); +} From 4d6354af48e5fba70d522ce75d8a0eb20cd661c6 Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Sat, 11 Oct 2025 01:52:10 +0200 Subject: [PATCH 655/780] feat: add gauge and stacked bar graph type --- include/AGraph.hpp | 19 ++- include/modules/custom_graph.hpp | 49 ++++++ meson.build | 1 + src/AGraph.cpp | 122 ++++++++++++- src/factory.cpp | 4 + src/modules/custom_graph.cpp | 283 +++++++++++++++++++++++++++++++ 6 files changed, 466 insertions(+), 12 deletions(-) create mode 100644 include/modules/custom_graph.hpp create mode 100644 src/modules/custom_graph.cpp diff --git a/include/AGraph.hpp b/include/AGraph.hpp index 3466e577..a49b215d 100644 --- a/include/AGraph.hpp +++ b/include/AGraph.hpp @@ -11,11 +11,12 @@ namespace waybar { +enum class GraphType { LINE, BAR, GAUGE }; + class AGraph : public AModule { public: - AGraph(const Json::Value &, const std::string &, const std::string &, - uint16_t interval = 0, bool enable_click = false, - bool enable_scroll = false); + AGraph(const Json::Value &, const std::string &, const std::string &, uint16_t interval = 0, + bool enable_click = false, bool enable_scroll = false); virtual ~AGraph() = default; auto update() -> void override; @@ -24,6 +25,7 @@ class AGraph : public AModule { std::deque values_; uint16_t datapoints_ = 20; uint16_t y_offset_ = 0; + GraphType graph_type_ = GraphType::LINE; void addValue(const int n); @@ -37,14 +39,21 @@ class AGraph : public AModule { private: void drawFilledArea(const Cairo::RefPtr &cr, - const std::vector> &points, - double height, const Gdk::RGBA &bg_color); + const std::vector> &points, double height, + const Gdk::RGBA &bg_color); void drawLine(const Cairo::RefPtr &cr, const std::vector> &points, const Gdk::RGBA &fg_color); void drawPath(const Cairo::RefPtr &cr, const std::vector> &points); + + void drawBars(const Cairo::RefPtr &cr, + double width, double height, int current_value, + const Gdk::RGBA &fg_color); + + void drawGauge(const Cairo::RefPtr &cr, double width, double height, + int current_value, const Gdk::RGBA &fg_color); }; } // namespace waybar diff --git a/include/modules/custom_graph.hpp b/include/modules/custom_graph.hpp new file mode 100644 index 00000000..a081f376 --- /dev/null +++ b/include/modules/custom_graph.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include + +#include +#include + +#include "AGraph.hpp" +#include "util/command.hpp" +#include "util/json.hpp" +#include "util/sleeper_thread.hpp" + +namespace waybar::modules { + +class CustomGraph : public AGraph { + public: + CustomGraph(const std::string&, const std::string&, const Json::Value&, const std::string&); + virtual ~CustomGraph(); + auto update() -> void override; + void refresh(int /*signal*/) override; + + private: + void delayWorker(); + void continuousWorker(); + void waitingWorker(); + void parseOutputRaw(); + void parseOutputJson(); + void handleEvent(); + bool handleScroll(GdkEventScroll* e) override; + bool handleToggle(GdkEventButton* const& e) override; + + const std::string name_; + const std::string output_name_; + std::string text_; + std::string id_; + std::string alt_; + std::string tooltip_; + const bool tooltip_format_enabled_; + std::vector class_; + int percentage_; + FILE* fp_; + int pid_; + util::command::res output_; + util::JsonParser parser_; + + util::SleeperThread thread_; +}; + +} // namespace waybar::modules diff --git a/meson.build b/meson.build index 713a723f..8413dbe1 100644 --- a/meson.build +++ b/meson.build @@ -165,6 +165,7 @@ src_files = files( 'src/AIconLabel.cpp', 'src/AAppIconLabel.cpp', 'src/modules/custom.cpp', + 'src/modules/custom_graph.cpp', 'src/modules/disk.cpp', 'src/modules/idle_inhibitor.cpp', 'src/modules/image.cpp', diff --git a/src/AGraph.cpp b/src/AGraph.cpp index 529cdb66..b8076ecc 100644 --- a/src/AGraph.cpp +++ b/src/AGraph.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -42,6 +43,17 @@ AGraph::AGraph(const Json::Value& config, const std::string& name, const std::st y_offset_ = config_["y_offset"].asUInt(); } + if (config_["graph_type"].isString()) { + std::string type = config_["graph_type"].asString(); + if (type == "line") { + graph_type_ = GraphType::LINE; + } else if (type == "bar") { + graph_type_ = GraphType::BAR; + } else if (type == "gauge") { + graph_type_ = GraphType::GAUGE; + } + } + // If a GTKMenu is requested in the config if (config_["menu"].isString()) { // Create the GTKMenu widget @@ -137,19 +149,26 @@ bool AGraph::onDraw(const Cairo::RefPtr& cr) { double y = height - (static_cast(value) / 100.0 * height); points.emplace_back(x, y); } - if (!points.empty()) { - - drawFilledArea(cr, points, height, bg_color); - - drawLine(cr, points, fg_color); + switch (graph_type_) { + case GraphType::LINE: + drawFilledArea(cr, points, height, bg_color); + drawLine(cr, points, fg_color); + break; + case GraphType::BAR: + drawBars(cr, width, height, values_.empty() ? 0 : values_.back(), fg_color); + break; + case GraphType::GAUGE: + drawGauge(cr, width, height, values_.empty() ? 0 : values_.back(), fg_color); + break; + } } return false; } void AGraph::drawFilledArea(const Cairo::RefPtr& cr, - const std::vector>& points, - double height, const Gdk::RGBA& bg_color) { + const std::vector>& points, double height, + const Gdk::RGBA& bg_color) { if (points.empty()) return; double first_x = points.front().first; @@ -194,4 +213,93 @@ void AGraph::drawPath(const Cairo::RefPtr& cr, } } +void AGraph::drawBars(const Cairo::RefPtr& cr, + double width, double height, int current_value, + const Gdk::RGBA& fg_color) { + + current_value = std::min(100, std::max(0, current_value)); + + double green_height = height * (std::min(current_value, 40) / 100.0); + cr->set_source_rgba(0.0, 1.0, 0.0, 1.0); + cr->rectangle(0, height - green_height, width, green_height); + cr->fill(); + + if (current_value > 40) { + double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; + cr->set_source_rgba(1.0, 1.0, 0.0, 1.0); + cr->rectangle(0, height - green_height - yellow_height, width, yellow_height); + cr->fill(); + } + + if (current_value > 75) { + double orange_height = height * (std::min(current_value, 85) - 75) / 100.0; + cr->set_source_rgba(1.0, 0.5, 0.0, 1.0); + double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; + cr->rectangle(0, height - green_height - yellow_height - orange_height, width, + orange_height); + cr->fill(); + } + + if (current_value > 85) { + double red_height = height * (current_value - 85) / 100.0; + cr->set_source_rgba(1.0, 0.0, 0.0, 1.0); + double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; + double orange_height = height * (std::min(current_value, 85) - 75) / 100.0; + cr->rectangle(0, height - green_height - yellow_height - orange_height - red_height, width, + red_height); + cr->fill(); + } + + double value_height = height * (current_value / 100.0); + cr->set_source_rgba(0.2, 0.2, 0.2, 0.8); + cr->rectangle(0, height - value_height, width, 2); + cr->fill(); +} + +void AGraph::drawGauge(const Cairo::RefPtr& cr, double width, double height, + int current_value, const Gdk::RGBA& fg_color) { + double center_x = width / 2.0; + double center_y = height; + double radius = height / 2.0; + + cr->set_line_width(10.0); + + double angle1 = M_PI; + double angle2 = angle1 + 0.3 * angle1; + + // Green section (0-33%) + cr->set_source_rgba(0.0, 1.0, 0.0, 1.0); + cr->arc(center_x, center_y, radius, angle1, angle2); + cr->stroke(); + + // Yellow section (33-66%) + angle1 = angle2; + angle2 = angle1 + 0.3 * angle1; + cr->set_source_rgba(1.0, 1.0, 0.0, 1.0); + cr->arc(center_x, center_y, radius, angle1, angle2); + cr->stroke(); + + // Red section (66-100%) + angle1 = angle2; + angle2 = 0.0; + cr->set_source_rgba(1.0, 0.0, 0.0, 0.8); + cr->arc(center_x, center_y, radius, angle1, angle2); + cr->stroke(); + + // Draw needle + double percentage = std::min(100, std::max(0, current_value)) / 100.0; + double needle_angle = M_PI * percentage; + double needle_length = radius; + + double needle_x = center_x - needle_length * cos(needle_angle); + double needle_y = center_y - needle_length * sin(needle_angle); + + cr->set_source_rgba(1.0, 1.0, 1.0, 1.0); + cr->set_line_width(2.0); + cr->begin_new_path(); + cr->move_to(center_x, center_y); + cr->line_to(needle_x, needle_y); + cr->stroke(); +} + } // namespace waybar diff --git a/src/factory.cpp b/src/factory.cpp index e82711b5..dfb5caf4 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -118,6 +118,7 @@ #include "modules/cava/cava_frontend.hpp" #include "modules/cffi.hpp" #include "modules/custom.hpp" +#include "modules/custom_graph.hpp" #include "modules/image.hpp" #include "modules/temperature.hpp" #include "modules/user.hpp" @@ -362,6 +363,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) { return new waybar::modules::Custom(ref.substr(7), id, config_[name], bar_.output->name); } + if (ref.compare(0, 13, "custom-graph/") == 0 && ref.size() > 7) { + return new waybar::modules::CustomGraph(ref.substr(7), id, config_[name], bar_.output->name); + } if (ref.compare(0, 5, "cffi/") == 0 && ref.size() > 5) { return new waybar::modules::CFFI(ref.substr(5), id, config_[name]); } diff --git a/src/modules/custom_graph.cpp b/src/modules/custom_graph.cpp new file mode 100644 index 00000000..288d97f2 --- /dev/null +++ b/src/modules/custom_graph.cpp @@ -0,0 +1,283 @@ +#include "modules/custom_graph.hpp" + +#include + +#include "util/scope_guard.hpp" + +waybar::modules::CustomGraph::CustomGraph(const std::string& name, const std::string& id, + const Json::Value& config, const std::string& output_name) + : AGraph(config, "custom-graph" + name, id), + name_(name), + output_name_(output_name), + id_(id), + tooltip_format_enabled_{config_["tooltip-format"].isString()}, + percentage_(0), + fp_(nullptr), + pid_(-1) { + if (config.isNull()) { + spdlog::warn("There is no configuration for 'custom-graph/{}', element will be hidden", name); + } + dp.emit(); + if (!config_["signal"].empty() && config_["interval"].empty() && + config_["restart-interval"].empty()) { + waitingWorker(); + } else if (interval_.count() > 0) { + delayWorker(); + } else if (config_["exec"].isString()) { + continuousWorker(); + } +} + +waybar::modules::CustomGraph::~CustomGraph() { + if (pid_ != -1) { + killpg(pid_, SIGTERM); + waitpid(pid_, NULL, 0); + pid_ = -1; + } +} + +void waybar::modules::CustomGraph::delayWorker() { + thread_ = [this] { + for (int i : this->pid_children_) { + int status; + waitpid(i, &status, 0); + } + + this->pid_children_.clear(); + + bool can_update = true; + if (config_["exec-if"].isString()) { + output_ = util::command::execNoRead(config_["exec-if"].asString()); + if (output_.exit_code != 0) { + can_update = false; + dp.emit(); + } + } + if (can_update) { + if (config_["exec"].isString()) { + output_ = util::command::exec(config_["exec"].asString(), output_name_); + } + dp.emit(); + } + thread_.sleep_for(interval_); + }; +} + +void waybar::modules::CustomGraph::continuousWorker() { + auto cmd = config_["exec"].asString(); + pid_ = -1; + fp_ = util::command::open(cmd, pid_, output_name_); + if (!fp_) { + throw std::runtime_error("Unable to open " + cmd); + } + thread_ = [this, cmd] { + char* buff = nullptr; + waybar::util::ScopeGuard buff_deleter([&buff]() { + if (buff) { + free(buff); + } + }); + size_t len = 0; + if (getline(&buff, &len, fp_) == -1) { + int exit_code = 1; + if (fp_) { + exit_code = WEXITSTATUS(util::command::close(fp_, pid_)); + fp_ = nullptr; + } + if (exit_code != 0) { + output_ = {exit_code, ""}; + dp.emit(); + spdlog::error("{} stopped unexpectedly, is it endless?", name_); + } + if (config_["restart-interval"].isUInt()) { + pid_ = -1; + thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt())); + fp_ = util::command::open(cmd, pid_, output_name_); + if (!fp_) { + throw std::runtime_error("Unable to open " + cmd); + } + } else { + thread_.stop(); + return; + } + } else { + std::string output = buff; + + // Remove last newline + if (!output.empty() && output[output.length() - 1] == '\n') { + output.erase(output.length() - 1); + } + output_ = {0, output}; + dp.emit(); + } + }; +} + +void waybar::modules::CustomGraph::waitingWorker() { + thread_ = [this] { + bool can_update = true; + if (config_["exec-if"].isString()) { + output_ = util::command::execNoRead(config_["exec-if"].asString()); + if (output_.exit_code != 0) { + can_update = false; + dp.emit(); + } + } + if (can_update) { + if (config_["exec"].isString()) { + output_ = util::command::exec(config_["exec"].asString(), output_name_); + } + dp.emit(); + } + thread_.sleep(); + }; +} + +void waybar::modules::CustomGraph::refresh(int sig) { + if (sig == SIGRTMIN + config_["signal"].asInt()) { + thread_.wake_up(); + } +} + +void waybar::modules::CustomGraph::handleEvent() { + if (!config_["exec-on-event"].isBool() || config_["exec-on-event"].asBool()) { + thread_.wake_up(); + } +} + +bool waybar::modules::CustomGraph::handleScroll(GdkEventScroll* e) { + auto ret = AGraph::handleScroll(e); + handleEvent(); + return ret; +} + +bool waybar::modules::CustomGraph::handleToggle(GdkEventButton* const& e) { + auto ret = AGraph::handleToggle(e); + handleEvent(); + return ret; +} + +auto waybar::modules::CustomGraph::update() -> void { + // Hide label if output is empty + if ((config_["exec"].isString() || config_["exec-if"].isString()) && + (output_.out.empty() || output_.exit_code != 0)) { + event_box_.hide(); + } else { + if (config_["return-type"].asString() == "json") { + parseOutputJson(); + } else { + parseOutputRaw(); + } + + try { + addValue(percentage_); + + if (tooltipEnabled()) { + if (tooltip_format_enabled_) { + auto tooltip = config_["tooltip-format"].asString(); + tooltip = fmt::format( + fmt::runtime(tooltip), fmt::arg("text", text_), fmt::arg("alt", alt_), + fmt::arg("percentage", percentage_)); + graph_.set_tooltip_markup(tooltip); + } else { + if (graph_.get_tooltip_markup() != tooltip_) { + graph_.set_tooltip_markup(tooltip_); + } + } + } + auto style = graph_.get_style_context(); + auto classes = style->list_classes(); + for (auto const& c : classes) { + if (c == id_) continue; + style->remove_class(c); + } + for (auto const& c : class_) { + style->add_class(c); + } + style->add_class("flat"); + style->add_class(MODULE_CLASS); + event_box_.show(); + } catch (const fmt::format_error& e) { + if (std::strcmp(e.what(), "cannot switch from manual to automatic argument indexing") != 0) + throw; + + throw fmt::format_error( + "mixing manual and automatic argument indexing is no longer supported; " + "try replacing \"{}\" with \"{text}\" in your format specifier"); + } + } + // Call parent update + AGraph::update(); +} + +void waybar::modules::CustomGraph::parseOutputRaw() { + std::istringstream output(output_.out); + std::string line; + int i = 0; + while (getline(output, line)) { + Glib::ustring validated_line = line; + if (!validated_line.validate()) { + validated_line = validated_line.make_valid(); + } + + if (i == 0) { + if (config_["escape"].isBool() && config_["escape"].asBool()) { + text_ = Glib::Markup::escape_text(validated_line); + tooltip_ = Glib::Markup::escape_text(validated_line); + } else { + text_ = validated_line; + tooltip_ = validated_line; + } + tooltip_ = validated_line; + class_.clear(); + } else if (i == 1) { + if (config_["escape"].isBool() && config_["escape"].asBool()) { + tooltip_ = Glib::Markup::escape_text(validated_line); + } else { + tooltip_ = validated_line; + } + } else if (i == 2) { + class_.push_back(validated_line); + } else { + break; + } + i++; + } +} + +void waybar::modules::CustomGraph::parseOutputJson() { + std::istringstream output(output_.out); + std::string line; + class_.clear(); + while (getline(output, line)) { + auto parsed = parser_.parse(line); + if (config_["escape"].isBool() && config_["escape"].asBool()) { + text_ = Glib::Markup::escape_text(parsed["text"].asString()); + } else { + text_ = parsed["text"].asString(); + } + if (config_["escape"].isBool() && config_["escape"].asBool()) { + alt_ = Glib::Markup::escape_text(parsed["alt"].asString()); + } else { + alt_ = parsed["alt"].asString(); + } + if (config_["escape"].isBool() && config_["escape"].asBool()) { + tooltip_ = Glib::Markup::escape_text(parsed["tooltip"].asString()); + } else { + tooltip_ = parsed["tooltip"].asString(); + } + if (parsed["class"].isString()) { + class_.push_back(parsed["class"].asString()); + } else if (parsed["class"].isArray()) { + for (auto const& c : parsed["class"]) { + class_.push_back(c.asString()); + } + } + if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) { + percentage_ = (int)lround(parsed["percentage"].asFloat()); + } else { + percentage_ = 0; + } + break; + } +} From dfc26364e15dfe202990e598426e9f5535f52fde Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Fri, 24 Oct 2025 19:47:43 +0200 Subject: [PATCH 656/780] feat: use the foreground color for cleaner look --- include/AGraph.hpp | 1 - man/waybar-cpu-graph.5.scd | 7 +------ src/AGraph.cpp | 26 +++++++++++++++----------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/include/AGraph.hpp b/include/AGraph.hpp index a49b215d..69045473 100644 --- a/include/AGraph.hpp +++ b/include/AGraph.hpp @@ -24,7 +24,6 @@ class AGraph : public AModule { Gtk::DrawingArea graph_; std::deque values_; uint16_t datapoints_ = 20; - uint16_t y_offset_ = 0; GraphType graph_type_ = GraphType::LINE; void addValue(const int n); diff --git a/man/waybar-cpu-graph.5.scd b/man/waybar-cpu-graph.5.scd index bfe9de42..c93aaf06 100644 --- a/man/waybar-cpu-graph.5.scd +++ b/man/waybar-cpu-graph.5.scd @@ -19,10 +19,6 @@ The *cpu graph* module displays a line graph with the CPU utilization. typeof: integer ++ The length in pixels the module should display. -*y_offset*: ++ - typeof: integer ++ - The margin in pixels at the bottom of the module. - *datapoints*: ++ typeof: integer ++ How many data points to show. @@ -72,8 +68,7 @@ Basic configuration: ``` "cpu_graph": { "interval": 2, - "width": 10, - "y_offset": 4 + "width": 10 } ``` diff --git a/src/AGraph.cpp b/src/AGraph.cpp index b8076ecc..1899b1b2 100644 --- a/src/AGraph.cpp +++ b/src/AGraph.cpp @@ -39,10 +39,6 @@ AGraph::AGraph(const Json::Value& config, const std::string& name, const std::st datapoints_ = config_["datapoints_"].asUInt(); } - if (config_["y_offset"].isUInt()) { - y_offset_ = config_["y_offset"].asUInt(); - } - if (config_["graph_type"].isString()) { std::string type = config_["graph_type"].asString(); if (type == "line") { @@ -122,7 +118,7 @@ void AGraph::addValue(const int n) { bool AGraph::onDraw(const Cairo::RefPtr& cr) { const int width = graph_.get_allocated_width(); - const int height = graph_.get_allocated_height() - 1 - y_offset_; + const int height = graph_.get_allocated_height() - 1; if (values_.empty() || width <= 0 || height <= 0) { return false; @@ -220,20 +216,23 @@ void AGraph::drawBars(const Cairo::RefPtr& cr, current_value = std::min(100, std::max(0, current_value)); double green_height = height * (std::min(current_value, 40) / 100.0); - cr->set_source_rgba(0.0, 1.0, 0.0, 1.0); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), + 0.5); cr->rectangle(0, height - green_height, width, green_height); cr->fill(); if (current_value > 40) { double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; - cr->set_source_rgba(1.0, 1.0, 0.0, 1.0); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), + 0.7); cr->rectangle(0, height - green_height - yellow_height, width, yellow_height); cr->fill(); } if (current_value > 75) { double orange_height = height * (std::min(current_value, 85) - 75) / 100.0; - cr->set_source_rgba(1.0, 0.5, 0.0, 1.0); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), + 0.85); double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; cr->rectangle(0, height - green_height - yellow_height - orange_height, width, orange_height); @@ -242,7 +241,8 @@ void AGraph::drawBars(const Cairo::RefPtr& cr, if (current_value > 85) { double red_height = height * (current_value - 85) / 100.0; - cr->set_source_rgba(1.0, 0.0, 0.0, 1.0); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), + 1.0); double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; double orange_height = height * (std::min(current_value, 85) - 75) / 100.0; cr->rectangle(0, height - green_height - yellow_height - orange_height - red_height, width, @@ -268,14 +268,16 @@ void AGraph::drawGauge(const Cairo::RefPtr& cr, double width, do double angle2 = angle1 + 0.3 * angle1; // Green section (0-33%) - cr->set_source_rgba(0.0, 1.0, 0.0, 1.0); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), + 0.5); cr->arc(center_x, center_y, radius, angle1, angle2); cr->stroke(); // Yellow section (33-66%) angle1 = angle2; angle2 = angle1 + 0.3 * angle1; - cr->set_source_rgba(1.0, 1.0, 0.0, 1.0); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), + 0.75); cr->arc(center_x, center_y, radius, angle1, angle2); cr->stroke(); @@ -283,6 +285,8 @@ void AGraph::drawGauge(const Cairo::RefPtr& cr, double width, do angle1 = angle2; angle2 = 0.0; cr->set_source_rgba(1.0, 0.0, 0.0, 0.8); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), + 1.0); cr->arc(center_x, center_y, radius, angle1, angle2); cr->stroke(); From 01c6ebdf9e56b943821dbbccbe8599976589dd04 Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Sat, 25 Oct 2025 00:13:15 +0200 Subject: [PATCH 657/780] feat: update man pages --- man/waybar-cpu-graph.5.scd | 2 +- man/waybar-custom-graph.5.scd | 189 ++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 man/waybar-custom-graph.5.scd diff --git a/man/waybar-cpu-graph.5.scd b/man/waybar-cpu-graph.5.scd index c93aaf06..1877aeeb 100644 --- a/man/waybar-cpu-graph.5.scd +++ b/man/waybar-cpu-graph.5.scd @@ -1,4 +1,4 @@ -waybar-cpu(5) +waybar-cpu-graph(5) # NAME diff --git a/man/waybar-custom-graph.5.scd b/man/waybar-custom-graph.5.scd new file mode 100644 index 00000000..4f020a6c --- /dev/null +++ b/man/waybar-custom-graph.5.scd @@ -0,0 +1,189 @@ +waybar-custom-graph(5) +# NAME + +waybar - custom graph module + +# DESCRIPTION + +The *custom-graph* module displays a graph with the percentage output of a script. + +# CONFIGURATION + +Addressed by *custom-graph/* + +*exec*: ++ + typeof: string ++ + The path to the script, which should be executed. + +*exec-if*: ++ + typeof: string ++ + The path to a script, which determines if the script in *exec* should be executed. ++ + *exec* will be executed if the exit code of *exec-if* equals 0. + +*exec-on-event*: ++ + typeof: bool ++ + default: true ++ + If an event command is set (e.g. *on-click* or *on-scroll-up*) then re-execute the script after executing the event command. + +*return-type*: ++ + typeof: string ++ + See *return-type* + +*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. + +*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*. + +*signal*: ++ + typeof: integer ++ + The signal number used to update the module. ++ + The number is valid between 1 and N, where *SIGRTMIN+N* = *SIGRTMAX*. ++ + If no interval is defined then a signal will be the only way to update the module. + +*format*: ++ + typeof: string ++ + default: {text} ++ + The format, how information should be displayed. On {text} data gets inserted. + +*format-icons*: ++ + typeof: array ++ + Based on the set percentage, the corresponding icon gets selected. The order is *low* to *high*. + +*rotate*: ++ + typeof: integer ++ + Positive value to rotate the text label (in 90 degree increments). + +*on-click*: ++ + typeof: string ++ + Command to execute when clicked on the module. + +*on-click-middle*: ++ + typeof: string ++ + Command to execute when middle-clicked on the module using mousewheel. + +*on-click-right*: ++ + typeof: string ++ + Command to execute when you right-click on the module. + +*on-update*: ++ + typeof: string ++ + Command to execute when the module is updated. + +*on-scroll-up*: ++ + typeof: string ++ + Command to execute when scrolling up on the module. + +*on-scroll-down*: ++ + typeof: string ++ + Command to execute when scrolling down on the module. + +*smooth-scrolling-threshold*: ++ + typeof: double ++ + Threshold to be used when scrolling. + +*tooltip*: ++ + typeof: bool ++ + default: true ++ + Option to disable tooltip on hover. + +*tooltip-format*: ++ + typeof: string ++ + The tooltip format. If specified, overrides any tooltip output from the script in *exec*. ++ + Uses the same format replacements as *format*. + +*escape*: ++ + typeof: bool ++ + default: false ++ + Option to enable escaping of script output. + +*menu*: ++ + typeof: string ++ + Action that popups the menu. + +*menu-file*: ++ + typeof: string ++ + Location of the menu descriptor file. There need to be an element of type + GtkMenu with id *menu* + +*menu-actions*: ++ + typeof: array ++ + The actions corresponding to the buttons of the menu. + +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + +# RETURN-TYPE + +When *return-type* is set to *json*, Waybar expects the *exec*-script to output its data in JSON format. +This should look like this: + +``` +{"text": "$text", "tooltip": "$tooltip", "class": "$class", "percentage": $percentage } +``` + +The *class* parameter also accepts an array of strings. + +If nothing or an invalid option is specified, Waybar expects i3blocks style output. Values are *newline* separated. +This should look like this: + +``` +$text\\n$tooltip\\n$class* +``` + +*class* is a CSS class, to apply different styles in *style.css* + +# FORMAT REPLACEMENTS + +*{text}*: Output of the script. + +*{percentage}* Percentage which can be set via a json return type. + +*{icon}*: An icon from 'format-icons' according to percentage. + +# EXAMPLES + +## Memory: + +``` +"custom-graph/memory": { + "interval": 60, + "graph_type": "gauge", + "width": 52, + "exec": "/path/mem.sh", + "signal": 8, + "return-type": "json" +}, +``` + +mem.sh: + +``` +#!/bin/bash + +mem_info=$(cat /proc/meminfo) +mem_total=$(echo "$mem_info" | grep '^MemTotal:' | awk '{print $2}') +mem_available=$(echo "$mem_info" | grep '^MemAvailable:' | awk '{print $2}') + +mem_used=$((mem_total - mem_available)) +mem_percent=$((mem_used * 100 / mem_total)) + +echo "{\"text\": \"${mem_percent}%\", \"percentage\": ${mem_percent},\"tooltip\": \"Memory: ${mem_used}KB used / ${mem_total}KB total\"}'" +``` + +# STYLE + +- *#custom-graph-* +- *#custom-graph-.* +- ** can be set by the script. For more information see *return-type* From 283515901e1b48a11d1e6af58a1776ea6e95fb64 Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Sat, 8 Nov 2025 01:46:31 +0100 Subject: [PATCH 658/780] fix: id read and name set --- src/factory.cpp | 4 ++-- src/modules/custom_graph.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/factory.cpp b/src/factory.cpp index dfb5caf4..b0ac2e8c 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -363,8 +363,8 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) { return new waybar::modules::Custom(ref.substr(7), id, config_[name], bar_.output->name); } - if (ref.compare(0, 13, "custom-graph/") == 0 && ref.size() > 7) { - return new waybar::modules::CustomGraph(ref.substr(7), id, config_[name], bar_.output->name); + if (ref.compare(0, 13, "custom-graph/") == 0 && ref.size() > 13) { + return new waybar::modules::CustomGraph(ref.substr(13), id, config_[name], bar_.output->name); } if (ref.compare(0, 5, "cffi/") == 0 && ref.size() > 5) { return new waybar::modules::CFFI(ref.substr(5), id, config_[name]); diff --git a/src/modules/custom_graph.cpp b/src/modules/custom_graph.cpp index 288d97f2..f8660c5f 100644 --- a/src/modules/custom_graph.cpp +++ b/src/modules/custom_graph.cpp @@ -6,7 +6,7 @@ waybar::modules::CustomGraph::CustomGraph(const std::string& name, const std::string& id, const Json::Value& config, const std::string& output_name) - : AGraph(config, "custom-graph" + name, id), + : AGraph(config, "custom-graph-" + name, id), name_(name), output_name_(output_name), id_(id), From dbf1cfb0f11b687cbaafa65f73acaf5ce2eeb282 Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Mon, 23 Feb 2026 16:20:58 +0100 Subject: [PATCH 659/780] fix: typo reading datapoints --- src/AGraph.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AGraph.cpp b/src/AGraph.cpp index 1899b1b2..139bddb2 100644 --- a/src/AGraph.cpp +++ b/src/AGraph.cpp @@ -36,7 +36,7 @@ AGraph::AGraph(const Json::Value& config, const std::string& name, const std::st event_box_.add(graph_); if (config_["datapoints"].isUInt()) { - datapoints_ = config_["datapoints_"].asUInt(); + datapoints_ = config_["datapoints"].asUInt(); } if (config_["graph_type"].isString()) { @@ -110,7 +110,7 @@ void AGraph::handleGtkMenuEvent(GtkMenuItem* /*menuitem*/, gpointer data) { } void AGraph::addValue(const int n) { - if (values_.size() >= datapoints_) { + if (datapoints_ > 0 && values_.size() >= datapoints_) { values_.pop_front(); } values_.push_back(n); From 132ca3ac456c374afba1a2d913d6bcf5aae2d418 Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Fri, 6 Mar 2026 23:48:12 +0100 Subject: [PATCH 660/780] fix: datapoints should be positive --- src/AGraph.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AGraph.cpp b/src/AGraph.cpp index 139bddb2..bd64410f 100644 --- a/src/AGraph.cpp +++ b/src/AGraph.cpp @@ -35,7 +35,7 @@ AGraph::AGraph(const Json::Value& config, const std::string& name, const std::st event_box_.add(graph_); - if (config_["datapoints"].isUInt()) { + if (config_["datapoints"].isUInt() && config_["datapoints"].asUInt() > 0) { datapoints_ = config_["datapoints"].asUInt(); } From 7d965a874f736854516f18320787ede9c6c47ef4 Mon Sep 17 00:00:00 2001 From: Ricardo Markiewicz Date: Fri, 6 Mar 2026 23:57:22 +0100 Subject: [PATCH 661/780] pr fixes --- src/AGraph.cpp | 31 ++++++++++--------------------- src/modules/custom_graph.cpp | 1 - 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/AGraph.cpp b/src/AGraph.cpp index bd64410f..f79e7218 100644 --- a/src/AGraph.cpp +++ b/src/AGraph.cpp @@ -209,40 +209,33 @@ void AGraph::drawPath(const Cairo::RefPtr& cr, } } -void AGraph::drawBars(const Cairo::RefPtr& cr, - double width, double height, int current_value, - const Gdk::RGBA& fg_color) { - +void AGraph::drawBars(const Cairo::RefPtr& cr, double width, double height, + int current_value, const Gdk::RGBA& fg_color) { current_value = std::min(100, std::max(0, current_value)); double green_height = height * (std::min(current_value, 40) / 100.0); - cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), - 0.5); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 0.5); cr->rectangle(0, height - green_height, width, green_height); cr->fill(); if (current_value > 40) { double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; - cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), - 0.7); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 0.7); cr->rectangle(0, height - green_height - yellow_height, width, yellow_height); cr->fill(); } if (current_value > 75) { double orange_height = height * (std::min(current_value, 85) - 75) / 100.0; - cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), - 0.85); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 0.85); double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; - cr->rectangle(0, height - green_height - yellow_height - orange_height, width, - orange_height); + cr->rectangle(0, height - green_height - yellow_height - orange_height, width, orange_height); cr->fill(); } if (current_value > 85) { double red_height = height * (current_value - 85) / 100.0; - cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), - 1.0); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 1.0); double yellow_height = height * (std::min(current_value, 75) - 40) / 100.0; double orange_height = height * (std::min(current_value, 85) - 75) / 100.0; cr->rectangle(0, height - green_height - yellow_height - orange_height - red_height, width, @@ -268,25 +261,21 @@ void AGraph::drawGauge(const Cairo::RefPtr& cr, double width, do double angle2 = angle1 + 0.3 * angle1; // Green section (0-33%) - cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), - 0.5); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 0.5); cr->arc(center_x, center_y, radius, angle1, angle2); cr->stroke(); // Yellow section (33-66%) angle1 = angle2; angle2 = angle1 + 0.3 * angle1; - cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), - 0.75); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 0.75); cr->arc(center_x, center_y, radius, angle1, angle2); cr->stroke(); // Red section (66-100%) angle1 = angle2; angle2 = 0.0; - cr->set_source_rgba(1.0, 0.0, 0.0, 0.8); - cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), - 1.0); + cr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), 1.0); cr->arc(center_x, center_y, radius, angle1, angle2); cr->stroke(); diff --git a/src/modules/custom_graph.cpp b/src/modules/custom_graph.cpp index f8660c5f..c23ed0d6 100644 --- a/src/modules/custom_graph.cpp +++ b/src/modules/custom_graph.cpp @@ -228,7 +228,6 @@ void waybar::modules::CustomGraph::parseOutputRaw() { text_ = validated_line; tooltip_ = validated_line; } - tooltip_ = validated_line; class_.clear(); } else if (i == 1) { if (config_["escape"].isBool() && config_["escape"].asBool()) { From 503d99b1288bd848758369cd51344add6092af10 Mon Sep 17 00:00:00 2001 From: xiyori Date: Thu, 9 Apr 2026 14:15:56 +0300 Subject: [PATCH 662/780] Add max-windows option to hyprland/workspaces --- include/modules/hyprland/workspaces.hpp | 3 +++ man/waybar-hyprland-workspaces.5.scd | 5 +++++ src/modules/hyprland/workspace.cpp | 15 ++++++++++----- src/modules/hyprland/workspaces.cpp | 10 ++++++++++ 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 03548ccb..94c2ee8a 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -56,6 +56,7 @@ class Workspaces : public AModule, public EventHandler { auto taskbarReverseDirection() const -> bool { return m_taskbarReverseDirection; } auto onClickWindow() const -> std::string { return m_onClickWindow; } auto getIgnoredWindows() const -> std::vector { return m_ignoreWindows; } + auto maxWindows() const -> int { return m_maxWindows; } enum class ActiveWindowPosition { NONE, FIRST, LAST }; auto activeWindowPosition() const -> ActiveWindowPosition { return m_activeWindowPosition; } @@ -89,6 +90,7 @@ class Workspaces : public AModule, public EventHandler { auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void; auto populateFormatWindowSeparatorConfig(const Json::Value& config) -> void; auto populateWindowRewriteConfig(const Json::Value& config) -> void; + auto populateMaxWindowsConfig(const Json::Value& config) -> void; auto populateWorkspaceTaskbarConfig(const Json::Value& config) -> void; void registerIpc(); @@ -202,6 +204,7 @@ class Workspaces : public AModule, public EventHandler { }; std::string m_onClickWindow; std::string m_currentActiveWindowAddress; + int m_maxWindows = 0; std::vector m_ignoreWorkspaces; std::vector m_ignoreWindows; diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index 5284ce99..d163e688 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -98,6 +98,11 @@ This setting is ignored if *workspace-taskbar.enable* is set to true. - {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. +*max-windows*: ++ + typeof: int ++ + default: 0 (unlimited) ++ + Maximum number of windows to show per workspace. When set, newest windows beyond the limit are not shown. Set to 0 for unlimited windows. + *show-special*: ++ typeof: bool ++ default: false ++ diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 21e7ef9b..5bd53d72 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -247,13 +247,14 @@ void Workspace::update(const std::string& workspace_icon) { auto windowSeparator = m_workspaceManager.getWindowSeparator(); bool isNotFirst = false; + auto end_it = m_workspaceManager.maxWindows() == 0 ? m_windowMap.end() : m_windowMap.begin() + m_workspaceManager.maxWindows(); - for (const auto& window_repr : m_windowMap) { + for (auto it = m_windowMap.begin(); it != end_it; ++it) { if (isNotFirst) { windows.append(windowSeparator); } isNotFirst = true; - windows.append(window_repr.repr_rewrite); + windows.append(it->repr_rewrite); } } @@ -339,12 +340,16 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) { }; if (m_workspaceManager.taskbarReverseDirection()) { - for (auto it = m_windowMap.rbegin(); it != m_windowMap.rend(); ++it) { + auto rend_it = m_workspaceManager.maxWindows() == 0 ? m_windowMap.rend() : m_windowMap.rbegin() + m_workspaceManager.maxWindows(); + + for (auto it = m_windowMap.rbegin(); it != rend_it; ++it) { processWindow(*it); } } else { - for (const auto& window_repr : m_windowMap) { - processWindow(window_repr); + auto end_it = m_workspaceManager.maxWindows() == 0 ? m_windowMap.end() : m_windowMap.begin() + m_workspaceManager.maxWindows(); + + for (auto it = m_windowMap.begin(); it != end_it; ++it) { + processWindow(*it); } } diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index f794249b..94d401de 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -665,6 +665,7 @@ auto Workspaces::parseConfig(const Json::Value& config) -> void { populateIgnoreWorkspacesConfig(config); populateFormatWindowSeparatorConfig(config); populateWindowRewriteConfig(config); + populateMaxWindowsConfig(config); if (withWindows) { populateWorkspaceTaskbarConfig(config); @@ -746,6 +747,15 @@ auto Workspaces::populateWindowRewriteConfig(const Json::Value& config) -> void [this](std::string& window_rule) { return windowRewritePriorityFunction(window_rule); }); } +auto Workspaces::populateMaxWindowsConfig(const Json::Value& config) -> void { + if (config["max-windows"].isInt()) { + m_maxWindows = config["max-windows"].asInt(); + if (m_maxWindows < 0) { + m_maxWindows = 0; + } + } +} + auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value& config) -> void { const auto& workspaceTaskbar = config["workspace-taskbar"]; if (!workspaceTaskbar.isObject()) { From 906a589715dc69876da77ce5e20c15ff6b985abf Mon Sep 17 00:00:00 2001 From: yubo <3597387401@qq.com> Date: Thu, 9 Apr 2026 21:18:15 +0800 Subject: [PATCH 663/780] fix the io failure for hotplug-in device --- src/modules/battery.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index d39c4920..7984c24d 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -126,7 +126,14 @@ void waybar::modules::Battery::refreshBatteries() { // Ignore non-system power supplies unless explicitly requested if (!bat_defined && fs::exists(node.path() / "scope")) { std::string scope; - std::ifstream(node.path() / "scope") >> scope; + try{ + //for hotplug-in device, access it is always unstable because you may remove the device anytime + //so just allow failure happen and do nothing + std::ifstream(node.path()/"scope")>>scope; + }catch(const std::ifstream::failure& e){ + scope.clear(); + continue; + } if (g_ascii_strcasecmp(scope.data(), "device") == 0) { continue; } From a8a46d1f1e1834e652bdeff9640e98b5ab36eb25 Mon Sep 17 00:00:00 2001 From: Visal Vijay Date: Thu, 9 Apr 2026 19:40:05 +0530 Subject: [PATCH 664/780] Refactor: simplify complex conditional expressions in Hyprland workspace module --- src/modules/hyprland/workspace.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 21e7ef9b..da383aa5 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -115,7 +115,11 @@ void Workspace::setActiveWindow(WindowAddress const& addr) { } auto activeWindowPos = m_workspaceManager.activeWindowPosition(); - if (activeIdx.has_value() && activeWindowPos != Workspaces::ActiveWindowPosition::NONE) { + const bool has_active_window = + activeIdx.has_value() && + activeWindowPos != Workspaces::ActiveWindowPosition::NONE; + + if (has_active_window) { auto window = std::move(m_windowMap[*activeIdx]); m_windowMap.erase(m_windowMap.begin() + *activeIdx); if (activeWindowPos == Workspaces::ActiveWindowPosition::FIRST) { @@ -130,7 +134,10 @@ void Workspace::insertWindow(WindowCreationPayload create_window_payload) { if (!create_window_payload.isEmpty(m_workspaceManager)) { auto repr = create_window_payload.repr(m_workspaceManager); - if (!repr.empty() || m_workspaceManager.enableTaskbar()) { + const bool should_display = + !repr.empty() || m_workspaceManager.enableTaskbar(); + + if (should_display) { auto addr = create_window_payload.getAddress(); auto it = std::ranges::find_if( m_windowMap, [&addr](const auto& window) { return window.address == addr; }); @@ -142,7 +149,7 @@ void Workspace::insertWindow(WindowCreationPayload create_window_payload) { } } } -}; +} bool Workspace::onWindowOpened(WindowCreationPayload const& create_window_payload) { if (create_window_payload.getWorkspaceName() == name()) { @@ -270,7 +277,9 @@ void Workspace::update(const std::string& workspace_icon) { bool Workspace::isEmpty() const { auto ignore_list = m_workspaceManager.getIgnoredWindows(); - if (ignore_list.empty()) { + const bool no_ignore_rules = ignore_list.empty(); + + if (no_ignore_rules) { return m_windows == 0; } // If there are windows but they are all ignored, consider the workspace empty @@ -349,7 +358,9 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) { } auto formatAfter = m_workspaceManager.formatAfter(); - if (!formatAfter.empty()) { + const bool has_format_after = !formatAfter.empty(); + + if (has_format_after) { m_labelAfter.set_markup(fmt::format(fmt::runtime(formatAfter), fmt::arg("id", id()), fmt::arg("name", name()), fmt::arg("icon", workspace_icon))); From efd245b552ee279c5eb88cff3bc64c486fd88a60 Mon Sep 17 00:00:00 2001 From: Mervius Date: Fri, 10 Apr 2026 11:42:23 -0500 Subject: [PATCH 665/780] Fix timeout with multiple monitors --- include/client.hpp | 3 +++ src/client.cpp | 31 ++++++++++++++++++++++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/include/client.hpp b/include/client.hpp index c3df3f21..8598e356 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -44,6 +44,7 @@ class Client { const char* interface, uint32_t version); static void handleGlobalRemove(void* data, struct wl_registry* registry, uint32_t name); static void handleOutputDone(void*, struct zxdg_output_v1*); + void createBarsBatch(); static void handleOutputName(void*, struct zxdg_output_v1*, const char*); static void handleOutputDescription(void*, struct zxdg_output_v1*, const char*); void handleMonitorAdded(Glib::RefPtr monitor); @@ -58,6 +59,8 @@ class Client { std::string m_cssFile; sigc::connection monitor_added_connection_; sigc::connection monitor_removed_connection_; + std::vector pending_outputs_; + bool bars_scheduled_ = false; }; } // namespace waybar diff --git a/src/client.cpp b/src/client.cpp index d1489186..ff354e18 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -91,11 +91,14 @@ void waybar::Client::handleOutputDone(void* data, struct zxdg_output_v1* /*xdg_o output.xdg_output.reset(); spdlog::debug("Output detection done: {} ({})", output.name, output.identifier); - auto configs = client->getOutputConfigs(output); - if (!configs.empty()) { - for (const auto& config : configs) { - client->bars.emplace_back(std::make_unique(&output, config)); - } + client->pending_outputs_.push_back(&output); + + if (!client->bars_scheduled_) { + client->bars_scheduled_ = true; + + Glib::signal_idle().connect_once([client]() { + client->createBarsBatch(); + }, Glib::PRIORITY_HIGH_IDLE); } } } catch (const std::exception& e) { @@ -103,6 +106,24 @@ void waybar::Client::handleOutputDone(void* data, struct zxdg_output_v1* /*xdg_o } } +void waybar::Client::createBarsBatch() { + for (auto* output : pending_outputs_) { + try { + auto configs = getOutputConfigs(*output); + if (!configs.empty()) { + for (const auto& config : configs) { + bars.emplace_back(std::make_unique(output, config)); + } + } + } catch (const std::exception& e) { + spdlog::warn("Error creating bar: {}", e.what()); + } + } + + pending_outputs_.clear(); + bars_scheduled_ = false; +} + void waybar::Client::handleOutputName(void* data, struct zxdg_output_v1* /*xdg_output*/, const char* name) { auto* client = waybar::Client::inst(); From ac62754b289569b71da33f6982c63eb927796182 Mon Sep 17 00:00:00 2001 From: yubo <3597387401@qq.com> Date: Thu, 9 Apr 2026 21:18:15 +0800 Subject: [PATCH 666/780] fix the lint problem --- src/modules/battery.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 7984c24d..30be3857 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -126,11 +126,11 @@ void waybar::modules::Battery::refreshBatteries() { // Ignore non-system power supplies unless explicitly requested if (!bat_defined && fs::exists(node.path() / "scope")) { std::string scope; - try{ - //for hotplug-in device, access it is always unstable because you may remove the device anytime - //so just allow failure happen and do nothing - std::ifstream(node.path()/"scope")>>scope; - }catch(const std::ifstream::failure& e){ + try { + // for hotplug-in device, access it is always unstable because you may remove the + // device anytime so just allow failure happen and do nothing + std::ifstream(node.path() / "scope") >> scope; + } catch (const std::ifstream::failure& e) { scope.clear(); continue; } From 450b6a785f4436862c49f0dca48a559153f143ae Mon Sep 17 00:00:00 2001 From: Arnaud Gaudard <24233760+maltherd@users.noreply.github.com> Date: Sun, 12 Apr 2026 17:22:19 +0200 Subject: [PATCH 667/780] Add "ignore-workspaces" option to sway/workspaces module --- include/modules/sway/workspaces.hpp | 3 +++ src/modules/sway/workspaces.cpp | 36 +++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/include/modules/sway/workspaces.hpp b/include/modules/sway/workspaces.hpp index 14528841..5270faee 100644 --- a/include/modules/sway/workspaces.hpp +++ b/include/modules/sway/workspaces.hpp @@ -30,6 +30,8 @@ class Workspaces : public AModule, public sigc::trackable { static int convertWorkspaceNameToNum(const std::string& name); static int windowRewritePriorityFunction(std::string const& window_rule); + auto populateIgnoreWorkspacesConfig(const Json::Value& config) -> void; + bool isWorkspaceIgnored(std::string const& name); void onCmd(const struct Ipc::ipc_response&); void onEvent(const struct Ipc::ipc_response&); bool filterButtons(); @@ -49,6 +51,7 @@ class Workspaces : public AModule, public sigc::trackable { std::vector workspaces_order_; Gtk::Box box_; std::string m_formatWindowSeparator; + std::vector m_ignoreWorkspaces; util::RegexCollection m_windowRewriteRules; util::JsonParser parser_; std::unordered_map buttons_; diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 6abe5383..59b7be69 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -69,6 +69,7 @@ Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& m_windowRewriteRules = waybar::util::RegexCollection( windowRewrite, std::move(windowRewriteDefault), windowRewritePriorityFunction); } + populateIgnoreWorkspacesConfig(config); ipc_.subscribe(R"(["workspace"])"); ipc_.subscribe(R"(["window"])"); ipc_.signal_event.connect(sigc::mem_fun(*this, &Workspaces::onEvent)); @@ -97,6 +98,36 @@ void Workspaces::onEvent(const struct Ipc::ipc_response& res) { } } +auto Workspaces::populateIgnoreWorkspacesConfig(const Json::Value& config) -> void { + auto ignoreWorkspaces = config["ignore-workspaces"]; + if (ignoreWorkspaces.isArray()) { + for (const auto& workspaceRegex : ignoreWorkspaces) { + if (workspaceRegex.isString()) { + std::string ruleString = workspaceRegex.asString(); + try { + const std::regex rule{ruleString, std::regex_constants::icase}; + m_ignoreWorkspaces.emplace_back(rule); + } catch (const std::regex_error& e) { + spdlog::error("Invalid rule {}: {}", ruleString, e.what()); + } + } else { + spdlog::error("Not a string: '{}'", workspaceRegex); + } + } + } +} + +bool Workspaces::isWorkspaceIgnored(std::string const& name) { + for (auto& rule : m_ignoreWorkspaces) { + if (std::regex_match(name, rule)) { + return true; + break; + } + } + + return false; +} + void Workspaces::onCmd(const struct Ipc::ipc_response& res) { if (res.type == IPC_GET_TREE) { try { @@ -118,8 +149,9 @@ void Workspaces::onCmd(const struct Ipc::ipc_response& res) { }); for (auto& output : outputs) { - std::copy(output["nodes"].begin(), output["nodes"].end(), - std::back_inserter(workspaces_)); + std::copy_if( + output["nodes"].begin(), output["nodes"].end(), std::back_inserter(workspaces_), + [&](const auto& node) { return !(isWorkspaceIgnored(node["name"].asString())); }); std::copy(output["floating_nodes"].begin(), output["floating_nodes"].end(), std::back_inserter(workspaces_)); } From 1ab1d41e4dd5e2c2aced0354b5c19c4348fbe2e4 Mon Sep 17 00:00:00 2001 From: lilly-lizard Date: Tue, 14 Apr 2026 08:25:04 +1200 Subject: [PATCH 668/780] removed residual debug logging --- src/modules/river/tags.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index 5712e9a0..a9f14faf 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -201,8 +201,6 @@ Tags::~Tags() { } void Tags::handle_show() { - spdlog::info("bruh1"); - 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); @@ -211,8 +209,6 @@ void Tags::handle_show() { zriver_status_manager_v1_destroy(status_manager_); status_manager_ = nullptr; - - spdlog::info("bruh2"); } void Tags::handle_primary_clicked(uint32_t tag) { From 0ee4fd4aa25948483e1704d5c4a35bd8d9a32292 Mon Sep 17 00:00:00 2001 From: lilly-lizard Date: Tue, 14 Apr 2026 13:47:53 +1200 Subject: [PATCH 669/780] added output class to dwl/mangowm allowing tags to display differently when the output is focused (for multi-monitor setups) --- include/modules/dwl/tags.hpp | 2 ++ man/waybar-dwl-tags.5.scd | 3 ++- src/modules/dwl/tags.cpp | 16 ++++++++++++++-- src/modules/river/tags.cpp | 5 ++--- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/include/modules/dwl/tags.hpp b/include/modules/dwl/tags.hpp index 856a35dd..45a6f01f 100644 --- a/include/modules/dwl/tags.hpp +++ b/include/modules/dwl/tags.hpp @@ -21,6 +21,8 @@ class Tags : public waybar::AModule { void handle_primary_clicked(uint32_t tag); bool handle_button_press(GdkEventButton* event_button, uint32_t tag); + void handle_active_output(zdwl_ipc_output_v2* zdwl_output_v2, uint32_t active); + struct zdwl_ipc_manager_v2* status_manager_; struct wl_seat* seat_; diff --git a/man/waybar-dwl-tags.5.scd b/man/waybar-dwl-tags.5.scd index 39d15b7a..f775b742 100644 --- a/man/waybar-dwl-tags.5.scd +++ b/man/waybar-dwl-tags.5.scd @@ -46,8 +46,9 @@ Addressed by *dwl/tags* - *#tags button.empty* - *#tags button.focused* - *#tags button.urgent* +- *#tags button.output* -Note that occupied/focused/urgent status may overlap. That is, a tag may be +Note that occupied/focused/urgent/output status may overlap. That is, a tag may be both occupied and focused at the same time. # SEE ALSO diff --git a/src/modules/dwl/tags.cpp b/src/modules/dwl/tags.cpp index 243aaa92..52554707 100644 --- a/src/modules/dwl/tags.cpp +++ b/src/modules/dwl/tags.cpp @@ -26,7 +26,7 @@ static void toggle_visibility(void* data, zdwl_ipc_output_v2* zdwl_output_v2) { } static void active(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t active) { - // Intentionally empty + static_cast(data)->handle_active_output(zdwl_output_v2, active); } static void set_tag(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t tag, uint32_t state, @@ -163,7 +163,7 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con i <<= 1; } - struct wl_output* output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); output_status_ = zdwl_ipc_manager_v2_get_output(status_manager_, output); zdwl_ipc_output_v2_add_listener(output_status_, &output_status_listener_impl, this); @@ -223,4 +223,16 @@ void Tags::handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint } } +void Tags::handle_active_output(zdwl_ipc_output_v2* zdwl_output_v2, uint32_t active) { + if (output_status_ == zdwl_output_v2) { + for (size_t i = 0; i < buttons_.size(); ++i) { + if (active == 0) { + buttons_[i].get_style_context()->remove_class("output"); + } else { + buttons_[i].get_style_context()->add_class("output"); + } + } + } +} + } /* namespace waybar::modules::dwl */ diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index f8faf60c..a864daea 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -114,8 +114,8 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con control_{nullptr}, seat_{nullptr}, bar_(bar), - box_{bar.orientation, 0}, output_{nullptr}, + box_{bar.orientation, 0}, output_status_{nullptr}, seat_status_{nullptr} { struct wl_display* display = Client::inst()->wl_display; @@ -204,8 +204,7 @@ Tags::~Tags() { void Tags::handle_show() { if (!status_manager_) return; - 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); + 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); seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_); From 8b5b1eebbfa40325b77629b03a62f82d4a048158 Mon Sep 17 00:00:00 2001 From: Mervius Date: Wed, 15 Apr 2026 10:35:26 -0500 Subject: [PATCH 670/780] Check if output still is in outputs before doing anything with it --- src/client.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/client.cpp b/src/client.cpp index ff354e18..99a74123 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -95,7 +95,7 @@ void waybar::Client::handleOutputDone(void* data, struct zxdg_output_v1* /*xdg_o if (!client->bars_scheduled_) { client->bars_scheduled_ = true; - + Glib::signal_idle().connect_once([client]() { client->createBarsBatch(); }, Glib::PRIORITY_HIGH_IDLE); @@ -107,6 +107,8 @@ void waybar::Client::handleOutputDone(void* data, struct zxdg_output_v1* /*xdg_o } void waybar::Client::createBarsBatch() { + pending_outputs_.remove_if([this](auto* output) { return std::none_of(outputs_.begin(), outputs_.end(), + [output](const auto& o) { return &o == output; }); }); for (auto* output : pending_outputs_) { try { auto configs = getOutputConfigs(*output); From 09b6df37caa2d05bfc783e5729120ecc81f74576 Mon Sep 17 00:00:00 2001 From: Mervius Date: Wed, 15 Apr 2026 10:39:21 -0500 Subject: [PATCH 671/780] Check if output still is in outputs before doing anything with it --- include/client.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/client.hpp b/include/client.hpp index 8598e356..10a634fc 100644 --- a/include/client.hpp +++ b/include/client.hpp @@ -59,7 +59,7 @@ class Client { std::string m_cssFile; sigc::connection monitor_added_connection_; sigc::connection monitor_removed_connection_; - std::vector pending_outputs_; + std::list pending_outputs_; bool bars_scheduled_ = false; }; From d9b5d24284f2e91f53f496ad15d31843bb879497 Mon Sep 17 00:00:00 2001 From: Mervius Date: Wed, 15 Apr 2026 10:55:49 -0500 Subject: [PATCH 672/780] Check if output still is in outputs before doing anything with it --- src/client.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index 99a74123..4d8bcb65 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -107,8 +107,7 @@ void waybar::Client::handleOutputDone(void* data, struct zxdg_output_v1* /*xdg_o } void waybar::Client::createBarsBatch() { - pending_outputs_.remove_if([this](auto* output) { return std::none_of(outputs_.begin(), outputs_.end(), - [output](const auto& o) { return &o == output; }); }); + pending_outputs_.remove_if([this](auto* output) { return std::none_of(outputs_.begin(), outputs_.end(), [output](const auto& o) { return &o == output; }); }); for (auto* output : pending_outputs_) { try { auto configs = getOutputConfigs(*output); From e2d608e7fdc4e69aff491a6561ef43212fd4a5fa Mon Sep 17 00:00:00 2001 From: Mervius Date: Wed, 15 Apr 2026 10:59:03 -0500 Subject: [PATCH 673/780] Check if output still is in outputs before doing anything with it --- src/client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.cpp b/src/client.cpp index 4d8bcb65..94fcfe41 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -107,7 +107,7 @@ void waybar::Client::handleOutputDone(void* data, struct zxdg_output_v1* /*xdg_o } void waybar::Client::createBarsBatch() { - pending_outputs_.remove_if([this](auto* output) { return std::none_of(outputs_.begin(), outputs_.end(), [output](const auto& o) { return &o == output; }); }); + pending_outputs_.remove_if([this](auto* output) { return std::none_of(outputs_.begin(), outputs_.end(), [&output](const auto& o) { return &o == output; }); }); for (auto* output : pending_outputs_) { try { auto configs = getOutputConfigs(*output); From 7e9c46e4d15563ba6b4dc6e470b7a4b445c26c63 Mon Sep 17 00:00:00 2001 From: B2krobbery <150381094+B2krobbery@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:34:39 +0530 Subject: [PATCH 674/780] fix(sni): use std::make_unique for Item allocation --- src/modules/sni/host.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/modules/sni/host.cpp b/src/modules/sni/host.cpp index 18eac643..567fbf9f 100644 --- a/src/modules/sni/host.cpp +++ b/src/modules/sni/host.cpp @@ -178,9 +178,11 @@ void Host::addRegisteredItem(const std::string& service) { return bus_name == item->bus_name && object_path == item->object_path; }); if (it == items_.end()) { - items_.emplace_back(new Item( - bus_name, object_path, config_, bar_, [this](Item& item) { itemReady(item); }, - [this](Item& item) { itemInvalidated(item); }, on_update_)); + items_.emplace_back(std::make_unique( + bus_name, object_path, config_, bar_, + [this](Item& item) { itemReady(item); }, + [this](Item& item) { itemInvalidated(item); }, + on_update_)); } } From 0241d0eac6febbfbcffd498535cad680263c0391 Mon Sep 17 00:00:00 2001 From: Volker Schlecht <47375452+VlkrS@users.noreply.github.com> Date: Fri, 17 Apr 2026 19:27:39 +0200 Subject: [PATCH 675/780] Remove redefinition or SIGRTMIN/SIGRTMAX for OpenBSD --- include/util/SafeSignal.hpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/util/SafeSignal.hpp b/include/util/SafeSignal.hpp index afb0893e..b1e3a91a 100644 --- a/include/util/SafeSignal.hpp +++ b/include/util/SafeSignal.hpp @@ -12,11 +12,6 @@ #include #include -#ifdef __OpenBSD__ -#define SIGRTMIN SIGUSR1 - 1 -#define SIGRTMAX SIGUSR1 + 1 -#endif - namespace waybar { /** From bd39795f806048a2d480c0aa5522fe3cde4047b1 Mon Sep 17 00:00:00 2001 From: Volker Schlecht <47375452+VlkrS@users.noreply.github.com> Date: Fri, 17 Apr 2026 19:31:59 +0200 Subject: [PATCH 676/780] Fix build for systems without SIGRTMIN/SIGRTMAX --- src/main.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 9d7d0ba2..0866a9b7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -55,9 +55,11 @@ static void catchSignals(waybar::SafeSignal& signal_handler) { std::signal(SIGINT, writeSignalToPipe); std::signal(SIGCHLD, writeSignalToPipe); +#ifdef SIGRTMIN for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) { std::signal(sig, writeSignalToPipe); } +#endif while (true) { int signum; @@ -119,13 +121,15 @@ void handleUserSignal(int signal, bool& reload) { // If this signal should restart or close the bar, this function will write // `true` or `false`, respectively, into `reload`. static void handleSignalMainThread(int signum, bool& reload) { +#ifdef SIGRTMIN if (signum >= SIGRTMIN + 1 && signum <= SIGRTMAX) { for (auto& bar : waybar::Client::inst()->bars) { bar->handleSignal(signum); } return; } - +#endif + switch (signum) { case SIGUSR1: handleUserSignal(SIGUSR1, reload); From 729dbbb82e81b5feb095c1312ccd04a5e3795289 Mon Sep 17 00:00:00 2001 From: Volker Schlecht <47375452+VlkrS@users.noreply.github.com> Date: Fri, 17 Apr 2026 19:33:27 +0200 Subject: [PATCH 677/780] Fix build for systems without SIGRTMIN/SIGRTMAX --- src/modules/image.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/image.cpp b/src/modules/image.cpp index 98bf3c46..48729cc4 100644 --- a/src/modules/image.cpp +++ b/src/modules/image.cpp @@ -46,9 +46,11 @@ void waybar::modules::Image::delayWorker() { } void waybar::modules::Image::refresh(int sig) { +#ifdef SIGRTMIN if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) { thread_.wake_up(); } +#endif } auto waybar::modules::Image::update() -> void { From 29979e9227e06db41bde47e89a088ecfc169c756 Mon Sep 17 00:00:00 2001 From: Volker Schlecht <47375452+VlkrS@users.noreply.github.com> Date: Fri, 17 Apr 2026 19:35:31 +0200 Subject: [PATCH 678/780] Fix build for systems without SIGRTMIN/SIGRTMAX --- src/modules/custom.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 28def8c9..99d072de 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -138,9 +138,11 @@ void waybar::modules::Custom::waitingWorker() { } void waybar::modules::Custom::refresh(int sig) { +#ifdef SIGRTMIN if (config_["signal"].isInt() && sig == SIGRTMIN + config_["signal"].asInt()) { thread_.wake_up(); } +#endif } void waybar::modules::Custom::handleEvent() { From ab7bc8de1c7319fa7398fcc448616a2bee075034 Mon Sep 17 00:00:00 2001 From: cebem1nt Date: Sat, 18 Apr 2026 13:34:29 -0300 Subject: [PATCH 679/780] feature: group - add reveal-delay config option for drawer --- include/group.hpp | 3 +++ src/group.cpp | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/include/group.hpp b/include/group.hpp index d5cbb289..c5faf7c3 100644 --- a/include/group.hpp +++ b/include/group.hpp @@ -10,6 +10,8 @@ namespace waybar { class Group : public AModule { + sigc::connection reveal_timeout_; + public: Group(const std::string&, const std::string&, const Json::Value&, bool); ~Group() override = default; @@ -26,6 +28,7 @@ class Group : public AModule { bool is_first_widget = true; bool is_drawer = false; bool click_to_reveal = false; + int reveal_delay = 0; std::string add_class_to_drawer_children; bool handleMouseEnter(GdkEventCrossing* const& ev) override; bool handleMouseLeave(GdkEventCrossing* const& ev) override; diff --git a/src/group.cpp b/src/group.cpp index acd7bd42..92734fc9 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -63,6 +63,7 @@ Group::Group(const std::string& name, const std::string& id, const Json::Value& ? drawer_config["transition-left-to-right"].asBool() : true); click_to_reveal = drawer_config["click-to-reveal"].asBool(); + reveal_delay = drawer_config["reveal-delay"].asInt(); const bool start_expanded = (drawer_config["start-expanded"].isBool() ? drawer_config["start-expanded"].asBool() @@ -104,13 +105,27 @@ void Group::hide_group() { bool Group::handleMouseEnter(GdkEventCrossing* const& e) { if (!click_to_reveal) { - show_group(); + if (reveal_delay > 0) { + if (reveal_timeout_.connected()) { + reveal_timeout_.disconnect(); + } + + reveal_timeout_ = Glib::signal_timeout().connect( + [this]() { show_group(); return false; }, + reveal_delay); + } else { + show_group(); + } } return false; } bool Group::handleMouseLeave(GdkEventCrossing* const& e) { if (!click_to_reveal && e->detail != GDK_NOTIFY_INFERIOR) { + if (reveal_delay > 0 && reveal_timeout_.connected()) { + reveal_timeout_.disconnect(); + } + hide_group(); } return false; From 7e0e99b5aa8423ada72a3018588104550d671315 Mon Sep 17 00:00:00 2001 From: GlassyBridge <188959481+GlassyBridge@users.noreply.github.com> Date: Sun, 19 Apr 2026 02:21:17 +0300 Subject: [PATCH 680/780] feat (hyprland/workspaces): add support for state specific icon overrides --- src/modules/hyprland/workspace.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 21e7ef9b..263e50e0 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -155,6 +155,11 @@ bool Workspace::onWindowOpened(WindowCreationPayload const& create_window_payloa std::string& Workspace::selectIcon(std::map& icons_map) { spdlog::trace("Selecting icon for workspace {}", name()); if (isUrgent()) { + auto urgentNamedIconIt = icons_map.find("urgent:" + name()); + if (urgentNamedIconIt != icons_map.end()) { + return urgentNamedIconIt->second; + } + auto urgentIconIt = icons_map.find("urgent"); if (urgentIconIt != icons_map.end()) { return urgentIconIt->second; @@ -162,6 +167,11 @@ std::string& Workspace::selectIcon(std::map& icons_map } if (isActive()) { + auto activeNamedIconIt = icons_map.find("active:" + name()); + if (activeNamedIconIt != icons_map.end()) { + return activeNamedIconIt->second; + } + auto activeIconIt = icons_map.find("active"); if (activeIconIt != icons_map.end()) { return activeIconIt->second; @@ -169,6 +179,11 @@ std::string& Workspace::selectIcon(std::map& icons_map } if (isSpecial()) { + auto specialNamedIconIt = icons_map.find("special:" + name()); + if (specialNamedIconIt != icons_map.end()) { + return specialNamedIconIt->second; + } + auto specialIconIt = icons_map.find("special"); if (specialIconIt != icons_map.end()) { return specialIconIt->second; From 59d09c2c1281cb678d98e925c94b987d31280d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Mon, 27 Apr 2026 11:29:03 +0200 Subject: [PATCH 681/780] fix various toctou bugs in battery.cpp --- src/modules/battery.cpp | 92 +++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 50 deletions(-) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 30be3857..25436baf 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -117,24 +117,16 @@ void waybar::modules::Battery::refreshBatteries() { if (((bat_defined && dir_name == config_["bat"].asString()) || !bat_defined) && (fs::exists(node.path() / "capacity") || fs::exists(node.path() / "charge_now")) && fs::exists(node.path() / "uevent") && - (fs::exists(node.path() / "status") || bat_compatibility) && - fs::exists(node.path() / "type")) { + (fs::exists(node.path() / "status") || bat_compatibility)) { std::string type; - std::ifstream(node.path() / "type") >> type; - - if (!type.compare("Battery")) { + if (std::ifstream{node.path() / "type"} >> type && !type.compare("Battery")) { // Ignore non-system power supplies unless explicitly requested - if (!bat_defined && fs::exists(node.path() / "scope")) { + if (!bat_defined) { std::string scope; - try { - // for hotplug-in device, access it is always unstable because you may remove the - // device anytime so just allow failure happen and do nothing - std::ifstream(node.path() / "scope") >> scope; - } catch (const std::ifstream::failure& e) { - scope.clear(); - continue; - } - if (g_ascii_strcasecmp(scope.data(), "device") == 0) { + // for hotplug-in device, access it is always unstable because you may remove the + // device anytime so just allow failure happen and do nothing + if (std::ifstream{node.path() / "scope"} >> scope && + g_ascii_strcasecmp(scope.data(), "device") == 0) { continue; } } @@ -282,11 +274,11 @@ waybar::modules::Battery::getInfos() { auto bat = item.first; std::string _status; - /* Check for adapter status if battery is not available */ - if (!std::ifstream(bat / "status")) { - std::getline(std::ifstream(adapter_ / "status"), _status); - } else { - std::getline(std::ifstream(bat / "status"), _status); + { + std::ifstream f{bat / "status"}; + if (!std::getline(f, _status)) { + std::getline(std::ifstream(adapter_ / "status"), _status); + } } // Some battery will report current and charge in μA/μAh. @@ -295,64 +287,64 @@ waybar::modules::Battery::getInfos() { uint32_t current_now = 0; int32_t _current_now_int = 0; bool current_now_exists = false; - if (fs::exists(bat / "current_now")) { + if (std::ifstream current_now_f{bat / "current_now"}) { current_now_exists = true; - std::ifstream(bat / "current_now") >> _current_now_int; - } else if (fs::exists(bat / "current_avg")) { + current_now_f >> _current_now_int; + } else if (std::ifstream current_avg_f{bat / "current_avg"}) { current_now_exists = true; - std::ifstream(bat / "current_avg") >> _current_now_int; + current_avg_f >> _current_now_int; } // Documentation ABI allows a negative value when discharging, positive // value when charging. current_now = std::abs(_current_now_int); - if (fs::exists(bat / "time_to_empty_now")) { + if (std::ifstream f{bat / "time_to_empty_now"}) { time_to_empty_now_exists = true; - std::ifstream(bat / "time_to_empty_now") >> time_to_empty_now; + f >> time_to_empty_now; } - if (fs::exists(bat / "time_to_full_now")) { + if (std::ifstream f{bat / "time_to_full_now"}) { time_to_full_now_exists = true; - std::ifstream(bat / "time_to_full_now") >> time_to_full_now; + f >> time_to_full_now; } uint32_t voltage_now = 0; bool voltage_now_exists = false; - if (fs::exists(bat / "voltage_now")) { + if (std::ifstream voltage_now_f{bat / "voltage_now"}) { voltage_now_exists = true; - std::ifstream(bat / "voltage_now") >> voltage_now; - } else if (fs::exists(bat / "voltage_avg")) { + voltage_now_f >> voltage_now; + } else if (std::ifstream voltage_avg_f{bat / "voltage_avg"}) { voltage_now_exists = true; - std::ifstream(bat / "voltage_avg") >> voltage_now; + voltage_avg_f >> voltage_now; } uint32_t charge_full = 0; bool charge_full_exists = false; - if (fs::exists(bat / "charge_full")) { + if (std::ifstream f{bat / "charge_full"}) { charge_full_exists = true; - std::ifstream(bat / "charge_full") >> charge_full; + f >> charge_full; } uint32_t charge_full_design = 0; bool charge_full_design_exists = false; - if (fs::exists(bat / "charge_full_design")) { + if (std::ifstream f{bat / "charge_full_design"}) { charge_full_design_exists = true; - std::ifstream(bat / "charge_full_design") >> charge_full_design; + f >> charge_full_design; } uint32_t charge_now = 0; bool charge_now_exists = false; - if (fs::exists(bat / "charge_now")) { + if (std::ifstream f{bat / "charge_now"}) { charge_now_exists = true; - std::ifstream(bat / "charge_now") >> charge_now; + f >> charge_now; } uint32_t power_now = 0; int32_t _power_now_int = 0; bool power_now_exists = false; - if (fs::exists(bat / "power_now")) { + if (std::ifstream f{bat / "power_now"}) { power_now_exists = true; - std::ifstream(bat / "power_now") >> _power_now_int; + f >> _power_now_int; } // Some drivers (example: Qualcomm) exposes use a negative value when // discharging, positive value when charging. @@ -360,28 +352,28 @@ waybar::modules::Battery::getInfos() { uint32_t energy_now = 0; bool energy_now_exists = false; - if (fs::exists(bat / "energy_now")) { + if (std::ifstream f{bat / "energy_now"}) { energy_now_exists = true; - std::ifstream(bat / "energy_now") >> energy_now; + f >> energy_now; } uint32_t energy_full = 0; bool energy_full_exists = false; - if (fs::exists(bat / "energy_full")) { + if (std::ifstream f{bat / "energy_full"}) { energy_full_exists = true; - std::ifstream(bat / "energy_full") >> energy_full; + f >> energy_full; } uint32_t energy_full_design = 0; bool energy_full_design_exists = false; - if (fs::exists(bat / "energy_full_design")) { + if (std::ifstream f{bat / "energy_full_design"}) { energy_full_design_exists = true; - std::ifstream(bat / "energy_full_design") >> energy_full_design; + f >> energy_full_design; } uint16_t cycleCount = 0; - if (fs::exists(bat / "cycle_count")) { - std::ifstream(bat / "cycle_count") >> cycleCount; + if (std::ifstream f{bat / "cycle_count"}) { + f >> cycleCount; } if (charge_full_design >= largestDesignCapacity) { largestDesignCapacity = charge_full_design; @@ -411,9 +403,9 @@ waybar::modules::Battery::getInfos() { } else if (energy_now_exists && energy_full_exists && energy_full != 0) { capacity_exists = true; capacity = 100 * (uint64_t)energy_now / (uint64_t)energy_full; - } else if (fs::exists(bat / "capacity")) { + } else if (std::ifstream f{bat / "capacity"}) { capacity_exists = true; - std::ifstream(bat / "capacity") >> capacity; + f >> capacity; } if (!voltage_now_exists) { From 5af324f375edf8a6a033869d5386e711b4e49f10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20Schl=C3=B6mer?= Date: Mon, 27 Apr 2026 11:41:09 +0200 Subject: [PATCH 682/780] two more toctou bugs --- src/AAppIconLabel.cpp | 26 +++++++++++++------------- src/modules/cpu_frequency/linux.cpp | 18 ++++++++---------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/AAppIconLabel.cpp b/src/AAppIconLabel.cpp index b72906c3..784f30c2 100644 --- a/src/AAppIconLabel.cpp +++ b/src/AAppIconLabel.cpp @@ -33,21 +33,21 @@ std::string toLowerCase(const std::string& input) { std::optional getFileBySuffix(const std::string& dir, const std::string& suffix, bool check_lower_case) { - if (!std::filesystem::exists(dir)) { - return {}; - } - for (const auto& entry : std::filesystem::recursive_directory_iterator(dir)) { - if (entry.is_regular_file()) { - std::string filename = entry.path().filename().string(); - if (filename.size() < suffix.size()) { - continue; - } - if ((filename.compare(filename.size() - suffix.size(), suffix.size(), suffix) == 0) || - (check_lower_case && filename.compare(filename.size() - suffix.size(), suffix.size(), - toLowerCase(suffix)) == 0)) { - return entry.path().string(); + try { + for (const auto& entry : std::filesystem::recursive_directory_iterator(dir)) { + if (entry.is_regular_file()) { + std::string filename = entry.path().filename().string(); + if (filename.size() < suffix.size()) { + continue; + } + if ((filename.compare(filename.size() - suffix.size(), suffix.size(), suffix) == 0) || + (check_lower_case && filename.compare(filename.size() - suffix.size(), suffix.size(), + toLowerCase(suffix)) == 0)) { + return entry.path().string(); + } } } + } catch (const std::filesystem::filesystem_error&) { } return {}; diff --git a/src/modules/cpu_frequency/linux.cpp b/src/modules/cpu_frequency/linux.cpp index 83f06aa5..7b927cdc 100644 --- a/src/modules/cpu_frequency/linux.cpp +++ b/src/modules/cpu_frequency/linux.cpp @@ -23,23 +23,21 @@ std::vector waybar::modules::CpuFrequency::parseCpuFrequencies() { if (frequencies.size() <= 0) { std::string cpufreq_dir = "/sys/devices/system/cpu/cpufreq"; - if (std::filesystem::exists(cpufreq_dir)) { + try { std::vector frequency_files = {"/cpuinfo_min_freq", "/cpuinfo_max_freq"}; for (auto& p : std::filesystem::directory_iterator(cpufreq_dir)) { for (const auto& freq_file : frequency_files) { std::string freq_file_path = p.path().string() + freq_file; - if (std::filesystem::exists(freq_file_path)) { - std::string freq_value; - std::ifstream freq(freq_file_path); - if (freq.is_open()) { - getline(freq, freq_value); - float frequency = std::strtol(freq_value.c_str(), nullptr, 10); - frequencies.push_back(frequency / 1000); - freq.close(); - } + std::string freq_value; + std::ifstream freq(freq_file_path); + if (freq.is_open()) { + getline(freq, freq_value); + float frequency = std::strtol(freq_value.c_str(), nullptr, 10); + frequencies.push_back(frequency / 1000); } } } + } catch (const std::filesystem::filesystem_error&) { } } From 255f17d13cf2d920f9459c3762d159b5dbcd9a91 Mon Sep 17 00:00:00 2001 From: Evans Date: Tue, 28 Apr 2026 03:24:55 +0000 Subject: [PATCH 683/780] battery: guard _exists = true assignment inside if condition --- src/modules/battery.cpp | 77 ++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 30be3857..537c62b0 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -296,63 +296,73 @@ waybar::modules::Battery::getInfos() { int32_t _current_now_int = 0; bool current_now_exists = false; if (fs::exists(bat / "current_now")) { - current_now_exists = true; - std::ifstream(bat / "current_now") >> _current_now_int; + if (std::ifstream(bat / "current_now") >> _current_now_int) { + current_now_exists = true; + } } else if (fs::exists(bat / "current_avg")) { - current_now_exists = true; - std::ifstream(bat / "current_avg") >> _current_now_int; + if (std::ifstream(bat / "current_avg") >> _current_now_int) { + current_now_exists = true; + } } // Documentation ABI allows a negative value when discharging, positive // value when charging. current_now = std::abs(_current_now_int); if (fs::exists(bat / "time_to_empty_now")) { - time_to_empty_now_exists = true; - std::ifstream(bat / "time_to_empty_now") >> time_to_empty_now; + if (std::ifstream(bat / "time_to_empty_now") >> time_to_empty_now) { + time_to_empty_now_exists = true; + } } if (fs::exists(bat / "time_to_full_now")) { - time_to_full_now_exists = true; - std::ifstream(bat / "time_to_full_now") >> time_to_full_now; + if (std::ifstream(bat / "time_to_full_now") >> time_to_full_now) { + time_to_full_now_exists = true; + } } uint32_t voltage_now = 0; bool voltage_now_exists = false; if (fs::exists(bat / "voltage_now")) { - voltage_now_exists = true; - std::ifstream(bat / "voltage_now") >> voltage_now; + if (std::ifstream(bat / "voltage_now") >> voltage_now) { + voltage_now_exists = true; + } } else if (fs::exists(bat / "voltage_avg")) { - voltage_now_exists = true; - std::ifstream(bat / "voltage_avg") >> voltage_now; + if (std::ifstream(bat / "voltage_avg") >> voltage_now) { + voltage_now_exists = true; + } } uint32_t charge_full = 0; bool charge_full_exists = false; if (fs::exists(bat / "charge_full")) { - charge_full_exists = true; - std::ifstream(bat / "charge_full") >> charge_full; + if (std::ifstream(bat / "charge_full") >> charge_full) { + charge_full_exists = true; + } } uint32_t charge_full_design = 0; bool charge_full_design_exists = false; if (fs::exists(bat / "charge_full_design")) { - charge_full_design_exists = true; - std::ifstream(bat / "charge_full_design") >> charge_full_design; + if (std::ifstream(bat / "charge_full_design") >> charge_full_design) { + charge_full_design_exists = true; + } } uint32_t charge_now = 0; bool charge_now_exists = false; if (fs::exists(bat / "charge_now")) { - charge_now_exists = true; - std::ifstream(bat / "charge_now") >> charge_now; + if (std::ifstream(bat / "charge_now") >> charge_now) { + charge_now_exists = true; + } } uint32_t power_now = 0; int32_t _power_now_int = 0; bool power_now_exists = false; if (fs::exists(bat / "power_now")) { - power_now_exists = true; - std::ifstream(bat / "power_now") >> _power_now_int; + if (std::ifstream(bat / "power_now") >> _power_now_int) { + power_now_exists = true; + } } // Some drivers (example: Qualcomm) exposes use a negative value when // discharging, positive value when charging. @@ -361,22 +371,25 @@ waybar::modules::Battery::getInfos() { uint32_t energy_now = 0; bool energy_now_exists = false; if (fs::exists(bat / "energy_now")) { - energy_now_exists = true; - std::ifstream(bat / "energy_now") >> energy_now; + if (std::ifstream(bat / "energy_now") >> energy_now) { + energy_now_exists = true; + } } uint32_t energy_full = 0; bool energy_full_exists = false; if (fs::exists(bat / "energy_full")) { - energy_full_exists = true; - std::ifstream(bat / "energy_full") >> energy_full; + if (std::ifstream(bat / "energy_full") >> energy_full) { + energy_full_exists = true; + } } uint32_t energy_full_design = 0; bool energy_full_design_exists = false; if (fs::exists(bat / "energy_full_design")) { - energy_full_design_exists = true; - std::ifstream(bat / "energy_full_design") >> energy_full_design; + if (std::ifstream(bat / "energy_full_design") >> energy_full_design) { + energy_full_design_exists = true; + } } uint16_t cycleCount = 0; @@ -412,8 +425,9 @@ waybar::modules::Battery::getInfos() { capacity_exists = true; capacity = 100 * (uint64_t)energy_now / (uint64_t)energy_full; } else if (fs::exists(bat / "capacity")) { - capacity_exists = true; - std::ifstream(bat / "capacity") >> capacity; + if (std::ifstream(bat / "capacity") >> capacity) { + capacity_exists = true; + } } if (!voltage_now_exists) { @@ -593,11 +607,12 @@ waybar::modules::Battery::getInfos() { float calculated_capacity{0.0f}; if (total_capacity_exists) { - if (total_capacity > 0.0f) + if (total_capacity > 0.0f) { calculated_capacity = (float)total_capacity / batteries_.size(); - else if (total_energy_full_exists && total_energy_exists) { - if (total_energy_full > 0.0f) + } else if (total_energy_full_exists && total_energy_exists) { + if (total_energy_full > 0.0f) { calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full); + } } } From a66d6b1596a6c32697776f5d1b4a7ed09da70d52 Mon Sep 17 00:00:00 2001 From: Neltharion Date: Wed, 29 Apr 2026 03:40:06 +0300 Subject: [PATCH 684/780] Do not reconnect if connection was refused Otherwise, it will try to reconnect recursively --- src/util/audio_backend.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index 4087e096..fb368ca5 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -99,15 +99,17 @@ void AudioBackend::contextStateCb(pa_context* c, void* data) { nullptr, nullptr); break; case PA_CONTEXT_FAILED: - // When pulseaudio server restarts, the connection is "failed". Try to reconnect. - // pa_threaded_mainloop_lock is already acquired in callback threads. - // So there is no need to lock it again. - if (backend->context_ != nullptr) { - pa_context_disconnect(backend->context_); - pa_context_unref(backend->context_); - backend->context_ = nullptr; + if (pa_context_errno(c) != PA_ERR_CONNECTIONREFUSED) { + // When pulseaudio server restarts, the connection is "failed". Try to reconnect. + // pa_threaded_mainloop_lock is already acquired in callback threads. + // So there is no need to lock it again. + if (backend->context_ != nullptr) { + pa_context_disconnect(backend->context_); + pa_context_unref(backend->context_); + backend->context_ = nullptr; + } + backend->connectContext(); } - backend->connectContext(); break; case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: From b28af75e62f8c6739990e94c383447e30cd74411 Mon Sep 17 00:00:00 2001 From: shiyi Date: Wed, 29 Apr 2026 23:34:09 +0800 Subject: [PATCH 685/780] feat(language): add tooltip support with configurable tooltip-format --- src/modules/hyprland/language.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index 6e0fe23d..ed608e10 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -58,6 +58,25 @@ auto Language::update() -> void { label_.hide(); } + // Tooltip support + if (tooltipEnabled()) { + std::string tooltipFormat; + if (config_["tooltip-format"].isString()) { + tooltipFormat = config_["tooltip-format"].asString(); + } else { + tooltipFormat = "{long}"; + } + auto tooltipText = trim(fmt::format( + fmt::runtime(tooltipFormat), + fmt::arg("long", layout_.full_name), + fmt::arg("short", layout_.short_name), + fmt::arg("shortDescription", layout_.short_description), + fmt::arg("variant", layout_.variant))); + label_.set_tooltip_text(tooltipText); + } else { + label_.set_tooltip_text(""); + } + ALabel::update(); } From e17c0d9f0a73acc370df60ec8c532b1ed2385c73 Mon Sep 17 00:00:00 2001 From: Higor Prado Date: Wed, 29 Apr 2026 15:53:09 -0300 Subject: [PATCH 686/780] fix(hyprland/workspaces): adapt dispatch commands for Lua IPC protocol Hyprland 0.54 replaced the text-based dispatch socket protocol with a Lua-based one. Commands like "dispatch workspace 1" are now interpreted as invalid Lua (return hl.dispatch(workspace 1)), breaking workspace clicks and scroll navigation. Add IPC::dispatch() that probes the running Hyprland on first call and routes commands through the new hl.dsp Lua API when the Lua protocol is detected, falling back to the old text format otherwise. --- include/modules/hyprland/backend.hpp | 14 ++++++ src/modules/hyprland/backend.cpp | 67 ++++++++++++++++++++++++++++ src/modules/hyprland/workspace.cpp | 12 ++--- src/modules/hyprland/workspaces.cpp | 8 ++-- 4 files changed, 91 insertions(+), 10 deletions(-) diff --git a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp index a6ebd191..4e16299b 100644 --- a/include/modules/hyprland/backend.hpp +++ b/include/modules/hyprland/backend.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,10 @@ class IPC { Json::Value getSocket1JsonReply(const std::string& rq); static std::filesystem::path getSocketFolder(const char* instanceSig); + /// Dispatch a Hyprland command. Automatically uses the correct protocol + /// (legacy text or Lua-based) depending on the running Hyprland version. + static std::string dispatch(const std::string& dispatcher, const std::string& arg); + protected: static std::filesystem::path socketFolder_; @@ -42,6 +47,15 @@ class IPC { void socketListener(); void parseIPC(const std::string&); + /// Detect whether the running Hyprland uses the Lua-based IPC protocol. + /// Returns true for Hyprland >= 0.54 (Lua config), false for older versions. + static bool isLuaProtocol(); + + /// Build a Lua-format dispatch command string. + static std::string buildLuaDispatch(const std::string& dispatcher, const std::string& arg); + + static std::optional s_luaProtocolDetected_; // cached detection result + std::thread ipcThread_; std::mutex callbackMutex_; std::mutex socketMutex_; diff --git a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp index d0371202..08cf97c1 100644 --- a/src/modules/hyprland/backend.cpp +++ b/src/modules/hyprland/backend.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "util/scoped_fd.hpp" @@ -20,6 +21,7 @@ namespace waybar::modules::hyprland { std::filesystem::path IPC::socketFolder_; +std::optional IPC::s_luaProtocolDetected_; std::filesystem::path IPC::getSocketFolder(const char* instanceSig) { static std::mutex folderMutex; @@ -290,4 +292,69 @@ Json::Value IPC::getSocket1JsonReply(const std::string& rq) { return parser_.parse(reply); } +bool IPC::isLuaProtocol() { + if (s_luaProtocolDetected_.has_value()) { + return *s_luaProtocolDetected_; + } + + // Probe: send a harmless old-style dispatch and check the error. + // In Lua-based Hyprland (>= 0.54) the error contains "hl.dispatch". + // In older versions it returns "ok" or a different error. + auto reply = getSocket1Reply("dispatch workspace __waybar_probe__"); + bool luaProto = reply.find("hl.dispatch") != std::string::npos; + + if (luaProto) { + spdlog::info("Hyprland IPC: detected Lua-based dispatch protocol (Hyprland >= 0.54)"); + } else { + spdlog::info("Hyprland IPC: detected legacy dispatch protocol"); + } + + s_luaProtocolDetected_ = luaProto; + return luaProto; +} + +std::string IPC::buildLuaDispatch(const std::string& dispatcher, const std::string& arg) { + // Map old-style dispatchers to the new Lua hl.dsp API. + // + // Old format: dispatch workspace 1 + // New format: /dispatch hl.dsp.focus({ workspace = "1" }) + // + // Old format: dispatch focusworkspaceoncurrentmonitor 2 + // New format: /dispatch hl.dsp.focus({ workspace = "2", monitor = "current" }) + // + // Old format: dispatch togglespecialworkspace name + // New format: /dispatch hl.dsp.workspace.toggle_special("name") + + if (dispatcher == "workspace") { + return "/dispatch hl.dsp.focus({ workspace = \"" + arg + "\" })"; + } + if (dispatcher == "focusworkspaceoncurrentmonitor") { + return "/dispatch hl.dsp.focus({ workspace = \"" + arg + "\", monitor = \"current\" })"; + } + if (dispatcher == "togglespecialworkspace") { + if (arg.empty()) { + return "/dispatch hl.dsp.workspace.toggle_special()"; + } + return "/dispatch hl.dsp.workspace.toggle_special(\"" + arg + "\")"; + } + + // Fallback for any other dispatcher: try the old format wrapped in dispatch(). + // This may not work for all dispatchers, but it's a reasonable default. + spdlog::warn("Hyprland IPC: unknown dispatcher '{}' in Lua mode, attempting generic format", + dispatcher); + return "/dispatch hl.dsp." + dispatcher + "(\"" + arg + "\")"; +} + +std::string IPC::dispatch(const std::string& dispatcher, const std::string& arg) { + if (isLuaProtocol()) { + return getSocket1Reply(buildLuaDispatch(dispatcher, arg)); + } + // Legacy format: "dispatch " + std::string cmd = "dispatch " + dispatcher; + if (!arg.empty()) { + cmd += " " + arg; + } + return getSocket1Reply(cmd); +} + } // namespace waybar::modules::hyprland diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 21e7ef9b..753893f2 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -71,20 +71,20 @@ bool Workspace::handleClicked(GdkEventButton* bt) const { try { if (id() > 0) { // normal if (m_workspaceManager.moveToMonitor()) { - m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor " + std::to_string(id())); + IPC::dispatch("focusworkspaceoncurrentmonitor", std::to_string(id())); } else { - m_ipc.getSocket1Reply("dispatch workspace " + std::to_string(id())); + IPC::dispatch("workspace", std::to_string(id())); } } else if (!isSpecial()) { // named (this includes persistent) if (m_workspaceManager.moveToMonitor()) { - m_ipc.getSocket1Reply("dispatch focusworkspaceoncurrentmonitor name:" + name()); + IPC::dispatch("focusworkspaceoncurrentmonitor", "name:" + name()); } else { - m_ipc.getSocket1Reply("dispatch workspace name:" + name()); + IPC::dispatch("workspace", "name:" + name()); } } else if (id() != -99) { // named special - m_ipc.getSocket1Reply("dispatch togglespecialworkspace " + name()); + IPC::dispatch("togglespecialworkspace", name()); } else { // special - m_ipc.getSocket1Reply("dispatch togglespecialworkspace"); + IPC::dispatch("togglespecialworkspace", ""); } return true; } catch (const std::exception& e) { diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index f794249b..2496117f 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -1195,15 +1195,15 @@ bool Workspaces::handleScroll(GdkEventScroll* e) { if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) { if (allOutputs()) { - m_ipc.getSocket1Reply("dispatch workspace e+1"); + IPC::dispatch("workspace", "e+1"); } else { - m_ipc.getSocket1Reply("dispatch workspace m+1"); + IPC::dispatch("workspace", "m+1"); } } else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) { if (allOutputs()) { - m_ipc.getSocket1Reply("dispatch workspace e-1"); + IPC::dispatch("workspace", "e-1"); } else { - m_ipc.getSocket1Reply("dispatch workspace m-1"); + IPC::dispatch("workspace", "m-1"); } } From c81a039a55271394e4aebd0e928ff1c83dd091a0 Mon Sep 17 00:00:00 2001 From: Yui Date: Fri, 1 May 2026 22:29:48 +0300 Subject: [PATCH 687/780] feat: add compact versions of bandwidthBytes to network module Adds the following: - bandwidthDownByesCompact - bandwidthUpByesCompact Differences from normal version: - drop the "/s" suffix - drop the deciaml part if less than 1MB/s --- include/util/format.hpp | 8 +++++--- src/modules/network.cpp | 8 ++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/include/util/format.hpp b/include/util/format.hpp index c8ed837a..2eb148c4 100644 --- a/include/util/format.hpp +++ b/include/util/format.hpp @@ -5,12 +5,13 @@ class pow_format { public: - pow_format(long long val, std::string&& unit, bool binary = false) - : val_(val), unit_(unit), binary_(binary) {}; + pow_format(long long val, std::string&& unit, bool binary = false, int min_pow_for_decimal = 0) + : val_(val), unit_(unit), binary_(binary), min_pow_for_decimal_(min_pow_for_decimal) {}; long long val_; std::string unit_; bool binary_; + int min_pow_for_decimal_; }; namespace fmt { @@ -74,7 +75,8 @@ struct formatter { break; case 0: default: - format = "{coefficient:.1f}{prefix}{unit}"; + format = pow < s.min_pow_for_decimal_ ? "{coefficient:.0f}{prefix}{unit}" + : "{coefficient:.1f}{prefix}{unit}"; break; } return fmt::format_to( diff --git a/src/modules/network.cpp b/src/modules/network.cpp index f43893c5..8bb54fce 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -368,6 +368,10 @@ auto waybar::modules::Network::update() -> void { pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")), fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")), fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")), + fmt::arg("bandwidthDownBytesCompact", + pow_format(bandwidth_down / elapsed_seconds, "B", false, 2)), + fmt::arg("bandwidthUpBytesCompact", + pow_format(bandwidth_up / elapsed_seconds, "B", false, 2)), fmt::arg("bandwidthTotalBytes", pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s"))); if (text.compare(label_.get_label()) != 0) { @@ -401,6 +405,10 @@ auto waybar::modules::Network::update() -> void { pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")), fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")), fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")), + fmt::arg("bandwidthDownBytesCompact", + pow_format(bandwidth_down / elapsed_seconds, "B", false, 2)), + fmt::arg("bandwidthUpBytesCompact", + pow_format(bandwidth_up / elapsed_seconds, "B", false, 2)), fmt::arg("bandwidthTotalBytes", pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s"))); if (label_.get_tooltip_text() != tooltip_text) { From 97917db59369b66ef412a87f90cfdbda3ad55225 Mon Sep 17 00:00:00 2001 From: Higor Prado Date: Sat, 2 May 2026 20:25:43 -0300 Subject: [PATCH 688/780] test(hyprland): expose dispatch internals for unit tests Move buildLuaDispatch and isLuaProtocol from private to protected/public so IPCTestHelper can access them. Add 7 tests covering all buildLuaDispatch branches, dispatch error path, and isLuaProtocol cache behavior. --- include/modules/hyprland/backend.hpp | 14 ++--- test/hyprland/backend.cpp | 79 ++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 7 deletions(-) diff --git a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp index 4e16299b..7c6369da 100644 --- a/include/modules/hyprland/backend.hpp +++ b/include/modules/hyprland/backend.hpp @@ -40,22 +40,22 @@ class IPC { /// (legacy text or Lua-based) depending on the running Hyprland version. static std::string dispatch(const std::string& dispatcher, const std::string& arg); + /// Build a Lua-format dispatch command string. + static std::string buildLuaDispatch(const std::string& dispatcher, const std::string& arg); + protected: static std::filesystem::path socketFolder_; - private: - void socketListener(); - void parseIPC(const std::string&); - /// Detect whether the running Hyprland uses the Lua-based IPC protocol. /// Returns true for Hyprland >= 0.54 (Lua config), false for older versions. static bool isLuaProtocol(); - /// Build a Lua-format dispatch command string. - static std::string buildLuaDispatch(const std::string& dispatcher, const std::string& arg); - static std::optional s_luaProtocolDetected_; // cached detection result + private: + void socketListener(); + void parseIPC(const std::string&); + std::thread ipcThread_; std::mutex callbackMutex_; std::mutex socketMutex_; diff --git a/test/hyprland/backend.cpp b/test/hyprland/backend.cpp index ccc2da65..62d23ae4 100644 --- a/test/hyprland/backend.cpp +++ b/test/hyprland/backend.cpp @@ -15,6 +15,10 @@ namespace { class IPCTestHelper : public hyprland::IPC { public: static void resetSocketFolder() { socketFolder_.clear(); } + static void resetLuaProtocolDetection() { s_luaProtocolDetected_.reset(); } + static void setLuaProtocolDetected(bool value) { s_luaProtocolDetected_ = value; } + using hyprland::IPC::buildLuaDispatch; + using hyprland::IPC::isLuaProtocol; }; std::size_t countOpenFds() { @@ -133,3 +137,78 @@ TEST_CASE("getSocket1Reply failure paths do not leak fds", "[getSocket1Reply][fd REQUIRE(after_connect_failures == baseline); } #endif + +// --- Tests for new Lua IPC dispatch functions --- + +TEST_CASE("buildLuaDispatch workspace", "[buildLuaDispatch]") { + SECTION("numeric workspace") { + auto result = IPCTestHelper::buildLuaDispatch("workspace", "1"); + REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"1\" })"); + } + SECTION("named workspace") { + auto result = IPCTestHelper::buildLuaDispatch("workspace", "name:term"); + REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"name:term\" })"); + } + SECTION("relative workspace") { + auto result = IPCTestHelper::buildLuaDispatch("workspace", "e+1"); + REQUIRE(result == "/dispatch hl.dsp.focus({ workspace = \"e+1\" })"); + } +} + +TEST_CASE("buildLuaDispatch focusworkspaceoncurrentmonitor", "[buildLuaDispatch]") { + auto result = + IPCTestHelper::buildLuaDispatch("focusworkspaceoncurrentmonitor", "3"); + REQUIRE( + result == + "/dispatch hl.dsp.focus({ workspace = \"3\", monitor = \"current\" })"); +} + +TEST_CASE("buildLuaDispatch togglespecialworkspace", "[buildLuaDispatch]") { + SECTION("with name") { + auto result = + IPCTestHelper::buildLuaDispatch("togglespecialworkspace", "scratchpad"); + REQUIRE(result == + "/dispatch hl.dsp.workspace.toggle_special(\"scratchpad\")"); + } + SECTION("empty arg") { + auto result = + IPCTestHelper::buildLuaDispatch("togglespecialworkspace", ""); + REQUIRE(result == "/dispatch hl.dsp.workspace.toggle_special()"); + } +} + +TEST_CASE("buildLuaDispatch unknown dispatcher fallback", "[buildLuaDispatch]") { + auto result = + IPCTestHelper::buildLuaDispatch("unknown_dispatcher", "some_arg"); + REQUIRE(result == + "/dispatch hl.dsp.unknown_dispatcher(\"some_arg\")"); +} + +TEST_CASE("dispatch throws when Hyprland is not running", "[dispatch]") { + unsetenv("HYPRLAND_INSTANCE_SIGNATURE"); + IPCTestHelper::resetSocketFolder(); + IPCTestHelper::resetLuaProtocolDetection(); + + CHECK_THROWS(hyprland::IPC::dispatch("workspace", "1")); +} + +TEST_CASE("isLuaProtocol uses cached value and avoids socket call", + "[isLuaProtocol]") { + unsetenv("HYPRLAND_INSTANCE_SIGNATURE"); + IPCTestHelper::resetSocketFolder(); + + SECTION("cached false") { + IPCTestHelper::setLuaProtocolDetected(false); + // Should return false without throwing (no socket call needed) + REQUIRE(IPCTestHelper::isLuaProtocol() == false); + } + + SECTION("cached true") { + IPCTestHelper::setLuaProtocolDetected(true); + // Should return true without throwing (no socket call needed) + REQUIRE(IPCTestHelper::isLuaProtocol() == true); + } + + // Cleanup: reset detection so other tests aren't affected + IPCTestHelper::resetLuaProtocolDetection(); +} From d1d501450d27eeb31504ff5379537f27ff7373dc Mon Sep 17 00:00:00 2001 From: nonamescm Date: Mon, 4 May 2026 03:38:17 -0300 Subject: [PATCH 689/780] Add static river tags --- man/waybar-river-tags.5.scd | 1 + src/modules/river/tags.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/man/waybar-river-tags.5.scd b/man/waybar-river-tags.5.scd index 64621229..7481b974 100644 --- a/man/waybar-river-tags.5.scd +++ b/man/waybar-river-tags.5.scd @@ -50,6 +50,7 @@ Addressed by *river/tags* - *#tags button.occupied* - *#tags button.focused* - *#tags button.urgent* +- *#tags button.tag-N* Note that occupied/focused/urgent status may overlap. That is, a tag may be both occupied and focused at the same time. diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index f06565f6..5381bf9f 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -149,6 +149,7 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con button.signal_button_press_event().connect( sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), (1 << tag))); } + button.get_style_context()->add_class("tag-" + std::to_string(tag + 1)); button.show(); } From 5c3ec1fd634731ccc61aca7c41211643af0295c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Rodon?= Date: Mon, 11 May 2026 22:47:24 +0200 Subject: [PATCH 690/780] fix(tray): hide module when no items are visible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tray::update() already had logic to hide the module when all child items were passive, but it was only re-run on item add/remove — never on a status change. When the last visible item transitioned to Passive, the item hid itself but the now-empty module remained visible. Connect Tray to each item's Gtk::EventBox signal_show/signal_hide so update() runs on every visibility transition, and simplify update() to check child->get_visible() directly instead of inspecting the `passive` CSS class. The Tray-level `show-passive-items` read becomes redundant since Item already honours it when deciding its own visibility; remove it along with the now-unused Tray::show_passive_ member. Fixes: #3721 --- include/modules/sni/tray.hpp | 1 - src/modules/sni/tray.cpp | 17 ++++------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/include/modules/sni/tray.hpp b/include/modules/sni/tray.hpp index 5f12d7f2..6cda35d7 100644 --- a/include/modules/sni/tray.hpp +++ b/include/modules/sni/tray.hpp @@ -21,7 +21,6 @@ class Tray : public AModule { void onRemove(std::unique_ptr& item); static inline std::size_t nb_hosts_ = 0; - bool show_passive_ = false; Gtk::Box box_; SNI::Watcher::singleton watcher_; SNI::Host host_; diff --git a/src/modules/sni/tray.cpp b/src/modules/sni/tray.cpp index 34a3c05f..126ee03d 100644 --- a/src/modules/sni/tray.cpp +++ b/src/modules/sni/tray.cpp @@ -23,9 +23,6 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config) if (config_["spacing"].isUInt()) { box_.set_spacing(config_["spacing"].asUInt()); } - if (config["show-passive-items"].isBool()) { - show_passive_ = config["show-passive-items"].asBool(); - } nb_hosts_ += 1; if (config_["icons"].isObject()) { IconManager::instance().setIconsConfig(config_["icons"]); @@ -39,6 +36,8 @@ void Tray::onAdd(std::unique_ptr& item) { } else { box_.pack_start(item->event_box); } + item->event_box.signal_show().connect([this] { dp.emit(); }); + item->event_box.signal_hide().connect([this] { dp.emit(); }); dp.emit(); } @@ -48,17 +47,9 @@ void Tray::onRemove(std::unique_ptr& item) { } auto Tray::update() -> void { - // Show tray only when items are available std::vector children = box_.get_children(); - if (show_passive_) { - event_box_.set_visible(!children.empty()); - } else { - event_box_.set_visible(!std::all_of(children.begin(), children.end(), [](Gtk::Widget* child) { - return child->get_style_context()->has_class("passive"); - })); - } - - // Call parent update + event_box_.set_visible(std::any_of(children.begin(), children.end(), + [](Gtk::Widget* child) { return child->get_visible(); })); AModule::update(); } From 2a05edaf6aea1509555d5b3f3bc57549287ae98e Mon Sep 17 00:00:00 2001 From: yyyumeniku Date: Tue, 12 May 2026 20:09:13 -0400 Subject: [PATCH 691/780] hyprland/workspaces: add debounce timer to prevent workspace button flicker When switching between workspaces rapidly (especially empty ones), Hyprland sends createworkspace and destroyworkspace events slightly out of order. This causes workspace buttons to render in wrong positions for a split second before snapping to their correct spots. Add a 7ms debounce timer that batches workspace events before calling dp.emit(), preventing the visual glitch. Fixes #4376 --- include/modules/hyprland/workspaces.hpp | 3 + src/modules/hyprland/workspaces.cpp | 90 +++++++++++++++---------- 2 files changed, 56 insertions(+), 37 deletions(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 03548ccb..be5bc66d 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -211,6 +211,9 @@ class Workspaces : public AModule, public EventHandler { Gtk::Box m_box; sigc::connection m_scrollEventConnection_; IPC& m_ipc; + + sigc::connection m_debounceTimer; + bool m_updatePending = false; }; } // namespace waybar::modules::hyprland diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 2496117f..762060e2 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -338,46 +338,62 @@ void Workspaces::loadPersistentWorkspacesFromWorkspaceRules(const Json::Value& c } void Workspaces::onEvent(const std::string& ev) { - std::lock_guard lock(m_mutex); - const auto separator = ev.find(">>"); - if (separator == std::string::npos) { - spdlog::warn("Malformed Hyprland workspace event: {}", ev); - return; - } - std::string eventName = ev.substr(0, separator); - std::string payload = ev.substr(separator + 2); + { + std::lock_guard lock(m_mutex); + const auto separator = ev.find(">>"); + if (separator == std::string::npos) { + spdlog::warn("Malformed Hyprland workspace event: {}", ev); + return; + } + std::string eventName = ev.substr(0, separator); + std::string payload = ev.substr(separator + 2); - if (eventName == "workspacev2") { - onWorkspaceActivated(payload); - } else if (eventName == "activespecial") { - onSpecialWorkspaceActivated(payload); - } else if (eventName == "destroyworkspacev2") { - onWorkspaceDestroyed(payload); - } else if (eventName == "createworkspacev2") { - onWorkspaceCreated(payload); - } else if (eventName == "focusedmonv2") { - onMonitorFocused(payload); - } else if (eventName == "moveworkspacev2") { - onWorkspaceMoved(payload); - } else if (eventName == "openwindow") { - onWindowOpened(payload); - } else if (eventName == "closewindow") { - onWindowClosed(payload); - } else if (eventName == "movewindowv2") { - onWindowMoved(payload); - } else if (eventName == "urgent") { - setUrgentWorkspace(payload); - } else if (eventName == "renameworkspace") { - onWorkspaceRenamed(payload); - } else if (eventName == "windowtitlev2") { - onWindowTitleEvent(payload); - } else if (eventName == "activewindowv2") { - onActiveWindowChanged(payload); - } else if (eventName == "configreloaded") { - onConfigReloaded(); + if (eventName == "workspacev2") { + onWorkspaceActivated(payload); + } else if (eventName == "activespecial") { + onSpecialWorkspaceActivated(payload); + } else if (eventName == "destroyworkspacev2") { + onWorkspaceDestroyed(payload); + } else if (eventName == "createworkspacev2") { + onWorkspaceCreated(payload); + } else if (eventName == "focusedmonv2") { + onMonitorFocused(payload); + } else if (eventName == "moveworkspacev2") { + onWorkspaceMoved(payload); + } else if (eventName == "openwindow") { + onWindowOpened(payload); + } else if (eventName == "closewindow") { + onWindowClosed(payload); + } else if (eventName == "movewindowv2") { + onWindowMoved(payload); + } else if (eventName == "urgent") { + setUrgentWorkspace(payload); + } else if (eventName == "renameworkspace") { + onWorkspaceRenamed(payload); + } else if (eventName == "windowtitlev2") { + onWindowTitleEvent(payload); + } else if (eventName == "activewindowv2") { + onActiveWindowChanged(payload); + } else if (eventName == "configreloaded") { + onConfigReloaded(); + } } - dp.emit(); + if (m_debounceTimer.connected()) { + m_debounceTimer.disconnect(); + m_updatePending = false; + } + + m_updatePending = true; + m_debounceTimer = Glib::signal_timeout().connect([this]() { + if (!m_updatePending) return false; + std::lock_guard lock(m_mutex); + if (m_updatePending) { + dp.emit(); + m_updatePending = false; + } + return false; + }, 7); } void Workspaces::onWorkspaceActivated(std::string const& payload) { From 3971bb6c00cc437dab6fc92c86ea4b8be63d42e7 Mon Sep 17 00:00:00 2001 From: Antoine Gaudreau Simard Date: Sat, 20 Dec 2025 18:07:52 -0500 Subject: [PATCH 692/780] feat: add support for multiple batteries in a single bluetooth device --- include/modules/bluetooth.hpp | 6 ++ man/waybar-bluetooth.5.scd | 14 ++++ src/modules/bluetooth.cpp | 151 +++++++++++++++++++++++++++++++++- 3 files changed, 168 insertions(+), 3 deletions(-) diff --git a/include/modules/bluetooth.hpp b/include/modules/bluetooth.hpp index b89383a0..a06d4645 100644 --- a/include/modules/bluetooth.hpp +++ b/include/modules/bluetooth.hpp @@ -41,6 +41,7 @@ class Bluetooth : public ALabel { bool services_resolved; // NOTE: experimental feature in bluez std::optional battery_percentage; + std::optional battery_percentage_peripheral; }; public: @@ -59,6 +60,11 @@ class Bluetooth : public ALabel { gpointer) -> void; auto getDeviceBatteryPercentage(GDBusObject*) -> std::optional; + auto getDeviceGattBatteryLevels(GDBusObject*, std::optional&, + std::optional&) -> void; + static auto processBatteryServiceCharacteristics(GList*, const std::string&, const std::string&, + const std::string&, std::optional&, + std::optional&) -> void; auto getDeviceProperties(GDBusObject*, DeviceInfo&) -> bool; auto getControllerProperties(GDBusObject*, ControllerInfo&) -> bool; diff --git a/man/waybar-bluetooth.5.scd b/man/waybar-bluetooth.5.scd index fd7d5fb5..36c6443a 100644 --- a/man/waybar-bluetooth.5.scd +++ b/man/waybar-bluetooth.5.scd @@ -178,6 +178,9 @@ At the time of writing, the experimental features of BlueZ need to be turned on, *{device_battery_percentage}*: Battery percentage of the displayed device if available. Use only in the config options defined below. +*{device_battery_percentage_peripheral}*: Battery percentage of the peripheral half of a split keyboard (e.g., ZMK keyboards with separate central and peripheral batteries). ++ +This is read from GATT Battery Service characteristics that have a User Description descriptor. Use only in the config options defined below. + ## CONFIGURATION *format-connected-battery*: ++ @@ -220,6 +223,17 @@ At the time of writing, the experimental features of BlueZ need to be turned on, } ``` +Split keyboard with separate central/peripheral batteries (e.g., ZMK): + +``` +"bluetooth": { + "format-device-preference": [ "Keyball44" ], + "format": "", + "format-connected-battery": " {device_battery_percentage}%|{device_battery_percentage_peripheral}%", + "tooltip-format-connected": "{device_alias}\\nCentral: {device_battery_percentage}%\\nPeripheral: {device_battery_percentage_peripheral}%" +} +``` + # STYLE - *#bluetooth* diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp index c59af3b5..97aad8b2 100644 --- a/src/modules/bluetooth.cpp +++ b/src/modules/bluetooth.cpp @@ -83,6 +83,66 @@ auto getUcharProperty(GDBusProxy* proxy, const char* property_name) -> unsigned return 0; } +auto isChildPath(const std::string& child, const std::string& parent) -> bool { + return child.starts_with(parent); +} + +auto readBatteryCharacteristicValue(GDBusProxy* proxy_char) -> std::optional { + GVariantBuilder builder; + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); + + GError* error = nullptr; + GVariant* gvar = g_dbus_proxy_call_sync(proxy_char, "ReadValue", g_variant_new("(a{sv})", &builder), + G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error); + if (error != nullptr) { + g_error_free(error); + return std::nullopt; + } + if (gvar == nullptr) { + return std::nullopt; + } + + GVariant* value_array = g_variant_get_child_value(gvar, 0); + gsize n_elements; + const auto* data = + static_cast(g_variant_get_fixed_array(value_array, &n_elements, sizeof(guchar))); + + std::optional result; + if (data != nullptr && n_elements > 0) { + result = data[0]; + } + + g_variant_unref(value_array); + g_variant_unref(gvar); + return result; +} + +auto hasUserDescriptionDescriptor(GList* objects, const std::string& char_path, + const std::string& user_description_uuid) -> bool { + for (GList* n = objects; n != nullptr; n = n->next) { + GDBusObject* desc_object = G_DBUS_OBJECT(n->data); + std::string desc_path = g_dbus_object_get_object_path(desc_object); + + if (!isChildPath(desc_path, char_path)) { + continue; + } + + GDBusProxy* proxy_desc = + G_DBUS_PROXY(g_dbus_object_get_interface(desc_object, "org.bluez.GattDescriptor1")); + if (proxy_desc == nullptr) { + continue; + } + + auto desc_uuid = getOptionalStringProperty(proxy_desc, "UUID"); + g_object_unref(proxy_desc); + + if (desc_uuid.has_value() && desc_uuid.value().find(user_description_uuid) != std::string::npos) { + return true; + } + } + return false; +} + } // namespace waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& config) @@ -232,8 +292,9 @@ auto waybar::modules::Bluetooth::update() -> void { fmt::arg("device_address", cur_focussed_device_.address), fmt::arg("device_address_type", cur_focussed_device_.address_type), fmt::arg("device_alias", cur_focussed_device_.alias), fmt::arg("icon", icon_label), - fmt::arg("device_battery_percentage", - cur_focussed_device_.battery_percentage.value_or(0)))); + fmt::arg("device_battery_percentage", cur_focussed_device_.battery_percentage.value_or(0)), + fmt::arg("device_battery_percentage_peripheral", + cur_focussed_device_.battery_percentage_peripheral.value_or(0)))); } if (tooltipEnabled()) { @@ -258,7 +319,9 @@ auto waybar::modules::Bluetooth::update() -> void { fmt::runtime(enumerate_format), fmt::arg("device_address", dev.address), fmt::arg("device_address_type", dev.address_type), fmt::arg("device_alias", dev.alias), fmt::arg("icon", enumerate_icon), - fmt::arg("device_battery_percentage", dev.battery_percentage.value_or(0))); + fmt::arg("device_battery_percentage", dev.battery_percentage.value_or(0)), + fmt::arg("device_battery_percentage_peripheral", + dev.battery_percentage_peripheral.value_or(0))); } } device_enumerate_ = ss.str(); @@ -278,6 +341,8 @@ auto waybar::modules::Bluetooth::update() -> void { fmt::arg("device_address_type", cur_focussed_device_.address_type), fmt::arg("device_alias", cur_focussed_device_.alias), fmt::arg("icon", icon_tooltip), fmt::arg("device_battery_percentage", cur_focussed_device_.battery_percentage.value_or(0)), + fmt::arg("device_battery_percentage_peripheral", + cur_focussed_device_.battery_percentage_peripheral.value_or(0)), fmt::arg("device_enumerate", device_enumerate_))); } @@ -398,6 +463,84 @@ auto waybar::modules::Bluetooth::getDeviceBatteryPercentage(GDBusObject* object) return std::nullopt; } +auto waybar::modules::Bluetooth::getDeviceGattBatteryLevels( + GDBusObject* device_object, std::optional& central_battery, + std::optional& peripheral_battery) -> void { + const std::string BATTERY_SERVICE_UUID = "0000180f-0000-1000-8000-00805f9b34fb"; + const std::string BATTERY_LEVEL_UUID = "00002a19-0000-1000-8000-00805f9b34fb"; + const std::string USER_DESCRIPTION_UUID = "00002901-0000-1000-8000-00805f9b34fb"; + + GList* objects = g_dbus_object_manager_get_objects(manager_.get()); + std::string device_path = g_dbus_object_get_object_path(device_object); + + for (GList* l = objects; l != nullptr; l = l->next) { + GDBusObject* service_object = G_DBUS_OBJECT(l->data); + std::string service_path = g_dbus_object_get_object_path(service_object); + + if (!isChildPath(service_path, device_path)) { + continue; + } + + GDBusProxy* proxy_service = + G_DBUS_PROXY(g_dbus_object_get_interface(service_object, "org.bluez.GattService1")); + if (proxy_service == nullptr) { + continue; + } + + auto service_uuid = getOptionalStringProperty(proxy_service, "UUID"); + g_object_unref(proxy_service); + + if (!service_uuid.has_value() || + service_uuid.value().find(BATTERY_SERVICE_UUID) == std::string::npos) { + continue; + } + + processBatteryServiceCharacteristics(objects, service_path, BATTERY_LEVEL_UUID, + USER_DESCRIPTION_UUID, central_battery, peripheral_battery); + } + + g_list_free_full(objects, g_object_unref); +} + +auto waybar::modules::Bluetooth::processBatteryServiceCharacteristics( + GList* objects, const std::string& service_path, const std::string& battery_level_uuid, + const std::string& user_description_uuid, std::optional& central_battery, + std::optional& peripheral_battery) -> void { + for (GList* m = objects; m != nullptr; m = m->next) { + GDBusObject* char_object = G_DBUS_OBJECT(m->data); + std::string char_path = g_dbus_object_get_object_path(char_object); + + if (!isChildPath(char_path, service_path)) { + continue; + } + + GDBusProxy* proxy_char = + G_DBUS_PROXY(g_dbus_object_get_interface(char_object, "org.bluez.GattCharacteristic1")); + if (proxy_char == nullptr) { + continue; + } + + auto char_uuid = getOptionalStringProperty(proxy_char, "UUID"); + if (!char_uuid.has_value() || char_uuid.value().find(battery_level_uuid) == std::string::npos) { + g_object_unref(proxy_char); + continue; + } + + auto battery_value = readBatteryCharacteristicValue(proxy_char); + g_object_unref(proxy_char); + + if (!battery_value.has_value()) { + continue; + } + + if (hasUserDescriptionDescriptor(objects, char_path, user_description_uuid)) { + peripheral_battery = battery_value.value(); + } else { + central_battery = battery_value.value(); + } + } +} + auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, DeviceInfo& device_info) -> bool { GDBusProxy* proxy_device = G_DBUS_PROXY(g_dbus_object_get_interface(object, "org.bluez.Device1")); @@ -418,6 +561,8 @@ auto waybar::modules::Bluetooth::getDeviceProperties(GDBusObject* object, Device g_object_unref(proxy_device); device_info.battery_percentage = getDeviceBatteryPercentage(object); + getDeviceGattBatteryLevels(object, device_info.battery_percentage, + device_info.battery_percentage_peripheral); return true; } From 7105ad9bbacc94ace219985ef909431fc250e83c Mon Sep 17 00:00:00 2001 From: Peter van Dijk Date: Thu, 14 May 2026 01:22:25 +0200 Subject: [PATCH 693/780] waybar-styles.5: add semicolon to css import statement --- man/waybar-styles.5.scd.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/waybar-styles.5.scd.in b/man/waybar-styles.5.scd.in index b11e15bd..e5a70517 100644 --- a/man/waybar-styles.5.scd.in +++ b/man/waybar-styles.5.scd.in @@ -21,7 +21,7 @@ It uses the first file found in this search order: An example user-controlled stylesheet that just changes the color of the clock to be green on black, while keeping the rest of the system config the same would be: ``` -@import url("file:///etc/xdg/waybar/style.css") +@import url("file:///etc/xdg/waybar/style.css"); #clock { background: #000000; From 3229c2c9fc2a7dd97eae1cd4b7d4937e591f7c96 Mon Sep 17 00:00:00 2001 From: Agnibha Mukherjee Date: Thu, 14 May 2026 11:01:58 +0530 Subject: [PATCH 694/780] feat: support Pango markup in remaining tooltips --- src/modules/systemd_failed_units.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index 8d7e866f..1a3fdf6e 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -292,7 +292,7 @@ auto SystemdFailedUnits::update() -> void { fmt::arg("user_state", user_state_), fmt::arg("overall_state", overall_state_), fmt::arg("failed_units_list", failed_list))); } else { - label_.set_tooltip_text(""); + label_.set_tooltip_markup(""); } } ALabel::update(); From 0ec6d06472cb42a061d9503fc426b47bf6cf3301 Mon Sep 17 00:00:00 2001 From: Milad Alizadeh Date: Fri, 15 May 2026 17:29:21 +0100 Subject: [PATCH 695/780] Guard set_current_layout against unknown layout names (wtype, hot-plugged devices) so transient events stop blanking layout_. Refresh layouts_map_ on "added"/"xkb_keymap" input events and union layouts across all keyboards so new devices contribute their layouts. Release mutex_ before the refresh sendCmd to avoid self-deadlock from the synchronous signal_cmd emit. --- src/modules/sway/language.cpp | 57 ++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/src/modules/sway/language.cpp b/src/modules/sway/language.cpp index 2e1103d3..28f1ef24 100644 --- a/src/modules/sway/language.cpp +++ b/src/modules/sway/language.cpp @@ -54,21 +54,28 @@ void Language::onCmd(const struct Ipc::ipc_response& res) { std::lock_guard lock(mutex_); auto payload = parser_.parse(res.payload); std::vector used_layouts; - // Display current layout of a device with a maximum count of layouts, expecting that all will - // be OK + // Union layout names across every keyboard input so hot-plugged devices contribute their + // layouts to the map. Track the device with the most layouts to seed the initially displayed + // layout, matching the previous behaviour at startup. Json::ArrayIndex max_id = 0, max = 0; for (Json::ArrayIndex i = 0; i < payload.size(); i++) { - auto size = payload[i][XKB_LAYOUT_NAMES_KEY].size(); - if (size > max) { - max = size; + if (payload[i]["type"].asString() != "keyboard") continue; + const auto& names = payload[i][XKB_LAYOUT_NAMES_KEY]; + if (names.size() > max) { + max = names.size(); max_id = i; } + for (const auto& layout : names) { + const auto name = layout.asString(); + if (std::find(used_layouts.begin(), used_layouts.end(), name) == used_layouts.end()) { + used_layouts.push_back(name); + } + } } - for (const auto& layout : payload[max_id][XKB_LAYOUT_NAMES_KEY]) { - used_layouts.push_back(layout.asString()); - } - + // Rebuild from scratch so init_layouts_map's duplicate-suffix pass doesn't compound across + // refreshes (e.g. "us" -> "us1" -> "us11"). + layouts_map_.clear(); init_layouts_map(used_layouts); set_current_layout(payload[max_id][XKB_ACTIVE_LAYOUT_NAME_KEY].asString()); dp.emit(); @@ -82,13 +89,26 @@ void Language::onEvent(const struct Ipc::ipc_response& res) { return; } + bool refresh_inputs = false; try { - std::lock_guard lock(mutex_); - auto payload = parser_.parse(res.payload)["input"]; - if (payload["type"].asString() == "keyboard") { - set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString()); + { + std::lock_guard lock(mutex_); + auto root = parser_.parse(res.payload); + auto change = root["change"].asString(); + auto payload = root["input"]; + if (payload["type"].asString() == "keyboard") { + // A device was added or its keymap changed - the layout set may have grown, so refresh + // layouts_map_ via IPC_GET_INPUTS once we've released mutex_. + refresh_inputs = (change == "added" || change == "xkb_keymap"); + set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString()); + } + dp.emit(); + } + // sendCmd is synchronous: it blocks on the IPC reply and then emits signal_cmd on this same + // thread, which lands in onCmd and re-locks mutex_. Must call it with mutex_ released. + if (refresh_inputs) { + ipc_.sendCmd(IPC_GET_INPUTS); } - dp.emit(); } catch (const std::exception& e) { spdlog::error("Language: {}", e.what()); } @@ -125,8 +145,15 @@ auto Language::update() -> void { } auto Language::set_current_layout(const std::string& current_layout) -> void { + // Guard against unknown / empty layout names: transient virtual keyboards (e.g. wtype) and + // hot-plugged devices whose layouts haven't made it into the map yet would otherwise blank out + // layout_ via map::operator[]'s default-construct-on-miss. + auto it = layouts_map_.find(current_layout); + if (it == layouts_map_.end()) { + return; + } label_.get_style_context()->remove_class(layout_.short_name); - layout_ = layouts_map_[current_layout]; + layout_ = it->second; label_.get_style_context()->add_class(layout_.short_name); } From 51becf2fbcf19bca2e7079657ca1d1a40647a43e Mon Sep 17 00:00:00 2001 From: Jack Barnes Date: Sat, 16 May 2026 13:03:53 +0800 Subject: [PATCH 696/780] feat(niri/workspaces): Add `enable-bar-scroll` option --- include/modules/niri/workspaces.hpp | 1 + man/waybar-niri-workspaces.5.scd | 5 ++++ src/modules/niri/workspaces.cpp | 45 +++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/include/modules/niri/workspaces.hpp b/include/modules/niri/workspaces.hpp index 08986412..01840b33 100644 --- a/include/modules/niri/workspaces.hpp +++ b/include/modules/niri/workspaces.hpp @@ -20,6 +20,7 @@ class Workspaces : public AModule, public EventHandler { void doUpdate(); Gtk::Button& addButton(const Json::Value& ws); std::string getIcon(const std::string& value, const Json::Value& ws); + bool handleScroll(GdkEventScroll* /*unused*/) override; const Bar& bar_; Gtk::Box box_; diff --git a/man/waybar-niri-workspaces.5.scd b/man/waybar-niri-workspaces.5.scd index 4be85eb3..ec329404 100644 --- a/man/waybar-niri-workspaces.5.scd +++ b/man/waybar-niri-workspaces.5.scd @@ -31,6 +31,11 @@ Addressed by *niri/workspaces* default: false ++ If set to false, you can click to change workspace. If set to true this behaviour is disabled. +*enable-bar-scroll*: ++ + typeof: bool ++ + default: false ++ + If set to false, you can't scroll to cycle throughout workspaces from the entire bar. If set to true this behaviour is enabled. + *disable-markup*: ++ typeof: bool ++ default: false ++ diff --git a/src/modules/niri/workspaces.cpp b/src/modules/niri/workspaces.cpp index 97d15215..97472260 100644 --- a/src/modules/niri/workspaces.cpp +++ b/src/modules/niri/workspaces.cpp @@ -22,6 +22,12 @@ Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& gIPC->registerForIPC("WorkspaceActiveWindowChanged", this); gIPC->registerForIPC("WorkspaceUrgencyChanged", this); + if (config["enable-bar-scroll"].asBool()) { + auto& window = const_cast(bar_).window; + window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); + window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll)); + } + dp.emit(); } @@ -198,4 +204,43 @@ std::string Workspaces::getIcon(const std::string& value, const Json::Value& ws) return value; } +bool Workspaces::handleScroll(GdkEventScroll* e) { + if (gdk_event_get_pointer_emulated((GdkEvent*)e) != 0) { + /** + * Ignore emulated scroll events on window + */ + return false; + } + + auto dir = AModule::getScrollDir(e); + if (dir == SCROLL_DIR::NONE) { + return true; + } + + try { + Json::Value request(Json::objectValue); + auto& action = (request["Action"] = Json::Value(Json::objectValue)); + + std::string action_name; + + if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) { + action_name = "FocusWorkspaceDown"; + } else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) { + action_name = "FocusWorkspaceUp"; + } else { + return true; + } + + action[action_name] = Json::Value(Json::objectValue); + + IPC::send(request); + + } catch (const std::exception& e) { + spdlog::error("Workspaces: {}", e.what()); + return false; + } + + return true; +} + } // namespace waybar::modules::niri From cdb792af41e78d01263c2152d8b5050c40705ea9 Mon Sep 17 00:00:00 2001 From: Tom Benham Date: Sat, 16 May 2026 18:03:09 +0200 Subject: [PATCH 697/780] fix(hyprland/workspaces): fix IPC call when using `"move-to-monitor": true` --- src/modules/hyprland/backend.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp index 08cf97c1..f38f8216 100644 --- a/src/modules/hyprland/backend.cpp +++ b/src/modules/hyprland/backend.cpp @@ -320,7 +320,7 @@ std::string IPC::buildLuaDispatch(const std::string& dispatcher, const std::stri // New format: /dispatch hl.dsp.focus({ workspace = "1" }) // // Old format: dispatch focusworkspaceoncurrentmonitor 2 - // New format: /dispatch hl.dsp.focus({ workspace = "2", monitor = "current" }) + // New format: /dispatch hl.dsp.focus({ workspace = "2", on_current_monitor = true }) // // Old format: dispatch togglespecialworkspace name // New format: /dispatch hl.dsp.workspace.toggle_special("name") @@ -329,7 +329,7 @@ std::string IPC::buildLuaDispatch(const std::string& dispatcher, const std::stri return "/dispatch hl.dsp.focus({ workspace = \"" + arg + "\" })"; } if (dispatcher == "focusworkspaceoncurrentmonitor") { - return "/dispatch hl.dsp.focus({ workspace = \"" + arg + "\", monitor = \"current\" })"; + return "/dispatch hl.dsp.focus({ workspace = \"" + arg + "\", on_current_monitor = true })"; } if (dispatcher == "togglespecialworkspace") { if (arg.empty()) { From 40c9d92e3de1ee287da1ac4901264d1571fe803d Mon Sep 17 00:00:00 2001 From: Tom Benham Date: Sat, 16 May 2026 21:29:23 +0200 Subject: [PATCH 698/780] test(hyprland): fix dispatch test --- test/hyprland/backend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hyprland/backend.cpp b/test/hyprland/backend.cpp index 62d23ae4..f2e10dae 100644 --- a/test/hyprland/backend.cpp +++ b/test/hyprland/backend.cpp @@ -160,7 +160,7 @@ TEST_CASE("buildLuaDispatch focusworkspaceoncurrentmonitor", "[buildLuaDispatch] IPCTestHelper::buildLuaDispatch("focusworkspaceoncurrentmonitor", "3"); REQUIRE( result == - "/dispatch hl.dsp.focus({ workspace = \"3\", monitor = \"current\" })"); + "/dispatch hl.dsp.focus({ workspace = \"3\", on_current_monitor = true })"); } TEST_CASE("buildLuaDispatch togglespecialworkspace", "[buildLuaDispatch]") { From 7e2afaa5b3d78ae5c526e4d95708c9e9f52bb0e5 Mon Sep 17 00:00:00 2001 From: Desuwa Date: Sat, 16 May 2026 14:05:17 -0700 Subject: [PATCH 699/780] fix: mpd specify timer interval in seconds --- include/modules/mpd/state.inl.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/modules/mpd/state.inl.hpp b/include/modules/mpd/state.inl.hpp index 895970e6..a55a7948 100644 --- a/include/modules/mpd/state.inl.hpp +++ b/include/modules/mpd/state.inl.hpp @@ -1,13 +1,14 @@ #pragma once namespace detail { +using namespace std::literals::chrono_literals; inline bool Context::is_connected() const { return mpd_module_->connection_ != nullptr; } inline bool Context::is_playing() const { return mpd_module_->playing(); } inline bool Context::is_paused() const { return mpd_module_->paused(); } inline bool Context::is_stopped() const { return mpd_module_->stopped(); } -constexpr inline std::size_t Context::interval() const { return mpd_module_->interval_.count(); } +constexpr inline std::size_t Context::interval() const { return mpd_module_->interval_ / 1s; } inline void Context::tryConnect() const { mpd_module_->tryConnect(); } inline unique_connection& Context::connection() { return mpd_module_->connection_; } constexpr inline mpd_state Context::state() const { return mpd_module_->state_; } From 546b53d6759c2b7cc54051889d99fccaa6c57295 Mon Sep 17 00:00:00 2001 From: Desuwa Date: Sat, 16 May 2026 15:41:09 -0700 Subject: [PATCH 700/780] mpd: Implement idle events for Playing state --- include/modules/mpd/state.hpp | 4 ++ src/modules/mpd/state.cpp | 114 +++++++++++++++++++++++++++++----- 2 files changed, 102 insertions(+), 16 deletions(-) diff --git a/include/modules/mpd/state.hpp b/include/modules/mpd/state.hpp index 2c9071b4..35e8dd39 100644 --- a/include/modules/mpd/state.hpp +++ b/include/modules/mpd/state.hpp @@ -82,6 +82,7 @@ class Idle : public State { class Playing : public State { Context* const ctx_; sigc::connection timer_connection_; + sigc::connection idle_connection_; public: Playing(Context* const ctx) : ctx_{ctx} {} @@ -98,7 +99,10 @@ class Playing : public State { Playing(Playing const&) = delete; Playing& operator=(Playing const&) = delete; + void timer() noexcept; + void idle() noexcept; bool on_timer(); + bool on_io(Glib::IOCondition const&); }; class Paused : public State { diff --git a/src/modules/mpd/state.cpp b/src/modules/mpd/state.cpp index 3d7c8561..80b4933d 100644 --- a/src/modules/mpd/state.cpp +++ b/src/modules/mpd/state.cpp @@ -19,13 +19,13 @@ auto format_as(enum mpd_idle val) { namespace waybar::modules::detail { -#define IDLE_RUN_NOIDLE_AND_CMD(...) \ +#define RUN_NOIDLE_AND_CMD(STATE, ...) \ if (idle_connection_.connected()) { \ idle_connection_.disconnect(); \ auto conn = ctx_->connection().get(); \ if (!mpd_run_noidle(conn)) { \ if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { \ - spdlog::error("mpd: Idle: failed to unregister for IDLE events"); \ + spdlog::error("mpd: STATE: failed to unregister for IDLE events"); \ ctx_->checkErrors(conn); \ } \ } \ @@ -33,24 +33,23 @@ namespace waybar::modules::detail { } void Idle::play() { - IDLE_RUN_NOIDLE_AND_CMD(mpd_run_play(conn)); + RUN_NOIDLE_AND_CMD(Idle, mpd_run_play(conn)); ctx_->setState(std::make_unique(ctx_)); } void Idle::pause() { - IDLE_RUN_NOIDLE_AND_CMD(mpd_run_pause(conn, true)); + RUN_NOIDLE_AND_CMD(Idle, mpd_run_pause(conn, true)); ctx_->setState(std::make_unique(ctx_)); } void Idle::stop() { - IDLE_RUN_NOIDLE_AND_CMD(mpd_run_stop(conn)); + RUN_NOIDLE_AND_CMD(Idle, mpd_run_stop(conn)); ctx_->setState(std::make_unique(ctx_)); } -#undef IDLE_RUN_NOIDLE_AND_CMD void Idle::update() noexcept { // This is intentionally blank. @@ -118,18 +117,50 @@ bool Idle::on_io(Glib::IOCondition const&) { } void Playing::entry() noexcept { - sigc::slot timer_slot = sigc::mem_fun(*this, &Playing::on_timer); - timer_connection_ = Glib::signal_timeout().connect_seconds(timer_slot, 1); + timer(); + idle(); spdlog::debug("mpd: Playing: enabled 1 second periodic timer."); } void Playing::exit() noexcept { + if (idle_connection_.connected()) { + idle_connection_.disconnect(); + spdlog::debug("mpd: Playing: unwatching FD"); + } + if (timer_connection_.connected()) { timer_connection_.disconnect(); spdlog::debug("mpd: Playing: disabled 1 second periodic timer."); } } +void Playing::timer() noexcept { + if (timer_connection_.connected()) { + timer_connection_.disconnect(); + } + + sigc::slot timer_slot = sigc::mem_fun(*this, &Playing::on_timer); + timer_connection_ = Glib::signal_timeout().connect_seconds(timer_slot, 1); +} + + +void Playing::idle() noexcept { + auto conn = ctx_->connection().get(); + assert(conn != nullptr); + + if (!mpd_send_idle_mask( + conn, static_cast(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) { + ctx_->checkErrors(conn); + spdlog::error("mpd: Playing: failed to register for IDLE events"); + } else if (!idle_connection_.connected()) { + spdlog::trace("mpd: Playing: watching FD"); + sigc::slot idle_slot = sigc::mem_fun(*this, &Playing::on_io); + idle_connection_ = + Glib::signal_io().connect(idle_slot, mpd_connection_get_fd(conn), + Glib::IO_IN | Glib::IO_PRI | Glib::IO_ERR | Glib::IO_HUP); + } +} + bool Playing::on_timer() { // Attempt to connect with MPD. try { @@ -141,6 +172,8 @@ bool Playing::on_timer() { return false; } + RUN_NOIDLE_AND_CMD(Playing); + ctx_->fetchState(); if (!ctx_->is_playing()) { @@ -154,6 +187,50 @@ bool Playing::on_timer() { ctx_->queryMPD(); ctx_->emit(); + + idle(); + } catch (std::exception const& e) { + spdlog::warn("mpd: Playing: error: {}", e.what()); + ctx_->setState(std::make_unique(ctx_)); + return false; + } + + return true; +} + +bool Playing::on_io(Glib::IOCondition const&) { + auto conn = ctx_->connection().get(); + + // callback should do this: + enum mpd_idle events = mpd_recv_idle(conn, /* ignore_timeout?= */ false); + spdlog::debug("mpd: Playing: recv_idle events -> {}", events); + + mpd_response_finish(conn); + try { + ctx_->checkErrors(conn); + + ctx_->fetchState(); + + if (!ctx_->is_playing()) { + if (ctx_->is_paused()) { + ctx_->setState(std::make_unique(ctx_)); + } else { + ctx_->setState(std::make_unique(ctx_)); + } + return false; + } + + ctx_->queryMPD(); + ctx_->emit(); + + if (!mpd_send_idle_mask( + conn, static_cast(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) { + ctx_->checkErrors(conn); + spdlog::error("mpd: Playing: failed to register for IDLE events"); + } + + // Defer the next timer + timer(); } catch (std::exception const& e) { spdlog::warn("mpd: Playing: error: {}", e.what()); ctx_->setState(std::make_unique(ctx_)); @@ -164,21 +241,25 @@ bool Playing::on_timer() { } void Playing::stop() { - if (timer_connection_.connected()) { - timer_connection_.disconnect(); + RUN_NOIDLE_AND_CMD(Playing, + if (timer_connection_.connected()) { + timer_connection_.disconnect(); - mpd_run_stop(ctx_->connection().get()); - } + mpd_run_stop(ctx_->connection().get()); + } + ); ctx_->setState(std::make_unique(ctx_)); } void Playing::pause() { - if (timer_connection_.connected()) { - timer_connection_.disconnect(); + RUN_NOIDLE_AND_CMD(Playing, + if (timer_connection_.connected()) { + timer_connection_.disconnect(); - mpd_run_pause(ctx_->connection().get(), true); - } + mpd_run_pause(ctx_->connection().get(), true); + } + ); ctx_->setState(std::make_unique(ctx_)); } @@ -387,4 +468,5 @@ bool Disconnected::on_timer() { void Disconnected::update() noexcept { ctx_->do_update(); } +#undef RUN_NOIDLE_AND_CMD } // namespace waybar::modules::detail From f7001410d2fa25792390a2fa4d48b6fa6a23881b Mon Sep 17 00:00:00 2001 From: Desuwa Date: Sat, 16 May 2026 16:32:42 -0700 Subject: [PATCH 701/780] mpd: ensure emit is called after state changes --- src/modules/mpd/state.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/modules/mpd/state.cpp b/src/modules/mpd/state.cpp index 80b4933d..9b22eb4e 100644 --- a/src/modules/mpd/state.cpp +++ b/src/modules/mpd/state.cpp @@ -96,19 +96,16 @@ bool Idle::on_io(Glib::IOCondition const&) { } ctx_->fetchState(); + ctx_->emit(); mpd_state state = ctx_->state(); if (state == MPD_STATE_STOP) { - ctx_->emit(); ctx_->setState(std::make_unique(ctx_)); } else if (state == MPD_STATE_PLAY) { - ctx_->emit(); ctx_->setState(std::make_unique(ctx_)); } else if (state == MPD_STATE_PAUSE) { - ctx_->emit(); ctx_->setState(std::make_unique(ctx_)); } else { - ctx_->emit(); // self transition ctx_->setState(std::make_unique(ctx_)); } @@ -177,6 +174,7 @@ bool Playing::on_timer() { ctx_->fetchState(); if (!ctx_->is_playing()) { + ctx_->emit(); if (ctx_->is_paused()) { ctx_->setState(std::make_unique(ctx_)); } else { @@ -212,6 +210,7 @@ bool Playing::on_io(Glib::IOCondition const&) { ctx_->fetchState(); if (!ctx_->is_playing()) { + ctx_->emit(); if (ctx_->is_paused()) { ctx_->setState(std::make_unique(ctx_)); } else { @@ -293,7 +292,6 @@ bool Paused::on_timer() { } ctx_->fetchState(); - ctx_->emit(); if (ctx_->is_paused()) { @@ -364,7 +362,6 @@ bool Stopped::on_timer() { } ctx_->fetchState(); - ctx_->emit(); if (ctx_->is_stopped()) { From ff76e96786cbdd0f586bc9c97e88a414635f2365 Mon Sep 17 00:00:00 2001 From: Adam Druzd Date: Sun, 17 May 2026 09:12:08 +0200 Subject: [PATCH 702/780] fix(hyprland): keep workspace hover over taskbar icons --- include/modules/hyprland/workspace.hpp | 11 +++ src/modules/hyprland/workspace.cpp | 102 +++++++++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/include/modules/hyprland/workspace.hpp b/include/modules/hyprland/workspace.hpp index 1d80d331..5730dda9 100644 --- a/include/modules/hyprland/workspace.hpp +++ b/include/modules/hyprland/workspace.hpp @@ -45,6 +45,15 @@ class Workspace { bool isUrgent() const { return m_isUrgent; }; bool handleClicked(GdkEventButton* bt) const; + + bool handleEnter(GdkEventCrossing* event); + bool handleLeave(GdkEventCrossing* event); + + void startHoverCheck(); + void stopHoverCheck(); + bool syncHoverClass(); + bool pointerInsideButton(); + void setActive(bool value = true) { m_isActive = value; }; void setPersistentRule(bool value = true) { m_isPersistentRule = value; }; void setPersistentConfig(bool value = true) { m_isPersistentConfig = value; }; @@ -80,6 +89,8 @@ class Workspace { bool m_isUrgent = false; bool m_isVisible = false; + sigc::connection m_hoverCheckConnection; + std::vector m_windowMap; Gtk::Button m_button; diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 753893f2..67ef4506 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -30,6 +31,11 @@ Workspace::Workspace(const Json::Value& workspace_data, Workspaces& workspace_ma } m_button.add_events(Gdk::BUTTON_PRESS_MASK); + m_button.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK); + + m_button.signal_enter_notify_event().connect(sigc::mem_fun(*this, &Workspace::handleEnter)); + m_button.signal_leave_notify_event().connect(sigc::mem_fun(*this, &Workspace::handleLeave)); + m_button.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handleClicked), false); @@ -66,6 +72,102 @@ std::optional Workspace::closeWindow(WindowAddress const& addr) { return std::nullopt; } +bool Workspace::pointerInsideButton() { + auto display = Gdk::Display::get_default(); + if (!display) { + return false; + } + + auto seat = display->get_default_seat(); + if (!seat) { + return false; + } + + auto pointer = seat->get_pointer(); + if (!pointer) { + return false; + } + + Glib::RefPtr screen; + int pointerRootX = 0; + int pointerRootY = 0; + + pointer->get_position(screen, pointerRootX, pointerRootY); + + Gtk::Widget* toplevel = m_button.get_toplevel(); + if (toplevel == nullptr || !toplevel->get_window()) { + return false; + } + + int buttonX = 0; + int buttonY = 0; + + if (!m_button.translate_coordinates(*toplevel, 0, 0, buttonX, buttonY)) { + return false; + } + + int windowRootX = 0; + int windowRootY = 0; + toplevel->get_window()->get_root_origin(windowRootX, windowRootY); + + const auto allocation = m_button.get_allocation(); + + const int buttonRootX = windowRootX + buttonX; + const int buttonRootY = windowRootY + buttonY; + const int buttonWidth = allocation.get_width(); + const int buttonHeight = allocation.get_height(); + + return pointerRootX >= buttonRootX && pointerRootY >= buttonRootY && + pointerRootX < buttonRootX + buttonWidth && + pointerRootY < buttonRootY + buttonHeight; +} + +bool Workspace::syncHoverClass() { + auto styleContext = m_button.get_style_context(); + + if (pointerInsideButton()) { + styleContext->add_class("workspace-hover"); + return true; + } + + styleContext->remove_class("workspace-hover"); + stopHoverCheck(); + return false; +} + +void Workspace::startHoverCheck() { + if (m_hoverCheckConnection.connected()) { + return; + } + + m_hoverCheckConnection = Glib::signal_timeout().connect( + sigc::mem_fun(*this, &Workspace::syncHoverClass), + 50); +} + +void Workspace::stopHoverCheck() { + if (m_hoverCheckConnection.connected()) { + m_hoverCheckConnection.disconnect(); + } +} + +bool Workspace::handleEnter(GdkEventCrossing* /*event*/) { + m_button.get_style_context()->add_class("workspace-hover"); + startHoverCheck(); + return false; +} + +bool Workspace::handleLeave(GdkEventCrossing* /*event*/) { + /* + * Do not remove immediately. + * Workspace taskbar children can fire misleading leave events while the + * pointer is still visually inside the workspace button. + * + * The polling check will remove the class once the pointer really leaves. + */ + startHoverCheck(); + return false; +} bool Workspace::handleClicked(GdkEventButton* bt) const { if (bt->type == GDK_BUTTON_PRESS) { try { From 0351ac8fb2d0f204f67f9b2ddbe7a1c8d54ba413 Mon Sep 17 00:00:00 2001 From: finitemonkey <56678540+finitemonkey@users.noreply.github.com> Date: Sat, 16 May 2026 20:40:01 +0100 Subject: [PATCH 703/780] Additional options when muted for the PulseAudio-Slider module: - Add "muted" class for styling the muted state. - Add "zero-on-mute" boolean option to control slider position when muted. - Add "unmute-on-volume-change" boolean option to control whether to automatically unmute when the volume changes. --- include/modules/pulseaudio_slider.hpp | 16 +++-- include/util/audio_backend.hpp | 11 +++- man/waybar-pulseaudio-slider.5.scd | 28 +++++++-- src/modules/pulseaudio_slider.cpp | 84 ++++++++++----------------- src/util/audio_backend.cpp | 24 ++++++++ 5 files changed, 98 insertions(+), 65 deletions(-) diff --git a/include/modules/pulseaudio_slider.hpp b/include/modules/pulseaudio_slider.hpp index 3ef44684..c9f82341 100644 --- a/include/modules/pulseaudio_slider.hpp +++ b/include/modules/pulseaudio_slider.hpp @@ -6,10 +6,6 @@ #include "util/audio_backend.hpp" namespace waybar::modules { -enum class PulseaudioSliderTarget { - Sink, - Source, -}; class PulseaudioSlider : public ASlider { public: @@ -21,7 +17,15 @@ class PulseaudioSlider : public ASlider { private: std::shared_ptr backend = nullptr; - PulseaudioSliderTarget target = PulseaudioSliderTarget::Sink; + util::PulseaudioTarget target = util::PulseaudioTarget::Sink; + + bool zero_on_mute = true; + bool unmute_on_volume_change = true; + // zero_on_mute and unmute_on_volume_change default to true + // in order to maintain previous behaviour when using a + // config in which these values are undefined + + bool previously_muted = false; }; -} // namespace waybar::modules \ No newline at end of file +} // namespace waybar::modules diff --git a/include/util/audio_backend.hpp b/include/util/audio_backend.hpp index 3737ae26..f5904b43 100644 --- a/include/util/audio_backend.hpp +++ b/include/util/audio_backend.hpp @@ -14,6 +14,11 @@ namespace waybar::util { +enum class PulseaudioTarget { + Sink, + Source, +}; + class AudioBackend { private: static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*); @@ -92,7 +97,11 @@ class AudioBackend { void toggleSourceMute(); void toggleSourceMute(bool); + uint16_t getVolume(PulseaudioTarget) const; + bool getMuted(PulseaudioTarget) const; + void unmute(PulseaudioTarget); + bool isBluetooth(); }; -} // namespace waybar::util \ No newline at end of file +} // namespace waybar::util diff --git a/man/waybar-pulseaudio-slider.5.scd b/man/waybar-pulseaudio-slider.5.scd index 0271e7c5..3f56a80c 100644 --- a/man/waybar-pulseaudio-slider.5.scd +++ b/man/waybar-pulseaudio-slider.5.scd @@ -28,9 +28,20 @@ The volume can be controlled by dragging the slider across the bar or clicking o The orientation of the slider. Can be either `horizontal` or `vertical`. *expand*: ++ - typeof: bool ++ - default: false ++ - Enables this module to consume all left over space dynamically. + typeof: bool ++ + default: false ++ + Enables this module to consume all left over space dynamically. + +*zero-on-mute*: ++ + typeof: bool ++ + default: true ++ + `true` = The slider will be set to `min` when the source/sink is muted. ++ + `false` = The slider will continue to show the unmuted volume level when the source/sink is muted. + +*unmute-on-volume-change*: ++ + typeof: bool ++ + default: true ++ + Specifies whether to unmute a muted souce/sink when its volume is changed by the user moving the slider. # EXAMPLES @@ -41,7 +52,9 @@ The volume can be controlled by dragging the slider across the bar or clicking o "pulseaudio/slider": { "min": 0, "max": 100, - "orientation": "horizontal" + "orientation": "horizontal", + "zero-on-mute": false, + "unmute-on-volume-change": false } ``` @@ -52,6 +65,9 @@ The slider is a component with multiple CSS Nodes, of which the following are ex *#pulseaudio-slider*: ++ Controls the style of the box *around* the slider and bar. +*#pulseaudio-slider.muted*: ++ + Controls the style when the audio source/sink is muted. + *#pulseaudio-slider slider*: ++ Controls the style of the slider handle. @@ -85,4 +101,8 @@ The slider is a component with multiple CSS Nodes, of which the following are ex border-radius: 5px; background: green; } + +#pulseaudio-slider.muted highlight { + background-color: orange; +} ``` diff --git a/src/modules/pulseaudio_slider.cpp b/src/modules/pulseaudio_slider.cpp index bf85584e..aeddeb24 100644 --- a/src/modules/pulseaudio_slider.cpp +++ b/src/modules/pulseaudio_slider.cpp @@ -10,73 +10,49 @@ PulseaudioSlider::PulseaudioSlider(const std::string& id, const Json::Value& con if (config_["target"].isString()) { std::string target = config_["target"].asString(); if (target == "sink") { - this->target = PulseaudioSliderTarget::Sink; + this->target = util::PulseaudioTarget::Sink; } else if (target == "source") { - this->target = PulseaudioSliderTarget::Source; + this->target = util::PulseaudioTarget::Source; } } + + if (config_["zero-on-mute"].isBool()) { + zero_on_mute = config_["zero-on-mute"].asBool(); + } + + if (config_["unmute-on-volume-change"].isBool()) { + unmute_on_volume_change = config_["unmute-on-volume-change"].asBool(); + } } void PulseaudioSlider::update() { - switch (target) { - case PulseaudioSliderTarget::Sink: - if (backend->getSinkMuted()) { - scale_.set_value(min_); - } else { - scale_.set_value(backend->getSinkVolume()); - } - break; + uint16_t display_value = backend->getVolume(target); + bool is_muted = backend->getMuted(target); - case PulseaudioSliderTarget::Source: - if (backend->getSourceMuted()) { - scale_.set_value(min_); - } else { - scale_.set_value(backend->getSourceVolume()); - } - break; + if (is_muted && !previously_muted) { + if (zero_on_mute) { + display_value = min_; + } + scale_.get_style_context()->add_class("muted"); + } else if (previously_muted && !is_muted) { + scale_.get_style_context()->remove_class("muted"); } + + scale_.set_value(display_value); + + previously_muted = is_muted; } void PulseaudioSlider::onValueChanged() { - bool is_mute = false; + uint16_t slider_value = scale_.get_value(); - switch (target) { - case PulseaudioSliderTarget::Sink: - if (backend->getSinkMuted()) { - is_mute = true; - } - break; - - case PulseaudioSliderTarget::Source: - if (backend->getSourceMuted()) { - is_mute = true; - } - break; - } - - uint16_t volume = scale_.get_value(); - - if (is_mute) { - // Avoid setting sink/source to volume 0 if the user muted if via another mean. - if (volume == 0) { - return; - } - - // If the sink/source is mute, but the user clicked the slider, unmute it! - else { - switch (target) { - case PulseaudioSliderTarget::Sink: - backend->toggleSinkMute(false); - break; - - case PulseaudioSliderTarget::Source: - backend->toggleSourceMute(false); - break; - } + // Avoid setting sink/source to volume 0 if the user muted it via other means. + if (!backend->getMuted(target) || slider_value != 0) { + if (unmute_on_volume_change) { + backend->unmute(target); } + backend->changeVolume(slider_value, min_, max_); } - - backend->changeVolume(volume, min_, max_); } -} // namespace waybar::modules \ No newline at end of file +} // namespace waybar::modules diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index 4087e096..d617813d 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -259,6 +259,14 @@ void AudioBackend::serverInfoCb(pa_context* context, const pa_server_info* i, vo pa_context_get_source_info_list(context, sourceInfoCb, data); } +uint16_t AudioBackend::getVolume(PulseaudioTarget target) const { + if (target == PulseaudioTarget::Source) { + return source_volume_; + } else { + return volume_; + } +} + void AudioBackend::changeVolume(uint16_t volume, uint16_t min_volume, uint16_t max_volume) { // Early return if context is not ready if ((context_ == nullptr) || pa_context_get_state(context_) != PA_CONTEXT_READY) { @@ -368,6 +376,14 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma pa_threaded_mainloop_unlock(mainloop_); } +bool AudioBackend::getMuted(PulseaudioTarget target) const { + if (target == PulseaudioTarget::Source) { + return source_muted_; + } else { + return muted_; + } +} + void AudioBackend::toggleSinkMute() { if (context_ == nullptr || pa_context_get_state(context_) != PA_CONTEXT_READY) return; muted_ = !muted_; @@ -404,6 +420,14 @@ void AudioBackend::toggleSourceMute(bool mute) { pa_threaded_mainloop_unlock(mainloop_); } +void AudioBackend::unmute(PulseaudioTarget target) { + if (target == PulseaudioTarget::Source) { + toggleSourceMute(false); + } else { + toggleSinkMute(false); + } +} + bool AudioBackend::isBluetooth() { return monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio monitor_.find("a2dp-sink") != std::string::npos || // PipeWire From 23e635acf8f1cd2e87674357aae319d5078820ea Mon Sep 17 00:00:00 2001 From: finitemonkey <56678540+finitemonkey@users.noreply.github.com> Date: Sat, 16 May 2026 21:49:36 +0100 Subject: [PATCH 704/780] Add missing documentation for the "target" option of the PulseAudio-Slider module. --- man/waybar-pulseaudio-slider.5.scd | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/man/waybar-pulseaudio-slider.5.scd b/man/waybar-pulseaudio-slider.5.scd index 0271e7c5..729fe910 100644 --- a/man/waybar-pulseaudio-slider.5.scd +++ b/man/waybar-pulseaudio-slider.5.scd @@ -22,6 +22,12 @@ The volume can be controlled by dragging the slider across the bar or clicking o default: 100 ++ The maximum volume value the slider should display and set. +*target*: ++ + typeof: string ++ + default: sink ++ + Whether the slider will control the default audio source or default audio sink. ++ + Valid values are either "sink" or "source". + *orientation*: ++ typeof: string ++ default: horizontal ++ From 6bdafede071c2e18711bfb3804f852208debd48b Mon Sep 17 00:00:00 2001 From: Ivan Ribeiro Date: Tue, 19 May 2026 21:21:56 +0100 Subject: [PATCH 705/780] feat(cpu): add {icons} format replacement for all per-core icons Concatenates all per-core icons into a single {icons} placeholder, so configs work across machines with different core counts without manually specifying {icon0}{icon1}...{iconN}. Closes #4240 --- man/waybar-cpu.5.scd | 12 ++++++++++++ src/modules/cpu.cpp | 6 +++++- src/modules/cpu_usage/common.cpp | 6 +++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/man/waybar-cpu.5.scd b/man/waybar-cpu.5.scd index 40682372..5dd4a893 100644 --- a/man/waybar-cpu.5.scd +++ b/man/waybar-cpu.5.scd @@ -106,6 +106,8 @@ The *cpu* module displays the current CPU utilization. *{icon*{n}*}*: Icon for CPU core n usage. Use like {icon0}. +*{icon0}{icon1}{icon2}{icon3}*: All per-core icons concatenated. Equivalent to {icon0}{icon1}...{icon*N*} but adapts to the number of cores automatically. + # EXAMPLES Basic configuration: @@ -128,6 +130,16 @@ CPU usage per core rendered as icons: }, ``` +Automatically determine number of icons according to number of logical cores: + +``` +"cpu": { + "interval": 1, + "format": "{icons} {usage:>2}% ", + "format-icons": ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"], +}, +``` + # STYLE - *#cpu* diff --git a/src/modules/cpu.cpp b/src/modules/cpu.cpp index 8cfda2c1..9f3245f0 100644 --- a/src/modules/cpu.cpp +++ b/src/modules/cpu.cpp @@ -48,13 +48,17 @@ auto waybar::modules::Cpu::update() -> void { store.push_back(fmt::arg("avg_frequency", avg_frequency)); std::vector arg_names; arg_names.reserve(cpu_usage.size() * 2); + std::string all_icons; for (size_t i = 1; i < cpu_usage.size(); ++i) { auto core_i = i - 1; arg_names.push_back(fmt::format("usage{}", core_i)); store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i])); + auto core_icon = getIcon(cpu_usage[i], icons); + all_icons += core_icon; arg_names.push_back(fmt::format("icon{}", core_i)); - store.push_back(fmt::arg(arg_names.back().c_str(), getIcon(cpu_usage[i], icons))); + store.push_back(fmt::arg(arg_names.back().c_str(), core_icon)); } + store.push_back(fmt::arg("icons", all_icons)); label_.set_markup(fmt::vformat(format, store)); if (tooltipEnabled()) { diff --git a/src/modules/cpu_usage/common.cpp b/src/modules/cpu_usage/common.cpp index 1571cb74..79ce6941 100644 --- a/src/modules/cpu_usage/common.cpp +++ b/src/modules/cpu_usage/common.cpp @@ -38,13 +38,17 @@ auto waybar::modules::CpuUsage::update() -> void { store.push_back(fmt::arg("icon", getIcon(total_usage, icons))); std::vector arg_names; arg_names.reserve(cpu_usage.size() * 2); + std::string all_icons; for (size_t i = 1; i < cpu_usage.size(); ++i) { auto core_i = i - 1; arg_names.push_back(fmt::format("usage{}", core_i)); store.push_back(fmt::arg(arg_names.back().c_str(), cpu_usage[i])); + auto core_icon = getIcon(cpu_usage[i], icons); + all_icons += core_icon; arg_names.push_back(fmt::format("icon{}", core_i)); - store.push_back(fmt::arg(arg_names.back().c_str(), getIcon(cpu_usage[i], icons))); + store.push_back(fmt::arg(arg_names.back().c_str(), core_icon)); } + store.push_back(fmt::arg("icons", all_icons)); label_.set_markup(fmt::vformat(format, store)); if (tooltipEnabled()) { From 876eaaf70f9ec9d42992849e0cb44533637a7f8c Mon Sep 17 00:00:00 2001 From: Francois Lachese Date: Wed, 20 May 2026 15:57:38 +0200 Subject: [PATCH 706/780] fix(amodule): prevent crash when cursor timeout fires after destruction --- include/AModule.hpp | 1 + src/AModule.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/include/AModule.hpp b/include/AModule.hpp index a338ffe3..7119b2f6 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -57,6 +57,7 @@ class AModule : public IModule { bool hasUserEvents_; gdouble distance_scrolled_y_; gdouble distance_scrolled_x_; + sigc::connection cursor_timeout_conn_; std::map eventActionMap_; static const inline std::map, std::string> eventMap_{ {std::make_pair(1, GdkEventType::GDK_BUTTON_PRESS), "on-click"}, diff --git a/src/AModule.cpp b/src/AModule.cpp index a5ba69d3..ea9fd2b4 100644 --- a/src/AModule.cpp +++ b/src/AModule.cpp @@ -17,7 +17,8 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std:: isTooltip{config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true}, isExpand{config_["expand"].isBool() ? config_["expand"].asBool() : false}, distance_scrolled_y_(0.0), - distance_scrolled_x_(0.0) { + distance_scrolled_x_(0.0), + cursor_timeout_conn_() { // Configure module action Map const Json::Value actions{config_["actions"]}; @@ -83,6 +84,9 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std:: } AModule::~AModule() { + if (cursor_timeout_conn_.connected()) { + cursor_timeout_conn_.disconnect(); + } for (const auto& pid : pid_children_) { if (pid != -1) { killpg(pid, SIGTERM); @@ -118,7 +122,7 @@ void AModule::setCursor(Gdk::CursorType const& c) { } else { // window may not be accessible yet, in this case, // schedule another call for setting the cursor in 1 sec - Glib::signal_timeout().connect_seconds( + cursor_timeout_conn_ = Glib::signal_timeout().connect_seconds( [this, c]() { setCursor(c); return false; From fcf1fb798cf9674761a4d83a5c11ff15213e2006 Mon Sep 17 00:00:00 2001 From: Ezequiel Colunga Acha Date: Wed, 20 May 2026 15:06:41 -0300 Subject: [PATCH 707/780] fix(wlr/taskbar): let task titles shrink within available space The taskbar currently requests enough width for every task title, which can push right-side modules off-screen when many windows are open. Allow task buttons to share the taskbar allocation and ellipsize title labels instead of forcing the whole module to grow. This keeps neighboring modules visible while preserving the existing taskbar click actions and formatting behavior. Co-authored-by: Cursor --- src/modules/wlr/taskbar.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index bde90910..6ba41ce3 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -93,10 +93,20 @@ Task::Task(const waybar::Bar& bar, const Json::Value& config, Taskbar* tbar, zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this); button.set_relief(Gtk::RELIEF_NONE); + button.set_hexpand(true); + content_.set_hexpand(true); + text_before_.set_ellipsize(Pango::ELLIPSIZE_END); + text_before_.set_single_line_mode(true); + text_before_.set_width_chars(1); + text_before_.set_xalign(0.0); + text_after_.set_ellipsize(Pango::ELLIPSIZE_END); + text_after_.set_single_line_mode(true); + text_after_.set_width_chars(1); + text_after_.set_xalign(0.0); - content_.add(text_before_); - content_.add(icon_); - content_.add(text_after_); + content_.pack_start(text_before_, true, true, 0); + content_.pack_start(icon_, false, false, 0); + content_.pack_start(text_after_, true, true, 0); content_.show(); button.add(content_); @@ -718,7 +728,7 @@ void Taskbar::handle_finished() { } void Taskbar::add_button(Gtk::Button& bt) { - box_.pack_start(bt, false, false); + box_.pack_start(bt, true, true); box_.get_style_context()->remove_class("empty"); } From eb3b86c4d83a8e2e54eeca77a36861b5ccc8d432 Mon Sep 17 00:00:00 2001 From: buzz Date: Thu, 21 May 2026 17:02:39 +0200 Subject: [PATCH 708/780] fix(hyprland/workspaces): support on-scroll-up and on-scroll-down config options The handleScroll method unconditionally dispatched workspace cycling commands and never checked for custom on-scroll-up/on-scroll-down config values. Register the scroll handler when custom scroll commands are configured (even without enable-bar-scroll), and delegate to AModule::handleScroll so user commands are executed, matching the pattern used by other modules. --- src/modules/hyprland/workspaces.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 2496117f..c9fffc97 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -50,7 +50,8 @@ void Workspaces::init() { if (m_scrollEventConnection_.connected()) { m_scrollEventConnection_.disconnect(); } - if (barScroll()) { + bool hasScrollConfig = config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString(); + if (barScroll() || hasScrollConfig) { auto& window = const_cast(m_bar).window; window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); m_scrollEventConnection_ = @@ -1188,6 +1189,12 @@ bool Workspaces::handleScroll(GdkEventScroll* e) { if (gdk_event_get_pointer_emulated((GdkEvent*)e)) { return false; } + + // Check for custom scroll commands first; delegate to base class + if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { + return AModule::handleScroll(e); + } + auto dir = AModule::getScrollDir(e); if (dir == SCROLL_DIR::NONE) { return true; From ea36aab3babe79f646e87b8cd3a49d041dc32443 Mon Sep 17 00:00:00 2001 From: buzz Date: Thu, 21 May 2026 17:33:43 +0200 Subject: [PATCH 709/780] docs(hyprland/workspaces): add on-scroll-up and on-scroll-down to man page --- man/waybar-hyprland-workspaces.5.scd | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index 5284ce99..c852f2ba 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -135,6 +135,14 @@ This setting is ignored if *workspace-taskbar.enable* is set to true. default: false ++ If set to false, you can't scroll to cycle throughout workspaces from the entire bar. If set to true this behaviour is enabled. +*on-scroll-up*: ++ + typeof: string ++ + Command to execute when scrolling up on the module. This replaces the default behaviour of workspace cycling. + +*on-scroll-down*: ++ + typeof: string ++ + Command to execute when scrolling down on the module. This replaces the default behaviour of workspace cycling. + *ignore-workspaces*: ++ typeof: array ++ default: [] ++ From 85163c684746fe3f08180598bcb05768bbf1016d Mon Sep 17 00:00:00 2001 From: buzz Date: Thu, 21 May 2026 18:50:43 +0200 Subject: [PATCH 710/780] fix(hyprland/workspaces): replace EventBox with Button for taskbar windows --- src/modules/hyprland/workspace.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 753893f2..ca427dc9 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -301,15 +301,18 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) { auto window_box = Gtk::make_managed(Gtk::ORIENTATION_HORIZONTAL); window_box->set_tooltip_markup(window_repr.window_title); - window_box->get_style_context()->add_class("taskbar-window"); + + auto button = Gtk::manage(new Gtk::Button()); + button->set_relief(Gtk::RELIEF_NONE); + button->add(*window_box); + button->get_style_context()->add_class("taskbar-window"); if (window_repr.isActive) { - window_box->get_style_context()->add_class("active"); + button->get_style_context()->add_class("active"); } - auto event_box = Gtk::manage(new Gtk::EventBox()); - event_box->add(*window_box); if (m_workspaceManager.onClickWindow() != "") { - event_box->signal_button_press_event().connect( - sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address)); + button->signal_button_press_event().connect( + sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address), + false); } auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()), @@ -334,8 +337,8 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) { window_box->pack_start(*window_label_after, true, true); } - m_content.pack_start(*event_box, true, false); - event_box->show_all(); + m_content.pack_start(*button, true, false); + button->show_all(); }; if (m_workspaceManager.taskbarReverseDirection()) { From f8cc7173cae8e2913ca03d33a638f83ddfb84edd Mon Sep 17 00:00:00 2001 From: Siriusuna Date: Fri, 22 May 2026 02:44:50 +0800 Subject: [PATCH 711/780] refactor: extract utf8 truncation logic to utility Move the UTF-8 visual-width measurement and truncation helper functions from the mpris module to a common utility (include/util/utf8_string.hpp and src/util/utf8_string.cpp). This decouples string truncation and width measurement from the mpris module, allowing other modules (like mpd) to reuse. The helper function `utf8_truncate` (formerly `truncate` in mpris) and `utf8_width` is exported under the `waybar::util` namespace, while the low-level `measure_and_truncate` (formerly `utf8_truncate`) is encapsulated in an anonymous namespace in src/util/utf8_string.cpp to avoid unnecessary API exposure. No functional changes were made to the mpris module's behavior. --- include/util/utf8_string.hpp | 7 ++++ meson.build | 3 +- src/modules/mpris/mpris.cpp | 62 +++++------------------------------- src/util/utf8_string.cpp | 62 ++++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 55 deletions(-) create mode 100644 include/util/utf8_string.hpp create mode 100644 src/util/utf8_string.cpp diff --git a/include/util/utf8_string.hpp b/include/util/utf8_string.hpp new file mode 100644 index 00000000..07a04466 --- /dev/null +++ b/include/util/utf8_string.hpp @@ -0,0 +1,7 @@ +#pragma once +#include + +namespace waybar::util { +size_t utf8_width(const std::string& str); +void utf8_truncate(std::string& s, const std::string& ellipsis, size_t max_len); +} // namespace waybar::util diff --git a/meson.build b/meson.build index 0c494eb2..e528a6da 100644 --- a/meson.build +++ b/meson.build @@ -186,7 +186,8 @@ src_files = files( 'src/util/icon_loader.cpp', 'src/util/regex_collection.cpp', 'src/util/css_reload_helper.cpp', - 'src/util/transform_8bit_to_rgba.cpp' + 'src/util/transform_8bit_to_rgba.cpp', + 'src/util/utf8_string.cpp' ) man_files = files( diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index e0a27c92..6ebf34f7 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -7,6 +7,7 @@ #include #include "util/scope_guard.hpp" +#include "util/utf8_string.hpp" extern "C" { #include @@ -202,69 +203,22 @@ auto Mpris::getIconFromJson(const Json::Value& icons, const std::string& key) -> // Wide characters count as two, zero-width characters count as zero // Modifies str in-place (unless width = std::string::npos) // Returns the total width of the string pre-truncating -size_t utf8_truncate(std::string& str, size_t width = std::string::npos) { - if (str.length() == 0) return 0; - - const gchar* trunc_end = nullptr; - - size_t total_width = 0; - - for (gchar *data = str.data(), *end = data + str.size(); data != nullptr;) { - gunichar c = g_utf8_get_char_validated(data, end - data); - if (c == -1U || c == -2U) { - // invalid unicode, treat string as ascii - if (width != std::string::npos && str.length() > width) str.resize(width); - return str.length(); - } else if (g_unichar_iswide(c)) { - total_width += 2; - } else if (!g_unichar_iszerowidth(c) && c != 0xAD) { // neither zero-width nor soft hyphen - total_width += 1; - } - - data = g_utf8_find_next_char(data, end); - if (width != std::string::npos && total_width <= width && !g_unichar_isspace(c)) - trunc_end = data; - } - - if (trunc_end) str.resize(trunc_end - str.data()); - - return total_width; -} - -size_t utf8_width(const std::string& str) { return utf8_truncate(const_cast(str)); } - -void truncate(std::string& s, const std::string& ellipsis, size_t max_len) { - if (max_len == 0) { - s.resize(0); - return; - } - size_t len = utf8_truncate(s, max_len); - if (len > max_len) { - size_t ellipsis_len = utf8_width(ellipsis); - if (max_len >= ellipsis_len) { - if (ellipsis_len) utf8_truncate(s, max_len - ellipsis_len); - s += ellipsis; - } else { - s.resize(0); - } - } -} auto Mpris::getArtistStr(const PlayerInfo& info, bool truncated) -> std::string { auto artist = info.artist.value_or(std::string()); - if (truncated && artist_len_ >= 0) truncate(artist, ellipsis_, artist_len_); + if (truncated && artist_len_ >= 0) waybar::util::utf8_truncate(artist, ellipsis_, artist_len_); return artist; } auto Mpris::getAlbumStr(const PlayerInfo& info, bool truncated) -> std::string { auto album = info.album.value_or(std::string()); - if (truncated && album_len_ >= 0) truncate(album, ellipsis_, album_len_); + if (truncated && album_len_ >= 0) waybar::util::utf8_truncate(album, ellipsis_, album_len_); return album; } auto Mpris::getTitleStr(const PlayerInfo& info, bool truncated) -> std::string { auto title = info.title.value_or(std::string()); - if (truncated && title_len_ >= 0) truncate(title, ellipsis_, title_len_); + if (truncated && title_len_ >= 0) waybar::util::utf8_truncate(title, ellipsis_, title_len_); return title; } @@ -292,9 +246,9 @@ auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) -> // keep position format same as length format auto position = getPositionStr(info, truncated && truncate_hours_ && length.length() < 6); - size_t artistLen = utf8_width(artist); - size_t albumLen = utf8_width(album); - size_t titleLen = utf8_width(title); + size_t artistLen = waybar::util::utf8_width(artist); + size_t albumLen = waybar::util::utf8_width(album); + size_t titleLen = waybar::util::utf8_width(title); size_t lengthLen = length.length(); size_t posLen = position.length(); @@ -313,7 +267,7 @@ auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) -> // Since the first element doesn't present a separator and we don't know a priori which one // it will be, we add a "virtual separatorLen" to the dynamicLen, since we are adding the // separatorLen to all the other lengths. - size_t separatorLen = utf8_width(dynamic_separator_); + size_t separatorLen = waybar::util::utf8_width(dynamic_separator_); size_t dynamicLen = dynamic_len_ + separatorLen; if (showArtist) artistLen += separatorLen; if (showAlbum) albumLen += separatorLen; diff --git a/src/util/utf8_string.cpp b/src/util/utf8_string.cpp new file mode 100644 index 00000000..f96f2bd1 --- /dev/null +++ b/src/util/utf8_string.cpp @@ -0,0 +1,62 @@ +#include + +#include +#include + +namespace waybar::util { + +namespace { +// Wide characters count as two, zero-width characters count as zero +// Modifies str in-place (unless width = std::string::npos) +// Returns the total width of the string pre-truncating +size_t measure_and_truncate(std::string& str, size_t width = std::string::npos) { + if (str.length() == 0) return 0; + + const gchar* trunc_end = nullptr; + + size_t total_width = 0; + + for (gchar *data = str.data(), *end = data + str.size(); data != nullptr;) { + gunichar c = g_utf8_get_char_validated(data, end - data); + if (c == -1U || c == -2U) { + // invalid unicode, treat string as ascii + if (width != std::string::npos && str.length() > width) str.resize(width); + return str.length(); + } else if (g_unichar_iswide(c)) { + total_width += 2; + } else if (!g_unichar_iszerowidth(c) && c != 0xAD) { // neither zero-width nor soft hyphen + total_width += 1; + } + + data = g_utf8_find_next_char(data, end); + if (width != std::string::npos && total_width <= width && !g_unichar_isspace(c)) + trunc_end = data; + } + + if (trunc_end) str.resize(trunc_end - str.data()); + + return total_width; +} +} // namespace + +size_t utf8_width(const std::string& str) { + return measure_and_truncate(const_cast(str)); +} + +void utf8_truncate(std::string& s, const std::string& ellipsis, size_t max_len) { + if (max_len == 0) { + s.resize(0); + return; + } + size_t len = measure_and_truncate(s, max_len); + if (len > max_len) { + size_t ellipsis_len = utf8_width(ellipsis); + if (max_len >= ellipsis_len) { + if (ellipsis_len) measure_and_truncate(s, max_len - ellipsis_len); + s += ellipsis; + } else { + s.resize(0); + } + } +} +} // namespace waybar::util From 435c212be10125cf0bde4c823ad2f66c10d8c229 Mon Sep 17 00:00:00 2001 From: Siriusuna Date: Fri, 22 May 2026 02:59:09 +0800 Subject: [PATCH 712/780] fix(mpris): escape Pango markup in tooltip Fix an issue where the mpris module's tooltip failed to render when track metadata contained unescaped XML/HTML markup characters. This occurred because the mpris module formatted raw strings into the tooltip template before passing the final string directly to `set_tooltip_markup()`, triggering GTK/Pango parsing warnings and rendering failures. Resolve this by wrapping tooltip metadata fields in `Glib::Markup::escape_text` before formatting, ensuring Pango-compliant strings are always delivered to the GTK tooltip markup. --- src/modules/mpris/mpris.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index 6ebf34f7..13ba836d 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -729,9 +729,12 @@ auto Mpris::update() -> void { auto tooltip_text = fmt::format( fmt::runtime(tooltipstr), fmt::arg("player", info.name), fmt::arg("status", info.status_string), - fmt::arg("artist", getArtistStr(info, tooltip_len_limits_)), - fmt::arg("title", getTitleStr(info, tooltip_len_limits_)), - fmt::arg("album", getAlbumStr(info, tooltip_len_limits_)), + fmt::arg("artist", + std::string(Glib::Markup::escape_text(getArtistStr(info, tooltip_len_limits_)))), + fmt::arg("title", + std::string(Glib::Markup::escape_text(getTitleStr(info, tooltip_len_limits_)))), + fmt::arg("album", + std::string(Glib::Markup::escape_text(getAlbumStr(info, tooltip_len_limits_)))), fmt::arg("length", tooltipLength), fmt::arg("position", tooltipPosition), fmt::arg("dynamic", getDynamicStr(info, tooltip_len_limits_, false)), fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)), From 81fd9ee4ab39b90b496c34aabe9abf6bc253b0eb Mon Sep 17 00:00:00 2001 From: Siriusuna Date: Fri, 22 May 2026 03:04:35 +0800 Subject: [PATCH 713/780] fix(mpd): truncate before sanitization and improve tooltip UX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix an issue in the MPD module where text failed to render when raw tags containing markup characters were truncated mid-sequence after sanitization, resulting in fragmented XML entities (such as `&` cut into `&am`). Resolve this by shifting the order of operations to truncate the raw strings *before* running `sanitize_string`. To keep `setLabel` decoupled and clean, we introduce helper methods (`getArtistStr`, `getAlbumArtistStr`, `getAlbumStr`, `getTitleStr`) inside the `MPD` class, matching the architectural style of MPRIS. Additionally: - Introduce support for the customizable `ellipsis` config option (defaulting to `…`). - Switch the truncation logic of the MPD module to be visual-width-aware using the newly extracted `waybar::util::utf8_truncate`. - Decouple the tooltip text from the main label's truncated text. The tooltip now displays un-truncated, complete metadata (properly escaped), providing a significantly better user experience. --- include/modules/mpd/mpd.hpp | 5 ++++ src/modules/mpd/mpd.cpp | 58 ++++++++++++++++++++++++++----------- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/include/modules/mpd/mpd.hpp b/include/modules/mpd/mpd.hpp index 9d81638e..609f69ed 100644 --- a/include/modules/mpd/mpd.hpp +++ b/include/modules/mpd/mpd.hpp @@ -33,6 +33,7 @@ class MPD : public ALabel { detail::unique_status status_; mpd_state state_; detail::unique_song song_; + std::string ellipsis_; public: MPD(const std::string&, const Json::Value&); @@ -45,6 +46,10 @@ class MPD : public ALabel { void setLabel(); std::string getStateIcon() const; std::string getOptionIcon(const std::string& optionName, bool activated) const; + std::string getArtistStr(bool truncated) const; + std::string getAlbumArtistStr(bool truncated) const; + std::string getAlbumStr(bool truncated) const; + std::string getTitleStr(bool truncated) const; // GUI-side methods bool handlePlayPause(GdkEventButton* const&); diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 9874f458..a3e7c23d 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -6,6 +6,7 @@ #include #include +#include using namespace waybar::util; #include "modules/mpd/state.hpp" @@ -24,7 +25,8 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config) timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000), connection_(nullptr, &mpd_connection_free), status_(nullptr, &mpd_status_free), - song_(nullptr, &mpd_song_free) { + song_(nullptr, &mpd_song_free), + ellipsis_(config_["ellipsis"].isString() ? config_["ellipsis"].asString() : "\u2026") { if (!config_["port"].isNull() && !config_["port"].isUInt()) { spdlog::warn("{}: `port` configuration should be an unsigned int", module_name_); } @@ -117,7 +119,6 @@ void waybar::modules::MPD::setLabel() { label_.get_style_context()->remove_class("disconnected"); auto format = format_; - Glib::ustring artist, album_artist, album, title; std::string date, filename, uri; int song_pos = 0, queue_length = 0, volume = 0; std::chrono::seconds elapsedTime, totalTime; @@ -145,10 +146,6 @@ void waybar::modules::MPD::setLabel() { stateIcon = getStateIcon(); - artist = sanitize_string(getTag(MPD_TAG_ARTIST)); - album_artist = sanitize_string(getTag(MPD_TAG_ALBUM_ARTIST)); - album = sanitize_string(getTag(MPD_TAG_ALBUM)); - title = sanitize_string(getTag(MPD_TAG_TITLE)); date = sanitize_string(getTag(MPD_TAG_DATE)); filename = sanitize_string(getFilename()); uri = mpd_song_get_uri(song_.get()); @@ -170,17 +167,12 @@ void waybar::modules::MPD::setLabel() { std::string repeatIcon = getOptionIcon("repeat", repeatActivated); bool singleActivated = mpd_status_get_single(status_.get()); std::string singleIcon = getOptionIcon("single", singleActivated); - if (config_["artist-len"].isInt()) artist = artist.substr(0, config_["artist-len"].asInt()); - if (config_["album-artist-len"].isInt()) - album_artist = album_artist.substr(0, config_["album-artist-len"].asInt()); - if (config_["album-len"].isInt()) album = album.substr(0, config_["album-len"].asInt()); - if (config_["title-len"].isInt()) title = title.substr(0, config_["title-len"].asInt()); try { auto text = fmt::format( - fmt::runtime(format), fmt::arg("artist", artist.raw()), - fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()), - fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume), + fmt::runtime(format), fmt::arg("artist", getArtistStr(true)), + fmt::arg("albumArtist", getAlbumArtistStr(true)), fmt::arg("album", getAlbumStr(true)), + fmt::arg("title", getTitleStr(true)), fmt::arg("date", date), fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), @@ -202,9 +194,9 @@ void waybar::modules::MPD::setLabel() { : "MPD (connected)"; try { auto tooltip_text = fmt::format( - fmt::runtime(tooltip_format), fmt::arg("artist", artist.raw()), - fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()), - fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume), + fmt::runtime(tooltip_format), fmt::arg("artist", getArtistStr(false)), + fmt::arg("albumArtist", getAlbumArtistStr(false)), fmt::arg("album", getAlbumStr(false)), + fmt::arg("title", getTitleStr(false)), fmt::arg("date", date), fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), @@ -257,6 +249,38 @@ std::string waybar::modules::MPD::getOptionIcon(const std::string& optionName, } } +std::string waybar::modules::MPD::getArtistStr(bool truncated) const { + std::string artist = getTag(MPD_TAG_ARTIST); + if (truncated && config_["artist-len"].isInt()) { + waybar::util::utf8_truncate(artist, ellipsis_, config_["artist-len"].asInt()); + } + return sanitize_string(artist); +} + +std::string waybar::modules::MPD::getAlbumArtistStr(bool truncated) const { + std::string album_artist = getTag(MPD_TAG_ALBUM_ARTIST); + if (truncated && config_["album-artist-len"].isInt()) { + waybar::util::utf8_truncate(album_artist, ellipsis_, config_["album-artist-len"].asInt()); + } + return sanitize_string(album_artist); +} + +std::string waybar::modules::MPD::getAlbumStr(bool truncated) const { + std::string album = getTag(MPD_TAG_ALBUM); + if (truncated && config_["album-len"].isInt()) { + waybar::util::utf8_truncate(album, ellipsis_, config_["album-len"].asInt()); + } + return sanitize_string(album); +} + +std::string waybar::modules::MPD::getTitleStr(bool truncated) const { + std::string title = getTag(MPD_TAG_TITLE); + if (truncated && config_["title-len"].isInt()) { + waybar::util::utf8_truncate(title, ellipsis_, config_["title-len"].asInt()); + } + return sanitize_string(title); +} + static bool isServerUnavailable(const std::error_code& ec) { if (ec.category() == std::system_category()) { switch (ec.value()) { From 89b5456cd3930d3bfa5a4782869732b3fb39385c Mon Sep 17 00:00:00 2001 From: Carlo Teubner Date: Sun, 24 May 2026 06:41:51 +0100 Subject: [PATCH 714/780] Sway IPC: fix socket path discovery Ensure to only read actual "sway --get-socketpath" output, not subsequent garbage in buffer. --- src/modules/sway/ipc/client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/sway/ipc/client.cpp b/src/modules/sway/ipc/client.cpp index 3ebccccd..53ad64a1 100644 --- a/src/modules/sway/ipc/client.cpp +++ b/src/modules/sway/ipc/client.cpp @@ -45,7 +45,7 @@ const std::string Ipc::getSocketPath() const { throw std::runtime_error("Failed to get socket path"); } while (fgets(buf, sizeof(buf), in) != nullptr) { - str_buf.append(buf, sizeof(buf)); + str_buf.append(buf); } pclose(in); str = str_buf; From 1bc633fc0ea454f1891d1de6f0c1aa857e9b4c34 Mon Sep 17 00:00:00 2001 From: Carlo Teubner Date: Sun, 24 May 2026 06:45:30 +0100 Subject: [PATCH 715/780] Sway IPC: more robust socket send/receive logic Handle EINTR/EAGAIN, partial sends, etc. --- src/modules/sway/ipc/client.cpp | 47 ++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/modules/sway/ipc/client.cpp b/src/modules/sway/ipc/client.cpp index 53ad64a1..fa6b31fd 100644 --- a/src/modules/sway/ipc/client.cpp +++ b/src/modules/sway/ipc/client.cpp @@ -1,11 +1,33 @@ #include "modules/sway/ipc/client.hpp" +#include #include #include #include +#include namespace waybar::modules::sway { +namespace { + +void sendAll(int fd, const char* data, size_t size, const char* what) { + size_t total = 0; + while (total < size) { + const auto res = ::send(fd, data + total, size - total, 0); + if (res < 0) { + if (errno == EINTR || errno == EAGAIN) { + continue; + } + throw std::runtime_error(what); + } + if (res == 0) { + throw std::runtime_error(what); + } + total += static_cast(res); + } +} + +} // namespace Ipc::Ipc() { const std::string& socketPath = getSocketPath(); @@ -89,10 +111,16 @@ struct Ipc::ipc_response Ipc::recv(int fd) { // IPC is closed so just return an empty response return {0, 0, ""}; } - if (res <= 0) { + if (res < 0) { + if (errno == EINTR || errno == EAGAIN) { + continue; + } throw std::runtime_error("Unable to receive IPC header"); } - total += res; + if (res == 0) { + throw std::runtime_error("Unable to receive IPC header"); + } + total += static_cast(res); } auto magic = std::string(header.data(), header.data() + ipc_magic_.size()); if (magic != ipc_magic_) { @@ -110,9 +138,12 @@ struct Ipc::ipc_response Ipc::recv(int fd) { } throw std::runtime_error("Unable to receive IPC payload"); } - total += res; + if (res == 0) { + throw std::runtime_error("Unable to receive IPC payload"); + } + total += static_cast(res); } - return {data32[0], data32[1], &payload.front()}; + return {data32[0], data32[1], std::move(payload)}; } struct Ipc::ipc_response Ipc::send(int fd, uint32_t type, const std::string& payload) { @@ -123,12 +154,8 @@ struct Ipc::ipc_response Ipc::send(int fd, uint32_t type, const std::string& pay data32[0] = payload.size(); data32[1] = type; - if (::send(fd, header.data(), ipc_header_size_, 0) == -1) { - throw std::runtime_error("Unable to send IPC header"); - } - if (::send(fd, payload.c_str(), payload.size(), 0) == -1) { - throw std::runtime_error("Unable to send IPC payload"); - } + sendAll(fd, header.data(), ipc_header_size_, "Unable to send IPC header"); + sendAll(fd, payload.data(), payload.size(), "Unable to send IPC payload"); return Ipc::recv(fd); } From d8848a478bdf2412c905f145d7311e27265c8841 Mon Sep 17 00:00:00 2001 From: Carlo Teubner Date: Sun, 24 May 2026 08:09:43 +0100 Subject: [PATCH 716/780] Sway IPC: code cleanups --- include/modules/sway/ipc/client.hpp | 13 ++--- src/modules/sway/ipc/client.cpp | 91 ++++++++++++++++------------- 2 files changed, 57 insertions(+), 47 deletions(-) diff --git a/include/modules/sway/ipc/client.hpp b/include/modules/sway/ipc/client.hpp index f6eb7c40..2665caf9 100644 --- a/include/modules/sway/ipc/client.hpp +++ b/include/modules/sway/ipc/client.hpp @@ -1,14 +1,10 @@ #pragma once #include -#include -#include -#include -#include -#include +#include +#include #include -#include #include #include "ipc.hpp" @@ -41,8 +37,9 @@ class Ipc { static inline const std::string ipc_magic_ = "i3-ipc"; static inline const size_t ipc_header_size_ = ipc_magic_.size() + 8; - const std::string getSocketPath() const; - int open(const std::string&) const; + static std::string getSocketPath(); + static int open(const std::string&); + struct ipc_response send(int fd, uint32_t type, const std::string& payload = ""); struct ipc_response recv(int fd); diff --git a/src/modules/sway/ipc/client.cpp b/src/modules/sway/ipc/client.cpp index fa6b31fd..c833fe64 100644 --- a/src/modules/sway/ipc/client.cpp +++ b/src/modules/sway/ipc/client.cpp @@ -1,12 +1,22 @@ #include "modules/sway/ipc/client.hpp" -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include +#include "modules/sway/ipc/ipc.hpp" + namespace waybar::modules::sway { namespace { @@ -30,7 +40,7 @@ void sendAll(int fd, const char* data, size_t size, const char* what) { } // namespace Ipc::Ipc() { - const std::string& socketPath = getSocketPath(); + const std::string socketPath = getSocketPath(); fd_ = util::ScopedFd(open(socketPath)); fd_event_ = util::ScopedFd(open(socketPath)); } @@ -51,49 +61,49 @@ Ipc::~Ipc() { } } -void Ipc::setWorker(std::function&& func) { thread_ = func; } +void Ipc::setWorker(std::function&& func) { thread_ = std::move(func); } -const std::string Ipc::getSocketPath() const { +std::string Ipc::getSocketPath() { const char* env = getenv("SWAYSOCK"); - if (env != nullptr) { - return std::string(env); + if (env != nullptr && env[0] != '\0') { + return {env}; } + + FILE* in = popen("sway --get-socketpath 2>/dev/null", "r"); + if (in == nullptr) { + throw std::runtime_error("Failed to get socket path"); + } + std::string str; - { - std::string str_buf; - FILE* in; - char buf[512] = {0}; - if ((in = popen("sway --get-socketpath 2>/dev/null", "r")) == nullptr) { - throw std::runtime_error("Failed to get socket path"); - } - while (fgets(buf, sizeof(buf), in) != nullptr) { - str_buf.append(buf); - } - pclose(in); - str = str_buf; - if (str.empty()) { - throw std::runtime_error("Socket path is empty"); - } + char buf[512] = {0}; + while (fgets(buf, sizeof(buf), in) != nullptr) { + str.append(buf); } - if (str.back() == '\n') { + + if (pclose(in) == -1) { + throw std::runtime_error("Failed to get socket path"); + } + + if (str.ends_with('\n')) { str.pop_back(); } + if (str.empty()) { + throw std::runtime_error("Socket path is empty"); + } + return str; } -int Ipc::open(const std::string& socketPath) const { +int Ipc::open(const std::string& socketPath) { util::ScopedFd fd(socket(AF_UNIX, SOCK_STREAM, 0)); if (fd == -1) { throw std::runtime_error("Unable to open Unix socket"); } (void)fcntl(fd, F_SETFD, FD_CLOEXEC); - struct sockaddr_un addr; - memset(&addr, 0, sizeof(struct sockaddr_un)); - addr.sun_family = AF_UNIX; + struct sockaddr_un addr{.sun_family = AF_UNIX}; strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1); addr.sun_path[sizeof(addr.sun_path) - 1] = 0; - int l = sizeof(struct sockaddr_un); - if (::connect(fd, reinterpret_cast(&addr), l) == -1) { + if (::connect(fd, reinterpret_cast(&addr), sizeof addr) == -1) { throw std::runtime_error("Unable to connect to Sway"); } return fd.release(); @@ -102,14 +112,13 @@ int Ipc::open(const std::string& socketPath) const { struct Ipc::ipc_response Ipc::recv(int fd) { std::string header; header.resize(ipc_header_size_); - auto data32 = reinterpret_cast(header.data() + ipc_magic_.size()); - size_t total = 0; + size_t total = 0; while (total < ipc_header_size_) { - auto res = ::recv(fd, header.data() + total, ipc_header_size_ - total, 0); + const ssize_t res = ::recv(fd, header.data() + total, ipc_header_size_ - total, 0); if (fd_event_ == -1 || fd_ == -1) { // IPC is closed so just return an empty response - return {0, 0, ""}; + return {.size = 0, .type = 0, .payload = ""}; } if (res < 0) { if (errno == EINTR || errno == EAGAIN) { @@ -122,16 +131,18 @@ struct Ipc::ipc_response Ipc::recv(int fd) { } total += static_cast(res); } - auto magic = std::string(header.data(), header.data() + ipc_magic_.size()); - if (magic != ipc_magic_) { + if (std::string_view(header.data(), ipc_magic_.size()) != ipc_magic_) { throw std::runtime_error("Invalid IPC magic"); } - total = 0; + const auto* data32 = reinterpret_cast(header.data() + ipc_magic_.size()); + std::string payload; payload.resize(data32[0]); + + total = 0; while (total < data32[0]) { - auto res = ::recv(fd, payload.data() + total, data32[0] - total, 0); + const ssize_t res = ::recv(fd, payload.data() + total, data32[0] - total, 0); if (res < 0) { if (errno == EINTR || errno == EAGAIN) { continue; @@ -143,19 +154,21 @@ struct Ipc::ipc_response Ipc::recv(int fd) { } total += static_cast(res); } - return {data32[0], data32[1], std::move(payload)}; + + return {.size = data32[0], .type = data32[1], .payload = std::move(payload)}; } struct Ipc::ipc_response Ipc::send(int fd, uint32_t type, const std::string& payload) { std::string header; header.resize(ipc_header_size_); - auto data32 = reinterpret_cast(header.data() + ipc_magic_.size()); - memcpy(header.data(), ipc_magic_.c_str(), ipc_magic_.size()); + memcpy(header.data(), ipc_magic_.data(), ipc_magic_.size()); + auto* data32 = reinterpret_cast(header.data() + ipc_magic_.size()); data32[0] = payload.size(); data32[1] = type; sendAll(fd, header.data(), ipc_header_size_, "Unable to send IPC header"); sendAll(fd, payload.data(), payload.size(), "Unable to send IPC payload"); + return Ipc::recv(fd); } From 3056bad95a5911b528b1dbc29cfb6ab4367f0696 Mon Sep 17 00:00:00 2001 From: Carlo Teubner Date: Sun, 24 May 2026 08:13:05 +0100 Subject: [PATCH 717/780] Sway IPC: avoid reinterpret_cast --- src/modules/sway/ipc/client.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/modules/sway/ipc/client.cpp b/src/modules/sway/ipc/client.cpp index c833fe64..dcbe7fa3 100644 --- a/src/modules/sway/ipc/client.cpp +++ b/src/modules/sway/ipc/client.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -135,14 +136,18 @@ struct Ipc::ipc_response Ipc::recv(int fd) { throw std::runtime_error("Invalid IPC magic"); } - const auto* data32 = reinterpret_cast(header.data() + ipc_magic_.size()); + uint32_t payload_size = 0; + uint32_t payload_type = 0; + memcpy(&payload_size, header.data() + ipc_magic_.size(), sizeof payload_size); + memcpy(&payload_type, header.data() + ipc_magic_.size() + sizeof payload_size, + sizeof payload_type); std::string payload; - payload.resize(data32[0]); + payload.resize(payload_size); total = 0; - while (total < data32[0]) { - const ssize_t res = ::recv(fd, payload.data() + total, data32[0] - total, 0); + while (total < payload_size) { + const ssize_t res = ::recv(fd, payload.data() + total, payload_size - total, 0); if (res < 0) { if (errno == EINTR || errno == EAGAIN) { continue; @@ -155,16 +160,19 @@ struct Ipc::ipc_response Ipc::recv(int fd) { total += static_cast(res); } - return {.size = data32[0], .type = data32[1], .payload = std::move(payload)}; + return {.size = payload_size, .type = payload_type, .payload = std::move(payload)}; } struct Ipc::ipc_response Ipc::send(int fd, uint32_t type, const std::string& payload) { std::string header; header.resize(ipc_header_size_); memcpy(header.data(), ipc_magic_.data(), ipc_magic_.size()); - auto* data32 = reinterpret_cast(header.data() + ipc_magic_.size()); - data32[0] = payload.size(); - data32[1] = type; + if (payload.size() > std::numeric_limits::max()) { + throw std::runtime_error("IPC payload is too large"); + } + const auto payload_size = static_cast(payload.size()); + memcpy(header.data() + ipc_magic_.size(), &payload_size, sizeof payload_size); + memcpy(header.data() + ipc_magic_.size() + sizeof payload_size, &type, sizeof type); sendAll(fd, header.data(), ipc_header_size_, "Unable to send IPC header"); sendAll(fd, payload.data(), payload.size(), "Unable to send IPC payload"); From 822071f36efbf828781e99ea3ab36a97d9a55cb7 Mon Sep 17 00:00:00 2001 From: stalker Date: Mon, 25 May 2026 08:18:12 +0400 Subject: [PATCH 718/780] feat(custom): add image --- include/modules/custom.hpp | 7 +++++-- src/modules/custom.cpp | 32 +++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/include/modules/custom.hpp b/include/modules/custom.hpp index a345a33b..442b1c37 100644 --- a/include/modules/custom.hpp +++ b/include/modules/custom.hpp @@ -5,14 +5,14 @@ #include #include -#include "ALabel.hpp" +#include "AIconLabel.hpp" #include "util/command.hpp" #include "util/json.hpp" #include "util/sleeper_thread.hpp" namespace waybar::modules { -class Custom : public ALabel { +class Custom : public AIconLabel { public: Custom(const std::string&, const std::string&, const Json::Value&, const std::string&); virtual ~Custom(); @@ -36,6 +36,9 @@ class Custom : public ALabel { std::string alt_; std::string tooltip_; std::string last_tooltip_markup_; + std::string image_path_; + std::string image_name_; + unsigned app_icon_size_{24}; const bool tooltip_format_enabled_; std::vector class_; int percentage_; diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 28def8c9..22465802 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -8,7 +8,7 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id, const Json::Value& config, const std::string& output_name) - : ALabel(config, "custom-" + name, id, "{}"), + : AIconLabel(config, "custom-" + name, id, "{}"), name_(name), output_name_(output_name), id_(id), @@ -28,6 +28,15 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id, } else if (config_["exec"].isString()) { continuousWorker(); } + if (config_["image-path"].isString()) { + image_path_ = config_["image-path"].asString(); + } + if (config_["image-name"].isString()) { + image_name_ = config_["image-name"].asString(); + } + if (config["icon-size"].isUInt()) { + app_icon_size_ = config["icon-size"].asUInt(); + } } waybar::modules::Custom::~Custom() { @@ -177,7 +186,7 @@ auto waybar::modules::Custom::update() -> void { auto str = fmt::format(fmt::runtime(format_), fmt::arg("text", text_), fmt::arg("alt", alt_), fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("percentage", percentage_)); - if ((config_["hide-empty-text"].asBool() && text_.empty()) || str.empty()) { + if ((config_["hide-empty-text"].asBool() && text_.empty()) || (str.empty() && image_path_.empty() && image_name_.empty())) { event_box_.hide(); } else { label_.set_markup(str); @@ -212,7 +221,20 @@ auto waybar::modules::Custom::update() -> void { style->add_class("flat"); style->add_class("text-button"); style->add_class(MODULE_CLASS); + auto image_style = image_.get_style_context(); + image_style->add_class("image-button"); event_box_.show(); + if (!image_path_.empty()) { + auto pixbuf = Gdk::Pixbuf::create_from_file(image_path_, app_icon_size_, app_icon_size_); + image_.set(pixbuf); + } else if (!image_name_.empty()) { + image_.set_from_icon_name(image_name_, Gtk::ICON_SIZE_INVALID); + image_.set_pixel_size(app_icon_size_); + } + + image_.set_visible(!image_name_.empty() || !image_path_.empty()); + label_.set_visible(!str.empty()); + } } catch (const fmt::format_error& e) { if (std::strcmp(e.what(), "cannot switch from manual to automatic argument indexing") != 0) @@ -222,9 +244,12 @@ auto waybar::modules::Custom::update() -> void { "mixing manual and automatic argument indexing is no longer supported; " "try replacing \"{}\" with \"{text}\" in your format specifier"); } + + + } // Call parent update - ALabel::update(); + AIconLabel::update(); } void waybar::modules::Custom::parseOutputRaw() { @@ -290,6 +315,7 @@ void waybar::modules::Custom::parseOutputJson() { class_.push_back(c.asString()); } } + if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) { percentage_ = (int)lround(parsed["percentage"].asFloat()); } else { From ec2651370e68e171e408ab2c932ca6631f7a50cf Mon Sep 17 00:00:00 2001 From: jacobs Date: Tue, 26 May 2026 11:09:45 +0100 Subject: [PATCH 719/780] (Fix) (sway workspace module) persistent workspaces always removed --- src/modules/sway/workspaces.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 6abe5383..b7d63b58 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -229,7 +229,10 @@ bool Workspaces::filterButtons() { auto ws = std::find_if(workspaces_.begin(), workspaces_.end(), [it](const auto& node) { return node["name"].asString() == it->first; }); if (ws == workspaces_.end() || - (!config_["all-outputs"].asBool() && (*ws)["output"].asString() != bar_.output->name)) { + ((*ws).isMember("target_output") ? (*ws)["target_output"].asString() != bar_.output->name && + (*ws)["target_output"].asString() != "" + : !config_["all-outputs"].asBool() && + (*ws)["output"].asString() != bar_.output->name)) { it = buttons_.erase(it); needReorder = true; } else { From 415da7aef4d7be5f9e004389aceb135e245cfb50 Mon Sep 17 00:00:00 2001 From: Artur Ciesielski Date: Tue, 26 May 2026 19:39:03 +0200 Subject: [PATCH 720/780] sway/workspaces: refer to outputs by their monitor identifier (make and model) --- src/modules/sway/workspaces.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/sway/workspaces.cpp b/src/modules/sway/workspaces.cpp index 6abe5383..512e4cb6 100644 --- a/src/modules/sway/workspaces.cpp +++ b/src/modules/sway/workspaces.cpp @@ -143,7 +143,8 @@ void Workspaces::onCmd(const struct Ipc::ipc_response& res) { if (p_w.isArray() && !p_w.empty()) { // Adding to target outputs for (const Json::Value& output : p_w) { - if (output.asString() == bar_.output->name) { + auto output_name = output.asString(); + if (output_name == bar_.output->name || output_name == bar_.output->identifier) { Json::Value v; v["name"] = p_w_name; v["target_output"] = bar_.output->name; From 97acbf30d0f0466990c68de41275b879a0c18dee Mon Sep 17 00:00:00 2001 From: Artur Ciesielski Date: Tue, 26 May 2026 20:32:38 +0200 Subject: [PATCH 721/780] Update man pages for persistent-workspaces in sway/workspaces --- man/waybar-sway-workspaces.5.scd | 1 + 1 file changed, 1 insertion(+) diff --git a/man/waybar-sway-workspaces.5.scd b/man/waybar-sway-workspaces.5.scd index 3d4e3ad0..f51c4155 100644 --- a/man/waybar-sway-workspaces.5.scd +++ b/man/waybar-sway-workspaces.5.scd @@ -147,6 +147,7 @@ an empty list denoting all outputs. "3": [], // Always show a workspace with name '3', on all outputs if it does not exist "4": ["eDP-1"], // Always show a workspace with name '4', on output 'eDP-1' if it does not exist "5": ["eDP-1", "DP-2"] // Always show a workspace with name '5', on outputs 'eDP-1' and 'DP-2' if it does not exist + "6": ["MonitorMaker 3000 ABC0123"], // Always show a workspace with name '6' on outputs with an identifier 'MonitorMaker 3000 ABC0123' (usually a triple of vendor/model/serial) } } ``` From 31a161eb06d82ff792c5339b1f9650f06d6b8b53 Mon Sep 17 00:00:00 2001 From: muratyalcin-ce Date: Wed, 27 May 2026 23:39:39 +0300 Subject: [PATCH 722/780] Fix (wayfire/backend): Fixed unhandled exception (out_of_range) after no valid outputs in unordered_map --- src/modules/wayfire/backend.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/modules/wayfire/backend.cpp b/src/modules/wayfire/backend.cpp index 42976d20..037a61e1 100644 --- a/src/modules/wayfire/backend.cpp +++ b/src/modules/wayfire/backend.cpp @@ -347,10 +347,13 @@ auto IPC::update_state_handler(const std::string& event, const Json::Value& data if (event == "output-wset-changed") { // data: { event, new-wset: wset.name, output: id, new-wset-data: wset, output-data: output } - auto& output = state.outputs.at(data["output-data"]["name"].asString()); - auto wset_idx = data["new-wset-data"]["index"].asUInt(); - state.wsets.at(wset_idx).output = output; - output.wset_idx = wset_idx; + try { + auto& output = state.outputs.at(data["output-data"]["name"].asString()); + auto wset_idx = data["new-wset-data"]["index"].asUInt(); + state.wsets.at(wset_idx).output = output; + output.wset_idx = wset_idx; + } catch (const std::exception&) { + } return; } From 5ea824c24cfbf503fd13200db875fbc489d2027d Mon Sep 17 00:00:00 2001 From: muratyalcin-ce Date: Thu, 28 May 2026 23:26:40 +0300 Subject: [PATCH 723/780] Use unit in config for memory module tooltip --- include/modules/memory.hpp | 2 -- src/modules/memory/common.cpp | 44 ++++++++++++++++------------------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/include/modules/memory.hpp b/include/modules/memory.hpp index c73ece23..0e405187 100644 --- a/include/modules/memory.hpp +++ b/include/modules/memory.hpp @@ -19,8 +19,6 @@ class Memory : public ALabel { private: void parseMeminfo(); - static float calc_divisor(const std::string& divisor); - std::unordered_map meminfo_; util::SleeperThread thread_; diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp index 731f0b08..d41b4fc2 100644 --- a/src/modules/memory/common.cpp +++ b/src/modules/memory/common.cpp @@ -1,5 +1,18 @@ #include "modules/memory.hpp" +namespace { +const std::unordered_map kUnits = { + {"kB", 1.000}, + {"kiB", 1.024}, + {"MB", 1.000 * 1000.0}, + {"MiB", 1.024 * 1024.0}, + {"GB", 1.000 * 1000.0 * 1000.0}, + {"GiB", 1.024 * 1024.0 * 1024.0}, + {"TB", 1.000 * 1000.0 * 1000.0 * 1000.0}, + {"TiB", 1.024 * 1024.0 * 1024.0 * 1024.0} +}; +} + waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config) : ALabel(config, "memory", id, "{}%", 30) { thread_ = [this] { @@ -8,6 +21,11 @@ waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config }; if (config["unit"].isString()) { unit_ = config["unit"].asString(); + if (!kUnits.contains(unit_)) { + unit_ = "GiB"; + } + } else { + unit_ = "GiB"; } } @@ -40,7 +58,7 @@ auto waybar::modules::Memory::update() -> void { used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal; } - float divisor = calc_divisor(unit_); + float divisor = kUnits.at(unit_); float total_ram = memtotal / divisor; float total_swap = swaptotal / divisor; float used_ram = (memtotal - memfree) / divisor; @@ -82,7 +100,7 @@ auto waybar::modules::Memory::update() -> void { fmt::arg("swapUsed", used_swap), fmt::arg("avail", available_ram), fmt::arg("swapAvail", available_swap))); } else { - label_.set_tooltip_markup(fmt::format("{:.{}f}GiB used", used_ram, 1)); + label_.set_tooltip_markup(fmt::format("{:.{}f}{} used", used_ram, 1, unit_)); } } } else { @@ -91,25 +109,3 @@ auto waybar::modules::Memory::update() -> void { // Call parent update ALabel::update(); } - -float waybar::modules::Memory::calc_divisor(const std::string& divisor) { - if (divisor == "kB") { - return 1.0; - } else if (divisor == "kiB") { - return 1.024; - } else if (divisor == "MB") { - return 1.000 * 1000.0; - } else if (divisor == "MiB") { - return 1.024 * 1024.0; - } else if (divisor == "GB") { - return 1.000 * 1000.0 * 1000.0; - } else if (divisor == "GiB") { - return 1.024 * 1024.0 * 1024.0; - } else if (divisor == "TB") { - return 1.000 * 1000.0 * 1000.0 * 1000.0; - } else if (divisor == "TiB") { - return 1.024 * 1024.0 * 1024.0 * 1024.0; - } else { // default to GiB if it is anything that we don't recongnise - return 1.024 * 1024.0 * 1024.0; - } -} From 3eeffbe3891f517f977d474decc8d3b794e63d83 Mon Sep 17 00:00:00 2001 From: DreamMaoMao <2523610504@qq.com> Date: Thu, 28 May 2026 15:25:35 +0800 Subject: [PATCH 724/780] feat: add mango modules: workspace,language,keymode,window,layout --- README.md | 1 + include/modules/mango/backend.hpp | 67 ++++++ include/modules/mango/keymode.hpp | 27 +++ include/modules/mango/language.hpp | 43 ++++ include/modules/mango/layout.hpp | 27 +++ include/modules/mango/window.hpp | 30 +++ include/modules/mango/workspaces.hpp | 40 ++++ man/waybar-mango-keymode.5.scd | 63 ++++++ man/waybar-mango-language.5.scd | 74 +++++++ man/waybar-mango-layout.5.scd | 75 +++++++ man/waybar-mango-window.5.scd | 70 ++++++ man/waybar-mango-workspaces.5.scd | 112 ++++++++++ meson.build | 19 ++ meson_options.txt | 1 + src/factory.cpp | 24 ++ src/modules/mango/backend.cpp | 317 +++++++++++++++++++++++++++ src/modules/mango/keymode.cpp | 58 +++++ src/modules/mango/language.cpp | 148 +++++++++++++ src/modules/mango/layout.cpp | 56 +++++ src/modules/mango/window.cpp | 93 ++++++++ src/modules/mango/workspaces.cpp | 252 +++++++++++++++++++++ 21 files changed, 1597 insertions(+) create mode 100644 include/modules/mango/backend.hpp create mode 100644 include/modules/mango/keymode.hpp create mode 100644 include/modules/mango/language.hpp create mode 100644 include/modules/mango/layout.hpp create mode 100644 include/modules/mango/window.hpp create mode 100644 include/modules/mango/workspaces.hpp create mode 100644 man/waybar-mango-keymode.5.scd create mode 100644 man/waybar-mango-language.5.scd create mode 100644 man/waybar-mango-layout.5.scd create mode 100644 man/waybar-mango-window.5.scd create mode 100644 man/waybar-mango-workspaces.5.scd create mode 100644 src/modules/mango/backend.cpp create mode 100644 src/modules/mango/keymode.cpp create mode 100644 src/modules/mango/language.cpp create mode 100644 src/modules/mango/layout.cpp create mode 100644 src/modules/mango/window.cpp create mode 100644 src/modules/mango/workspaces.cpp diff --git a/README.md b/README.md index 9ca041d1..687f8234 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ - River (Mapping mode, Tags, Focused window name) - Hyprland (Window Icons, Workspaces, Focused window name) - Niri (Workspaces, Focused window name, Language) +- Mango (Workspaces, Focused window name, Language, Keymode) - DWL (Tags, Focused window name) [requires dwl ipc patch](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/ipc) - Tray [#21](https://github.com/Alexays/Waybar/issues/21) - Local time diff --git a/include/modules/mango/backend.hpp b/include/modules/mango/backend.hpp new file mode 100644 index 00000000..a1b0392e --- /dev/null +++ b/include/modules/mango/backend.hpp @@ -0,0 +1,67 @@ +// include/modules/mango/backend.hpp +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "util/json.hpp" + +namespace waybar::modules::mango { + +class EventHandler { + public: + virtual void onEvent(const Json::Value& ev) = 0; + virtual ~EventHandler() = default; +}; + +class IPC { + public: + static IPC& getInstance(); + IPC(const IPC&) = delete; + IPC& operator=(const IPC&) = delete; + + void registerForIPC(const std::string& ev, EventHandler* handler); + void unregisterForIPC(EventHandler* handler); + + static Json::Value send(const Json::Value& request); + static void sendAsync(const Json::Value& request); + + std::unique_lock lockData() { return std::unique_lock(data_mutex_); } + + std::unordered_map getMonitors() const; + Json::Value getMonitor(const std::string& name); + Json::Value getActiveClientForMonitor(const std::string& name) const; + std::string getKeyboardLayout() const; + std::string getKeymode() const; + std::string getLayoutSymbolForMonitor(const std::string& name) const; + + private: + IPC(); + ~IPC(); + void startIPC(); + static int connectToSocket(); + void parseIPC(const std::string& line); + + void handleMonitorUpdate(const Json::Value& mon); + void updateFocusingClient(const Json::Value& client); + void updateKeyboardLayout(const std::string& layout); + + static Json::Value sendCommand(const std::string& cmd); + + int sockfd_ = -1; + std::thread ipc_thread_; + mutable std::mutex data_mutex_; + std::unordered_map monitors_; + std::unordered_map clients_; + uint64_t focusing_client_id_ = 0; + std::string keyboard_layout_; + std::string keymode_; + Json::Value active_client_; + std::mutex callback_mutex_; + std::list> callbacks_; +}; +} // namespace waybar::modules::mango \ No newline at end of file diff --git a/include/modules/mango/keymode.hpp b/include/modules/mango/keymode.hpp new file mode 100644 index 00000000..5e3ef685 --- /dev/null +++ b/include/modules/mango/keymode.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include "ALabel.hpp" +#include "bar.hpp" +#include "modules/mango/backend.hpp" + +namespace waybar::modules::mango { + +class Keymode : public ALabel, public EventHandler { + public: + Keymode(const std::string&, const Bar&, const Json::Value&); + ~Keymode() override; + void update() override; + + private: + void onEvent(const Json::Value& ev) override; + void doUpdate(); + + std::mutex mutex_; + const Bar& bar_; + std::string last_keymode_; +}; + +} // namespace waybar::modules::mango \ No newline at end of file diff --git a/include/modules/mango/language.hpp b/include/modules/mango/language.hpp new file mode 100644 index 00000000..3d91515b --- /dev/null +++ b/include/modules/mango/language.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include + +#include "ALabel.hpp" +#include "bar.hpp" +#include "modules/mango/backend.hpp" + +namespace waybar::modules::mango { + +class Language : public ALabel, public EventHandler { + public: + Language(const std::string&, const Bar&, const Json::Value&); + ~Language() override; + void update() override; + + private: + void updateFromIPC(); + void onEvent(const Json::Value& ev) override; + void doUpdate(); + + struct Layout { + std::string full_name; + std::string short_name; + std::string variant; + std::string short_description; + }; + + Layout getLayout(const std::string& fullName); + + std::mutex mutex_; + const Bar& bar_; + + std::vector layouts_; + unsigned current_idx_; + std::string last_short_name_; + + struct rxkb_context* rxkb_ctx_ = nullptr; +}; + +} // namespace waybar::modules::mango \ No newline at end of file diff --git a/include/modules/mango/layout.hpp b/include/modules/mango/layout.hpp new file mode 100644 index 00000000..4a349876 --- /dev/null +++ b/include/modules/mango/layout.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include "ALabel.hpp" +#include "bar.hpp" +#include "modules/mango/backend.hpp" + +namespace waybar::modules::mango { + +class Layout : public ALabel, public EventHandler { + public: + Layout(const std::string&, const Bar&, const Json::Value&); + ~Layout() override; + void update() override; + + private: + void onEvent(const Json::Value& ev) override; + void doUpdate(); + + std::mutex mutex_; + const Bar& bar_; + std::string last_symbol_; +}; + +} // namespace waybar::modules::mango \ No newline at end of file diff --git a/include/modules/mango/window.hpp b/include/modules/mango/window.hpp new file mode 100644 index 00000000..987da34b --- /dev/null +++ b/include/modules/mango/window.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#include + +#include "AAppIconLabel.hpp" +#include "bar.hpp" +#include "modules/mango/backend.hpp" + +namespace waybar::modules::mango { + +class Window : public AAppIconLabel, public EventHandler { + public: + Window(const std::string&, const Bar&, const Json::Value&); + ~Window() override; + void update() override; + + private: + void onEvent(const Json::Value& ev) override; + void doUpdate(); + void setClass(const std::string& className, bool enable); + + const Bar& bar_; + std::string oldAppId_; + std::mutex mutex_; +}; + +} // namespace waybar::modules::mango \ No newline at end of file diff --git a/include/modules/mango/workspaces.hpp b/include/modules/mango/workspaces.hpp new file mode 100644 index 00000000..f5e52022 --- /dev/null +++ b/include/modules/mango/workspaces.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include + +#include "AModule.hpp" +#include "bar.hpp" +#include "modules/mango/backend.hpp" + +namespace waybar::modules::mango { + +class Workspaces : public AModule, public EventHandler { + public: + Workspaces(const std::string&, const Bar&, const Json::Value&); + ~Workspaces() override; + void update() override; + + private: + void onEvent(const Json::Value& ev) override; + void doUpdate(); + + Gtk::Button& addButton(uint64_t idx); + void updateButtonState(Gtk::Button& button, const Json::Value& tag, const Json::Value& monitor); + std::string getIcon(const std::string& value, const Json::Value& tag); + bool handleButtonClick(GdkEventButton* event, uint64_t idx, bool isOverview); + + const Bar& bar_; + Gtk::Box box_; + + std::unordered_map buttons_; + Gtk::Button* overview_button_ = nullptr; + + std::string on_click_left_; + std::string on_click_middle_; + std::string on_click_right_; +}; + +} // namespace waybar::modules::mango \ No newline at end of file diff --git a/man/waybar-mango-keymode.5.scd b/man/waybar-mango-keymode.5.scd new file mode 100644 index 00000000..b96c1903 --- /dev/null +++ b/man/waybar-mango-keymode.5.scd @@ -0,0 +1,63 @@ +waybar-mango-keymode(5) + +# NAME + +waybar - mango keymode module + +# DESCRIPTION + +The *keymode* module displays the current keyboard mode (e.g. "resize", "default") in the Mango compositor. It is hidden when no mode is active. + +# CONFIGURATION + +Addressed by *mango/keymode* + +*format*: ++ + typeof: string ++ + default: {} ++ + The format, how the mode should be displayed. *{mode}* is replaced by the current mode name. + +*format-*: ++ + typeof: string ++ + Provide a custom format for a specific keymode. ** is the mode name as reported by Mango (e.g. "resize"). The value can contain *{mode}* as a placeholder. + If this option is set, it overrides the main *format* for that mode. + +*menu*: ++ + typeof: string ++ + Action that pops up a menu. + +*menu-file*: ++ + typeof: string ++ + Location of the menu descriptor file. + +*menu-actions*: ++ + typeof: array ++ + Actions for the menu buttons. + +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables the module to consume all leftover space. + +# FORMAT REPLACEMENTS + +*{mode}*: The name of the current keymode. + +# EXAMPLES + +``` +"mango/keymode": { + "format": "[{mode}]", + "format-resize": " Resizing" +} +``` + +# STYLE + +- *#keymode* + +A CSS class with the current mode name (e.g. *.resize*) is added to the widget, allowing per‑mode styling: + +``` +#keymode.resize { background: #ff0000; } +``` diff --git a/man/waybar-mango-language.5.scd b/man/waybar-mango-language.5.scd new file mode 100644 index 00000000..53fd9569 --- /dev/null +++ b/man/waybar-mango-language.5.scd @@ -0,0 +1,74 @@ +waybar-mango-language(5) + +# NAME + +waybar - mango language module + +# DESCRIPTION + +The *language* module displays the currently active keyboard layout in the Mango compositor. + +# CONFIGURATION + +Addressed by *mango/language* + +*format*: ++ + typeof: string ++ + default: {} ++ + The format, how the layout should be displayed. See *FORMAT REPLACEMENTS*. + +*format-*: ++ + typeof: string ++ + Provide an alternative format string for a given language. + is the short description of the layout (e.g. "us", "de"). + The value is used as the replacement for *{}* in the main *format*. + This option can be repeated for multiple languages. + +*menu*: ++ + typeof: string ++ + Action that pops up a menu. + +*menu-file*: ++ + typeof: string ++ + Location of the menu descriptor file. It must contain an element of type GtkMenu with id *menu*. + +*menu-actions*: ++ + typeof: array ++ + Actions corresponding to the buttons of the menu. + +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables this module to consume all leftover space dynamically. + +# FORMAT REPLACEMENTS + +*{short}*: Short name of the layout (e.g. "us"). This is also the default when no format is specified. + +*{shortDescription}*: Short description of the layout (same as *{short}* in most cases). + +*{long}*: Full name of the layout as reported by Mango (e.g. "English (US)"). + +*{variant}*: Variant of the layout, if any. + +# EXAMPLES + +``` +"mango/language": { + "format": " {long} ", + "format-us": "US", + "format-de": "DE" +} +``` + +# STYLE + +- *#language* + +A CSS class matching the current layout's short name is added to the widget. +This allows per‑layout styling: + +``` +#language.us { color: #00ff00; } +#language.de { color: #ff0000; } +``` diff --git a/man/waybar-mango-layout.5.scd b/man/waybar-mango-layout.5.scd new file mode 100644 index 00000000..6cf3290b --- /dev/null +++ b/man/waybar-mango-layout.5.scd @@ -0,0 +1,75 @@ +waybar-mango-layout(5) + +# NAME + +waybar - mango layout module + +# DESCRIPTION + +The *layout* module displays the current layout symbol of the monitor (e.g. "S", "M") in the Mango compositor. +It supports dynamic CSS classes and custom formats based on the active layout symbol. + +# CONFIGURATION + +Addressed by *mango/layout* + +*format*: ++ + typeof: string ++ + default: {symbol} ++ + The default format, how the layout symbol should be displayed. *{symbol}* is replaced by the current layout symbol. + +*format-*: ++ + typeof: string ++ + default: *none* ++ + The custom format to use when a specific layout symbol is active (e.g., *format-S*, *format-M*). Note that the symbol string is strictly case-sensitive. If no match is found, it falls back to *format*. + +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables the module to consume all leftover space. + +# FORMAT REPLACEMENTS + +*{symbol}*: The layout symbol reported by Mango (e.g., "S", "M", "Dwindle"). + +# CUSTOM FORMATS + +You can define specific formats for different layouts by appending the exact layout symbol to the *format-* key in your configuration. + +For example, if your Mango compositor reports the symbol "S" for a spiral layout and "M" for a master layout, you can use *format-S* and *format-M* to define unique icons or text for each. Keep in mind that JSON keys are case-sensitive, so if the compositor sends "S", the key must be exactly *format-S*. + +# STYLE + +The layout module provides dynamic CSS classes based on the current layout symbol, allowing you to style each layout differently. + +* *#mango-layout* +* *.* - The current layout symbol is dynamically added as a CSS class name (e.g., *.S*, *.M*). + +# EXAMPLES + +``` +"mango/layout": { + "format": "[] {symbol}", + "format-S": "󰌌 {symbol}", + "format-M": "󰕰 {symbol}" +} +``` + +## CSS Example + +``` +#mango-layout { + color: #ffffff; + padding: 0 5px; +} + +/* Specific color for the "S" layout */ +#mango-layout.S { + color: #a6e3a1; +} + +/* Specific color for the "M" layout */ +#mango-layout.M { + color: #f38ba8; +} +``` diff --git a/man/waybar-mango-window.5.scd b/man/waybar-mango-window.5.scd new file mode 100644 index 00000000..7ee9b02c --- /dev/null +++ b/man/waybar-mango-window.5.scd @@ -0,0 +1,70 @@ +waybar-mango-window(5) + +# NAME + +waybar - mango window module + +# DESCRIPTION + +The *window* module displays the title and app ID of the currently focused window in the Mango compositor. + +# CONFIGURATION + +Addressed by *mango/window* + +*format*: ++ + typeof: string ++ + default: {title} ++ + The format string. See *FORMAT REPLACEMENTS*. + +*rewrite*: ++ + typeof: object ++ + Rules to rewrite the window title. Each key is a regular expression and its value is the replacement string. Captures can be used with *$1*, *$2*, etc. + +*icon*: ++ + typeof: bool ++ + default: false ++ + Whether to show the application icon. + +*icon-size*: ++ + typeof: integer ++ + default: 24 ++ + Size of the application icon in pixels. + +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables the module to consume all leftover space. + +# FORMAT REPLACEMENTS + +*{title}*: The current window title. + +*{app_id}*: The app ID of the focused window. + +# REWRITE RULES + +If the title matches a regular expression from the *rewrite* object, it is replaced by the corresponding value. Regular expression syntax follows ECMAScript rules. Unmatched titles are left unchanged. + +# EXAMPLES + +``` +"mango/window": { + "format": "{title}", + "rewrite": { + "(.*) - Mozilla Firefox": "🌎 $1", + "(.*) - zsh": "> [$1]" + }, + "icon": true, + "icon-size": 20 +} +``` + +# STYLE + +- *#window* +- *#window.empty* – applied when no window is focused (module hidden by default) +- *#window.solo* – applied when only one window is present on the active workspace +- *#window.* – applied when a single window with the given app ID is on the workspace + +The classes *.empty*, *.solo*, and the app‑ID class are set on the module’s event box. \ No newline at end of file diff --git a/man/waybar-mango-workspaces.5.scd b/man/waybar-mango-workspaces.5.scd new file mode 100644 index 00000000..985398e7 --- /dev/null +++ b/man/waybar-mango-workspaces.5.scd @@ -0,0 +1,112 @@ +waybar-mango-workspaces(5) + +# NAME + +waybar - mango workspaces module + +# DESCRIPTION + +The *workspaces* module displays the tags (workspaces) of the Mango compositor. It shows an overview button when the overview mode is active (active tag is 0), and individual tag buttons otherwise. + +# CONFIGURATION + +Addressed by *mango/workspaces* + +*format*: ++ + typeof: string ++ + default: {value} ++ + The format for each tag button. See *FORMAT REPLACEMENTS*. + +*format-icons*: ++ + typeof: object ++ + Icons to be used instead of the workspace index or name. Keys can be a workspace index (as a string), or one of the following special state keys: *default*, *active*, *urgent*, *empty*. + +*disable-markup*: ++ + typeof: bool ++ + default: false ++ + If true, the button label will not be interpreted as Pango markup. + +*current-only*: ++ + typeof: bool ++ + default: false ++ + If true, only the currently active workspace button is shown. + +*hide-empty*: ++ + typeof: bool ++ + default: false ++ + If true, buttons for empty (client_count == 0) workspaces are hidden, unless the workspace is active. + +*on-click*: ++ + typeof: string ++ + Command to execute on left click. Typically set to *activate* or *toggle*. + +*on-click-middle*: ++ + typeof: string ++ + Command for middle click. Same actions as *on-click*. + +*on-click-right*: ++ + typeof: string ++ + Command for right click. Same actions as *on-click*. + +*overview-label*: ++ + typeof: string ++ + default: "OVERVIEW" ++ + Label shown on the overview button when the overview is active. + +*expand*: ++ + typeof: bool ++ + default: false ++ + Enables the module to consume leftover space. + +# FORMAT REPLACEMENTS + +*{value}*: Workspace index (same as *{index}* for unnamed workspaces). + +*{name}*: Workspace name (equal to the index for unnamed workspaces in Mango). + +*{icon}*: Icon selected from *format-icons* based on workspace index and state. + +*{index}*: Numeric index of the workspace. + +*{output}*: Name of the output where the workspace is located. + +# CLICK ACTIONS + +When a tag button is clicked, the action from *on-click* (or its middle/right variants) is evaluated. +Supported actions: + +- *activate*: dispatch view, +- *toggle*: dispatch toggleview, + +When the overview button is clicked, the actions change to: + +- *activate*: dispatch overview +- *toggle*: dispatch toggleoverview + +# EXAMPLES + +``` +"mango/workspaces": { + "format": "{icon}", + "format-icons": { + "1": "一", + "2": "二", + "active": "", + "default": "", + "urgent": "", + "empty": "" + }, + "on-click": "activate", + "on-click-right": "toggle", + "overview-label": "" +} +``` + +# STYLE + +- *#workspaces button* +- *#workspaces button.active* – the workspace is active (visible) on its output. +- *#workspaces button.urgent* – the workspace has at least one urgent window. +- *#workspaces button.empty* – the workspace contains no clients. +- *#workspaces button.current_output* – the workspace belongs to the output where the bar is shown. +- *#workspaces button.overview* – the overview button (visible in overview mode). \ No newline at end of file diff --git a/meson.build b/meson.build index 0c494eb2..7a957559 100644 --- a/meson.build +++ b/meson.build @@ -348,6 +348,25 @@ if get_option('niri') ) endif +if get_option('mango') + add_project_arguments('-DHAVE_MANGO', language: 'cpp') + src_files += files( + 'src/modules/mango/backend.cpp', + 'src/modules/mango/language.cpp', + 'src/modules/mango/keymode.cpp', + 'src/modules/mango/window.cpp', + 'src/modules/mango/workspaces.cpp', + 'src/modules/mango/layout.cpp' + ) + man_files += files( + 'man/waybar-mango-language.5.scd', + 'man/waybar-mango-keymode.5.scd', + 'man/waybar-mango-window.5.scd', + 'man/waybar-mango-workspaces.5.scd', + 'man/waybar-mango-layout.5.scd' + ) +endif + if true add_project_arguments('-DHAVE_WAYFIRE', language: 'cpp') src_files += files( diff --git a/meson_options.txt b/meson_options.txt index c0f63f28..65ff2a64 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -20,5 +20,6 @@ option('jack', type: 'feature', value: 'auto', description: 'Enable support for option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber') option('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava') option('niri', type: 'boolean', description: 'Enable support for niri') +option('mango', type: 'boolean', description: 'Enable support for mango') option('login-proxy', type: 'boolean', description: 'Enable interfacing with dbus login interface') option('gps', type: 'feature', value: 'auto', description: 'Enable support for gps') diff --git a/src/factory.cpp b/src/factory.cpp index 2fd3e3b8..47166f1a 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -42,6 +42,13 @@ #include "modules/niri/window.hpp" #include "modules/niri/workspaces.hpp" #endif +#ifdef HAVE_MANGO +#include "modules/mango/language.hpp" +#include "modules/mango/keymode.hpp" +#include "modules/mango/window.hpp" +#include "modules/mango/workspaces.hpp" +#include "modules/mango/layout.hpp" +#endif #ifdef HAVE_WAYFIRE #include "modules/wayfire/window.hpp" #include "modules/wayfire/workspaces.hpp" @@ -231,6 +238,23 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name, return new waybar::modules::niri::Workspaces(id, bar_, config_[name]); } #endif +#ifdef HAVE_MANGO + if (ref == "mango/window") { + return new waybar::modules::mango::Window(id, bar_, config_[name]); + } + if (ref == "mango/workspaces") { + return new waybar::modules::mango::Workspaces(id, bar_, config_[name]); + } + if (ref == "mango/language") { + return new waybar::modules::mango::Language(id, bar_, config_[name]); + } + if (ref == "mango/keymode") { + return new waybar::modules::mango::Keymode(id, bar_, config_[name]); + } + if (ref == "mango/layout") { + return new waybar::modules::mango::Layout(id, bar_, config_[name]); + } +#endif #ifdef HAVE_WAYFIRE if (ref == "wayfire/window") { return new waybar::modules::wayfire::Window(id, bar_, config_[name]); diff --git a/src/modules/mango/backend.cpp b/src/modules/mango/backend.cpp new file mode 100644 index 00000000..a8976f21 --- /dev/null +++ b/src/modules/mango/backend.cpp @@ -0,0 +1,317 @@ +#include "modules/mango/backend.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "util/scoped_fd.hpp" + +namespace waybar::modules::mango { + +int IPC::connectToSocket() { + const char* socket_path = getenv("MANGO_INSTANCE_SIGNATURE"); + if (!socket_path) { + throw std::runtime_error("Mango IPC: MANGO_INSTANCE_SIGNATURE not set"); + } + + struct sockaddr_un addr; + util::ScopedFd fd(socket(AF_UNIX, SOCK_STREAM, 0)); + if (fd == -1) throw std::runtime_error("socket() failed"); + + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + addr.sun_path[sizeof(addr.sun_path) - 1] = 0; + + if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { + throw std::runtime_error("connect() failed"); + } + return fd.release(); +} + +Json::Value IPC::sendCommand(const std::string& cmd) { + util::ScopedFd fd(IPC::connectToSocket()); + std::string full_cmd = cmd + "\n"; + + ssize_t total_written = 0; + while (total_written < (ssize_t)full_cmd.size()) { + ssize_t res = write(fd, full_cmd.c_str() + total_written, full_cmd.size() - total_written); + if (res < 0) { + if (errno == EINTR) continue; + throw std::runtime_error("Failed to write command"); + } + total_written += res; + } + + char buf[4096]; + std::string response; + while (true) { + ssize_t n = read(fd, buf, sizeof(buf) - 1); + if (n <= 0) { + if (n == 0) break; + if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue; + throw std::runtime_error("Read error"); + } + buf[n] = '\0'; + response += buf; + if (response.find('\n') != std::string::npos) break; + } + + Json::Value root; + std::istringstream iss(response); + Json::CharReaderBuilder builder; + std::string errors; + if (!Json::parseFromStream(builder, iss, &root, &errors)) { + throw std::runtime_error("JSON parse error: " + errors); + } + return root; +} + +Json::Value IPC::send(const Json::Value& request) { + if (!request.isMember("command")) { + throw std::runtime_error("Mango IPC: request must have 'command' field"); + } + return sendCommand(request["command"].asString()); +} + +void IPC::sendAsync(const Json::Value& request) { + if (!request.isMember("command")) { + spdlog::error("Mango IPC: request must have 'command' field"); + return; + } + std::string cmd = request["command"].asString(); + + std::thread([cmd]() { + try { + IPC::sendCommand(cmd); + } catch (const std::exception& e) { + spdlog::error("IPC async send failed: {}", e.what()); + } + }).detach(); +} + +IPC::IPC() : sockfd_(-1), active_client_(Json::nullValue) { startIPC(); } + +IPC::~IPC() { + if (sockfd_ != -1) close(sockfd_); + if (ipc_thread_.joinable()) ipc_thread_.join(); +} + +void IPC::startIPC() { + sockfd_ = IPC::connectToSocket(); + + ipc_thread_ = std::thread([this]() { + spdlog::info("Mango IPC thread started"); + + struct pollfd pfd; + pfd.fd = sockfd_; + pfd.events = POLLIN; + + const std::vector subs = {"watch all-monitors"}; + for (const auto& cmd : subs) { + if (write(sockfd_, cmd.c_str(), cmd.size()) != (ssize_t)cmd.size() || + write(sockfd_, "\n", 1) != 1) { + spdlog::error("Failed to subscribe to {}", cmd); + return; + } + } + + char buf[4096]; + std::string buffer; + while (true) { + int ret = poll(&pfd, 1, 1000); + if (ret == 0) continue; + if (ret < 0) { + if (errno == EINTR) continue; + spdlog::error("IPC poll error: {}", strerror(errno)); + break; + } + + if (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) { + spdlog::info("Mango IPC socket closed or invalid"); + break; + } + + if (pfd.revents & POLLIN) { + ssize_t n = read(sockfd_, buf, sizeof(buf)); + if (n == 0) { + spdlog::info("Mango IPC connection closed"); + break; + } + if (n < 0) { + if (errno == EINTR) continue; + spdlog::error("IPC read error: {}", strerror(errno)); + break; + } + buffer.append(buf, n); + + size_t pos; + while ((pos = buffer.find('\n')) != std::string::npos) { + std::string line = buffer.substr(0, pos); + buffer.erase(0, pos + 1); + if (line.empty()) continue; + try { + parseIPC(line); + } catch (const std::exception& e) { + spdlog::warn("Failed to parse IPC line: {} - {}", line, e.what()); + } + } + } + } + }); +} + +void IPC::parseIPC(const std::string& line) { + Json::Value root; + Json::CharReaderBuilder builder; + std::string errors; + std::istringstream iss(line); + if (!Json::parseFromStream(builder, iss, &root, &errors)) { + throw std::runtime_error("JSON parse error: " + errors); + } + + if (root.isMember("monitors") && root["monitors"].isArray()) { + for (const auto& mon : root["monitors"]) { + handleMonitorUpdate(mon); + } + + Json::Value active_monitor; + for (const auto& mon : root["monitors"]) { + if (mon["active"].asBool()) { + active_monitor = mon; + break; + } + } + + if (!active_monitor.isNull()) { + const auto& active_client = active_monitor["active_client"]; + updateFocusingClient(active_client); + + if (active_monitor.isMember("keyboardlayout")) { + updateKeyboardLayout(active_monitor["keyboardlayout"].asString()); + } + + if (active_monitor.isMember("keymode")) { + std::lock_guard lock(data_mutex_); + keymode_ = active_monitor["keymode"].asString(); + } + } + + std::vector handlers_to_notify; + { + std::lock_guard lock(callback_mutex_); + for (auto& [ev, handler] : callbacks_) { + if (ev == "monitor") { + handlers_to_notify.push_back(handler); + } + } + } + + for (auto* handler : handlers_to_notify) { + handler->onEvent(root); + } + + return; + } + + spdlog::debug("Unhandled IPC message: {}", line); +} + +std::unordered_map IPC::getMonitors() const { + std::lock_guard lock(data_mutex_); + return monitors_; +} + +IPC& IPC::getInstance() { + static IPC instance; + return instance; +} + +Json::Value IPC::getMonitor(const std::string& name) { + std::lock_guard lock(data_mutex_); + auto it = monitors_.find(name); + if (it != monitors_.end()) { + return it->second; + } + return Json::nullValue; +} + +std::string IPC::getKeyboardLayout() const { + std::lock_guard lock(data_mutex_); + return keyboard_layout_; +} + +std::string IPC::getKeymode() const { + std::lock_guard lock(data_mutex_); + return keymode_; +} + +Json::Value IPC::getActiveClientForMonitor(const std::string& name) const { + std::lock_guard lock(data_mutex_); + auto it = monitors_.find(name); + if (it != monitors_.end() && it->second.isMember("active_client")) { + return it->second["active_client"]; + } + return Json::nullValue; +} + +std::string IPC::getLayoutSymbolForMonitor(const std::string& name) const { + std::lock_guard lock(data_mutex_); + auto it = monitors_.find(name); + if (it != monitors_.end() && it->second.isMember("layout_symbol")) { + return it->second["layout_symbol"].asString(); + } + return {}; +} + +void IPC::handleMonitorUpdate(const Json::Value& mon) { + std::lock_guard lock(data_mutex_); + monitors_[mon["name"].asString()] = mon; +} + +void IPC::updateFocusingClient(const Json::Value& client) { + { + std::lock_guard lock(data_mutex_); + active_client_ = client; + + if (client.isNull() || !client.isObject() || client["id"].isNull()) { + focusing_client_id_ = 0; + } else { + focusing_client_id_ = client["id"].asUInt64(); + clients_[focusing_client_id_] = client; + } + } +} + +void IPC::updateKeyboardLayout(const std::string& layout) { + { + std::lock_guard lock(data_mutex_); + keyboard_layout_ = layout; + } +} + +void IPC::registerForIPC(const std::string& ev, EventHandler* handler) { + if (!handler) return; + std::lock_guard lock(callback_mutex_); + callbacks_.emplace_back(ev, handler); +} + +void IPC::unregisterForIPC(EventHandler* handler) { + if (!handler) return; + std::lock_guard lock(callback_mutex_); + for (auto it = callbacks_.begin(); it != callbacks_.end();) { + if (it->second == handler) + it = callbacks_.erase(it); + else + ++it; + } +} + +} // namespace waybar::modules::mango \ No newline at end of file diff --git a/src/modules/mango/keymode.cpp b/src/modules/mango/keymode.cpp new file mode 100644 index 00000000..16009712 --- /dev/null +++ b/src/modules/mango/keymode.cpp @@ -0,0 +1,58 @@ +#include "modules/mango/keymode.hpp" + +#include + +namespace waybar::modules::mango { + +Keymode::Keymode(const std::string& id, const Bar& bar, const Json::Value& config) + : ALabel(config, "keymode", id, "{}", 0, false), bar_(bar) { + IPC::getInstance().registerForIPC("monitor", this); + dp.emit(); +} + +Keymode::~Keymode() { IPC::getInstance().unregisterForIPC(this); } + +void Keymode::onEvent(const Json::Value& ev) { dp.emit(); } + +void Keymode::doUpdate() { + std::lock_guard lock(mutex_); + + std::string current = IPC::getInstance().getKeymode(); + + // if keymode is empty, hide the label + if (current.empty()) { + label_.hide(); + last_keymode_.clear(); + return; + } + + // if keymode is the same as last time, skip style changes + if (current != last_keymode_) { + if (!last_keymode_.empty()) label_.get_style_context()->remove_class(last_keymode_); + label_.get_style_context()->add_class(current); + last_keymode_ = current; + } + + // support config's format-keymode custom format (such as format-default, format-resize, etc.) + std::string text; + std::string format_key = "format-" + current; + if (config_.isMember(format_key)) { + text = fmt::format(fmt::runtime(config_[format_key].asString()), fmt::arg("mode", current)); + } else { + text = fmt::format(fmt::runtime(format_), fmt::arg("mode", current)); + } + + if (!text.empty()) { + label_.show(); + label_.set_markup(text); + } else { + label_.hide(); + } +} + +void Keymode::update() { + doUpdate(); + ALabel::update(); +} + +} // namespace waybar::modules::mango \ No newline at end of file diff --git a/src/modules/mango/language.cpp b/src/modules/mango/language.cpp new file mode 100644 index 00000000..9415002a --- /dev/null +++ b/src/modules/mango/language.cpp @@ -0,0 +1,148 @@ +#include "modules/mango/language.hpp" + +#include +#include +#include + +#include "util/string.hpp" + +namespace waybar::modules::mango { + +Language::Language(const std::string& id, const Bar& bar, const Json::Value& config) + : ALabel(config, "language", id, "{}", 0, false), bar_(bar), rxkb_ctx_(nullptr) { + rxkb_ctx_ = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES); + if (rxkb_ctx_) { + rxkb_context_parse_default_ruleset(rxkb_ctx_); + } + + IPC::getInstance().registerForIPC("monitor", this); + updateFromIPC(); + dp.emit(); +} + +Language::~Language() { + IPC::getInstance().unregisterForIPC(this); + if (rxkb_ctx_) rxkb_context_unref(rxkb_ctx_); +} + +void Language::updateFromIPC() { + std::lock_guard lock(mutex_); + std::string layout = IPC::getInstance().getKeyboardLayout(); + + layouts_.clear(); + if (!layout.empty()) { + Layout l = getLayout(layout); + layouts_.push_back(l); + current_idx_ = 0; + } else { + current_idx_ = 0; + } +} + +void Language::doUpdate() { + std::lock_guard lock(mutex_); + if (layouts_.empty() || current_idx_ >= layouts_.size()) { + label_.hide(); + return; + } + const auto& layout = layouts_[current_idx_]; + + 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; + } + + std::string layoutName; + std::string variant_key = "format-" + layout.short_description + "-" + layout.variant; + if (!layout.variant.empty() && config_.isMember(variant_key)) { + layoutName = + fmt::format(fmt::runtime(config_[variant_key].asString()), + fmt::arg("long", layout.full_name), fmt::arg("short", layout.short_name), + fmt::arg("shortDescription", layout.short_description), + fmt::arg("variant", layout.variant)); + } else if (config_.isMember("format-" + layout.short_description)) { + std::string key = "format-" + layout.short_description; + layoutName = + fmt::format(fmt::runtime(config_[key].asString()), fmt::arg("long", layout.full_name), + fmt::arg("short", layout.short_name), + fmt::arg("shortDescription", layout.short_description), + fmt::arg("variant", layout.variant)); + } else { + layoutName = fmt::format(fmt::runtime(format_), fmt::arg("long", layout.full_name), + fmt::arg("short", layout.short_name), + fmt::arg("shortDescription", layout.short_description), + fmt::arg("variant", layout.variant)); + } + + if (!layoutName.empty()) { + label_.show(); + label_.set_markup(layoutName); + } else { + label_.hide(); + } +} + +void Language::update() { + updateFromIPC(); + doUpdate(); + ALabel::update(); +} + +void Language::onEvent(const Json::Value& ev) { + updateFromIPC(); + dp.emit(); +} + +Language::Layout Language::getLayout(const std::string& fullName) { + if (rxkb_ctx_) { + rxkb_layout* layout = rxkb_layout_first(rxkb_ctx_); + while (layout != nullptr) { + std::string desc = rxkb_layout_get_description(layout); + if (desc == fullName) { + std::string short_name = rxkb_layout_get_name(layout); + const char* variant_ptr = rxkb_layout_get_variant(layout); + std::string variant = variant_ptr ? variant_ptr : ""; + const char* brief_ptr = rxkb_layout_get_brief(layout); + std::string short_description = brief_ptr ? brief_ptr : ""; + + if (short_description.empty()) { + short_description = short_name; + } + + short_description = short_name; + + Layout info{desc, short_name, variant, short_description}; + return info; + } + layout = rxkb_layout_next(layout); + } + } + + spdlog::warn("mango language: rxkb failed to find layout '{}', using string parsing fallback", + fullName); + + Layout l; + l.full_name = fullName; + l.variant = ""; + + size_t paren_start = fullName.find('('); + size_t paren_end = fullName.find(')'); + if (paren_start != std::string::npos && paren_end != std::string::npos && + paren_end > paren_start) { + l.short_name = fullName.substr(paren_start + 1, paren_end - paren_start - 1); + } else if (fullName.length() >= 2) { + l.short_name = fullName.substr(0, 2); + } else { + l.short_name = fullName; + } + + std::transform(l.short_name.begin(), l.short_name.end(), l.short_name.begin(), + [](unsigned char c) { return std::tolower(c); }); + + l.short_description = l.short_name; + + return l; +} + +} // namespace waybar::modules::mango \ No newline at end of file diff --git a/src/modules/mango/layout.cpp b/src/modules/mango/layout.cpp new file mode 100644 index 00000000..124d26cb --- /dev/null +++ b/src/modules/mango/layout.cpp @@ -0,0 +1,56 @@ +#include "modules/mango/layout.hpp" + +#include + +namespace waybar::modules::mango { + +Layout::Layout(const std::string& id, const Bar& bar, const Json::Value& config) + : ALabel(config, "layout", id, "{}", 0, false), bar_(bar) { + IPC::getInstance().registerForIPC("monitor", this); + dp.emit(); +} + +Layout::~Layout() { IPC::getInstance().unregisterForIPC(this); } + +void Layout::onEvent(const Json::Value& ev) { dp.emit(); } + +void Layout::doUpdate() { + std::lock_guard lock(mutex_); + + std::string symbol = IPC::getInstance().getLayoutSymbolForMonitor(bar_.output->name); + + if (symbol.empty()) { + label_.hide(); + last_symbol_.clear(); + return; + } + + if (symbol != last_symbol_) { + if (!last_symbol_.empty()) label_.get_style_context()->remove_class(last_symbol_); + label_.get_style_context()->add_class(symbol); + last_symbol_ = symbol; + } + + std::string text; + std::string format_key = "format-" + symbol; + + if (config_.isMember(format_key)) { + text = fmt::format(fmt::runtime(config_[format_key].asString()), fmt::arg("symbol", symbol)); + } else { + text = fmt::format(fmt::runtime(format_), fmt::arg("symbol", symbol)); + } + + if (!text.empty()) { + label_.show(); + label_.set_markup(text); + } else { + label_.hide(); + } +} + +void Layout::update() { + doUpdate(); + ALabel::update(); +} + +} // namespace waybar::modules::mango \ No newline at end of file diff --git a/src/modules/mango/window.cpp b/src/modules/mango/window.cpp new file mode 100644 index 00000000..54f677bb --- /dev/null +++ b/src/modules/mango/window.cpp @@ -0,0 +1,93 @@ +#include "modules/mango/window.hpp" + +#include + +#include "util/rewrite_string.hpp" +#include "util/sanitize_str.hpp" + +namespace waybar::modules::mango { + +Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) + : AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) { + IPC::getInstance().registerForIPC("monitor", this); + dp.emit(); +} + +Window::~Window() { IPC::getInstance().unregisterForIPC(this); } + +void Window::onEvent(const Json::Value& ev) { dp.emit(); } + +void Window::doUpdate() { + std::lock_guard lock(mutex_); + + const Json::Value& client = IPC::getInstance().getActiveClientForMonitor(bar_.output->name); + + // judge whether to hide: active_client is null or title field is null + if (client.isNull() || !client.isObject() || client["title"].isNull()) { + event_box_.hide(); + label_.hide(); + updateAppIconName("", ""); + setClass("empty", true); + if (!oldAppId_.empty()) setClass(oldAppId_, false); + oldAppId_.clear(); + return; + } + + // if we have a valid client, show the label and update content + event_box_.show(); + label_.show(); + setClass("empty", false); + + std::string title = client["title"].asString(); + std::string appid = client["appid"].asString(); + std::string sanitized_title = waybar::util::sanitize_string(title); + std::string sanitized_appid = waybar::util::sanitize_string(appid); + + label_.set_markup(waybar::util::rewriteString( + fmt::format(fmt::runtime(format_), fmt::arg("title", sanitized_title), + fmt::arg("app_id", sanitized_appid)), + config_["rewrite"])); + + updateAppIconName(appid, ""); + if (tooltipEnabled()) label_.set_tooltip_markup(title); + + // Solo judgment + bool solo = false; + if (client.isMember("tags") && client["tags"].isArray() && client["tags"].size() == 1) { + int tag_idx = client["tags"][0].asInt(); + const auto& monitors = IPC::getInstance().getMonitors(); + auto mon_it = monitors.find(client["monitor"].asString()); + if (mon_it != monitors.end()) { + const auto& tags = mon_it->second["tags"]; + for (const auto& tag : tags) { + if (tag["index"].asInt() == tag_idx) { + solo = (tag["client_count"].asInt() == 1); + break; + } + } + } + } + setClass("solo", solo); + if (!appid.empty()) setClass(appid, solo); + + if (oldAppId_ != appid) { + if (!oldAppId_.empty()) setClass(oldAppId_, false); + oldAppId_ = appid; + } +} + +void Window::update() { + doUpdate(); + AAppIconLabel::update(); +} + +void Window::setClass(const std::string& className, bool enable) { + auto style_context = event_box_.get_style_context(); + if (enable) { + if (!style_context->has_class(className)) style_context->add_class(className); + } else { + style_context->remove_class(className); + } +} + +} // namespace waybar::modules::mango \ No newline at end of file diff --git a/src/modules/mango/workspaces.cpp b/src/modules/mango/workspaces.cpp new file mode 100644 index 00000000..f8849228 --- /dev/null +++ b/src/modules/mango/workspaces.cpp @@ -0,0 +1,252 @@ +#include "modules/mango/workspaces.hpp" + +#include + +#include + +namespace waybar::modules::mango { + +Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value& config) + : AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) { + box_.set_name("workspaces"); + if (!id.empty()) box_.get_style_context()->add_class(id); + box_.get_style_context()->add_class(MODULE_CLASS); + event_box_.add(box_); + + if (config_["on-click"].isString()) on_click_left_ = config_["on-click"].asString(); + if (config_["on-click-middle"].isString()) + on_click_middle_ = config_["on-click-middle"].asString(); + if (config_["on-click-right"].isString()) on_click_right_ = config_["on-click-right"].asString(); + + overview_button_ = new Gtk::Button("OVERVIEW"); + overview_button_->set_relief(Gtk::RELIEF_NONE); + box_.pack_start(*overview_button_, false, false, 0); + + if (!on_click_left_.empty() || !on_click_middle_.empty() || !on_click_right_.empty()) { + overview_button_->add_events(Gdk::BUTTON_PRESS_MASK); + overview_button_->signal_button_press_event().connect( + [this](GdkEventButton* event) -> bool { return handleButtonClick(event, 0, true); }, false); + } + + IPC::getInstance().registerForIPC("monitor", this); + dp.emit(); +} + +Workspaces::~Workspaces() { + IPC::getInstance().unregisterForIPC(this); + + if (overview_button_) { + box_.remove(*overview_button_); + delete overview_button_; + overview_button_ = nullptr; + } + + for (auto& [idx, btn] : buttons_) { + box_.remove(btn); + } + buttons_.clear(); +} + +void Workspaces::onEvent(const Json::Value& ev) { dp.emit(); } + +void Workspaces::doUpdate() { + Json::Value monitor = IPC::getInstance().getMonitor(bar_.output->name); + if (monitor.isNull()) return; + + const auto& tags = monitor["tags"]; + + bool overview_mode = false; + if (monitor.isMember("active_tags") && monitor["active_tags"].isArray()) { + const auto& active_tags = monitor["active_tags"]; + if (active_tags.size() == 1 && active_tags[0].asInt() == 0) { + overview_mode = true; + } + } + + for (auto& [idx, btn] : buttons_) { + btn.hide(); + } + + if (overview_mode) { + overview_button_->show(); + auto style = overview_button_->get_style_context(); + style->add_class("overview"); + if (monitor["active"].asBool()) + style->add_class("current_output"); + else + style->remove_class("current_output"); + + std::string label = + config_["overview-label"].isString() ? config_["overview-label"].asString() : "OVERVIEW"; + + if (!config_["disable-markup"].asBool()) { + if (auto gtk_label = dynamic_cast(overview_button_->get_child())) { + gtk_label->set_markup(label); + } + } else { + overview_button_->set_label(label); + } + } else { + overview_button_->hide(); + + for (auto btn_it = buttons_.begin(); btn_it != buttons_.end();) { + uint64_t id = btn_it->first; + bool found = std::any_of(tags.begin(), tags.end(), [id](const Json::Value& tag) { + return tag["index"].asUInt64() == id; + }); + + if (!found) { + box_.remove(btn_it->second); + btn_it = buttons_.erase(btn_it); + } else { + ++btn_it; + } + } + + for (const auto& tag : tags) { + uint64_t idx = tag["index"].asUInt64(); + auto btn_it = buttons_.find(idx); + Gtk::Button& button = (btn_it == buttons_.end()) ? addButton(idx) : btn_it->second; + updateButtonState(button, tag, monitor); + } + + std::vector indices; + for (const auto& tag : tags) indices.push_back(tag["index"].asUInt64()); + std::sort(indices.begin(), indices.end()); + int pos = 0; + for (uint64_t idx : indices) { + box_.reorder_child(buttons_[idx], pos + 1); + pos++; + } + } +} + +void Workspaces::update() { + doUpdate(); + AModule::update(); +} + +Gtk::Button& Workspaces::addButton(uint64_t idx) { + auto [it, _] = buttons_.emplace(idx, std::to_string(idx)); + auto& button = it->second; + box_.pack_start(button, false, false, 0); + button.set_relief(Gtk::RELIEF_NONE); + + if (!on_click_left_.empty() || !on_click_middle_.empty() || !on_click_right_.empty()) { + button.add_events(Gdk::BUTTON_PRESS_MASK); + button.signal_button_press_event().connect( + [this, idx](GdkEventButton* event) -> bool { return handleButtonClick(event, idx, false); }, + false); + } + + button.show_all(); + return button; +} + +void Workspaces::updateButtonState(Gtk::Button& button, const Json::Value& tag, + const Json::Value& monitor) { + auto style = button.get_style_context(); + bool active = tag["is_active"].asBool(); + bool urgent = tag["is_urgent"].asBool(); + bool empty = (tag["client_count"].asInt() == 0); + + if (active) + style->add_class("active"); + else + style->remove_class("active"); + + if (urgent) + style->add_class("urgent"); + else + style->remove_class("urgent"); + + if (empty) + style->add_class("empty"); + else + style->remove_class("empty"); + + if (monitor["active"].asBool()) + style->add_class("current_output"); + else + style->remove_class("current_output"); + + uint64_t idx = tag["index"].asUInt64(); + std::string name = std::to_string(idx); + if (config_["format"].isString()) { + name = fmt::format(fmt::runtime(config_["format"].asString()), + fmt::arg("icon", getIcon(name, tag)), fmt::arg("value", name), + fmt::arg("index", idx), fmt::arg("output", monitor["name"].asString())); + } + + if (!config_["disable-markup"].asBool()) { + if (auto gtk_label = dynamic_cast(button.get_child())) { + gtk_label->set_markup(name); + } + } else { + button.set_label(name); + } + + if (config_["current-only"].asBool()) { + if (active) + button.show(); + else + button.hide(); + } else if (config_["hide-empty"].asBool() && empty && !active) { + button.hide(); + } else { + button.show(); + } +} + +std::string Workspaces::getIcon(const std::string& value, const Json::Value& tag) { + const auto& icons = config_["format-icons"]; + if (!icons) return value; + + if (tag["is_urgent"].asBool() && icons["urgent"]) return icons["urgent"].asString(); + if (tag["is_active"].asBool() && icons["active"]) return icons["active"].asString(); + if (tag["client_count"].asInt() == 0 && icons["empty"]) return icons["empty"].asString(); + + std::string idx = std::to_string(tag["index"].asUInt()); + if (icons[idx]) return icons[idx].asString(); + if (icons["default"]) return icons["default"].asString(); + return value; +} + +bool Workspaces::handleButtonClick(GdkEventButton* event, uint64_t idx, bool isOverview) { + std::string action; + if (event->button == 1) + action = on_click_left_; + else if (event->button == 2) + action = on_click_middle_; + else if (event->button == 3) + action = on_click_right_; + + if (action.empty()) return true; + + try { + std::string cmd; + if (isOverview) { + if (action == "activate") + cmd = "dispatch overview"; + else if (action == "toggle") + cmd = "dispatch toggleoverview"; + } else { + if (action == "activate") + cmd = "dispatch view," + std::to_string(idx); + else if (action == "toggle") + cmd = "dispatch toggleview," + std::to_string(idx); + } + + if (!cmd.empty()) { + Json::Value req; + req["command"] = cmd; + IPC::sendAsync(req); + } + } catch (const std::exception& e) { + spdlog::error("Error sending IPC command: {}", e.what()); + } + + return true; +} + +} // namespace waybar::modules::mango \ No newline at end of file From 0af3ca1a4ed399d53d853ebe7dd6c7f8c00ea321 Mon Sep 17 00:00:00 2001 From: Matthew Orlando Date: Sun, 31 May 2026 09:58:09 -0700 Subject: [PATCH 725/780] custom_modules: Fix mediaplayer.py handling untitled track * Fix crash calling method on None * Show artist if title is None --- resources/custom_modules/mediaplayer.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/resources/custom_modules/mediaplayer.py b/resources/custom_modules/mediaplayer.py index 524d4d2a..4de1edf8 100755 --- a/resources/custom_modules/mediaplayer.py +++ b/resources/custom_modules/mediaplayer.py @@ -105,24 +105,26 @@ class PlayerManager: current_player = self.get_first_playing_player() if current_player is not None: self.on_metadata_changed(current_player, current_player.props.metadata) - else: + else: self.clear_output() def on_metadata_changed(self, player, metadata, _=None): logger.debug(f"Metadata changed for player {player.props.player_name}") player_name = player.props.player_name artist = player.get_artist() - artist = artist.replace("&", "&") + artist = artist and artist.replace("&", "&") title = player.get_title() - title = title.replace("&", "&") + title = title and title.replace("&", "&") track_info = "" if player_name == "spotify" and "mpris:trackid" in metadata.keys() and ":ad:" in player.props.metadata["mpris:trackid"]: track_info = "Advertisement" elif artist is not None and title is not None: track_info = f"{artist} - {title}" - else: + elif title is not None: track_info = title + elif artist is not None: + track_info = artist if track_info: if player.props.status == "Playing": From 3315631012befb1bd33f7e231272b54a86b79488 Mon Sep 17 00:00:00 2001 From: oqlatulesba3 Date: Tue, 2 Jun 2026 10:51:05 +0300 Subject: [PATCH 726/780] fix(sni): retry Host proxy creation on transient errors When Host::proxyReady fails to create the SnWatcher proxy (e.g. because the Watcher has not finished exporting /StatusNotifierWatcher yet), the cancellable is left set, causing nameAppeared to early-return on every subsequent event (see the // TODO marker). The Host is then stuck without a watcher, and the tray module reports 'No such object path /StatusNotifierWatcher' until the bar is fully restarted. Clear the cancellable on non-CANCELLED errors and schedule a single delayed retry of nameAppeared. The guard `watcher_ != nullptr` skips the retry if a parallel call already succeeded. Closes #3468 --- src/modules/sni/host.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/modules/sni/host.cpp b/src/modules/sni/host.cpp index 567fbf9f..091c45d5 100644 --- a/src/modules/sni/host.cpp +++ b/src/modules/sni/host.cpp @@ -6,6 +6,8 @@ namespace waybar::modules::SNI { +static const unsigned RETRY_DELAY_MS = 200; + Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar, const std::function&)>& on_add, const std::function&)>& on_remove, @@ -72,11 +74,21 @@ void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) { return; } auto host = static_cast(data); - host->watcher_ = watcher; if (error != nullptr) { spdlog::error("Host: {}", error->message); + g_clear_object(&host->cancellable_); + Glib::signal_timeout().connect_once( + [host]() { + if (host->watcher_ != nullptr) { + return; + } + auto conn = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::BUS_TYPE_SESSION); + host->nameAppeared(conn, "org.kde.StatusNotifierWatcher", ""); + }, + RETRY_DELAY_MS); return; } + host->watcher_ = watcher; sn_watcher_call_register_host(host->watcher_, host->object_path_.c_str(), host->cancellable_, &Host::registerHost, data); } From 5cce616d3d72b402dabc64aabb872e0cfb25165c Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Thu, 4 Jun 2026 14:41:31 -0700 Subject: [PATCH 727/780] perf(json): parse directly from string buffers --- include/util/json.hpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/include/util/json.hpp b/include/util/json.hpp index 5f756f35..5f45ae92 100644 --- a/include/util/json.hpp +++ b/include/util/json.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #if (FMT_VERSION >= 90000) @@ -26,14 +27,19 @@ class JsonParser { Json::Value root; // replace all occurrences of "\x" with "\u00", because JSON doesn't allow "\x" escape sequences - std::string modifiedJsonStr = replaceHexadecimalEscape(jsonStr); + std::string modifiedJsonStr; + const std::string* json = &jsonStr; + if (jsonStr.find("\\x") != std::string::npos) { + modifiedJsonStr = replaceHexadecimalEscape(jsonStr); + json = &modifiedJsonStr; + } - std::istringstream jsonStream(modifiedJsonStr); std::string errs; // Use local CharReaderBuilder for thread safety - the IPC singleton's // parser can be called concurrently from multiple module threads Json::CharReaderBuilder readerBuilder; - if (!Json::parseFromStream(readerBuilder, jsonStream, &root, &errs)) { + auto reader = std::unique_ptr(readerBuilder.newCharReader()); + if (!reader->parse(json->data(), json->data() + json->size(), &root, &errs)) { throw std::runtime_error("Error parsing JSON: " + errs); } return root; From 2190871a68730d52cd0e0ce7e2ed959e11c23ead Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Thu, 4 Jun 2026 15:18:43 -0700 Subject: [PATCH 728/780] perf(label): skip redundant markup updates --- include/ALabel.hpp | 9 +++++++++ src/ALabel.cpp | 20 ++++++++++++++++++++ src/modules/mpd/mpd.cpp | 8 ++++---- src/modules/sway/window.cpp | 4 ++-- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/include/ALabel.hpp b/include/ALabel.hpp index b3852877..7c5150a0 100644 --- a/include/ALabel.hpp +++ b/include/ALabel.hpp @@ -4,6 +4,8 @@ #include #include +#include + #include "AModule.hpp" namespace waybar { @@ -25,12 +27,19 @@ class ALabel : public AModule { bool alt_ = false; std::string default_format_; + bool setLabelMarkup(const Glib::ustring& markup); + bool setTooltipMarkup(const Glib::ustring& markup); + bool handleToggle(GdkEventButton* const& e) override; virtual std::string getState(uint8_t value, bool lesser = false); std::map submenus_; std::map menuActionsMap_; static void handleGtkMenuEvent(GtkMenuItem* menuitem, gpointer data); + + private: + std::optional last_label_markup_; + std::optional last_tooltip_markup_; }; } // namespace waybar diff --git a/src/ALabel.cpp b/src/ALabel.cpp index 795f87f1..f2a585fe 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -139,6 +139,26 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st auto ALabel::update() -> void { AModule::update(); } +bool ALabel::setLabelMarkup(const Glib::ustring& markup) { + if (last_label_markup_ == markup) { + return false; + } + + label_.set_markup(markup); + last_label_markup_ = markup; + return true; +} + +bool ALabel::setTooltipMarkup(const Glib::ustring& markup) { + if (last_tooltip_markup_ == markup) { + return false; + } + + label_.set_tooltip_markup(markup); + last_tooltip_markup_ = markup; + return true; +} + std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_t max) { auto format_icons = config_["format-icons"]; if (format_icons.isObject()) { diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 9874f458..873b6251 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -98,7 +98,7 @@ void waybar::modules::MPD::setLabel() { ? config_["format-disconnected"].asString() : "disconnected"; if (format.empty()) { - label_.set_markup(format); + setLabelMarkup(format); label_.show(); } else { label_.hide(); @@ -110,7 +110,7 @@ void waybar::modules::MPD::setLabel() { ? config_["tooltip-format-disconnected"].asString() : "MPD (disconnected)"; // Nothing to format - label_.set_tooltip_markup(tooltip_format); + setTooltipMarkup(tooltip_format); } return; } @@ -190,7 +190,7 @@ void waybar::modules::MPD::setLabel() { label_.hide(); } else { label_.show(); - label_.set_markup(text); + setLabelMarkup(text); } } catch (fmt::format_error const& e) { spdlog::warn("mpd: format error: {}", e.what()); @@ -210,7 +210,7 @@ void waybar::modules::MPD::setLabel() { fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename), fmt::arg("uri", uri)); - label_.set_tooltip_markup(tooltip_text); + setTooltipMarkup(tooltip_text); } catch (fmt::format_error const& e) { spdlog::warn("mpd: format error (tooltip): {}", e.what()); } diff --git a/src/modules/sway/window.cpp b/src/modules/sway/window.cpp index 2908b85c..2b62da14 100644 --- a/src/modules/sway/window.cpp +++ b/src/modules/sway/window.cpp @@ -94,12 +94,12 @@ auto Window::update() -> void { old_app_id_ = app_id_; } - label_.set_markup(waybar::util::rewriteString( + setLabelMarkup(waybar::util::rewriteString( fmt::format(fmt::runtime(format_), fmt::arg("title", window_), fmt::arg("app_id", app_id_), fmt::arg("shell", shell_), fmt::arg("marks", marks_)), config_["rewrite"])); if (tooltipEnabled()) { - label_.set_tooltip_markup(window_); + setTooltipMarkup(window_); } updateAppIcon(); From 0cccf0d8b50dd7576f2295b24065616ef4fe4d0a Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Thu, 4 Jun 2026 15:23:54 -0700 Subject: [PATCH 729/780] perf(mpd): avoid duplicate playing-state updates --- include/modules/mpd/mpd.hpp | 1 - include/modules/mpd/state.hpp | 1 - include/modules/mpd/state.inl.hpp | 1 - src/modules/mpd/mpd.cpp | 15 --------------- src/modules/mpd/state.cpp | 1 - 5 files changed, 19 deletions(-) diff --git a/include/modules/mpd/mpd.hpp b/include/modules/mpd/mpd.hpp index 9d81638e..27c56153 100644 --- a/include/modules/mpd/mpd.hpp +++ b/include/modules/mpd/mpd.hpp @@ -54,7 +54,6 @@ class MPD : public ALabel { void tryConnect(); void checkErrors(mpd_connection* conn); void fetchState(); - void queryMPD(); inline bool stopped() const { return connection_ && state_ == MPD_STATE_STOP; } inline bool playing() const { return connection_ && state_ == MPD_STATE_PLAY; } diff --git a/include/modules/mpd/state.hpp b/include/modules/mpd/state.hpp index 2c9071b4..0624c196 100644 --- a/include/modules/mpd/state.hpp +++ b/include/modules/mpd/state.hpp @@ -197,7 +197,6 @@ class Context { void tryConnect() const; void checkErrors(mpd_connection*) const; void do_update(); - void queryMPD() const; void fetchState() const; constexpr mpd_state state() const; void emit() const; diff --git a/include/modules/mpd/state.inl.hpp b/include/modules/mpd/state.inl.hpp index 895970e6..06232b95 100644 --- a/include/modules/mpd/state.inl.hpp +++ b/include/modules/mpd/state.inl.hpp @@ -15,7 +15,6 @@ constexpr inline mpd_state Context::state() const { return mpd_module_->state_; inline void Context::do_update() { mpd_module_->setLabel(); } inline void Context::checkErrors(mpd_connection* conn) const { mpd_module_->checkErrors(conn); } -inline void Context::queryMPD() const { mpd_module_->queryMPD(); } inline void Context::fetchState() const { mpd_module_->fetchState(); } inline void Context::emit() const { mpd_module_->emit(); } diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 9874f458..a84abf5c 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -51,21 +51,6 @@ auto waybar::modules::MPD::update() -> void { ALabel::update(); } -void waybar::modules::MPD::queryMPD() { - if (connection_ != nullptr) { - spdlog::trace("{}: fetching state information", module_name_); - try { - fetchState(); - spdlog::trace("{}: fetch complete", module_name_); - } catch (std::exception const& e) { - spdlog::error("{}: {}", module_name_, e.what()); - state_ = MPD_STATE_UNKNOWN; - } - - dp.emit(); - } -} - std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) const { std::string result = config_["unknown-tag"].isString() ? config_["unknown-tag"].asString() : "N/A"; diff --git a/src/modules/mpd/state.cpp b/src/modules/mpd/state.cpp index 3d7c8561..eb95665f 100644 --- a/src/modules/mpd/state.cpp +++ b/src/modules/mpd/state.cpp @@ -152,7 +152,6 @@ bool Playing::on_timer() { return false; } - ctx_->queryMPD(); ctx_->emit(); } catch (std::exception const& e) { spdlog::warn("mpd: Playing: error: {}", e.what()); From bef7df8f08b66c1f33910037e84fdfe170d2bbcf Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Thu, 4 Jun 2026 16:08:50 -0700 Subject: [PATCH 730/780] fix(mpd): avoid false no-song warning when stopped --- src/modules/mpd/mpd.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 9874f458..19a59239 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -124,8 +124,9 @@ void waybar::modules::MPD::setLabel() { std::string stateIcon = ""; bool no_song = song_.get() == nullptr; - if (stopped() || no_song) { - if (no_song) spdlog::warn("Bug in mpd: no current song but state is not stopped."); + bool is_stopped = stopped(); + if (is_stopped || no_song) { + if (no_song && !is_stopped) spdlog::warn("mpd: no current song while state is not stopped"); format = config_["format-stopped"].isString() ? config_["format-stopped"].asString() : "stopped"; label_.get_style_context()->add_class("stopped"); From f7b9d8c1238db93325ed1f50681fbc3f9a98ccc9 Mon Sep 17 00:00:00 2001 From: Zuodong Yu Date: Sun, 7 Jun 2026 17:35:37 +0800 Subject: [PATCH 731/780] modules/niri: add {total} format token to workspaces --- man/waybar-niri-workspaces.5.scd | 2 ++ src/modules/niri/workspaces.cpp | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/man/waybar-niri-workspaces.5.scd b/man/waybar-niri-workspaces.5.scd index 4be85eb3..80cc7eb5 100644 --- a/man/waybar-niri-workspaces.5.scd +++ b/man/waybar-niri-workspaces.5.scd @@ -63,6 +63,8 @@ as defined by niri. *{output}*: Output where the workspace is located. +*{total}*: The total number of workspaces. + # ICONS Additional to workspace name matching, the following *format-icons* can be set. diff --git a/src/modules/niri/workspaces.cpp b/src/modules/niri/workspaces.cpp index 97d15215..ce0d2376 100644 --- a/src/modules/niri/workspaces.cpp +++ b/src/modules/niri/workspaces.cpp @@ -100,7 +100,8 @@ void Workspaces::doUpdate() { name = fmt::format(fmt::runtime(format), fmt::arg("icon", getIcon(name, ws)), fmt::arg("value", name), fmt::arg("name", ws["name"].asString()), fmt::arg("index", ws["idx"].asUInt()), - fmt::arg("output", ws["output"].asString())); + fmt::arg("output", ws["output"].asString()), + fmt::arg("total", my_workspaces.size())); } if (!config_["disable-markup"].asBool()) { static_cast(button.get_children()[0])->set_markup(name); From fb829c8a8e037fd8631af94dc2f888075612d1d0 Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Tue, 9 Jun 2026 06:20:05 +0000 Subject: [PATCH 732/780] ## fix(niri/workspaces): crash (segfault) when setting workspace button markup ### Problem Waybar segfaults on startup when `niri/workspaces` is the only configured module (default options). Right after the first `WorkspacesChanged` event: Thread 1 "waybar" received signal SIGSEGV #0 gtk_label_set_markup () #1 waybar::modules::niri::Workspaces::doUpdate () at src/modules/niri/workspaces.cpp:106 ### Root cause [doUpdate()](cci:1://file:///home/lj/Downloads/Waybar/src/modules/niri/window.cpp:27:0-90:1) set the label markup via: static_cast(button.get_children()[0])->set_markup(name); The button's child *is* a valid `GtkLabel`, but gtkmm's `get_children()` returns it wrapped as a generic `Gtk::Widget` (confirmed: `dynamic_cast` to `Gtk::Label*` yields `nullptr`). The unchecked `static_cast` then performs an invalid downcast, producing a corrupt pointer whose `gobj()` is garbage (`0x1`), so `gtk_label_set_markup()` dereferences it and crashes. This is reliably triggered when no other module has instantiated a `Gtk::Label` yet (so the `Gtk::Label` wrapper isn't registered), which is exactly the case for a `niri/workspaces`-only bar. The same idiom exists in `sway/workspaces` and `wayfire/workspaces`; it's masked there because typical configs include other label-using modules. ### Testing - Reproduced the crash on 0.15.0 / current `master` with a minimal `"modules-left": ["niri/workspaces"]` config under niri. - After the fix: no crash; three workspace buttons render with correct names/labels (`niri-workspace-1/2/3`). --- src/modules/niri/workspaces.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/modules/niri/workspaces.cpp b/src/modules/niri/workspaces.cpp index 97d15215..a817ab9c 100644 --- a/src/modules/niri/workspaces.cpp +++ b/src/modules/niri/workspaces.cpp @@ -103,7 +103,9 @@ void Workspaces::doUpdate() { fmt::arg("output", ws["output"].asString())); } if (!config_["disable-markup"].asBool()) { - static_cast(button.get_children()[0])->set_markup(name); + auto* child = gtk_bin_get_child(GTK_BIN(button.gobj())); + if (child != nullptr && GTK_IS_LABEL(child)) + gtk_label_set_markup(GTK_LABEL(child), name.c_str()); } else { button.set_label(name); } From 25061962744c196560cc9853243c2ce986b80d60 Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Tue, 9 Jun 2026 06:49:41 +0000 Subject: [PATCH 733/780] Update privacy.hpp --- include/modules/privacy/privacy.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/modules/privacy/privacy.hpp b/include/modules/privacy/privacy.hpp index 68d2bf19..729fcc6a 100644 --- a/include/modules/privacy/privacy.hpp +++ b/include/modules/privacy/privacy.hpp @@ -28,6 +28,7 @@ class Privacy : public AModule { // Config Gtk::Box box_; + std::vector modules_; uint iconSpacing = 4; uint iconSize = 20; uint transition_duration = 250; From 2d9fee144e7427fb3abfd48921931ade5d08149d Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Tue, 9 Jun 2026 06:51:14 +0000 Subject: [PATCH 734/780] Update privacy.cpp --- src/modules/privacy/privacy.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/modules/privacy/privacy.cpp b/src/modules/privacy/privacy.cpp index 904c8fd9..4e613971 100644 --- a/src/modules/privacy/privacy.cpp +++ b/src/modules/privacy/privacy.cpp @@ -71,6 +71,7 @@ Privacy::Privacy(const std::string& id, const Json::Value& config, Gtk::Orientat auto* item = Gtk::make_managed(module, nodeType, nodePtr, orientation, pos, iconSize, transition_duration); box_.add(*item); + modules_.push_back(item); } } @@ -148,9 +149,7 @@ auto Privacy::update() -> void { bool useAudioOut = false; mutex_.lock(); - for (Gtk::Widget* widget : box_.get_children()) { - auto* module = dynamic_cast(widget); - if (module == nullptr) continue; + for (PrivacyItem* module : modules_) { switch (module->privacy_type) { case util::PipewireBackend::PRIVACY_NODE_TYPE_VIDEO_INPUT: setScreenshare = true; From 61178f7b0e99e30617cc0a7b51aaecbea5e9da9a Mon Sep 17 00:00:00 2001 From: InetAddress <78892259+InetAddress@users.noreply.github.com> Date: Fri, 12 Jun 2026 04:38:10 +0000 Subject: [PATCH 735/780] Change error handling to warning Fixes a bug where removing a hot-plugged device mid-loop causes a filesystem error that turns into a fatal runtime error that crashes waybar entirely, changed to warning instead of runtime error. --- src/modules/battery.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 25436baf..67d6bb57 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -153,7 +153,7 @@ void waybar::modules::Battery::refreshBatteries() { } } } catch (fs::filesystem_error& e) { - throw std::runtime_error(e.what()); + spdlog::warn("Battery directory tracking failed: {}", e.what()); } if (warnFirstTime_ && batteries_.empty()) { if (config_["bat"].isString()) { From 4ef828f7123a6067a619c409f330e2c21660179c Mon Sep 17 00:00:00 2001 From: martuscellifaria Date: Fri, 12 Jun 2026 09:14:04 +0200 Subject: [PATCH 736/780] Fixing CPU usage spike on Waybar --- src/modules/custom.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 28def8c9..c6104e40 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -61,6 +61,10 @@ void waybar::modules::Custom::delayWorker() { } dp.emit(); } + if (!config_["exec"].isString() && !config_["exec-if"].isString()) { + thread_.stop(); + return; + } thread_.sleep_for(interval_); }; } From eb3669decc8efb1a95d4a29b6846f7e93ed5abeb Mon Sep 17 00:00:00 2001 From: martuscellifaria Date: Fri, 12 Jun 2026 09:48:44 +0200 Subject: [PATCH 737/780] Rearranging code in a non-redundant way. --- src/modules/custom.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index c6104e40..7fa5cf01 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -39,12 +39,16 @@ waybar::modules::Custom::~Custom() { } void waybar::modules::Custom::delayWorker() { + if (!config_["exec"].isString() && !config_["exec-if"].isString()) { + dp.emit(); + return; + } + thread_ = [this] { for (int i : this->pid_children_) { int status; waitpid(i, &status, 0); } - this->pid_children_.clear(); bool can_update = true; @@ -61,10 +65,6 @@ void waybar::modules::Custom::delayWorker() { } dp.emit(); } - if (!config_["exec"].isString() && !config_["exec-if"].isString()) { - thread_.stop(); - return; - } thread_.sleep_for(interval_); }; } From 0bc664a99eb27dab7bc351b7e490d82868e3470d Mon Sep 17 00:00:00 2001 From: martuscellifaria Date: Fri, 12 Jun 2026 09:52:03 +0200 Subject: [PATCH 738/780] Readded the whitespace from the original code --- src/modules/custom.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 7fa5cf01..1d9b320c 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -49,6 +49,7 @@ void waybar::modules::Custom::delayWorker() { int status; waitpid(i, &status, 0); } + this->pid_children_.clear(); bool can_update = true; From 14e59daae4137c88157545397644853f22e4eab6 Mon Sep 17 00:00:00 2001 From: iamcheyan Date: Sat, 13 Jun 2026 11:34:29 +0900 Subject: [PATCH 739/780] feat(wlr/taskbar): expose active toplevel state on the bar window Add opt-in bar-css-states option to wlr/taskbar module. When enabled, the module tracks the active workspace and updates CSS classes on window#waybar to reflect the current window state: - toplevel-active: when a window is focused - toplevel-maximized: when any non-minimized window on active workspace is maximized - toplevel-minimized: when the active window is minimized - toplevel-fullscreen: when any non-minimized window on active workspace is fullscreen This feature requires compositor support for ext-workspace-v1 protocol to correctly track workspace membership. Without it, classes fall back to the active application state only. Also includes active-only option implementation. Changes: - Add ext-workspace-v1 protocol support for workspace tracking - Implement WorkspaceState tracking in Taskbar class - Add workspace management lifecycle (create/remove/done) - Add bar CSS class update logic with aggregation - Add visible() getter to Task class - Implement active-only task visibility control - Update man page with documentation and CSS examples Signed-off-by: iamcheyan --- include/modules/wlr/taskbar.hpp | 27 ++++ man/waybar-wlr-taskbar.5.scd | 45 ++++++ src/modules/wlr/taskbar.cpp | 245 +++++++++++++++++++++++++++++++- 3 files changed, 316 insertions(+), 1 deletion(-) diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp index 413eae85..3ad41c6e 100644 --- a/include/modules/wlr/taskbar.hpp +++ b/include/modules/wlr/taskbar.hpp @@ -18,6 +18,7 @@ #include "AModule.hpp" #include "bar.hpp" #include "client.hpp" +#include "ext-workspace-v1-client-protocol.h" #include "giomm/desktopappinfo.h" #include "util/icon_loader.hpp" #include "util/json.hpp" @@ -80,6 +81,7 @@ class Task { std::string title_; std::string app_id_; uint32_t state_ = 0; + struct ext_workspace_handle_v1* workspace_ = nullptr; int32_t drag_start_x; int32_t drag_start_y; @@ -102,6 +104,9 @@ class Task { bool minimized() const { return state_ & MINIMIZED; } bool active() const { return state_ & ACTIVE; } bool fullscreen() const { return state_ & FULLSCREEN; } + bool visible() const { return button_visible_; } + struct ext_workspace_handle_v1* workspace() const { return workspace_; } + void set_workspace(struct ext_workspace_handle_v1* workspace) { workspace_ = workspace; } public: /* Callbacks for the wlr protocol */ @@ -142,6 +147,12 @@ using TaskPtr = std::unique_ptr; class Taskbar : public waybar::AModule { public: + struct WorkspaceState { + Taskbar* taskbar; + struct ext_workspace_handle_v1* handle; + uint32_t state = 0; + }; + Taskbar(const std::string&, const waybar::Bar&, const Json::Value&); ~Taskbar(); void update(); @@ -156,22 +167,35 @@ class Taskbar : public waybar::AModule { std::map app_ids_replace_map_; struct zwlr_foreign_toplevel_manager_v1* manager_; + struct ext_workspace_manager_v1* workspace_manager_; struct wl_seat* seat_; + std::vector workspace_groups_; + std::vector> workspaces_; + struct ext_workspace_handle_v1* current_workspace_ = nullptr; public: /* Callbacks for global registration */ void register_manager(struct wl_registry*, uint32_t name, uint32_t version); + void register_workspace_manager(struct wl_registry*, uint32_t name, uint32_t version); void register_seat(struct wl_registry*, uint32_t name, uint32_t version); /* Callbacks for the wlr protocol */ void handle_toplevel_create(struct zwlr_foreign_toplevel_handle_v1*); void handle_finished(); + void handle_workspace_group_create(struct ext_workspace_group_handle_v1*); + void handle_workspace_group_removed(struct ext_workspace_group_handle_v1*); + void handle_workspace_create(struct ext_workspace_handle_v1*); + void handle_workspace_done(); + void handle_workspace_finished(); + void handle_workspace_removed(struct ext_workspace_handle_v1*); public: void add_button(Gtk::Button&); void move_button(Gtk::Button&, int); void remove_button(Gtk::Button&); void remove_task(uint32_t); + void assign_current_workspace(Task&); + void update_bar_css_classes(); bool show_output(struct wl_output*) const; bool all_outputs() const; @@ -179,6 +203,9 @@ class Taskbar : public waybar::AModule { const IconLoader& icon_loader() const; const std::unordered_set& ignore_list() const; const std::map& app_ids_replace_map() const; + + private: + void set_bar_css_class(const std::string&, bool); }; } /* namespace waybar::modules::wlr */ diff --git a/man/waybar-wlr-taskbar.5.scd b/man/waybar-wlr-taskbar.5.scd index af1ba97f..77cf91e5 100644 --- a/man/waybar-wlr-taskbar.5.scd +++ b/man/waybar-wlr-taskbar.5.scd @@ -18,6 +18,13 @@ Addressed by *wlr/taskbar* default: false ++ If set to false applications on the waybar's current output will be shown. Otherwise, all applications are shown. +*bar-css-states*: ++ + typeof: bool ++ + default: false ++ + If set to true, application state is exposed as CSS classes on the Waybar + window. Maximized and fullscreen state is aggregated across applications + known to belong to the active workspace. See *Bar state style* below. + *format*: ++ typeof: string ++ default: {icon} ++ @@ -52,6 +59,12 @@ Addressed by *wlr/taskbar* default: false ++ If set to true, always reorder the tasks in the taskbar so that the currently active one is first. Otherwise don't reorder. +*active-only*: ++ + typeof: bool ++ + default: false ++ + If set to true, only the currently active application button is shown. + Other applications remain tracked and reappear when activated. + *sort-by-app-id*: ++ typeof: bool ++ default: false ++ @@ -156,3 +169,35 @@ Invalid expressions (e.g., mismatched parentheses) are skipped. - *#taskbar button.minimized* - *#taskbar button.active* - *#taskbar button.fullscreen* + +# Bar state style + +When *bar-css-states* is enabled, the following classes are added to +*window#waybar*: + +- *window#waybar.toplevel-active* +- *window#waybar.toplevel-maximized* +- *window#waybar.toplevel-minimized* +- *window#waybar.toplevel-fullscreen* + +The active, minimized classes describe the active application. The maximized +and fullscreen classes are set if any non-minimized application known to belong +to the active workspace has that state. + +Workspace membership is learned when an application is activated and requires +the compositor to support *ext-workspace-v1*. Before an application has been +activated during the current Waybar session, its workspace may be unknown. On +compositors without *ext-workspace-v1*, these classes fall back to the active +application's state. + +For example: + +``` +window#waybar { + background-color: rgba(0, 0, 0, 0.5); +} + +window#waybar.toplevel-maximized { + background-color: rgba(0, 0, 0, 1); +} +``` diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index bde90910..ab76d89e 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -293,9 +293,12 @@ void Task::handle_output_enter(struct wl_output* output) { button.signal_size_allocate().connect_notify( sigc::mem_fun(this, &Task::on_button_size_allocated)); tbar_->add_button(button); - button.show(); + if (!config_["active-only"].asBool() || active()) { + button.show(); + } button_visible_ = true; spdlog::debug("{} now visible on {}", repr(), bar_.output->name); + tbar_->update_bar_css_classes(); } } @@ -308,6 +311,7 @@ void Task::handle_output_leave(struct wl_output* output) { button.hide(); button_visible_ = false; spdlog::debug("{} now invisible on {}", repr(), bar_.output->name); + tbar_->update_bar_css_classes(); } } @@ -350,6 +354,20 @@ void Task::handle_done() { button.get_style_context()->remove_class("fullscreen"); } + if (button_visible_ && config_["active-only"].asBool()) { + if (active()) { + button.show(); + } else { + button.hide(); + } + } + + if (active()) { + tbar_->assign_current_workspace(*this); + } + + tbar_->update_bar_css_classes(); + if (config_["active-first"].isBool() && config_["active-first"].asBool() && active()) tbar_->move_button(button, 0); @@ -551,6 +569,8 @@ static void handle_global(void* data, struct wl_registry* registry, uint32_t nam const char* interface, uint32_t version) { if (std::strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) { static_cast(data)->register_manager(registry, name, version); + } else if (std::strcmp(interface, ext_workspace_manager_v1_interface.name) == 0) { + static_cast(data)->register_workspace_manager(registry, name, version); } else if (std::strcmp(interface, wl_seat_interface.name) == 0) { static_cast(data)->register_seat(registry, name, version); } @@ -568,6 +588,7 @@ Taskbar::Taskbar(const std::string& id, const waybar::Bar& bar, const Json::Valu bar_(bar), box_{bar.orientation, 0}, manager_{nullptr}, + workspace_manager_{nullptr}, seat_{nullptr} { box_.set_name("taskbar"); if (!id.empty()) { @@ -623,6 +644,27 @@ Taskbar::Taskbar(const std::string& id, const waybar::Bar& bar, const Json::Valu } Taskbar::~Taskbar() { + for (auto& workspace : workspaces_) { + ext_workspace_handle_v1_destroy(workspace->handle); + } + workspaces_.clear(); + for (auto* group : workspace_groups_) { + ext_workspace_group_handle_v1_destroy(group); + } + workspace_groups_.clear(); + + if (workspace_manager_) { + struct wl_display* display = Client::inst()->wl_display; + ext_workspace_manager_v1_stop(workspace_manager_); + wl_display_roundtrip(display); + + if (workspace_manager_) { + spdlog::warn("Workspace manager destroyed before .finished event"); + ext_workspace_manager_v1_destroy(workspace_manager_); + workspace_manager_ = nullptr; + } + } + if (manager_) { struct wl_display* display = Client::inst()->wl_display; /* @@ -639,6 +681,13 @@ Taskbar::~Taskbar() { manager_ = nullptr; } } + + if (config_["bar-css-states"].asBool()) { + set_bar_css_class("toplevel-active", false); + set_bar_css_class("toplevel-maximized", false); + set_bar_css_class("toplevel-minimized", false); + set_bar_css_class("toplevel-fullscreen", false); + } } void Taskbar::update() { @@ -674,6 +723,80 @@ static const struct zwlr_foreign_toplevel_manager_v1_listener toplevel_manager_i .finished = tm_handle_finished, }; +static void workspace_handle_id(void*, struct ext_workspace_handle_v1*, const char*) {} +static void workspace_handle_name(void*, struct ext_workspace_handle_v1*, const char*) {} +static void workspace_handle_coordinates(void*, struct ext_workspace_handle_v1*, struct wl_array*) { +} + +static void workspace_handle_state(void* data, struct ext_workspace_handle_v1*, uint32_t state) { + static_cast(data)->state = state; +} + +static void workspace_handle_capabilities(void*, struct ext_workspace_handle_v1*, uint32_t) {} + +static void workspace_handle_removed(void* data, struct ext_workspace_handle_v1* handle) { + static_cast(data)->taskbar->handle_workspace_removed(handle); +} + +static const struct ext_workspace_handle_v1_listener workspace_handle_impl = { + .id = workspace_handle_id, + .name = workspace_handle_name, + .coordinates = workspace_handle_coordinates, + .state = workspace_handle_state, + .capabilities = workspace_handle_capabilities, + .removed = workspace_handle_removed, +}; + +static void workspace_group_handle_capabilities(void*, struct ext_workspace_group_handle_v1*, + uint32_t) {} +static void workspace_group_handle_output_enter(void*, struct ext_workspace_group_handle_v1*, + struct wl_output*) {} +static void workspace_group_handle_output_leave(void*, struct ext_workspace_group_handle_v1*, + struct wl_output*) {} +static void workspace_group_handle_workspace_enter(void*, struct ext_workspace_group_handle_v1*, + struct ext_workspace_handle_v1*) {} +static void workspace_group_handle_workspace_leave(void*, struct ext_workspace_group_handle_v1*, + struct ext_workspace_handle_v1*) {} + +static void workspace_group_handle_removed(void* data, + struct ext_workspace_group_handle_v1* group) { + static_cast(data)->handle_workspace_group_removed(group); +} + +static const struct ext_workspace_group_handle_v1_listener workspace_group_impl = { + .capabilities = workspace_group_handle_capabilities, + .output_enter = workspace_group_handle_output_enter, + .output_leave = workspace_group_handle_output_leave, + .workspace_enter = workspace_group_handle_workspace_enter, + .workspace_leave = workspace_group_handle_workspace_leave, + .removed = workspace_group_handle_removed, +}; + +static void workspace_manager_handle_group(void* data, struct ext_workspace_manager_v1*, + struct ext_workspace_group_handle_v1* group) { + static_cast(data)->handle_workspace_group_create(group); +} + +static void workspace_manager_handle_workspace(void* data, struct ext_workspace_manager_v1*, + struct ext_workspace_handle_v1* workspace) { + static_cast(data)->handle_workspace_create(workspace); +} + +static void workspace_manager_handle_done(void* data, struct ext_workspace_manager_v1*) { + static_cast(data)->handle_workspace_done(); +} + +static void workspace_manager_handle_finished(void* data, struct ext_workspace_manager_v1*) { + static_cast(data)->handle_workspace_finished(); +} + +static const struct ext_workspace_manager_v1_listener workspace_manager_impl = { + .workspace_group = workspace_manager_handle_group, + .workspace = workspace_manager_handle_workspace, + .done = workspace_manager_handle_done, + .finished = workspace_manager_handle_finished, +}; + void Taskbar::register_manager(struct wl_registry* registry, uint32_t name, uint32_t version) { if (manager_) { spdlog::warn("Register foreign toplevel manager again although already existing!"); @@ -698,6 +821,18 @@ void Taskbar::register_manager(struct wl_registry* registry, uint32_t name, uint spdlog::debug("Failed to register manager"); } +void Taskbar::register_workspace_manager(struct wl_registry* registry, uint32_t name, + uint32_t version) { + if (workspace_manager_) { + return; + } + + version = std::min(version, ext_workspace_manager_v1_interface.version); + workspace_manager_ = static_cast( + wl_registry_bind(registry, name, &ext_workspace_manager_v1_interface, version)); + ext_workspace_manager_v1_add_listener(workspace_manager_, &workspace_manager_impl, this); +} + void Taskbar::register_seat(struct wl_registry* registry, uint32_t name, uint32_t version) { if (seat_) { spdlog::warn("Register seat again although already existing!"); @@ -717,6 +852,64 @@ void Taskbar::handle_finished() { manager_ = nullptr; } +void Taskbar::handle_workspace_group_create(struct ext_workspace_group_handle_v1* handle) { + ext_workspace_group_handle_v1_add_listener(handle, &workspace_group_impl, this); + workspace_groups_.push_back(handle); +} + +void Taskbar::handle_workspace_group_removed(struct ext_workspace_group_handle_v1* handle) { + const auto group = std::find(workspace_groups_.begin(), workspace_groups_.end(), handle); + if (group != workspace_groups_.end()) { + ext_workspace_group_handle_v1_destroy(*group); + workspace_groups_.erase(group); + } +} + +void Taskbar::handle_workspace_create(struct ext_workspace_handle_v1* handle) { + auto workspace = std::make_unique(WorkspaceState{this, handle}); + ext_workspace_handle_v1_add_listener(handle, &workspace_handle_impl, workspace.get()); + workspaces_.push_back(std::move(workspace)); +} + +void Taskbar::handle_workspace_done() { + const auto active_workspace = + std::find_if(workspaces_.begin(), workspaces_.end(), [](const auto& workspace) { + return workspace->state & EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE; + }); + current_workspace_ = + active_workspace == workspaces_.end() ? nullptr : (*active_workspace)->handle; + if (current_workspace_) { + const auto active_task = + std::find_if(tasks_.begin(), tasks_.end(), [](const auto& task) { return task->active(); }); + if (active_task != tasks_.end()) { + (*active_task)->set_workspace(current_workspace_); + } + } + update_bar_css_classes(); +} + +void Taskbar::handle_workspace_finished() { workspace_manager_ = nullptr; } + +void Taskbar::handle_workspace_removed(struct ext_workspace_handle_v1* handle) { + if (current_workspace_ == handle) { + current_workspace_ = nullptr; + } + for (auto& task : tasks_) { + if (task->workspace() == handle) { + task->set_workspace(nullptr); + } + } + + const auto workspace = + std::find_if(workspaces_.begin(), workspaces_.end(), + [handle](const auto& workspace) { return workspace->handle == handle; }); + if (workspace != workspaces_.end()) { + ext_workspace_handle_v1_destroy((*workspace)->handle); + workspaces_.erase(workspace); + } + update_bar_css_classes(); +} + void Taskbar::add_button(Gtk::Button& bt) { box_.pack_start(bt, false, false); box_.get_style_context()->remove_class("empty"); @@ -741,6 +934,56 @@ void Taskbar::remove_task(uint32_t id) { } tasks_.erase(it); + update_bar_css_classes(); +} + +void Taskbar::assign_current_workspace(Task& task) { + if (current_workspace_) { + task.set_workspace(current_workspace_); + } +} + +void Taskbar::update_bar_css_classes() { + if (!config_["bar-css-states"].asBool()) { + return; + } + + const auto active_task = std::find_if(tasks_.begin(), tasks_.end(), [](const TaskPtr& task) { + return task->visible() && task->active(); + }); + + const bool has_active_task = active_task != tasks_.end(); + const auto on_current_workspace = [this](const TaskPtr& task) { + if (!current_workspace_) { + return task->active(); + } + return task->workspace() == current_workspace_; + }; + const bool has_maximized_task = + std::any_of(tasks_.begin(), tasks_.end(), [&on_current_workspace](const TaskPtr& task) { + return task->visible() && !task->minimized() && on_current_workspace(task) && + task->maximized(); + }); + const bool has_fullscreen_task = + std::any_of(tasks_.begin(), tasks_.end(), [&on_current_workspace](const TaskPtr& task) { + return task->visible() && !task->minimized() && on_current_workspace(task) && + task->fullscreen(); + }); + set_bar_css_class("toplevel-active", has_active_task); + set_bar_css_class("toplevel-maximized", has_maximized_task); + set_bar_css_class("toplevel-minimized", has_active_task && (*active_task)->minimized()); + set_bar_css_class("toplevel-fullscreen", has_fullscreen_task); +} + +void Taskbar::set_bar_css_class(const std::string& class_name, bool enabled) { + const auto style = bar_.window.get_style_context(); + if (enabled && !style->has_class(class_name)) { + spdlog::trace("Adding bar class: {}", class_name); + style->add_class(class_name); + } else if (!enabled && style->has_class(class_name)) { + spdlog::trace("Removing bar class: {}", class_name); + style->remove_class(class_name); + } } bool Taskbar::show_output(struct wl_output* output) const { From 942411d7a102951fd3b38407823c066481521120 Mon Sep 17 00:00:00 2001 From: "Robert M. Thomson" Date: Mon, 15 Jun 2026 09:53:44 +0200 Subject: [PATCH 740/780] Force layer-shell commit after configure Assisted-by: pi:gpt-5.5 --- include/bar.hpp | 1 + src/bar.cpp | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/include/bar.hpp b/include/bar.hpp index 54b332bf..292085ae 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -99,6 +99,7 @@ class Bar : public sigc::trackable { void setMode(const bar_mode&); void setPassThrough(bool passthrough); void setPosition(Gtk::PositionType position); + void forceLayerCommit(); void onConfigure(GdkEventConfigure* ev); void configureGlobalOffset(int width, int height); void onOutputGeometryChanged(); diff --git a/src/bar.cpp b/src/bar.cpp index 7ff73c4f..65017c8b 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -309,6 +309,19 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) setupWidgets(); window.show_all(); + /* + * If gtk-layer-shell's synchronous wait for the initial configure timed out, show_all() can + * return with a configured but not-yet-presented surface. Kick GTK/layer-shell once control has + * returned to the main loop, when any late initial configure has been dispatched and widgets have + * had a chance to allocate/draw. + */ + Glib::signal_idle().connect(sigc::track_obj([this] { + window.queue_resize(); + window.queue_draw(); + forceLayerCommit(); + return false; + }, *this)); + if (spdlog::should_log(spdlog::level::debug)) { // Unfortunately, this function isn't in the C++ bindings, so we have to call the C version. char* gtk_tree = gtk_style_context_to_string( @@ -374,7 +387,14 @@ void waybar::Bar::setMode(const struct bar_mode& mode) { * gtk-layer-shell schedules a commit on the next frame event in GTK, but this could fail in * certain scenarios, such as fully occluded bar. */ - gtk_layer_try_force_commit(gtk_window); + forceLayerCommit(); +} + +void waybar::Bar::forceLayerCommit() { + auto* gtk_window = window.gobj(); + if (gtk_window != nullptr && gtk_widget_get_realized(GTK_WIDGET(gtk_window))) { + gtk_layer_try_force_commit(gtk_window); + } wl_display_flush(Client::inst()->wl_display); } @@ -652,6 +672,17 @@ void waybar::Bar::onConfigure(GdkEventConfigure* ev) { configureGlobalOffset(ev->width, ev->height); spdlog::info(BAR_SIZE_MSG, ev->width, ev->height, output->name); + + /* + * gtk-layer-shell waits for the compositor's initial configure while realizing the window. On a + * busy compositor (common during session startup) that wait can time out even though the initial + * configure arrives shortly afterwards. In that case GTK may not schedule the frame commit that + * presents the first layer-surface buffer, leaving an otherwise configured bar invisible. Force a + * commit after every configure so late initial configures, and later compositor-driven resizes, + * always result in a submitted surface state. + */ + window.queue_draw(); + forceLayerCommit(); } void waybar::Bar::configureGlobalOffset(int width, int height) { From 47b2ff09b71e243b8c836b6f42ec08fedf753e67 Mon Sep 17 00:00:00 2001 From: Johannes Haase Date: Tue, 16 Jun 2026 23:27:27 +0200 Subject: [PATCH 741/780] feat(cursor): use cursor-shape-v1 cursor names instead of Gdk::CursorType --- include/AModule.hpp | 2 +- man/waybar-styles.5.scd.in | 20 +++++++++----------- src/AModule.cpp | 14 +++++++------- src/modules/user.cpp | 1 - 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/include/AModule.hpp b/include/AModule.hpp index a338ffe3..634994da 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -41,7 +41,7 @@ class AModule : public IModule { const Json::Value& config_; Gtk::EventBox event_box_; - virtual void setCursor(Gdk::CursorType const& c); + virtual void setCursor(std::string const& c); virtual bool handleToggle(GdkEventButton* const& ev); virtual bool handleMouseEnter(GdkEventCrossing* const& ev); diff --git a/man/waybar-styles.5.scd.in b/man/waybar-styles.5.scd.in index b11e15bd..5f31a5bb 100644 --- a/man/waybar-styles.5.scd.in +++ b/man/waybar-styles.5.scd.in @@ -43,18 +43,17 @@ You can apply special styling to any module for when the cursor hovers it. Most, if not all, module types support setting the `cursor` option. This is configured in your `config.jsonc`. If set to `false`, when hovering the module a -"pointer"(as commonly known from web CSS styling `cursor: pointer`) style cursor +"pointer" (as commonly known from web CSS styling `cursor: pointer`) style cursor will not be shown. Default behavior is to indicate an interaction event is available. -There are more cursor types to choose from by setting the `cursor` option to -a number, see Gdk3 official docs for all possible cursor types: -https://docs.gtk.org/gdk3/enum.CursorType.html. -However, note that not all cursor options listed may be available on -your system. If you attempt to use a cursor which is not available, the -application will crash. +If set to a string value, it must be a valid cursor name +(e.g. `"pointer"`, `"default"`, `"grab"`, `"text"`, `"crosshair"`, etc.), +see the cursor-shape-v1 protocol for all possible cursor types: +https://wayland.app/protocols/cursor-shape-v1#wp_cursor_shape_device_v1:enum:shape. +Depending on the compositor and cursor theme used, cursors not listed in the protocol may also work. -Example of disabling pointer(`Gdk::Hand2`) cursor type on a custom module: +Example of disabling the cursor on a custom module: ``` "custom/my-custom-module": { @@ -63,13 +62,12 @@ Example of disabling pointer(`Gdk::Hand2`) cursor type on a custom module: } ``` -Example of setting cursor type to `Gdk::Boat`(according to -https://docs.gtk.org/gdk3/enum.CursorType.html#boat): +Example of setting the cursor type to `"grab"`: ``` "custom/my-custom-module": { ... - "cursor": 8, + "cursor": "grab", } ``` diff --git a/src/AModule.cpp b/src/AModule.cpp index a5ba69d3..11e78123 100644 --- a/src/AModule.cpp +++ b/src/AModule.cpp @@ -73,9 +73,9 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std:: // Respect user configuration of cursor if (config_.isMember("cursor")) { if (config_["cursor"].isBool() && config_["cursor"].asBool()) { - setCursor(Gdk::HAND2); - } else if (config_["cursor"].isInt()) { - setCursor(Gdk::CursorType(config_["cursor"].asInt())); + setCursor("pointer"); + } else if (config_["cursor"].isString()) { + setCursor(config_["cursor"].asString()); } else { spdlog::warn("unknown cursor option configured on module {}", name_); } @@ -110,10 +110,10 @@ auto AModule::doAction(const std::string& name) -> void { } } -void AModule::setCursor(Gdk::CursorType const& c) { +void AModule::setCursor(std::string const& c) { auto gdk_window = event_box_.get_window(); if (gdk_window) { - auto cursor = Gdk::Cursor::create(c); + auto cursor = Gdk::Cursor::create(gdk_window->get_display(), c); gdk_window->set_cursor(cursor); } else { // window may not be accessible yet, in this case, @@ -134,7 +134,7 @@ bool AModule::handleMouseEnter(GdkEventCrossing* const& e) { // Default behavior indicating event availability if (hasUserEvents_ && !config_.isMember("cursor")) { - setCursor(Gdk::HAND2); + setCursor("pointer"); } return false; @@ -147,7 +147,7 @@ bool AModule::handleMouseLeave(GdkEventCrossing* const& e) { // Default behavior indicating event availability if (hasUserEvents_ && !config_.isMember("cursor")) { - setCursor(Gdk::ARROW); + setCursor("default"); } return false; diff --git a/src/modules/user.cpp b/src/modules/user.cpp index 3ca5e431..87f5ea0f 100644 --- a/src/modules/user.cpp +++ b/src/modules/user.cpp @@ -7,7 +7,6 @@ #include #include -#include "gdkmm/cursor.h" #include "gdkmm/event.h" #include "gdkmm/types.h" #include "glibmm/fileutils.h" From e37fcee35d3124a98e90c220fa6cb5c0862fd296 Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Thu, 18 Jun 2026 14:28:39 +0000 Subject: [PATCH 742/780] fix(sni): ignore name/pixmap updates for custom icons --- include/modules/sni/item.hpp | 1 + src/modules/sni/item.cpp | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/modules/sni/item.hpp b/include/modules/sni/item.hpp index 74d54f4c..df87bfb7 100644 --- a/include/modules/sni/item.hpp +++ b/include/modules/sni/item.hpp @@ -46,6 +46,7 @@ class Item : public sigc::trackable { std::string title; std::string icon_name; Glib::RefPtr icon_pixmap; + bool has_custom_icon_ = false; Glib::RefPtr icon_theme; std::string overlay_icon_name; Glib::RefPtr overlay_icon_pixmap; diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 2f368083..8e2e2570 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -190,9 +190,17 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { } else if (name == "Status") { setStatus(get_variant(value)); } else if (name == "IconName") { - icon_name = get_variant(value); + if (has_custom_icon_) { + spdlog::trace("Item '{}': ignoring IconName update, custom icon is set", id); + } else { + icon_name = get_variant(value); + } } else if (name == "IconPixmap") { - icon_pixmap = this->extractPixBuf(value.gobj()); + if (has_custom_icon_) { + spdlog::trace("Item '{}': ignoring IconPixmap update, custom icon is set", id); + } else { + icon_pixmap = this->extractPixBuf(value.gobj()); + } } else if (name == "OverlayIconName") { overlay_icon_name = get_variant(value); } else if (name == "OverlayIconPixmap") { @@ -270,11 +278,13 @@ void Item::setCustomIcon(const std::string& id) { Glib::RefPtr custom_pixbuf = Gdk::Pixbuf::create_from_file(custom_icon); icon_name = ""; // icon_name has priority over pixmap icon_pixmap = custom_pixbuf; + has_custom_icon_ = true; } catch (const Glib::Error& e) { spdlog::error("Failed to load custom icon {}: {}", custom_icon, e.what()); } } else { // if file doesn't exist it's most likely an icon_name icon_name = custom_icon; + has_custom_icon_ = true; } } } From 42f2832d212aacc8e663212ea095ba78c86d03fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20NICOLAS?= Date: Sat, 20 Jun 2026 17:42:23 +0200 Subject: [PATCH 743/780] docs(man): document light and dark stylesheets --- man/waybar-styles.5.scd.in | 26 ++++++++++++++++++++------ man/waybar.5.scd.in | 2 +- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/man/waybar-styles.5.scd.in b/man/waybar-styles.5.scd.in index b11e15bd..130e5db3 100644 --- a/man/waybar-styles.5.scd.in +++ b/man/waybar-styles.5.scd.in @@ -8,13 +8,27 @@ waybar-styles - using stylesheets for waybar Waybar uses Cascading Style Sheets (CSS) to configure its appearance. -It uses the first file found in this search order: +When the system appearance is light, Waybar first looks for *style-light.css*. +When the system appearance is dark, Waybar first looks for *style-dark.css*. +If no appearance-specific stylesheet is found, it falls back to *style.css*. -- *$XDG_CONFIG_HOME/waybar/style.css* -- *~/.config/waybar/style.css* -- *~/waybar/style.css* -- */etc/xdg/waybar/style.css* -- *@sysconfdir@/xdg/waybar/style.css* +Waybar uses the first file found in this search order for each stylesheet name: + +- *$XDG_CONFIG_HOME/waybar/* +- *~/.config/waybar/* +- *~/waybar/* +- */etc/xdg/waybar/* +- *@sysconfdir@/xdg/waybar/* + +For example, a light theme is loaded from the first available file among: + +- *$XDG_CONFIG_HOME/waybar/style-light.css* +- *~/.config/waybar/style-light.css* +- *~/waybar/style-light.css* +- */etc/xdg/waybar/style-light.css* +- *@sysconfdir@/xdg/waybar/style-light.css* + +If no file is found there, Waybar repeats the same search using *style.css*. # EXAMPLE diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in index 8b923eef..4dba6694 100644 --- a/man/waybar.5.scd.in +++ b/man/waybar.5.scd.in @@ -19,7 +19,7 @@ Valid locations for this file are: A good starting point is the default configuration found at https://github.com/Alexays/Waybar/blob/master/resources/config.jsonc Also, a minimal example configuration can be found at the bottom of this man page. -The visual display elements for waybar use a CSS stylesheet, see *waybar-styles(5)* for details. +The visual display elements for waybar use a CSS stylesheet. Waybar supports *style.css* and the appearance-specific *style-light.css* and *style-dark.css* files; see *waybar-styles(5)* for details. # BAR CONFIGURATION From 22ce5887835b87653ca6cdccdbc317ec69d7a6f0 Mon Sep 17 00:00:00 2001 From: Muhammed Sahin Date: Sat, 27 Jun 2026 17:18:52 +0200 Subject: [PATCH 744/780] Fallback formatting when format-paused is missing uses ALabel With the minimal change the paused state, if format-paused state is not set, will show the Alabel if the Alabel was active. --- src/modules/mpd/mpd.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp index 9874f458..80cb6559 100644 --- a/src/modules/mpd/mpd.cpp +++ b/src/modules/mpd/mpd.cpp @@ -137,8 +137,9 @@ void waybar::modules::MPD::setLabel() { label_.get_style_context()->add_class("playing"); label_.get_style_context()->remove_class("paused"); } else if (paused()) { - format = config_["format-paused"].isString() ? config_["format-paused"].asString() - : config_["format"].asString(); + if (config_["format-paused"].isString()) { + format = config_["format-paused"].asString(); + } label_.get_style_context()->add_class("paused"); label_.get_style_context()->remove_class("playing"); } From 601e84d159e9b2b85e6e32a6366d2f3877b17744 Mon Sep 17 00:00:00 2001 From: Grin Date: Wed, 1 Jul 2026 17:23:51 -0300 Subject: [PATCH 745/780] feat(ALabel): allow custom percentage thresholds in format-icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, format-icons with N icons would divide the 0-100 range into N equal parts. This made it impossible to define custom ranges like 0%, 1-69%, 70-100%. This adds support for an alternative object syntax in format-icons: "format-icons": [ { "icon": "▁", "max": 0}, { "icon": "▅", "max": 69 }, { "icon": "▇", "max": 100 } ] The array is iterated in order and the first icon whose max value is >= the current percentage is returned. If no threshold matches, the last icon is used as fallback. The existing string array syntax remains fully supported and unchanged. Applies to both getIcon overloads, affecting all modules that use format-icons (battery, pulseaudio, network, backlight, etc.). Closes #5150 --- src/ALabel.cpp | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/ALabel.cpp b/src/ALabel.cpp index 795f87f1..46eeac2f 100644 --- a/src/ALabel.cpp +++ b/src/ALabel.cpp @@ -150,7 +150,26 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_ } if (format_icons.isArray()) { auto size = format_icons.size(); - if (size != 0U) { + if (size != 0U && format_icons[0].isObject()) { + std::string last_icon; + for (const auto& threshold : format_icons) { + if (!threshold.isObject() || !threshold["icon"].isString() || !threshold["max"].isUInt()) { + static bool warned = false; + if (!warned) { + spdlog::warn("format-icons: skipping invalid threshold object, expected {\"icon\": \"...\", \"max\": N}"); + warned = true; + } + continue; + } + last_icon = threshold["icon"].asString(); + if (percentage <= threshold["max"].asUInt()) { + return last_icon; + } + } + if (!last_icon.empty()) { + return last_icon; + } + } else if (size != 0U) { auto divisor = std::max(1U, (max == 0 ? 100U : static_cast(max)) / size); auto idx = std::clamp(percentage / divisor, 0U, size - 1); format_icons = format_icons[idx]; @@ -177,7 +196,26 @@ std::string ALabel::getIcon(uint16_t percentage, const std::vector& } if (format_icons.isArray()) { auto size = format_icons.size(); - if (size != 0U) { + if (size != 0U && format_icons[0].isObject()) { + std::string last_icon; + for (const auto& threshold : format_icons) { + if (!threshold.isObject() || !threshold["icon"].isString() || !threshold["max"].isUInt()) { + static bool warned = false; + if (!warned) { + spdlog::warn("format-icons: skipping invalid threshold object, expected {\"icon\": \"...\", \"max\": N}"); + warned = true; + } + continue; + } + last_icon = threshold["icon"].asString(); + if (percentage <= threshold["max"].asUInt()) { + return last_icon; + } + } + if (!last_icon.empty()) { + return last_icon; + } + } else if (size != 0U) { auto divisor = std::max(1U, (max == 0 ? 100U : static_cast(max)) / size); auto idx = std::clamp(percentage / divisor, 0U, size - 1); format_icons = format_icons[idx]; From 4d1a561cdcbacb9e408a534b49b567821923331a Mon Sep 17 00:00:00 2001 From: Ibrahim Issa Date: Fri, 3 Jul 2026 18:44:03 +0200 Subject: [PATCH 746/780] feat(modules): pause background threads when display is asleep Currently, modules polling hardware nodes continuously wake up kernel drivers even when the monitor is asleep (e.g., DPMS off), flooding dmesg. - Adds 'disable-on-sleep' config flag - Hooks GTK window map/unmap signals to track DPMS state - Propagates suspend/resume calls to AModule worker threads --- include/AModule.hpp | 6 ++++++ include/bar.hpp | 2 ++ include/modules/temperature.hpp | 2 ++ include/util/sleeper_thread.hpp | 24 ++++++++++++++++++++++++ src/AModule.cpp | 3 +++ src/bar.cpp | 33 +++++++++++++++++++++++++++++++-- src/modules/temperature.cpp | 4 ++++ 7 files changed, 72 insertions(+), 2 deletions(-) diff --git a/include/AModule.hpp b/include/AModule.hpp index a338ffe3..e4e4a0ca 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -25,6 +25,10 @@ class AModule : public IModule { bool expandEnabled() const; + virtual void suspend() {}; + virtual void resume() {}; + bool shouldSuspend() const { return disable_on_sleep_; } + protected: // Don't need to make an object directly // Derived classes are able to use it @@ -48,6 +52,8 @@ class AModule : public IModule { virtual bool handleMouseLeave(GdkEventCrossing* const& ev); virtual bool handleScroll(GdkEventScroll*); virtual bool handleRelease(GdkEventButton* const& ev); + + bool disable_on_sleep_{false}; GObject* menu_ = nullptr; private: diff --git a/include/bar.hpp b/include/bar.hpp index 54b332bf..8f23d6e8 100644 --- a/include/bar.hpp +++ b/include/bar.hpp @@ -75,6 +75,8 @@ class Bar : public sigc::trackable { util::KillSignalAction getOnSigusr1Action(); util::KillSignalAction getOnSigusr2Action(); + void toggleSuspend(bool suspend); + struct waybar_output* output; Json::Value config; struct wl_surface* surface; diff --git a/include/modules/temperature.hpp b/include/modules/temperature.hpp index 918281be..d65ef84d 100644 --- a/include/modules/temperature.hpp +++ b/include/modules/temperature.hpp @@ -14,6 +14,8 @@ class Temperature : public ALabel { Temperature(const std::string&, const Json::Value&); virtual ~Temperature() = default; auto update() -> void override; + void suspend() override; + void resume() override; private: float getTemperature(); diff --git a/include/util/sleeper_thread.hpp b/include/util/sleeper_thread.hpp index 1f3a372d..9d20f252 100644 --- a/include/util/sleeper_thread.hpp +++ b/include/util/sleeper_thread.hpp @@ -79,6 +79,12 @@ class SleeperThread { auto sleep_for(std::chrono::system_clock::duration dur) { std::unique_lock lk(mutex_); CancellationGuard cancel_lock; + + condvar_.wait(lk, [this] { + return !is_paused_ || signal_.load(std::memory_order_relaxed) || + !do_run_.load(std::memory_order_relaxed); + }); + constexpr auto max_time_point = std::chrono::steady_clock::time_point::max(); auto wait_end = max_time_point; auto now = std::chrono::steady_clock::now(); @@ -95,6 +101,12 @@ class SleeperThread { time_point) { std::unique_lock lk(mutex_); CancellationGuard cancel_lock; + + condvar_.wait(lk, [this] { + return !is_paused_ || signal_.load(std::memory_order_relaxed) || + !do_run_.load(std::memory_order_relaxed); + }); + return condvar_.wait_until(lk, time_point, [this] { return signal_.load(std::memory_order_relaxed) || !do_run_.load(std::memory_order_relaxed); }); @@ -122,6 +134,17 @@ class SleeperThread { } } + void pause() { + std::lock_guard lock(mutex_); + is_paused_ = true; + } + + void resume() { + std::lock_guard lock(mutex_); + is_paused_ = false; + condvar_.notify_all(); + } + ~SleeperThread() { connection_.disconnect(); stop(); @@ -137,6 +160,7 @@ class SleeperThread { std::atomic do_run_ = true; std::atomic signal_ = false; sigc::connection connection_; + bool is_paused_{false}; }; } // namespace waybar::util diff --git a/src/AModule.cpp b/src/AModule.cpp index a5ba69d3..d36688f8 100644 --- a/src/AModule.cpp +++ b/src/AModule.cpp @@ -21,6 +21,9 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std:: // Configure module action Map const Json::Value actions{config_["actions"]}; + disable_on_sleep_ = + config_["disable-on-sleep"].isBool() ? config_["disable-on-sleep"].asBool() : false; + for (Json::Value::const_iterator it = actions.begin(); it != actions.end(); ++it) { if (it.key().isString() && it->isString()) if (!eventActionMap_.contains(it.key().asString())) { diff --git a/src/bar.cpp b/src/bar.cpp index 7ff73c4f..44d02c5d 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -263,6 +263,16 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); + window.signal_unmap().connect([this]() { + spdlog::debug("Output {} unmapped (DPMS off), suspending modules", output->name); + toggleSuspend(true); + }); + + window.signal_map().connect([this]() { + spdlog::debug("Output {} mapped (DPMS on), resuming modules", output->name); + toggleSuspend(false); + }); + #if HAVE_SWAY if (auto ipc = config["ipc"]; ipc.isBool() && ipc.asBool()) { bar_id = Client::inst()->bar_id; @@ -545,8 +555,8 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos, if (group_config["modules"].isNull()) { spdlog::warn("Group definition '{}' has not been found, group will be hidden", ref); } - auto group_module = std::make_unique( - id_name, class_name, group_config, vertical); + auto group_module = + std::make_unique(id_name, class_name, group_config, vertical); getModules(factory, ref, group_module.get()); module = group_module.release(); @@ -696,3 +706,22 @@ void waybar::Bar::configureGlobalOffset(int width, int height) { void waybar::Bar::onOutputGeometryChanged() { configureGlobalOffset(window.get_width(), window.get_height()); } + +void waybar::Bar::toggleSuspend(bool suspend) { + auto process_modules = [suspend](Gtk::Box& module_box) { + for (auto* widget : module_box.get_children()) { + auto* module = dynamic_cast(widget); + if (module && module->shouldSuspend()) { + if (suspend) { + module->suspend(); + } else { + module->resume(); + } + } + } + }; + + process_modules(left_); + process_modules(center_); + process_modules(right_); +} diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index bb5ff02c..dcfb0b2f 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -155,3 +155,7 @@ bool waybar::modules::Temperature::isCritical(uint16_t temperature_c) { return config_["critical-threshold"].isInt() && temperature_c >= config_["critical-threshold"].asInt(); } + +void waybar::modules::Temperature::suspend() { thread_.pause(); } + +void waybar::modules::Temperature::resume() { thread_.resume(); } From 6793b97eed90bbe3d97cf4f0ac31ea722b84a394 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 21:15:35 +0200 Subject: [PATCH 747/780] fix(hyprland/language): handle commas in keyboard-name config The active-layout event payload is `KEYBOARDNAME,LAYOUT`, and kbName was taken as everything before the first comma. Keyboard names reported by Hyprland can themselves contain commas (e.g. some MSI keyboards), so a `keyboard-name` configured with a comma never matched the truncated value and the module stopped updating. Match the configured keyboard-name as a full prefix (followed by the ',' separator) instead. Layout parsing already handles commas via the last-comma-before-parenthesis heuristic, so only the keyboard-name filter needed fixing. Addresses the keyboard-name case originally reported by @tirsek in #3406. --- src/modules/hyprland/language.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index 6e0fe23d..890dc86b 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -74,8 +74,6 @@ void Language::onEvent(const std::string& ev) { spdlog::warn("hyprland language received malformed event payload: {}", ev); return; } - std::string kbName = payload.substr(0, kbSeparator); - // Last comma before variants parenthesis, eg: // activelayout>>micro-star-int'l-co.,-ltd.-msi-gk50-elite-gaming-keyboard,English (US, intl., // with dead keys) @@ -93,8 +91,15 @@ void Language::onEvent(const std::string& ev) { } auto layoutName = payload.substr(layoutSeparator + 1); - if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString()) - return; // ignore + if (config_.isMember("keyboard-name")) { + const auto keyboardName = config_["keyboard-name"].asString(); + // The keyboard name itself can contain commas, so match it as a full prefix + // (followed by the ',' separator) rather than comparing against the substring + // before the first comma, which would truncate such names and drop the event. + if (payload.size() <= keyboardName.size() || payload[keyboardName.size()] != ',' || + payload.compare(0, keyboardName.size(), keyboardName) != 0) + return; // ignore + } layoutName = waybar::util::sanitize_string(layoutName); From dc017d8fd3e2550fb397627bea9981c6ec707ab7 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 21:32:55 +0200 Subject: [PATCH 748/780] fix(hyprland/workspaces): disconnect hover-check timeout on Workspace destruction The hover-check Glib::signal_timeout re-arms every 50ms and is not owned by the Workspace. Since Workspaces are destroyed at runtime, a workspace removed while its check is armed would let the timeout fire on freed memory (use-after- free, also touching the destroyed m_button). Add a destructor that calls stopHoverCheck() to disconnect the source. --- include/modules/hyprland/workspace.hpp | 1 + src/modules/hyprland/workspace.cpp | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/include/modules/hyprland/workspace.hpp b/include/modules/hyprland/workspace.hpp index 5730dda9..a4cccee6 100644 --- a/include/modules/hyprland/workspace.hpp +++ b/include/modules/hyprland/workspace.hpp @@ -30,6 +30,7 @@ class Workspace { public: explicit Workspace(const Json::Value& workspace_data, Workspaces& workspace_manager, const Json::Value& clients_data = Json::Value::nullRef); + ~Workspace(); std::string& selectIcon(std::map& icons_map); Gtk::Button& button() { return m_button; }; diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index e31132fe..b8868236 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -51,6 +51,12 @@ Workspace::Workspace(const Json::Value& workspace_data, Workspaces& workspace_ma initializeWindowMap(clients_data); } +Workspace::~Workspace() { + // Disconnect the hover-check timeout so it can't fire on this destroyed + // instance (Workspaces are removed at runtime while a check may be armed). + stopHoverCheck(); +} + void addOrRemoveClass(const Glib::RefPtr& context, bool condition, const std::string& class_name) { if (condition) { From 806f6333182c4015e61e20fca7a0bc38e682f629 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 21:38:46 +0200 Subject: [PATCH 749/780] hyprland/workspaces: refresh locally instead of reloading Hyprland config Replace the getSocket1JsonReply("reload") call in onSpecialWorkspaceActivated with the module's normal local refresh path: the state update to m_activeSpecialWorkspaceName is picked up by the update() render (triggered by the dp.emit() already issued at the end of onEvent), which re-evaluates active state, icons and CSS classes. This avoids reloading the entire Hyprland config on every special workspace activation while keeping the special-active icon and CSS improvements. --- src/modules/hyprland/workspaces.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index a95fd9a5..3fa92920 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -396,7 +396,6 @@ void Workspaces::onWorkspaceActivated(std::string const& payload) { void Workspaces::onSpecialWorkspaceActivated(std::string const& payload) { std::string name(begin(payload), begin(payload) + payload.find_first_of(',')); m_activeSpecialWorkspaceName = (!name.starts_with("special:") ? name : name.substr(8)); - m_ipc.getSocket1JsonReply("reload"); } void Workspaces::onWorkspaceDestroyed(std::string const& payload) { From 5181f84648c20742e2a73146387b887c3ad1a3eb Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 21:39:36 +0200 Subject: [PATCH 750/780] fix(sni): prevent use-after-free and infinite retry in Host proxy retry Store the retry timeout connection in a Host member and disconnect it in ~Host, so a Host destroyed within the retry window no longer invokes the timeout callback on freed memory. Cap the retries (MAX_RETRIES) so a watcher that never reappears no longer spins forever every 200ms; reset the counter on success and when the watcher name vanishes. --- include/modules/sni/host.hpp | 2 ++ src/modules/sni/host.cpp | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/include/modules/sni/host.hpp b/include/modules/sni/host.hpp index d76ec74a..f18cf414 100644 --- a/include/modules/sni/host.hpp +++ b/include/modules/sni/host.hpp @@ -43,6 +43,8 @@ class Host { std::size_t watcher_id_; GCancellable* cancellable_ = nullptr; SnWatcher* watcher_ = nullptr; + sigc::connection retry_connection_; + unsigned retry_count_ = 0; const Json::Value& config_; const Bar& bar_; const std::function&)> on_add_; diff --git a/src/modules/sni/host.cpp b/src/modules/sni/host.cpp index 091c45d5..2c0c42c7 100644 --- a/src/modules/sni/host.cpp +++ b/src/modules/sni/host.cpp @@ -7,6 +7,7 @@ namespace waybar::modules::SNI { static const unsigned RETRY_DELAY_MS = 200; +static const unsigned MAX_RETRIES = 10; Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar, const std::function&)>& on_add, @@ -24,6 +25,7 @@ Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar, on_update_(on_update) {} Host::~Host() { + retry_connection_.disconnect(); if (bus_name_id_ > 0) { Gio::DBus::unown_name(bus_name_id_); bus_name_id_ = 0; @@ -55,6 +57,8 @@ void Host::nameAppeared(const Glib::RefPtr& conn, const G } void Host::nameVanished(const Glib::RefPtr& conn, const Glib::ustring name) { + retry_connection_.disconnect(); + retry_count_ = 0; g_cancellable_cancel(cancellable_); g_clear_object(&cancellable_); g_clear_object(&watcher_); @@ -77,17 +81,26 @@ void Host::proxyReady(GObject* src, GAsyncResult* res, gpointer data) { if (error != nullptr) { spdlog::error("Host: {}", error->message); g_clear_object(&host->cancellable_); - Glib::signal_timeout().connect_once( + if (host->retry_count_ >= MAX_RETRIES) { + spdlog::warn("Host: giving up on watcher proxy creation after {} retries", host->retry_count_); + return; + } + host->retry_count_ += 1; + // Store the timeout connection so it is disconnected in ~Host, avoiding a + // use-after-free if the Host is destroyed before the retry fires. + host->retry_connection_ = Glib::signal_timeout().connect( [host]() { if (host->watcher_ != nullptr) { - return; + return false; } auto conn = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::BUS_TYPE_SESSION); host->nameAppeared(conn, "org.kde.StatusNotifierWatcher", ""); + return false; }, RETRY_DELAY_MS); return; } + host->retry_count_ = 0; host->watcher_ = watcher; sn_watcher_call_register_host(host->watcher_, host->object_path_.c_str(), host->cancellable_, &Host::registerHost, data); From 488a75fadaa110c4a4fa16ab73fd3795d38829b3 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 21:39:48 +0200 Subject: [PATCH 751/780] fix(wlr/taskbar): gate title shrink/expand behind 'expand' option Only stretch buttons and ellipsize titles when the new boolean 'expand' option is enabled and the bar is horizontal. Restores the previous content-based sizing as the default and avoids truncating every title to an ellipsis on vertical bars. --- man/waybar-wlr-taskbar.5.scd | 5 ++++ src/modules/wlr/taskbar.cpp | 52 ++++++++++++++++++++++++++---------- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/man/waybar-wlr-taskbar.5.scd b/man/waybar-wlr-taskbar.5.scd index af1ba97f..2aa26f3f 100644 --- a/man/waybar-wlr-taskbar.5.scd +++ b/man/waybar-wlr-taskbar.5.scd @@ -57,6 +57,11 @@ Addressed by *wlr/taskbar* default: false ++ If set to true, group tasks by their app_id. Cannot be used with 'active-first'. +*expand*: ++ + typeof: bool ++ + default: false ++ + If set to true, task buttons stretch to fill the available space in the taskbar and long titles are ellipsized to fit. Only takes effect on a horizontal bar; on a vertical bar the buttons keep their content-based size. If set to false, buttons are sized to their content. + *on-click*: ++ typeof: string ++ The action which should be triggered when clicking on the application button with the left mouse button. diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index 6ba41ce3..f382e1fc 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -93,20 +93,36 @@ Task::Task(const waybar::Bar& bar, const Json::Value& config, Taskbar* tbar, zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this); button.set_relief(Gtk::RELIEF_NONE); - button.set_hexpand(true); - content_.set_hexpand(true); - text_before_.set_ellipsize(Pango::ELLIPSIZE_END); - text_before_.set_single_line_mode(true); - text_before_.set_width_chars(1); - text_before_.set_xalign(0.0); - text_after_.set_ellipsize(Pango::ELLIPSIZE_END); - text_after_.set_single_line_mode(true); - text_after_.set_width_chars(1); - text_after_.set_xalign(0.0); - content_.pack_start(text_before_, true, true, 0); - content_.pack_start(icon_, false, false, 0); - content_.pack_start(text_after_, true, true, 0); + /* When "expand" is enabled the buttons stretch to fill the taskbar and the + * titles ellipsize to fit within the available space. This only makes sense + * on a horizontal bar; on a vertical bar the box grows along the vertical + * axis, so ellipsizing/forcing width_chars(1) would truncate every title to + * "…". Keep the historical behavior (buttons sized to their content) as the + * default and when the bar is vertical. */ + bool expand = config_["expand"].isBool() && config_["expand"].asBool(); + bool horizontal = bar.orientation == Gtk::ORIENTATION_HORIZONTAL; + + if (expand && horizontal) { + button.set_hexpand(true); + content_.set_hexpand(true); + text_before_.set_ellipsize(Pango::ELLIPSIZE_END); + text_before_.set_single_line_mode(true); + text_before_.set_width_chars(1); + text_before_.set_xalign(0.0); + text_after_.set_ellipsize(Pango::ELLIPSIZE_END); + text_after_.set_single_line_mode(true); + text_after_.set_width_chars(1); + text_after_.set_xalign(0.0); + + content_.pack_start(text_before_, true, true, 0); + content_.pack_start(icon_, false, false, 0); + content_.pack_start(text_after_, true, true, 0); + } else { + content_.add(text_before_); + content_.add(icon_); + content_.add(text_after_); + } content_.show(); button.add(content_); @@ -728,7 +744,15 @@ void Taskbar::handle_finished() { } void Taskbar::add_button(Gtk::Button& bt) { - box_.pack_start(bt, true, true); + /* Only let the buttons expand to fill the taskbar when "expand" is enabled + * and the bar is horizontal (see the Task constructor for details). */ + bool expand = config_["expand"].isBool() && config_["expand"].asBool(); + bool horizontal = bar_.orientation == Gtk::ORIENTATION_HORIZONTAL; + if (expand && horizontal) { + box_.pack_start(bt, true, true); + } else { + box_.pack_start(bt, false, false); + } box_.get_style_context()->remove_class("empty"); } From 31a3d7e1bb9c047ca3449490cb5d695abbd98e0b Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 21:40:41 +0200 Subject: [PATCH 752/780] fix(battery): gate Full-as-Plugged behind full-at-plugged option, drop debug output Reporting a full battery on AC as 'Plugged' is now opt-in via the 'full-at-plugged' config option (default false), so existing format-full / tooltip-format-full setups keep working. Also documents the option. --- man/waybar-battery.5.scd | 5 +++++ src/modules/battery.cpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/man/waybar-battery.5.scd b/man/waybar-battery.5.scd index 42b9e046..0c71518c 100644 --- a/man/waybar-battery.5.scd +++ b/man/waybar-battery.5.scd @@ -27,6 +27,11 @@ The *battery* module displays the current capacity and state (eg. charging) of y default: false ++ Option to use the battery design capacity instead of its current maximal capacity. +*full-at-plugged*: ++ + typeof: bool ++ + default: false ++ + When enabled, a battery that is *Full* while the adapter is online is reported with the *Plugged* status instead of *Full* (so you can style/format it separately). Disabled by default to preserve the existing *Full* behaviour. + *interval*: ++ typeof: integer ++ default: 60 ++ diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 53305068..2581ef9a 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -706,7 +706,7 @@ auto waybar::modules::Battery::update() -> void { if (!adapter_.empty()) { std::ifstream(adapter_ / "online") >> adapter_online; } - if (status == "Full" && adapter_online) { + if (config_["full-at-plugged"].asBool() && status == "Full" && adapter_online) { status = "Plugged"; } auto status_pretty = status; From 9ac3f9b95198408f8bb084f7fc900b29d4be900e Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 21:41:10 +0200 Subject: [PATCH 753/780] clock: make calendar padding portable across FormatStream char types std::setfill is templated on the fill char type, which must match the Glib::ustring FormatStream's char type. That type is wchar_t with libstdc++/glibmm but char with libc++, so no fixed literal (' ' or L' ') compiles everywhere. The stream's default fill is already a space, so drop std::setfill entirely; std::setw/std::left/std::right are char-type-agnostic and keep the exact padding behavior. --- src/modules/clock.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 36581e51..97777667 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -402,8 +402,13 @@ auto waybar::modules::Clock::get_calendar(const year_month_day& today, const yea data = g_utf8_find_next_char(data, end); } } + // Note: the stream's default fill character is already a space (L' ' on + // libstdc++'s wide FormatStream, ' ' on libc++'s narrow one), so no + // std::setfill is needed. Passing std::setfill(' ')/std::setfill(L' ') + // here is not portable because the fill char type must match the + // FormatStream's char type, which differs between standard libraries. os << Glib::ustring::format( - (cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right, std::setfill(' '), + (cldWPos_ != WS::LEFT || line == 0) ? std::left : std::right, std::setw(cldMonColLen_ + ((line < 2) ? cldWnLen_ - wideCharCount : 0)), calendarLine); From 8f7ed880c413d85fc51c13e0252d8e18fdaf827c Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 21:49:25 +0200 Subject: [PATCH 754/780] bar: fix indentation of height type warning --- src/bar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bar.cpp b/src/bar.cpp index 626124da..fe44b3ed 100644 --- a/src/bar.cpp +++ b/src/bar.cpp @@ -173,7 +173,7 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config) } if (config.isMember("height") && !config["height"].isUInt()) { - spdlog::warn("Invalid type for 'height', expected unsigned integer"); + spdlog::warn("Invalid type for 'height', expected unsigned integer"); } height_ = config["height"].isUInt() ? config["height"].asUInt() : 0; From cbc70d10f81f85ab51f5c18d04fecbce340c7f53 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 21:49:54 +0200 Subject: [PATCH 755/780] pulseaudio_slider: keep zero-on-mute applied on every update while muted The zero-on-mute display_value = min_ assignment was nested inside the !previously_muted transition branch, so subsequent updates while still muted re-displayed the real volume. Apply it on every update as long as the target is muted, keeping the CSS class add/remove on transitions only. --- src/modules/pulseaudio_slider.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/modules/pulseaudio_slider.cpp b/src/modules/pulseaudio_slider.cpp index aeddeb24..d0e54459 100644 --- a/src/modules/pulseaudio_slider.cpp +++ b/src/modules/pulseaudio_slider.cpp @@ -29,12 +29,14 @@ void PulseaudioSlider::update() { uint16_t display_value = backend->getVolume(target); bool is_muted = backend->getMuted(target); - if (is_muted && !previously_muted) { + if (is_muted) { if (zero_on_mute) { display_value = min_; } - scale_.get_style_context()->add_class("muted"); - } else if (previously_muted && !is_muted) { + if (!previously_muted) { + scale_.get_style_context()->add_class("muted"); + } + } else if (previously_muted) { scale_.get_style_context()->remove_class("muted"); } From 26c834a59b65f56b0e3faa864c57a95c75346d58 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 21:51:07 +0200 Subject: [PATCH 756/780] cpu: respect tooltip-format- without overwriting it The tooltip selection block redundantly reassigned tooltip_format to config_["tooltip-format"], overwriting the state-specific format (and emptying it when no global tooltip-format was set). A lone tooltip-format- therefore produced an empty tooltip. Consolidate the tooltip handling into a single block that prefers tooltip-format-, falls back to the global tooltip-format, then to the computed default tooltip, reusing the existing format arg store. --- src/modules/cpu.cpp | 47 ++++++++------------------------------------- 1 file changed, 8 insertions(+), 39 deletions(-) diff --git a/src/modules/cpu.cpp b/src/modules/cpu.cpp index 254c6945..5c461851 100644 --- a/src/modules/cpu.cpp +++ b/src/modules/cpu.cpp @@ -28,15 +28,11 @@ auto waybar::modules::Cpu::update() -> void { auto [max_frequency, min_frequency, avg_frequency] = CpuFrequency::getCpuFrequency(); auto format = format_; - std::string tooltip_format; auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0]; auto state = getState(total_usage); if (!state.empty() && config_["format-" + state].isString()) { format = config_["format-" + state].asString(); } - if (config_["tooltip-format-" + state].isString()) { - tooltip_format = config_["tooltip-format-" + state].asString(); - } if (format.empty()) { event_box_.hide(); @@ -62,47 +58,20 @@ auto waybar::modules::Cpu::update() -> void { label_.set_markup(fmt::vformat(format, store)); if (tooltipEnabled()) { - if (config_["tooltip-format"].isString()) { - tooltip = config_["tooltip-format"].asString(); - label_.set_tooltip_markup(fmt::vformat(tooltip, store)); + std::string tooltip_format; + if (!state.empty() && config_["tooltip-format-" + state].isString()) { + tooltip_format = config_["tooltip-format-" + state].asString(); + } else if (config_["tooltip-format"].isString()) { + tooltip_format = config_["tooltip-format"].asString(); + } + if (!tooltip_format.empty()) { + label_.set_tooltip_markup(fmt::vformat(tooltip_format, store)); } else { label_.set_tooltip_markup(tooltip); } } } - if (tooltipEnabled()) { - if (tooltip_format.empty() && config_["tooltip-format"].isString()) { - tooltip_format = config_["tooltip-format"].asString(); - } - if (!tooltip_format.empty()) { - tooltip_format = config_["tooltip-format"].asString(); - - auto icons = std::vector{state}; - fmt::dynamic_format_arg_store store; - store.push_back(fmt::arg("load", load1)); - store.push_back(fmt::arg("usage", total_usage)); - store.push_back(fmt::arg("icon", getIcon(total_usage, icons))); - store.push_back(fmt::arg("max_frequency", max_frequency)); - store.push_back(fmt::arg("min_frequency", min_frequency)); - store.push_back(fmt::arg("avg_frequency", avg_frequency)); - for (size_t i = 1; i < cpu_usage.size(); ++i) { - auto core_i = i - 1; - auto core_format = fmt::format("usage{}", core_i); - store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i])); - auto icon_format = fmt::format("icon{}", core_i); - store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons))); - } - auto tooltip_text = fmt::vformat(tooltip_format, store); - - if (label_.get_tooltip_text() != tooltip_text) { - label_.set_tooltip_markup(tooltip_text); - } - } else { - label_.set_tooltip_text(tooltip); - } - } - // Call parent update ALabel::update(); } From 25e57bc62585793593660420a8a17c82fcb0a64f Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 21:51:20 +0200 Subject: [PATCH 757/780] fix(hyprland/window): use 2-space indentation for fallback block (clang-format) --- src/modules/hyprland/window.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index 423257db..029f9722 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -1,5 +1,6 @@ #include "modules/hyprland/window.hpp" +#include #include #include #include @@ -13,8 +14,6 @@ #include "util/rewrite_string.hpp" #include "util/sanitize_str.hpp" -#include - namespace waybar::modules::hyprland { std::shared_mutex windowIpcSmtx; @@ -56,11 +55,11 @@ auto Window::update() -> void { if (!format_.empty()) { label_.show(); - // If the focused window name is empty and fallback is configured, use fallback text - std::string displayTitle = windowName; - if (displayTitle.empty() && config_["fallback"].isString()) { - displayTitle = config_["fallback"].asString(); - } + // If the focused window name is empty and fallback is configured, use fallback text + std::string displayTitle = windowName; + if (displayTitle.empty() && config_["fallback"].isString()) { + displayTitle = config_["fallback"].asString(); + } label_text = waybar::util::rewriteString( fmt::format(fmt::runtime(format_), fmt::arg("title", displayTitle), From 4bf82b88a06a8ee10f4468616d177087b904eae6 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 22:01:20 +0200 Subject: [PATCH 758/780] style(pulseaudio): clang-format audio_backend helpers --- include/modules/pulseaudio_slider.hpp | 1 - src/util/audio_backend.cpp | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/include/modules/pulseaudio_slider.hpp b/include/modules/pulseaudio_slider.hpp index c9f82341..597b3cc8 100644 --- a/include/modules/pulseaudio_slider.hpp +++ b/include/modules/pulseaudio_slider.hpp @@ -6,7 +6,6 @@ #include "util/audio_backend.hpp" namespace waybar::modules { - class PulseaudioSlider : public ASlider { public: PulseaudioSlider(const std::string&, const Json::Value&); diff --git a/src/util/audio_backend.cpp b/src/util/audio_backend.cpp index 601a9cc0..96a5bdb4 100644 --- a/src/util/audio_backend.cpp +++ b/src/util/audio_backend.cpp @@ -263,9 +263,9 @@ void AudioBackend::serverInfoCb(pa_context* context, const pa_server_info* i, vo uint16_t AudioBackend::getVolume(PulseaudioTarget target) const { if (target == PulseaudioTarget::Source) { - return source_volume_; + return source_volume_; } else { - return volume_; + return volume_; } } @@ -380,9 +380,9 @@ void AudioBackend::changeVolume(ChangeType change_type, double step, uint16_t ma bool AudioBackend::getMuted(PulseaudioTarget target) const { if (target == PulseaudioTarget::Source) { - return source_muted_; + return source_muted_; } else { - return muted_; + return muted_; } } @@ -424,9 +424,9 @@ void AudioBackend::toggleSourceMute(bool mute) { void AudioBackend::unmute(PulseaudioTarget target) { if (target == PulseaudioTarget::Source) { - toggleSourceMute(false); + toggleSourceMute(false); } else { - toggleSinkMute(false); + toggleSinkMute(false); } } From e736a934ba2f6a5754428e8d61dbf84316eae96a Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 22:10:13 +0200 Subject: [PATCH 759/780] fix(clock): correct parentheses in fmt::format for calendar tooltip color --- src/modules/clock.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index 5d09f72f..df3c2cc6 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -214,7 +214,7 @@ auto waybar::modules::Clock::update() -> void { context->remove_class(css_class); const std::string replace_str = fmt::format("color='#{:02x}{:02x}{:02x}'", - static_cast(color.get_red() * 255, + static_cast(color.get_red() * 255), static_cast(color.get_green() * 255), static_cast(color.get_blue() * 255)); From c5cefb5520d249d0c0b1188186a0c7db32be0506 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 22:10:16 +0200 Subject: [PATCH 760/780] AIconLabel: make regex static and initialize label_contains_icon Compile the icon-extraction std::regex objects once via static locals instead of rebuilding them on every update() of every AIconLabel-derived module (custom, mpris, ...). Also default-initialize label_contains_icon to false at its declaration. --- include/AIconLabel.hpp | 2 +- src/AIconLabel.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/AIconLabel.hpp b/include/AIconLabel.hpp index 1fac814c..80aa431a 100644 --- a/include/AIconLabel.hpp +++ b/include/AIconLabel.hpp @@ -21,7 +21,7 @@ class AIconLabel : public ALabel { Gtk::Box box_; unsigned app_icon_size_{24}; - bool label_contains_icon; + bool label_contains_icon{false}; bool iconEnabled() const; }; diff --git a/src/AIconLabel.cpp b/src/AIconLabel.cpp index 9cadcf39..c17eb8d4 100644 --- a/src/AIconLabel.cpp +++ b/src/AIconLabel.cpp @@ -65,12 +65,12 @@ std::tuple AIconLabel::extractIcon(const std::string& std::string icon_result = ""; std::string label_result = input; try { - const std::regex icon_search(R"((?=\\0icon\\1f).+?(?=\\n))"); + static const std::regex icon_search(R"((?=\\0icon\\1f).+?(?=\\n))"); std::smatch icon_match; if (std::regex_search(input, icon_match, icon_search)) { icon_result = icon_match[0].str().substr(9); - - const std::regex clean_label_pattern(R"(\\0icon\\1f.+?\\n)"); + + static const std::regex clean_label_pattern(R"(\\0icon\\1f.+?\\n)"); label_result = std::regex_replace(input, clean_label_pattern, ""); } } catch (const std::exception& e) { From 6dc168c4f2635bc62013ea418f39210fc0fae989 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 22:10:46 +0200 Subject: [PATCH 761/780] river/tags: fix memory leak of output_status_/seat_status_ The constructor created output_status_ and seat_status_ without adding any listener, then handle_show() recreated both (leaking the constructor-created objects) before destroying status_manager_. Additionally seat_status_ was never destroyed in ~Tags(). Create these objects lazily (with listeners) only in handle_show(), and destroy seat_status_ in the destructor. --- src/modules/river/tags.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index a864daea..aac304ed 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -138,9 +138,10 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con return; } + // Store the output this module belongs to; the river_output_status and + // river_seat_status objects (and their listeners) are created lazily in + // handle_show() to avoid leaking objects without listeners here. output_ = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); - output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output_); - seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_); box_.set_name("tags"); if (!id.empty()) { @@ -193,6 +194,10 @@ Tags::~Tags() { zriver_output_status_v1_destroy(output_status_); } + if (seat_status_) { + zriver_seat_status_v1_destroy(seat_status_); + } + if (control_) { zriver_control_v1_destroy(control_); } From fe0e1c139bc4c6044f4592d708063a63d3725ffa Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 22:11:51 +0200 Subject: [PATCH 762/780] power-profiles-daemon: preserve backward compatibility for driver Keep the {driver} placeholder working and support older power-profiles-daemon versions that only expose a single Driver DBus property, while still reading the new CpuDriver/PlatformDriver fields. - Read the legacy Driver property and fall back to it when CpuDriver or PlatformDriver are missing (older daemon). - Derive {driver} from CpuDriver when the daemon no longer exposes Driver (recent daemon), so existing configs using {driver} keep working. - Align the default tooltip-format in the constructor with the documented default. --- include/modules/power_profiles_daemon.hpp | 10 ++++++- man/waybar-power-profiles-daemon.5.scd | 2 +- src/modules/power_profiles_daemon.cpp | 35 +++++++++++++++++++---- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/include/modules/power_profiles_daemon.hpp b/include/modules/power_profiles_daemon.hpp index eb636662..1aa087a4 100644 --- a/include/modules/power_profiles_daemon.hpp +++ b/include/modules/power_profiles_daemon.hpp @@ -9,10 +9,18 @@ namespace waybar::modules { struct Profile { std::string name; + // Legacy driver field, kept for backward compatibility with the + // `{driver}` format placeholder and with older power-profiles-daemon + // versions that only expose a single `Driver` DBus property. + std::string driver; std::string cpuDriver; std::string platformDriver; - Profile(std::string n, std::string cd, std::string pd) : name(std::move(n)), cpuDriver(std::move(cd)), platformDriver(std::move(pd)) {} + Profile(std::string n, std::string d, std::string cd, std::string pd) + : name(std::move(n)), + driver(std::move(d)), + cpuDriver(std::move(cd)), + platformDriver(std::move(pd)) {} }; class PowerProfilesDaemon : public ALabel { diff --git a/man/waybar-power-profiles-daemon.5.scd b/man/waybar-power-profiles-daemon.5.scd index c1b2f5b8..da331795 100644 --- a/man/waybar-power-profiles-daemon.5.scd +++ b/man/waybar-power-profiles-daemon.5.scd @@ -26,7 +26,7 @@ $XDG_CONFIG_HOME/waybar/config |[ *tooltip-format* :[ string :[ "Power profile: {profile}\\nCPU driver: {cpu_driver}\\nPlatform driver: {platform_driver}" -:[ Messaged displayed in the module tooltip. {icon} and {profile} are respectively substituted with the icon representing the active profile and its full name. +:[ Messaged displayed in the module tooltip. {icon} and {profile} are respectively substituted with the icon representing the active profile and its full name. {cpu_driver} and {platform_driver} are substituted with the CPU and platform drivers reported by recent power-profiles-daemon versions. {driver} is kept for backward compatibility: it resolves to the legacy single driver on older daemons and falls back to the CPU driver on recent ones. |[ *tooltip* :[ bool :[ true diff --git a/src/modules/power_profiles_daemon.cpp b/src/modules/power_profiles_daemon.cpp index b272805e..d7eb5bcd 100644 --- a/src/modules/power_profiles_daemon.cpp +++ b/src/modules/power_profiles_daemon.cpp @@ -12,7 +12,8 @@ PowerProfilesDaemon::PowerProfilesDaemon(const std::string& id, const Json::Valu if (config_["tooltip-format"].isString()) { tooltipFormat_ = config_["tooltip-format"].asString(); } else { - tooltipFormat_ = "Power profile: {profile}\nCpuDriver: {cpu_driver}"; + tooltipFormat_ = + "Power profile: {profile}\nCPU driver: {cpu_driver}\nPlatform driver: {platform_driver}"; } // Fasten your seatbelt, we're up for quite a ride. The rest of the // init is performed asynchronously. There's 2 callbacks involved. @@ -95,11 +96,17 @@ void PowerProfilesDaemon::populateInitState() { powerProfilesProxy_->get_cached_property(profilesVariant, "Profiles"); for (auto& variantDict : profilesVariant.get()) { Glib::ustring name; + // Legacy single `Driver` property, still exposed by older + // power-profiles-daemon versions. + Glib::ustring driver; Glib::ustring cpuDriver; Glib::ustring platformDriver; if (auto p = variantDict.find("Profile"); p != variantDict.end()) { name = p->second.get(); } + if (auto d = variantDict.find("Driver"); d != variantDict.end()) { + driver = d->second.get(); + } if (auto cd = variantDict.find("CpuDriver"); cd != variantDict.end()) { cpuDriver = cd->second.get(); } @@ -107,17 +114,33 @@ void PowerProfilesDaemon::populateInitState() { platformDriver = pd->second.get(); } + // Recent power-profiles-daemon versions split the single `Driver` + // property into `CpuDriver` and `PlatformDriver`. When talking to an + // older daemon that only exposes `Driver`, fall back to it so the new + // {cpu_driver}/{platform_driver} placeholders still resolve. if (cpuDriver.empty()) { - cpuDriver = "Unavailable"; - spdlog::warn("Cannot find power profiles daemon cpu driver."); + cpuDriver = driver; } if (platformDriver.empty()) { + platformDriver = driver; + } + // Conversely, keep the legacy {driver} placeholder working against a + // recent daemon that no longer exposes `Driver` by deriving it from + // the CPU driver. + if (driver.empty()) { + driver = cpuDriver; + } + + if (driver.empty()) { + driver = "Unavailable"; + cpuDriver = "Unavailable"; platformDriver = "Unavailable"; - spdlog::warn("Cannot find power profiles daemon platform driver."); + spdlog::warn("Cannot find power profiles daemon driver."); } if (!name.empty()) { - availableProfiles_.emplace_back(std::move(name), std::move(cpuDriver), std::move(platformDriver)); + availableProfiles_.emplace_back(std::move(name), std::move(driver), std::move(cpuDriver), + std::move(platformDriver)); } else { spdlog::error( "Power profiles daemon: power-profiles-daemon sent us an empty power profile name. " @@ -167,6 +190,8 @@ auto PowerProfilesDaemon::update() -> void { // Set label fmt::dynamic_format_arg_store store; store.push_back(fmt::arg("profile", profile.name)); + // Legacy placeholder, kept for backward compatibility with existing configs. + store.push_back(fmt::arg("driver", profile.driver)); store.push_back(fmt::arg("cpu_driver", profile.cpuDriver)); store.push_back(fmt::arg("platform_driver", profile.platformDriver)); store.push_back(fmt::arg("icon", getIcon(0, profile.name))); From 573ad779bad545269ef52380a5cf5e9788b5c0a6 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 22:21:03 +0200 Subject: [PATCH 763/780] Apply clang-format to clock.cpp --- src/modules/clock.cpp | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index df3c2cc6..bdeaf889 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -200,12 +200,11 @@ auto waybar::modules::Clock::update() -> void { auto context = label_.get_style_context(); static const std::vector> calendar_class_map = { - {"calendar-today", "class='today'"}, - {"calendar-days", "class='days'"}, - {"calendar-weeks", "class='weeks'"}, + {"calendar-today", "class='today'"}, + {"calendar-days", "class='days'"}, + {"calendar-weeks", "class='weeks'"}, {"calendar-weekdays", "class='weekdays'"}, - {"calendar-months", "class='months'"} - }; + {"calendar-months", "class='months'"}}; for (const auto& [css_class, search_str] : calendar_class_map) { try { @@ -213,18 +212,17 @@ auto waybar::modules::Clock::update() -> void { const Gdk::RGBA color = context->get_color(); context->remove_class(css_class); - const std::string replace_str = fmt::format("color='#{:02x}{:02x}{:02x}'", - static_cast(color.get_red() * 255), - static_cast(color.get_green() * 255), - static_cast(color.get_blue() * 255)); + const std::string replace_str = fmt::format( + "color='#{:02x}{:02x}{:02x}'", static_cast(color.get_red() * 255), + static_cast(color.get_green() * 255), static_cast(color.get_blue() * 255)); m_tlpText_ = std::regex_replace(m_tlpText_, std::regex(search_str), replace_str); } catch (const Glib::Error& e) { - spdlog::warn("Clock: Failed to fetch CSS color for {}: {}", css_class, e.what().raw()); - continue; + spdlog::warn("Clock: Failed to fetch CSS color for {}: {}", css_class, e.what().raw()); + continue; } catch (...) { - // Catch-all for any other weirdness. - continue; + // Catch-all for any other weirdness. + continue; } } From cd2b1802cbcb9d49803b5742a1b7d11455b6e43b Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 22:21:07 +0200 Subject: [PATCH 764/780] Apply clang-format to wireplumber module --- src/modules/wireplumber.cpp | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index 2607d22d..751e885c 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -1,6 +1,7 @@ #include "modules/wireplumber.hpp" #include + #include #include @@ -450,7 +451,6 @@ auto waybar::modules::Wireplumber::update() -> void { label_.get_style_context()->remove_class("source-muted"); } - double vol_cube = pow(volume_, 3); double source_vol_cube = pow(source_volume_, 3); @@ -460,7 +460,6 @@ auto waybar::modules::Wireplumber::update() -> void { double vol_db = 20.0 * log10(volume_); double source_vol_db = 20.0 * log10(source_volume_); - // Get the state and apply state-specific format if available auto state = getState(vol); if (!state.empty()) { @@ -492,8 +491,9 @@ auto waybar::modules::Wireplumber::update() -> void { fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source), fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_), fmt::arg("volume_linear", volume_), fmt::arg("volume_cubic", vol_cube), - fmt::arg("volume_db", vol_db),fmt::arg("source_volume_linear", source_volume_), - fmt::arg("source_volume_cubic", source_vol_cube), fmt::arg("source_volume_db", source_vol_db)); + fmt::arg("volume_db", vol_db), fmt::arg("source_volume_linear", source_volume_), + fmt::arg("source_volume_cubic", source_vol_cube), + fmt::arg("source_volume_db", source_vol_db)); label_.set_markup(markup); if (tooltipEnabled()) { @@ -508,7 +508,8 @@ auto waybar::modules::Wireplumber::update() -> void { fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_), fmt::arg("volume_linear", volume_), fmt::arg("volume_cubic", vol_cube), fmt::arg("volume_db", vol_db), fmt::arg("source_volume_linear", source_volume_), - fmt::arg("source_volume_cubic", source_vol_cube), fmt::arg("source_volume_db", source_vol_db))); + fmt::arg("source_volume_cubic", source_vol_cube), + fmt::arg("source_volume_db", source_vol_db))); } else { label_.set_tooltip_markup(node_name_); } @@ -543,15 +544,12 @@ bool waybar::modules::Wireplumber::handleScroll(GdkEventScroll* e) { if (scale == "cubic") { vol = pow(vol, 3); - } - else if (scale == "db") { + } else if (scale == "db") { vol = log10(vol) * 20.0; - } - else if (scale == "cubic_percent") { + } else if (scale == "cubic_percent") { vol = pow(vol, 3) * 100.0; } - double newVol = vol; if (dir == SCROLL_DIR::UP) { newVol = vol + step; @@ -561,11 +559,9 @@ bool waybar::modules::Wireplumber::handleScroll(GdkEventScroll* e) { if (scale == "cubic") { newVol = cbrt(newVol); - } - else if (scale == "db") { + } else if (scale == "db") { newVol = exp10(newVol / 20.0); - } - else if (scale == "cubic_percent") { + } else if (scale == "cubic_percent") { newVol = cbrt(newVol / 100.0); } @@ -579,8 +575,10 @@ bool waybar::modules::Wireplumber::handleScroll(GdkEventScroll* e) { } } - if (newVol < 0) newVol = 0; - else if (newVol > maxVolume) newVol = maxVolume; + if (newVol < 0) + newVol = 0; + else if (newVol > maxVolume) + newVol = maxVolume; if (newVol != volume_) { if (mixer_api_ == nullptr) return true; From 22f6e3c83c4fe6e5efd01271c2c2c9a578a5f78a Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 22:22:35 +0200 Subject: [PATCH 765/780] mpd: adapt Playing::on_io to post-refactor Context API queryMPD() was removed from Context by the master-side dedup refactor (perf(mpd): avoid duplicate playing-state updates). fetchState() is already called earlier in on_io and emit() updates the GUI, so the redundant queryMPD() call is dropped, matching the identical resolution applied to Playing::on_timer on master. --- src/modules/mpd/state.cpp | 45 +++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/src/modules/mpd/state.cpp b/src/modules/mpd/state.cpp index 5f529983..8888a884 100644 --- a/src/modules/mpd/state.cpp +++ b/src/modules/mpd/state.cpp @@ -19,17 +19,17 @@ auto format_as(enum mpd_idle val) { namespace waybar::modules::detail { -#define RUN_NOIDLE_AND_CMD(STATE, ...) \ - if (idle_connection_.connected()) { \ - idle_connection_.disconnect(); \ - auto conn = ctx_->connection().get(); \ - if (!mpd_run_noidle(conn)) { \ - if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { \ +#define RUN_NOIDLE_AND_CMD(STATE, ...) \ + if (idle_connection_.connected()) { \ + idle_connection_.disconnect(); \ + auto conn = ctx_->connection().get(); \ + if (!mpd_run_noidle(conn)) { \ + if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { \ spdlog::error("mpd: STATE: failed to unregister for IDLE events"); \ - ctx_->checkErrors(conn); \ - } \ - } \ - __VA_ARGS__; \ + ctx_->checkErrors(conn); \ + } \ + } \ + __VA_ARGS__; \ } void Idle::play() { @@ -50,7 +50,6 @@ void Idle::stop() { ctx_->setState(std::make_unique(ctx_)); } - void Idle::update() noexcept { // This is intentionally blank. } @@ -140,7 +139,6 @@ void Playing::timer() noexcept { timer_connection_ = Glib::signal_timeout().connect_seconds(timer_slot, 1); } - void Playing::idle() noexcept { auto conn = ctx_->connection().get(); assert(conn != nullptr); @@ -218,7 +216,6 @@ bool Playing::on_io(Glib::IOCondition const&) { return false; } - ctx_->queryMPD(); ctx_->emit(); if (!mpd_send_idle_mask( @@ -239,25 +236,23 @@ bool Playing::on_io(Glib::IOCondition const&) { } void Playing::stop() { - RUN_NOIDLE_AND_CMD(Playing, - if (timer_connection_.connected()) { - timer_connection_.disconnect(); + RUN_NOIDLE_AND_CMD( + Playing, if (timer_connection_.connected()) { + timer_connection_.disconnect(); - mpd_run_stop(ctx_->connection().get()); - } - ); + mpd_run_stop(ctx_->connection().get()); + }); ctx_->setState(std::make_unique(ctx_)); } void Playing::pause() { - RUN_NOIDLE_AND_CMD(Playing, - if (timer_connection_.connected()) { - timer_connection_.disconnect(); + RUN_NOIDLE_AND_CMD( + Playing, if (timer_connection_.connected()) { + timer_connection_.disconnect(); - mpd_run_pause(ctx_->connection().get(), true); - } - ); + mpd_run_pause(ctx_->connection().get(), true); + }); ctx_->setState(std::make_unique(ctx_)); } From 1afb1a5229798b11f669061306b95dce0e879f7c Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 23:35:12 +0200 Subject: [PATCH 766/780] docs(man): document options implemented but missing from man pages Sync man pages with the code for options that were read but undocumented: - sway/workspaces: ignore-workspaces - niri/workspaces: sort-by-number, hide-empty - wayfire/workspaces: disable-scroll, enable-bar-scroll - river/tags: set-tags, toggle-tags - hyprland/workspaces: persistent-workspaces (formalized) - hyprland/window: fallback, tooltip-format - cpu: format-, tooltip-format, tooltip-format- - pulseaudio/slider: ignored-sinks - hyprland/niri/mango language: format-- (+ hyprland tooltip variant) --- man/waybar-cpu.5.scd | 12 ++++++++++++ man/waybar-hyprland-language.5.scd | 8 ++++++++ man/waybar-hyprland-window.5.scd | 9 +++++++++ man/waybar-hyprland-workspaces.5.scd | 5 +++++ man/waybar-mango-language.5.scd | 4 ++++ man/waybar-niri-language.5.scd | 4 ++++ man/waybar-niri-workspaces.5.scd | 10 ++++++++++ man/waybar-pulseaudio-slider.5.scd | 5 +++++ man/waybar-river-tags.5.scd | 8 ++++++++ man/waybar-sway-workspaces.5.scd | 5 +++++ man/waybar-wayfire-workspaces.5.scd | 10 ++++++++++ 11 files changed, 80 insertions(+) diff --git a/man/waybar-cpu.5.scd b/man/waybar-cpu.5.scd index f6e98a1c..3f68998f 100644 --- a/man/waybar-cpu.5.scd +++ b/man/waybar-cpu.5.scd @@ -83,6 +83,18 @@ The *cpu* module displays the current CPU utilization. default: true ++ Option to disable tooltip on hover. +*tooltip-format*: ++ + typeof: string ++ + The format of the tooltip shown on hover. Supports the same replacements as *format*. + +*format-*: ++ + typeof: string ++ + The format to use when the given *state* (see *states*) is active. Supports the same replacements as *format*. + +*tooltip-format-*: ++ + typeof: string ++ + The tooltip format to use when the given *state* (see *states*) is active. Takes precedence over *tooltip-format*. + *expand*: ++ typeof: bool ++ default: false ++ diff --git a/man/waybar-hyprland-language.5.scd b/man/waybar-hyprland-language.5.scd index b5ce80aa..88dd82d7 100644 --- a/man/waybar-hyprland-language.5.scd +++ b/man/waybar-hyprland-language.5.scd @@ -21,6 +21,10 @@ Addressed by *hyprland/language* typeof: string++ Provide an alternative name to display per language where is the language of your choosing. Can be passed multiple times with multiple languages as shown by the example below. +*format--* ++ + typeof: string ++ + Like *format-* but also matches the layout variant, taking precedence over *format-* when both the language and variant match. + *keyboard-name*: ++ typeof: string ++ Specifies which keyboard to use from hyprctl devices output. Using the option that begins with "at-translated-set..." is recommended. @@ -52,6 +56,10 @@ Addressed by *hyprland/language* typeof: string ++ Allows specifying a different tooltip format for each language. The ** should be replaced with the language code. This can be used to provide a custom tooltip for each language. +*tooltip-format--*: ++ + typeof: string ++ + Like *tooltip-format-* but also matches the layout variant, taking precedence over *tooltip-format-* when both the language and variant match. + *expand*: ++ typeof: bool ++ default: false ++ diff --git a/man/waybar-hyprland-window.5.scd b/man/waybar-hyprland-window.5.scd index 34ebf89b..ecbacd4b 100644 --- a/man/waybar-hyprland-window.5.scd +++ b/man/waybar-hyprland-window.5.scd @@ -25,6 +25,15 @@ Addressed by *hyprland/window* typeof: bool ++ Show the active window of the monitor the bar belongs to, instead of the focused window. +*fallback*: ++ + typeof: string ++ + Text to display when the focused window title is empty (for example when no window is focused). + +*tooltip-format*: ++ + typeof: string ++ + default: {title} ++ + The format of the tooltip shown on hover. Supports the same replacements as *format*. Requires *tooltip* to be enabled. + *icon*: ++ typeof: bool ++ default: false ++ diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index d60557fa..85bbf4be 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -118,6 +118,11 @@ This setting is ignored if *workspace-taskbar.enable* is set to true. default: false ++ If set to true, only persistent workspaces will be shown on bar. +*persistent-workspaces*: ++ + typeof: object ++ + default: empty ++ + Lists workspaces that should always be shown, even when they do not exist. Keys are workspace names and values are arrays of output names on which the workspace should be shown (an empty array means all outputs). See the examples below. + *all-outputs*: ++ typeof: bool ++ default: false ++ diff --git a/man/waybar-mango-language.5.scd b/man/waybar-mango-language.5.scd index 53fd9569..b153f0d3 100644 --- a/man/waybar-mango-language.5.scd +++ b/man/waybar-mango-language.5.scd @@ -24,6 +24,10 @@ Addressed by *mango/language* The value is used as the replacement for *{}* in the main *format*. This option can be repeated for multiple languages. +*format--*: ++ + typeof: string ++ + Like *format-* but also matches the layout variant, taking precedence over *format-* when both the language and variant match. + *menu*: ++ typeof: string ++ Action that pops up a menu. diff --git a/man/waybar-niri-language.5.scd b/man/waybar-niri-language.5.scd index 78cf14f8..d58408d8 100644 --- a/man/waybar-niri-language.5.scd +++ b/man/waybar-niri-language.5.scd @@ -21,6 +21,10 @@ Addressed by *niri/language* typeof: string++ Provide an alternative name to display per language where is the language of your choosing. Can be passed multiple times with multiple languages as shown by the example below. +*format--* ++ + typeof: string ++ + Like *format-* but also matches the layout variant, taking precedence over *format-* when both the language and variant match. + *menu*: ++ typeof: string ++ Action that popups the menu. diff --git a/man/waybar-niri-workspaces.5.scd b/man/waybar-niri-workspaces.5.scd index b8fd15ec..af0a53dc 100644 --- a/man/waybar-niri-workspaces.5.scd +++ b/man/waybar-niri-workspaces.5.scd @@ -32,6 +32,11 @@ Addressed by *niri/workspaces* default: false ++ Sort workspaces by id, taking precedence over other sort options. +*sort-by-number*: ++ + typeof: bool ++ + default: false ++ + Deprecated alias for *sort-by-id*; prefer *sort-by-id* instead. + If none of the sorting options are enabled, workspaces keep their output/index order. *format*: ++ @@ -58,6 +63,11 @@ If none of the sorting options are enabled, workspaces keep their output/index o default: false ++ If set to true, only the active or focused workspace will be shown. +*hide-empty*: ++ + typeof: bool ++ + default: false ++ + If set to true, empty workspaces will not be shown. + *on-update*: ++ typeof: string ++ Command to execute when the module is updated. diff --git a/man/waybar-pulseaudio-slider.5.scd b/man/waybar-pulseaudio-slider.5.scd index 7ed5ba41..ef2988ff 100644 --- a/man/waybar-pulseaudio-slider.5.scd +++ b/man/waybar-pulseaudio-slider.5.scd @@ -48,6 +48,11 @@ The volume can be controlled by dragging the slider across the bar or clicking o default: sink ++ The audio target to control. Can be either `sink` (output/speakers) or `source` (input/microphone). +*ignored-sinks*: ++ + typeof: array ++ + default: empty ++ + A list of sink descriptions to ignore when tracking the default sink, so switching to those sinks does not update the slider. + # EXAMPLES ``` diff --git a/man/waybar-river-tags.5.scd b/man/waybar-river-tags.5.scd index 03281640..1966d335 100644 --- a/man/waybar-river-tags.5.scd +++ b/man/waybar-river-tags.5.scd @@ -26,6 +26,14 @@ Addressed by *river/tags* default: false ++ If set to false, you can left-click to set focused tag. Right-click to toggle tag focus. If set to true this behaviour is disabled. +*set-tags*: ++ + typeof: array ++ + An array of tag bitmasks, one per tag button. Left-clicking a tag sets the focused tags to the corresponding bitmask instead of the single tag. Requires *disable-click* to be false. + +*toggle-tags*: ++ + typeof: array ++ + An array of tag bitmasks, one per tag button. Right-clicking a tag toggles the corresponding bitmask in the focused tags instead of the single tag. Requires *disable-click* to be false. + *expand*: ++ typeof: bool ++ default: false ++ diff --git a/man/waybar-sway-workspaces.5.scd b/man/waybar-sway-workspaces.5.scd index ec39982d..b90f860c 100644 --- a/man/waybar-sway-workspaces.5.scd +++ b/man/waybar-sway-workspaces.5.scd @@ -70,6 +70,11 @@ Addressed by *sway/workspaces* default: false ++ If set to true. Only focused workspaces will be shown. +*ignore-workspaces*: ++ + typeof: array ++ + default: empty ++ + List of regular expressions. Workspaces whose name matches any of the given patterns are not shown. + *persistent-workspaces*: ++ typeof: json (see below) ++ default: empty ++ diff --git a/man/waybar-wayfire-workspaces.5.scd b/man/waybar-wayfire-workspaces.5.scd index 53a179e8..04f3be52 100644 --- a/man/waybar-wayfire-workspaces.5.scd +++ b/man/waybar-wayfire-workspaces.5.scd @@ -26,6 +26,16 @@ Addressed by *wayfire/workspaces* default: false ++ If set to false, you can click to change workspace. If set to true this behaviour is disabled. +*disable-scroll*: ++ + typeof: bool ++ + default: false ++ + If set to false, you can scroll to cycle through workspaces. If set to true this behaviour is disabled. + +*enable-bar-scroll*: ++ + typeof: bool ++ + default: false ++ + If set to false, you can't scroll to cycle through workspaces from the entire bar. If set to true this behaviour is enabled. + *disable-markup*: ++ typeof: bool ++ default: false ++ From 8eb916ac8485b9faf0b4ca1c20254ac2f8364a03 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 23:46:31 +0200 Subject: [PATCH 767/780] refactor(ALabel): add generic label+tooltip helper to remove duplication Modules duplicated the same boilerplate to format their label and tooltip (read tooltip-format, fmt::format, set_tooltip_markup) across ~29 modules. Add updateLabelAndTooltip(labelFormat, tooltipDefault, args...) and its state-aware variant updateLabelAndTooltipForState(state, ...) to ALabel: they build a single fmt arg store, render the label and the resolved tooltip format (tooltip-format[-state] or default) through the dedup-aware setters, honoring the tooltip toggle. resolveTooltipFormat() centralizes the format resolution. Migrate temperature and disk as the first adopters. --- include/ALabel.hpp | 39 +++++++++++++++++++++++++++++++++++++ src/modules/disk.cpp | 20 ++++--------------- src/modules/temperature.cpp | 17 ++++------------ 3 files changed, 47 insertions(+), 29 deletions(-) diff --git a/include/ALabel.hpp b/include/ALabel.hpp index 4b04c00d..bb68768d 100644 --- a/include/ALabel.hpp +++ b/include/ALabel.hpp @@ -1,10 +1,14 @@ #pragma once +#include +#include #include #include #include #include +#include +#include #include "AModule.hpp" @@ -30,6 +34,41 @@ class ALabel : public AModule { bool setLabelMarkup(const Glib::ustring& markup); bool setTooltipMarkup(const Glib::ustring& markup); + // Resolve the tooltip format string: prefers `tooltip-format-` (when a + // non-empty state is given), then `tooltip-format`, then `defaultFormat`. + std::string resolveTooltipFormat(const std::string& defaultFormat, + const std::string& state = "") const { + if (!state.empty() && config_["tooltip-format-" + state].isString()) { + return config_["tooltip-format-" + state].asString(); + } + if (config_["tooltip-format"].isString()) { + return config_["tooltip-format"].asString(); + } + return defaultFormat; + } + + // Combined label + tooltip helper. Builds a single fmt argument store from + // `args`, renders `labelFormat` into the label and the resolved tooltip format + // into the tooltip, both through the dedup-aware setters. Honors the `tooltip` + // toggle. This replaces the label/tooltip formatting boilerplate that modules + // used to duplicate. `state` selects `tooltip-format-` when non-empty. + template + void updateLabelAndTooltipForState(const std::string& state, const std::string& labelFormat, + const std::string& tooltipDefault, Args&&... args) { + fmt::dynamic_format_arg_store store; + (store.push_back(std::forward(args)), ...); + setLabelMarkup(fmt::vformat(labelFormat, store)); + if (tooltipEnabled()) { + setTooltipMarkup(fmt::vformat(resolveTooltipFormat(tooltipDefault, state), store)); + } + } + + template + void updateLabelAndTooltip(const std::string& labelFormat, const std::string& tooltipDefault, + Args&&... args) { + updateLabelAndTooltipForState("", labelFormat, tooltipDefault, std::forward(args)...); + } + bool handleToggle(GdkEventButton* const& e) override; void copyToClipboard(const std::string&); virtual std::string getState(uint8_t value, bool lesser = false); diff --git a/src/modules/disk.cpp b/src/modules/disk.cpp index 462940b2..5b9d7e41 100644 --- a/src/modules/disk.cpp +++ b/src/modules/disk.cpp @@ -68,25 +68,13 @@ auto waybar::modules::Disk::update() -> void { event_box_.hide(); } else { event_box_.show(); - label_.set_markup(fmt::format( - fmt::runtime(format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), + updateLabelAndTooltip( + format, "{used} used out of {total} on {path} ({percentage_used}%)", + stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used), fmt::arg("percentage_used", percentage_used), fmt::arg("total", total), fmt::arg("path", path_), fmt::arg("specific_free", specific_free), - fmt::arg("specific_used", specific_used), fmt::arg("specific_total", specific_total))); - } - - if (tooltipEnabled()) { - std::string tooltip_format = "{used} used out of {total} on {path} ({percentage_used}%)"; - if (config_["tooltip-format"].isString()) { - tooltip_format = config_["tooltip-format"].asString(); - } - label_.set_tooltip_markup(fmt::format( - fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), - fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used), - fmt::arg("percentage_used", percentage_used), fmt::arg("total", total), - fmt::arg("path", path_), fmt::arg("specific_free", specific_free), - fmt::arg("specific_used", specific_used), fmt::arg("specific_total", specific_total))); + fmt::arg("specific_used", specific_used), fmt::arg("specific_total", specific_total)); } // Call parent update ALabel::update(); diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp index 1d5e6522..4b410cd3 100644 --- a/src/modules/temperature.cpp +++ b/src/modules/temperature.cpp @@ -155,19 +155,10 @@ auto waybar::modules::Temperature::update() -> void { event_box_.show(); auto max_temp = config_["critical-threshold"].isInt() ? config_["critical-threshold"].asInt() : 0; - label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg("temperatureC", temperature_c), - fmt::arg("temperatureF", temperature_f), - fmt::arg("temperatureK", temperature_k), - fmt::arg("icon", getIcon(temperature_c, "", max_temp)))); - if (tooltipEnabled()) { - std::string tooltip_format = "{temperatureC}°C"; - if (config_["tooltip-format"].isString()) { - tooltip_format = config_["tooltip-format"].asString(); - } - label_.set_tooltip_markup(fmt::format( - fmt::runtime(tooltip_format), fmt::arg("temperatureC", temperature_c), - fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k))); - } + updateLabelAndTooltip(format, "{temperatureC}°C", fmt::arg("temperatureC", temperature_c), + fmt::arg("temperatureF", temperature_f), + fmt::arg("temperatureK", temperature_k), + fmt::arg("icon", getIcon(temperature_c, "", max_temp))); // Call parent update ALabel::update(); } From 2ac1f1c437456b6cdc39e76be661d7ac1c88eb56 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 23:51:10 +0200 Subject: [PATCH 768/780] Fix clang-format --- src/modules/network.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index e7cdfcaf..0981d571 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -322,8 +322,7 @@ auto waybar::modules::Network::update() -> void { if (!state_.empty() && label_.get_style_context()->has_class(state_)) { label_.get_style_context()->remove_class(state_); } - if (!threshold_state.empty() && - config_["format-" + state + "-" + threshold_state].isString()) { + if (!threshold_state.empty() && config_["format-" + state + "-" + threshold_state].isString()) { default_format_ = config_["format-" + state + "-" + threshold_state].asString(); } else if (config_["format-" + state].isString()) { default_format_ = config_["format-" + state].asString(); From 913cde915abbff9e70827e9d57cd440d87c62361 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 23:51:45 +0200 Subject: [PATCH 769/780] Fix clang-format --- include/modules/bluetooth.hpp | 3 ++- src/modules/bluetooth.cpp | 15 +++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/include/modules/bluetooth.hpp b/include/modules/bluetooth.hpp index a06d4645..5e03b118 100644 --- a/include/modules/bluetooth.hpp +++ b/include/modules/bluetooth.hpp @@ -63,7 +63,8 @@ class Bluetooth : public ALabel { auto getDeviceGattBatteryLevels(GDBusObject*, std::optional&, std::optional&) -> void; static auto processBatteryServiceCharacteristics(GList*, const std::string&, const std::string&, - const std::string&, std::optional&, + const std::string&, + std::optional&, std::optional&) -> void; auto getDeviceProperties(GDBusObject*, DeviceInfo&) -> bool; auto getControllerProperties(GDBusObject*, ControllerInfo&) -> bool; diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp index 97aad8b2..d54c4d07 100644 --- a/src/modules/bluetooth.cpp +++ b/src/modules/bluetooth.cpp @@ -92,8 +92,9 @@ auto readBatteryCharacteristicValue(GDBusProxy* proxy_char) -> std::optional std::optional(g_variant_get_fixed_array(value_array, &n_elements, sizeof(guchar))); + const auto* data = static_cast( + g_variant_get_fixed_array(value_array, &n_elements, sizeof(guchar))); std::optional result; if (data != nullptr && n_elements > 0) { @@ -136,7 +137,8 @@ auto hasUserDescriptionDescriptor(GList* objects, const std::string& char_path, auto desc_uuid = getOptionalStringProperty(proxy_desc, "UUID"); g_object_unref(proxy_desc); - if (desc_uuid.has_value() && desc_uuid.value().find(user_description_uuid) != std::string::npos) { + if (desc_uuid.has_value() && + desc_uuid.value().find(user_description_uuid) != std::string::npos) { return true; } } @@ -496,7 +498,8 @@ auto waybar::modules::Bluetooth::getDeviceGattBatteryLevels( } processBatteryServiceCharacteristics(objects, service_path, BATTERY_LEVEL_UUID, - USER_DESCRIPTION_UUID, central_battery, peripheral_battery); + USER_DESCRIPTION_UUID, central_battery, + peripheral_battery); } g_list_free_full(objects, g_object_unref); From c4c9345fef30a5a29f5ab44ad16b1d3c6413ad86 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 23:52:02 +0200 Subject: [PATCH 770/780] Fix clang-format --- include/AGraph.hpp | 31 +++++++++++++++---------------- include/modules/cpu_graph.hpp | 6 +++--- src/factory.cpp | 6 +++--- src/modules/custom_graph.cpp | 7 +++---- 4 files changed, 24 insertions(+), 26 deletions(-) diff --git a/include/AGraph.hpp b/include/AGraph.hpp index 69045473..e2ada835 100644 --- a/include/AGraph.hpp +++ b/include/AGraph.hpp @@ -15,7 +15,7 @@ enum class GraphType { LINE, BAR, GAUGE }; class AGraph : public AModule { public: - AGraph(const Json::Value &, const std::string &, const std::string &, uint16_t interval = 0, + AGraph(const Json::Value&, const std::string&, const std::string&, uint16_t interval = 0, bool enable_click = false, bool enable_scroll = false); virtual ~AGraph() = default; auto update() -> void override; @@ -30,29 +30,28 @@ class AGraph : public AModule { const std::chrono::seconds interval_; - bool onDraw(const Cairo::RefPtr &cr); + bool onDraw(const Cairo::RefPtr& cr); - std::map submenus_; + std::map submenus_; std::map menuActionsMap_; - static void handleGtkMenuEvent(GtkMenuItem *menuitem, gpointer data); + static void handleGtkMenuEvent(GtkMenuItem* menuitem, gpointer data); private: - void drawFilledArea(const Cairo::RefPtr &cr, - const std::vector> &points, double height, - const Gdk::RGBA &bg_color); + void drawFilledArea(const Cairo::RefPtr& cr, + const std::vector>& points, double height, + const Gdk::RGBA& bg_color); - void drawLine(const Cairo::RefPtr &cr, - const std::vector> &points, const Gdk::RGBA &fg_color); + void drawLine(const Cairo::RefPtr& cr, + const std::vector>& points, const Gdk::RGBA& fg_color); - void drawPath(const Cairo::RefPtr &cr, - const std::vector> &points); + void drawPath(const Cairo::RefPtr& cr, + const std::vector>& points); - void drawBars(const Cairo::RefPtr &cr, - double width, double height, int current_value, - const Gdk::RGBA &fg_color); + void drawBars(const Cairo::RefPtr& cr, double width, double height, + int current_value, const Gdk::RGBA& fg_color); - void drawGauge(const Cairo::RefPtr &cr, double width, double height, - int current_value, const Gdk::RGBA &fg_color); + void drawGauge(const Cairo::RefPtr& cr, double width, double height, + int current_value, const Gdk::RGBA& fg_color); }; } // namespace waybar diff --git a/include/modules/cpu_graph.hpp b/include/modules/cpu_graph.hpp index df5f6ea3..cf74a3fe 100644 --- a/include/modules/cpu_graph.hpp +++ b/include/modules/cpu_graph.hpp @@ -21,9 +21,9 @@ class CpuGraph : public AGraph { auto update() -> void override; private: - static constexpr const char *MODERATE_CLASS = "cpu-moderate"; - static constexpr const char *HIGH_CLASS = "cpu-high"; - static constexpr const char *INTENSIVE_CLASS = "cpu-intensive"; + static constexpr const char* MODERATE_CLASS = "cpu-moderate"; + static constexpr const char* HIGH_CLASS = "cpu-high"; + static constexpr const char* INTENSIVE_CLASS = "cpu-intensive"; std::vector> prev_times_; util::SleeperThread thread_; diff --git a/src/factory.cpp b/src/factory.cpp index cd29554c..6969da6b 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -43,11 +43,11 @@ #include "modules/niri/workspaces.hpp" #endif #ifdef HAVE_MANGO -#include "modules/mango/language.hpp" #include "modules/mango/keymode.hpp" +#include "modules/mango/language.hpp" +#include "modules/mango/layout.hpp" #include "modules/mango/window.hpp" #include "modules/mango/workspaces.hpp" -#include "modules/mango/layout.hpp" #endif #ifdef HAVE_WAYFIRE #include "modules/wayfire/window.hpp" @@ -58,8 +58,8 @@ #endif #if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD) #include "modules/cpu.hpp" -#include "modules/cpu_graph.hpp" #include "modules/cpu_frequency.hpp" +#include "modules/cpu_graph.hpp" #include "modules/cpu_usage.hpp" #include "modules/load.hpp" #endif diff --git a/src/modules/custom_graph.cpp b/src/modules/custom_graph.cpp index c23ed0d6..2a12f0c1 100644 --- a/src/modules/custom_graph.cpp +++ b/src/modules/custom_graph.cpp @@ -5,7 +5,7 @@ #include "util/scope_guard.hpp" waybar::modules::CustomGraph::CustomGraph(const std::string& name, const std::string& id, - const Json::Value& config, const std::string& output_name) + const Json::Value& config, const std::string& output_name) : AGraph(config, "custom-graph-" + name, id), name_(name), output_name_(output_name), @@ -175,9 +175,8 @@ auto waybar::modules::CustomGraph::update() -> void { if (tooltipEnabled()) { if (tooltip_format_enabled_) { auto tooltip = config_["tooltip-format"].asString(); - tooltip = fmt::format( - fmt::runtime(tooltip), fmt::arg("text", text_), fmt::arg("alt", alt_), - fmt::arg("percentage", percentage_)); + tooltip = fmt::format(fmt::runtime(tooltip), fmt::arg("text", text_), + fmt::arg("alt", alt_), fmt::arg("percentage", percentage_)); graph_.set_tooltip_markup(tooltip); } else { if (graph_.get_tooltip_markup() != tooltip_) { From 885ced58fca19cb7400969ba1b5ed5ced622ffbb Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 23:52:13 +0200 Subject: [PATCH 771/780] Fix clang-format --- src/modules/custom.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index 9b60eeb1..37198e3c 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -193,7 +193,8 @@ auto waybar::modules::Custom::update() -> void { auto str = fmt::format(fmt::runtime(format_), fmt::arg("text", text_), fmt::arg("alt", alt_), fmt::arg("icon", getIcon(percentage_, alt_)), fmt::arg("percentage", percentage_)); - if ((config_["hide-empty-text"].asBool() && text_.empty()) || (str.empty() && image_path_.empty() && image_name_.empty())) { + if ((config_["hide-empty-text"].asBool() && text_.empty()) || + (str.empty() && image_path_.empty() && image_name_.empty())) { event_box_.hide(); } else { label_.set_markup(str); @@ -231,17 +232,16 @@ auto waybar::modules::Custom::update() -> void { auto image_style = image_.get_style_context(); image_style->add_class("image-button"); event_box_.show(); - if (!image_path_.empty()) { - auto pixbuf = Gdk::Pixbuf::create_from_file(image_path_, app_icon_size_, app_icon_size_); - image_.set(pixbuf); - } else if (!image_name_.empty()) { - image_.set_from_icon_name(image_name_, Gtk::ICON_SIZE_INVALID); - image_.set_pixel_size(app_icon_size_); - } + if (!image_path_.empty()) { + auto pixbuf = Gdk::Pixbuf::create_from_file(image_path_, app_icon_size_, app_icon_size_); + image_.set(pixbuf); + } else if (!image_name_.empty()) { + image_.set_from_icon_name(image_name_, Gtk::ICON_SIZE_INVALID); + image_.set_pixel_size(app_icon_size_); + } image_.set_visible(!image_name_.empty() || !image_path_.empty()); label_.set_visible(!str.empty()); - } } catch (const fmt::format_error& e) { if (std::strcmp(e.what(), "cannot switch from manual to automatic argument indexing") != 0) @@ -251,9 +251,6 @@ auto waybar::modules::Custom::update() -> void { "mixing manual and automatic argument indexing is no longer supported; " "try replacing \"{}\" with \"{text}\" in your format specifier"); } - - - } // Call parent update AIconLabel::update(); @@ -322,7 +319,7 @@ void waybar::modules::Custom::parseOutputJson() { class_.push_back(c.asString()); } } - + if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) { percentage_ = (int)lround(parsed["percentage"].asFloat()); } else { From 331fe0e963ea867eb60dde2ebb6c98de6b0a1080 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 23:52:24 +0200 Subject: [PATCH 772/780] Fix clang-format --- include/modules/clock.hpp | 5 +++-- src/modules/clock.cpp | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp index 4ac91d61..5fd9df54 100644 --- a/include/modules/clock.hpp +++ b/include/modules/clock.hpp @@ -89,8 +89,9 @@ class Clock final : public ALabel { {"shift_reset", &waybar::modules::Clock::cldShift_reset}, {"tz_up", &waybar::modules::Clock::tz_up}, {"tz_down", &waybar::modules::Clock::tz_down}}; - static inline std::map actionWithArgsMap_{ - {"exec", &waybar::modules::Clock::action_exec}}; + static inline std::map + actionWithArgsMap_{{"exec", &waybar::modules::Clock::action_exec}}; }; } // namespace waybar::modules diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index b92f2264..6050df31 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -550,7 +550,6 @@ void waybar::modules::Clock::action_exec(const std::string& action) { pid_children_.push_back(util::command::forkExec(cmd)); } - #ifdef HAVE_LANGINFO_1STDAY template using deleter_from_fn = std::integral_constant; From 66ecba4e99fcbb3a91acd197c6c552d730a608a7 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 23:58:46 +0200 Subject: [PATCH 773/780] idle_inhibitor: apply clang-format --- src/modules/idle_inhibitor.cpp | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index 8dfe0b85..9638d0fc 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -23,7 +23,7 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& // Read the wait-for-activity config option if (config_["wait-for-activity"].isBool()) { wait_for_activity_ = config_["wait-for-activity"].asBool(); - + // Check if ext-idle-notify protocol is available when wait-for-activity is enabled if (wait_for_activity_ && waybar::Client::inst()->idle_notifier == nullptr) { throw std::runtime_error("wait-for-activity requires ext-idle-notify-v1 protocol support"); @@ -162,31 +162,31 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { return true; } -void waybar::modules::IdleInhibitor::handleIdled(void* data, - ext_idle_notification_v1* /*notification*/) { +void waybar::modules::IdleInhibitor::handleIdled(void* data, + ext_idle_notification_v1* /*notification*/) { spdlog::info("deactivating idle_inhibitor due to user inactivity"); status = false; - + // Clean up the notification since we're deactivating auto* self = static_cast(data); if (self != nullptr) { self->teardownIdleNotification(); } - + for (auto const& module : waybar::modules::IdleInhibitor::modules) { module->update(); } } void waybar::modules::IdleInhibitor::handleResumed(void* data, - ext_idle_notification_v1* /*notification*/) { + ext_idle_notification_v1* /*notification*/) { // User became active again - notification will continue monitoring spdlog::debug("user activity detected, idle_inhibitor still active"); } void waybar::modules::IdleInhibitor::setupIdleNotification() { spdlog::debug("idle_inhibitor: setting up idle notification"); - + // Clean up any existing notification first if (idle_notification_ != nullptr) { spdlog::debug("idle_inhibitor: cleaning up existing notification before setup"); @@ -208,11 +208,12 @@ void waybar::modules::IdleInhibitor::setupIdleNotification() { auto* wl_seat = gdk_wayland_seat_get_wl_seat(gdk_seat); // Check protocol version to determine which function to use - uint32_t version = wl_proxy_get_version(reinterpret_cast(client->idle_notifier)); - - spdlog::debug("idle_inhibitor: creating notification with timeout {} ms (protocol version {})", + uint32_t version = + wl_proxy_get_version(reinterpret_cast(client->idle_notifier)); + + spdlog::debug("idle_inhibitor: creating notification with timeout {} ms (protocol version {})", idle_timeout_ms_, version); - + if (version >= 2) { // Version 2+: Use get_input_idle_notification which ignores idle inhibitors // This allows us to detect actual user inactivity even while the inhibitor is active @@ -222,10 +223,13 @@ void waybar::modules::IdleInhibitor::setupIdleNotification() { } else { // Version 1: Fall back to get_idle_notification // WARNING: This respects idle inhibitors, so it won't fire while inhibitor is active - spdlog::warn("idle_inhibitor: ext-idle-notifier-v1 version {} doesn't support get_input_idle_notification, " - "wait-for-activity may not work correctly", version); - idle_notification_ = ext_idle_notifier_v1_get_idle_notification( - client->idle_notifier, idle_timeout_ms_, wl_seat); + spdlog::warn( + "idle_inhibitor: ext-idle-notifier-v1 version {} doesn't support " + "get_input_idle_notification, " + "wait-for-activity may not work correctly", + version); + idle_notification_ = ext_idle_notifier_v1_get_idle_notification(client->idle_notifier, + idle_timeout_ms_, wl_seat); } if (idle_notification_ == nullptr) { From cdfd3cb08ab413d5d8da66387a41d7eb1a840f49 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 23:59:55 +0200 Subject: [PATCH 774/780] refactor: migrate complex-tooltip modules to ALabel dedup setters Switch direct label_.set_markup/set_tooltip_markup calls to the deduplicating setLabelMarkup/setTooltipMarkup helpers in clock, custom and mpris, keeping their custom tooltip construction logic intact. taskbar is left untouched: its tooltip lives on per-task Gtk::Button, not on an ALabel label_. --- src/modules/clock.cpp | 2 +- src/modules/custom.cpp | 7 ++----- src/modules/mpris/mpris.cpp | 4 ++-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp index b265c0ae..6f570e2d 100644 --- a/src/modules/clock.cpp +++ b/src/modules/clock.cpp @@ -162,7 +162,7 @@ auto waybar::modules::Clock::update() -> void { const auto* tz = tzList_[tzCurrIdx_] != nullptr ? tzList_[tzCurrIdx_] : local_zone(); const zoned_time now{tz, floor(system_clock::now())}; - label_.set_markup(fmt_lib::vformat(m_locale_, format_, fmt_lib::make_format_args(now))); + setLabelMarkup(fmt_lib::vformat(m_locale_, format_, fmt_lib::make_format_args(now))); if (tooltipEnabled()) { const year_month_day today{floor(now.get_local_time())}; diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp index e4a4eb37..7303d8ff 100644 --- a/src/modules/custom.cpp +++ b/src/modules/custom.cpp @@ -187,7 +187,7 @@ auto waybar::modules::Custom::update() -> void { if ((config_["hide-empty-text"].asBool() && text_.empty()) || str.empty()) { event_box_.hide(); } else { - label_.set_markup(str); + setLabelMarkup(str); if (tooltipEnabled()) { std::string tooltip_markup; if (tooltip_format_enabled_) { @@ -202,10 +202,7 @@ auto waybar::modules::Custom::update() -> void { tooltip_markup = tooltip_; } - if (last_tooltip_markup_ != tooltip_markup) { - label_.set_tooltip_markup(tooltip_markup); - last_tooltip_markup_ = std::move(tooltip_markup); - } + setTooltipMarkup(tooltip_markup); } auto style = label_.get_style_context(); auto classes = style->list_classes(); diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp index ce084723..c3e0235e 100644 --- a/src/modules/mpris/mpris.cpp +++ b/src/modules/mpris/mpris.cpp @@ -735,7 +735,7 @@ auto Mpris::update() -> void { if (label_format.empty()) { label_.hide(); } else { - label_.set_markup(label_format); + setLabelMarkup(label_format); label_.show(); } } catch (fmt::format_error const& e) { @@ -758,7 +758,7 @@ auto Mpris::update() -> void { fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)), fmt::arg("status_icon", getIconFromJson(config_["status-icons"], info.status_string))); - label_.set_tooltip_markup(tooltip_text); + setTooltipMarkup(tooltip_text); } catch (fmt::format_error const& e) { spdlog::warn("mpris: format error (tooltip): {}", e.what()); } From db4941efe74aa2d3acc7c9c8a4f90b3a2afacac8 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 4 Jul 2026 00:00:15 +0200 Subject: [PATCH 775/780] refactor(tooltip): migrate modules to ALabel generic tooltip helper Migrate idle_inhibitor, sway/language, sway/scratchpad, hyprland/language and hyprland/window to the shared label/tooltip setters. idle_inhibitor uses updateLabelAndTooltipForState; the others keep their custom label/tooltip resolution (trim, precomputed tooltip, rewriteString, per-lang/variant formats) and switch to the dedup-aware setLabelMarkup/setTooltipMarkup. --- src/modules/hyprland/language.cpp | 18 +++++++++--------- src/modules/hyprland/window.cpp | 19 +++++++++---------- src/modules/idle_inhibitor.cpp | 11 ++--------- src/modules/sway/language.cpp | 6 +++--- src/modules/sway/scratchpad.cpp | 4 ++-- 5 files changed, 25 insertions(+), 33 deletions(-) diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp index ff7cad64..dfe24b0a 100644 --- a/src/modules/hyprland/language.cpp +++ b/src/modules/hyprland/language.cpp @@ -77,9 +77,9 @@ auto Language::update() -> void { if (!format_.empty()) { label_.show(); - label_.set_markup(layoutName); + setLabelMarkup(layoutName); if (tooltip_enabled) { - label_.set_tooltip_markup(tooltipContent); + setTooltipMarkup(tooltipContent); } } else { label_.hide(); @@ -93,12 +93,11 @@ auto Language::update() -> void { } else { tooltipFormat = "{long}"; } - auto tooltipText = trim(fmt::format( - fmt::runtime(tooltipFormat), - fmt::arg("long", layout_.full_name), - fmt::arg("short", layout_.short_name), - fmt::arg("shortDescription", layout_.short_description), - fmt::arg("variant", layout_.variant))); + auto tooltipText = + trim(fmt::format(fmt::runtime(tooltipFormat), fmt::arg("long", layout_.full_name), + fmt::arg("short", layout_.short_name), + fmt::arg("shortDescription", layout_.short_description), + fmt::arg("variant", layout_.variant))); label_.set_tooltip_text(tooltipText); } else { label_.set_tooltip_text(""); @@ -185,7 +184,8 @@ void Language::initLanguage() { auto Language::removeXkbLayoutCssClass() -> void { label_.get_style_context()->remove_class(layout_.short_name); - spdlog::debug("hyprland language try to remove currently short_name css class {}", layout_.short_name); + spdlog::debug("hyprland language try to remove currently short_name css class {}", + layout_.short_name); } auto Language::addXkbLayoutCssClass() -> void { label_.get_style_context()->add_class(layout_.short_name); diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp index 029f9722..13993482 100644 --- a/src/modules/hyprland/window.cpp +++ b/src/modules/hyprland/window.cpp @@ -67,7 +67,7 @@ auto Window::update() -> void { fmt::arg("class", windowData_.class_name), fmt::arg("initialClass", windowData_.initial_class_name)), config_["rewrite"]); - label_.set_markup(label_text); + setLabelMarkup(label_text); } else { label_.hide(); } @@ -78,13 +78,12 @@ auto Window::update() -> void { tooltip_format = config_["tooltip-format"].asString(); } if (!tooltip_format.empty()) { - label_.set_tooltip_markup( - fmt::format(fmt::runtime(tooltip_format), fmt::arg("title", windowName), - fmt::arg("initialTitle", windowData_.initial_title), - fmt::arg("class", windowData_.class_name), - fmt::arg("initialClass", windowData_.initial_class_name))); + setTooltipMarkup(fmt::format(fmt::runtime(tooltip_format), fmt::arg("title", windowName), + fmt::arg("initialTitle", windowData_.initial_title), + fmt::arg("class", windowData_.class_name), + fmt::arg("initialClass", windowData_.initial_class_name))); } else if (!label_text.empty()) { - label_.set_tooltip_markup(label_text); + setTooltipMarkup(label_text); } } @@ -222,9 +221,9 @@ void Window::queryActiveWorkspace() { std::vector visibleWindows; std::ranges::copy_if(workspaceWindows, std::back_inserter(visibleWindows), [&](const Json::Value& window) { return !window["hidden"].asBool(); }); - solo_ = 1 == std::count_if( - visibleWindows.begin(), visibleWindows.end(), - [&](const Json::Value& window) { return !window["floating"].asBool(); }); + solo_ = + 1 == std::count_if(visibleWindows.begin(), visibleWindows.end(), + [&](const Json::Value& window) { return !window["floating"].asBool(); }); allFloating_ = std::ranges::all_of( visibleWindows, [&](const Json::Value& window) { return window["floating"].asBool(); }); fullscreen_ = windowData_.fullscreen; diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp index a5fc9ac7..e18c28f0 100644 --- a/src/modules/idle_inhibitor.cpp +++ b/src/modules/idle_inhibitor.cpp @@ -63,16 +63,9 @@ auto waybar::modules::IdleInhibitor::update() -> void { } std::string status_text = status ? "activated" : "deactivated"; - label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("status", status_text), - fmt::arg("icon", getIcon(0, status_text)))); + updateLabelAndTooltipForState(status_text, format_, "{status}", fmt::arg("status", status_text), + fmt::arg("icon", getIcon(0, status_text))); label_.get_style_context()->add_class(status_text); - if (tooltipEnabled()) { - auto config = config_[status ? "tooltip-format-activated" : "tooltip-format-deactivated"]; - auto tooltip_format = config.isString() ? config.asString() : "{status}"; - label_.set_tooltip_markup(fmt::format(fmt::runtime(tooltip_format), - fmt::arg("status", status_text), - fmt::arg("icon", getIcon(0, status_text)))); - } // Call parent update ALabel::update(); } diff --git a/src/modules/sway/language.cpp b/src/modules/sway/language.cpp index 28f1ef24..7113d9ef 100644 --- a/src/modules/sway/language.cpp +++ b/src/modules/sway/language.cpp @@ -124,7 +124,7 @@ auto Language::update() -> void { fmt::runtime(format_), fmt::arg("short", layout_.short_name), fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag()))); - label_.set_markup(display_layout); + setLabelMarkup(display_layout); if (tooltipEnabled()) { if (tooltip_format_ != "") { auto tooltip_display_layout = trim( @@ -132,9 +132,9 @@ auto Language::update() -> void { fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag()))); - label_.set_tooltip_markup(tooltip_display_layout); + setTooltipMarkup(tooltip_display_layout); } else { - label_.set_tooltip_markup(display_layout); + setTooltipMarkup(display_layout); } } diff --git a/src/modules/sway/scratchpad.cpp b/src/modules/sway/scratchpad.cpp index 17dc2707..94f35999 100644 --- a/src/modules/sway/scratchpad.cpp +++ b/src/modules/sway/scratchpad.cpp @@ -31,12 +31,12 @@ Scratchpad::Scratchpad(const std::string& id, const Json::Value& config) auto Scratchpad::update() -> void { if (count_ || show_empty_) { event_box_.show(); - label_.set_markup( + setLabelMarkup( fmt::format(fmt::runtime(format_), fmt::arg("icon", getIcon(count_, "", config_["format-icons"].size())), fmt::arg("count", count_))); if (tooltip_enabled_) { - label_.set_tooltip_markup(tooltip_text_); + setTooltipMarkup(tooltip_text_); } } else { event_box_.hide(); From 836449d253a517f56311454d5639b5083b08fca8 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 4 Jul 2026 00:00:17 +0200 Subject: [PATCH 776/780] refactor(modules): use ALabel dedup tooltip helper in simpleclock, systemd-failed-units, upower simpleclock: migrate to updateLabelAndTooltip (label + tooltip share the localtime arg; default tooltip falls back to the label format). systemd-failed-units: use setLabelMarkup/setTooltipMarkup dedup setters; label/tooltip formats are selected by different conditions so the combined helper does not apply. upower: use setLabelMarkup for the label; tooltip stays a custom GTK widget. mpd already uses the dedup setters (label/tooltip use different truncated args), so no change. --- src/modules/simpleclock.cpp | 13 +------------ src/modules/systemd_failed_units.cpp | 6 +++--- src/modules/upower.cpp | 2 +- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/modules/simpleclock.cpp b/src/modules/simpleclock.cpp index 59a2b1c1..b8fc2423 100644 --- a/src/modules/simpleclock.cpp +++ b/src/modules/simpleclock.cpp @@ -18,18 +18,7 @@ auto waybar::modules::Clock::update() -> void { tzset(); // Update timezone information auto now = std::chrono::system_clock::now(); auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); - auto text = fmt::format(fmt::runtime(format_), localtime); - label_.set_markup(text); - - if (tooltipEnabled()) { - if (config_["tooltip-format"].isString()) { - auto tooltip_format = config_["tooltip-format"].asString(); - auto tooltip_text = fmt::format(fmt::runtime(tooltip_format), localtime); - label_.set_tooltip_markup(tooltip_text); - } else { - label_.set_tooltip_markup(text); - } - } + updateLabelAndTooltip(format_, format_, localtime); // Call parent update ALabel::update(); } diff --git a/src/modules/systemd_failed_units.cpp b/src/modules/systemd_failed_units.cpp index a20580f5..faa1ca7e 100644 --- a/src/modules/systemd_failed_units.cpp +++ b/src/modules/systemd_failed_units.cpp @@ -281,7 +281,7 @@ auto SystemdFailedUnits::update() -> void { last_status_ = overall_state_; - label_.set_markup(fmt::format( + setLabelMarkup(fmt::format( fmt::runtime(nr_failed_ == 0 ? format_ok_ : format_), fmt::arg("nr_failed", nr_failed_), fmt::arg("nr_failed_system", nr_failed_system_), fmt::arg("nr_failed_user", nr_failed_user_), fmt::arg("system_state", system_state_), fmt::arg("user_state", user_state_), @@ -290,14 +290,14 @@ auto SystemdFailedUnits::update() -> void { std::string failed_list = BuildTooltipFailedList(); auto tooltip_template = overall_state_ == "ok" ? tooltip_format_ok_ : tooltip_format_; if (!tooltip_template.empty()) { - label_.set_tooltip_markup(fmt::format( + setTooltipMarkup(fmt::format( fmt::runtime(tooltip_template), fmt::arg("nr_failed", nr_failed_), fmt::arg("nr_failed_system", nr_failed_system_), fmt::arg("nr_failed_user", nr_failed_user_), fmt::arg("system_state", system_state_), fmt::arg("user_state", user_state_), fmt::arg("overall_state", overall_state_), fmt::arg("failed_units_list", failed_list))); } else { - label_.set_tooltip_markup(""); + setTooltipMarkup(""); } } ALabel::update(); diff --git a/src/modules/upower.cpp b/src/modules/upower.cpp index d260ba83..f5d72639 100644 --- a/src/modules/upower.cpp +++ b/src/modules/upower.cpp @@ -223,7 +223,7 @@ auto UPower::update() -> void { return; } - label_.set_markup(getText(upDevice_, format_)); + setLabelMarkup(getText(upDevice_, format_)); // Set icon if (upDevice_.icon_name == NULL || !gtkTheme_->has_icon(upDevice_.icon_name)) upDevice_.icon_name = (char*)NO_BATTERY.c_str(); From 86b8dd6b1baba67bf663f487cdedd9950196fc1c Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 4 Jul 2026 00:01:17 +0200 Subject: [PATCH 777/780] refactor: migrate backlight/battery/bluetooth/gps to updateLabelAndTooltip --- src/modules/backlight.cpp | 22 +++------------ src/modules/battery.cpp | 4 +-- src/modules/bluetooth.cpp | 4 +-- src/modules/gps.cpp | 56 +++++++++------------------------------ 4 files changed, 21 insertions(+), 65 deletions(-) diff --git a/src/modules/backlight.cpp b/src/modules/backlight.cpp index 367d6434..24e08d54 100644 --- a/src/modules/backlight.cpp +++ b/src/modules/backlight.cpp @@ -55,24 +55,10 @@ auto waybar::modules::Backlight::update() -> void { } } - std::string desc = - fmt::format(fmt::runtime(current_format), fmt::arg("percent", percent), - fmt::arg("percent_exp", percent_exp), fmt::arg("icon", getIcon(percent)), - fmt::arg("icon_exp", getIcon(percent_exp))); - label_.set_markup(desc); - if (tooltipEnabled()) { - std::string tooltip_format; - if (config_["tooltip-format"].isString()) { - tooltip_format = config_["tooltip-format"].asString(); - } - if (!tooltip_format.empty()) { - label_.set_tooltip_markup(fmt::format(fmt::runtime(tooltip_format), - fmt::arg("percent", percent), - fmt::arg("icon", getIcon(percent)))); - } else { - label_.set_tooltip_markup(desc); - } - } + updateLabelAndTooltip(current_format, current_format, fmt::arg("percent", percent), + fmt::arg("percent_exp", percent_exp), + fmt::arg("icon", getIcon(percent)), + fmt::arg("icon_exp", getIcon(percent_exp))); } else { event_box_.hide(); } diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp index 2fa4ded0..cec8beb8 100644 --- a/src/modules/battery.cpp +++ b/src/modules/battery.cpp @@ -767,7 +767,7 @@ auto waybar::modules::Battery::update() -> void { } else if (config_["tooltip-format"].isString()) { tooltip_format = config_["tooltip-format"].asString(); } - label_.set_tooltip_markup( + setTooltipMarkup( fmt::format(fmt::runtime(tooltip_format), fmt::arg("timeTo", tooltip_text_default), fmt::arg("power", power), fmt::arg("capacity", capacity), fmt::arg("time", time_remaining_formatted), fmt::arg("cycles", cycles), @@ -790,7 +790,7 @@ auto waybar::modules::Battery::update() -> void { } else { event_box_.show(); auto icons = std::vector{status + "-" + state, status, state}; - label_.set_markup(fmt::format( + setLabelMarkup(fmt::format( fmt::runtime(format), fmt::arg("capacity", capacity), fmt::arg("power", power), fmt::arg("icon", getIcon(capacity, icons)), fmt::arg("time", time_remaining_formatted), fmt::arg("cycles", cycles), fmt::arg("health", fmt::format("{:.3}", health)))); diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp index c59af3b5..d2afd897 100644 --- a/src/modules/bluetooth.cpp +++ b/src/modules/bluetooth.cpp @@ -222,7 +222,7 @@ auto waybar::modules::Bluetooth::update() -> void { event_box_.hide(); } else { event_box_.show(); - label_.set_markup(fmt::format( + setLabelMarkup(fmt::format( fmt::runtime(format_), fmt::arg("status", state_), fmt::arg("num_connections", connected_devices_.size()), fmt::arg("controller_address", cur_controller_ ? cur_controller_->address : "null"), @@ -267,7 +267,7 @@ auto waybar::modules::Bluetooth::update() -> void { device_enumerate_.erase(0, 1); } } - label_.set_tooltip_markup(fmt::format( + setTooltipMarkup(fmt::format( fmt::runtime(tooltip_format), fmt::arg("status", state_), fmt::arg("num_connections", connected_devices_.size()), fmt::arg("controller_address", cur_controller_ ? cur_controller_->address : "null"), diff --git a/src/modules/gps.cpp b/src/modules/gps.cpp index a7bab659..285a6938 100644 --- a/src/modules/gps.cpp +++ b/src/modules/gps.cpp @@ -141,7 +141,7 @@ auto waybar::modules::Gps::update() -> void { // Show the module if (!event_box_.get_visible()) event_box_.set_visible(true); - std::string tooltip_format; + std::string tooltip_state; if (!alt_) { auto state = getFixModeName(); @@ -155,57 +155,27 @@ auto waybar::modules::Gps::update() -> void { } else { default_format_ = DEFAULT_FORMAT; } - if (config_["tooltip-format-" + state].isString()) { - tooltip_format = config_["tooltip-format-" + state].asString(); - } if (!label_.get_style_context()->has_class(state)) { label_.get_style_context()->add_class(state); } format_ = default_format_; state_ = state; + tooltip_state = state; } auto format = format_; - fmt::dynamic_format_arg_store store; - store.push_back(fmt::arg("mode", getFixModeString())); - store.push_back(fmt::arg("status", getFixStatusString())); - - store.push_back(fmt::arg("latitude", gps_data_.fix.latitude)); - store.push_back(fmt::arg("latitude_error", gps_data_.fix.epy)); - - store.push_back(fmt::arg("longitude", gps_data_.fix.longitude)); - store.push_back(fmt::arg("longitude_error", gps_data_.fix.epx)); - - store.push_back(fmt::arg("altitude_hae", gps_data_.fix.altHAE)); - store.push_back(fmt::arg("altitude_msl", gps_data_.fix.altMSL)); - store.push_back(fmt::arg("altitude_error", gps_data_.fix.epv)); - - store.push_back(fmt::arg("speed", gps_data_.fix.speed)); - store.push_back(fmt::arg("speed_error", gps_data_.fix.eps)); - - store.push_back(fmt::arg("climb", gps_data_.fix.climb)); - store.push_back(fmt::arg("climb_error", gps_data_.fix.epc)); - - store.push_back(fmt::arg("satellites_used", gps_data_.satellites_used)); - store.push_back(fmt::arg("satellites_visible", gps_data_.satellites_visible)); - - auto text = fmt::vformat(format, store); - - if (tooltipEnabled()) { - if (tooltip_format.empty() && config_["tooltip-format"].isString()) { - tooltip_format = config_["tooltip-format"].asString(); - } - if (!tooltip_format.empty()) { - auto tooltip_text = fmt::vformat(tooltip_format, store); - if (label_.get_tooltip_text() != tooltip_text) { - label_.set_tooltip_markup(tooltip_text); - } - } else if (label_.get_tooltip_text() != text) { - label_.set_tooltip_markup(text); - } - } - label_.set_markup(text); + updateLabelAndTooltipForState( + tooltip_state, format, format, fmt::arg("mode", getFixModeString()), + fmt::arg("status", getFixStatusString()), fmt::arg("latitude", gps_data_.fix.latitude), + fmt::arg("latitude_error", gps_data_.fix.epy), fmt::arg("longitude", gps_data_.fix.longitude), + fmt::arg("longitude_error", gps_data_.fix.epx), + fmt::arg("altitude_hae", gps_data_.fix.altHAE), + fmt::arg("altitude_msl", gps_data_.fix.altMSL), fmt::arg("altitude_error", gps_data_.fix.epv), + fmt::arg("speed", gps_data_.fix.speed), fmt::arg("speed_error", gps_data_.fix.eps), + fmt::arg("climb", gps_data_.fix.climb), fmt::arg("climb_error", gps_data_.fix.epc), + fmt::arg("satellites_used", gps_data_.satellites_used), + fmt::arg("satellites_visible", gps_data_.satellites_visible)); // Call parent update ALabel::update(); } From 014c95a9fe8a961e4c5418bfb533b12abbc6521f Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 4 Jul 2026 00:04:47 +0200 Subject: [PATCH 778/780] refactor(modules): migrate audio/network/power modules to ALabel tooltip helpers Migrate network, pulseaudio, wireplumber, jack and power_profiles_daemon to the generic ALabel tooltip helpers: - jack and power-profiles-daemon use updateLabelAndTooltip() since their label/tooltip share a single arg set and a format-string tooltip default. - network, pulseaudio and wireplumber keep their rich in-module format selection but build a single fmt arg store, render the label/tooltip through the dedup-aware setLabelMarkup()/setTooltipMarkup() setters and resolveTooltipFormat(), preserving their custom fallbacks (label text, sink/node description) and visibility handling exactly. --- src/modules/jack.cpp | 17 ++--- src/modules/network.cpp | 95 ++++++++++++--------------- src/modules/power_profiles_daemon.cpp | 20 +++--- src/modules/pulseaudio.cpp | 29 ++++---- src/modules/wireplumber.cpp | 41 +++++------- 5 files changed, 85 insertions(+), 117 deletions(-) diff --git a/src/modules/jack.cpp b/src/modules/jack.cpp index 578fb4e0..c7bd5d92 100644 --- a/src/modules/jack.cpp +++ b/src/modules/jack.cpp @@ -72,19 +72,10 @@ auto JACK::update() -> void { } else format = "{load}%"; - label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg("load", std::round(load_)), - fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_), - fmt::arg("latency", fmt::format("{:.2f}", latency)), - fmt::arg("xruns", xruns_))); - - if (tooltipEnabled()) { - std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms"; - if (config_["tooltip-format"].isString()) tooltip_format = config_["tooltip-format"].asString(); - label_.set_tooltip_markup(fmt::format( - fmt::runtime(tooltip_format), fmt::arg("load", std::round(load_)), - fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_), - fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_))); - } + updateLabelAndTooltip( + format, "{bufsize}/{samplerate} {latency}ms", fmt::arg("load", std::round(load_)), + fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_), + fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_)); // Call parent update ALabel::update(); diff --git a/src/modules/network.cpp b/src/modules/network.cpp index 8bb54fce..04a1642d 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -350,32 +350,45 @@ auto waybar::modules::Network::update() -> void { final_ipaddr_ += ipaddr6_; } - auto text = fmt::format( - fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_), - fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), - fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), - fmt::arg("netmask", netmask_), fmt::arg("netmask6", netmask6_), - 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 / elapsed_seconds, "b/s")), - fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / elapsed_seconds, "b/s")), + fmt::dynamic_format_arg_store store; + store.push_back(fmt::arg("essid", essid_)); + store.push_back(fmt::arg("bssid", bssid_)); + store.push_back(fmt::arg("signaldBm", signal_strength_dbm_)); + store.push_back(fmt::arg("signalStrength", signal_strength_)); + store.push_back(fmt::arg("signalStrengthApp", signal_strength_app_)); + store.push_back(fmt::arg("ifname", ifname_)); + store.push_back(fmt::arg("netmask", netmask_)); + store.push_back(fmt::arg("netmask6", netmask6_)); + store.push_back(fmt::arg("ipaddr", final_ipaddr_)); + store.push_back(fmt::arg("gwaddr", gwaddr_)); + store.push_back(fmt::arg("cidr", cidr_)); + store.push_back(fmt::arg("cidr6", cidr6_)); + store.push_back(fmt::arg("frequency", fmt::format("{:.1f}", frequency_))); + store.push_back(fmt::arg("icon", getIcon(signal_strength_, state_))); + store.push_back( + fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / elapsed_seconds, "b/s"))); + store.push_back( + fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / elapsed_seconds, "b/s"))); + store.push_back( fmt::arg("bandwidthTotalBits", - pow_format((bandwidth_up + bandwidth_down) * 8ull / elapsed_seconds, "b/s")), - fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / elapsed_seconds, "o/s")), - fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / elapsed_seconds, "o/s")), - fmt::arg("bandwidthTotalOctets", - pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")), - fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")), - fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")), - fmt::arg("bandwidthDownBytesCompact", - pow_format(bandwidth_down / elapsed_seconds, "B", false, 2)), - fmt::arg("bandwidthUpBytesCompact", - pow_format(bandwidth_up / elapsed_seconds, "B", false, 2)), - fmt::arg("bandwidthTotalBytes", - pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s"))); - if (text.compare(label_.get_label()) != 0) { - label_.set_markup(text); + pow_format((bandwidth_up + bandwidth_down) * 8ull / elapsed_seconds, "b/s"))); + store.push_back( + fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / elapsed_seconds, "o/s"))); + store.push_back(fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / elapsed_seconds, "o/s"))); + store.push_back(fmt::arg("bandwidthTotalOctets", + pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s"))); + store.push_back( + fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s"))); + store.push_back(fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s"))); + store.push_back(fmt::arg("bandwidthDownBytesCompact", + pow_format(bandwidth_down / elapsed_seconds, "B", false, 2))); + store.push_back(fmt::arg("bandwidthUpBytesCompact", + pow_format(bandwidth_up / elapsed_seconds, "B", false, 2))); + store.push_back(fmt::arg("bandwidthTotalBytes", + pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s"))); + + auto text = fmt::vformat(format_, store); + if (setLabelMarkup(text)) { if (text.empty()) { event_box_.hide(); } else { @@ -387,35 +400,9 @@ auto waybar::modules::Network::update() -> void { tooltip_format = config_["tooltip-format"].asString(); } if (!tooltip_format.empty()) { - auto tooltip_text = fmt::format( - fmt::runtime(tooltip_format), fmt::arg("essid", essid_), fmt::arg("bssid", bssid_), - fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_), - fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), - fmt::arg("netmask", netmask_), fmt::arg("netmask6", netmask6_), - 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 / elapsed_seconds, "b/s")), - fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / elapsed_seconds, "b/s")), - fmt::arg("bandwidthTotalBits", - pow_format((bandwidth_up + bandwidth_down) * 8ull / elapsed_seconds, "b/s")), - fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / elapsed_seconds, "o/s")), - fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / elapsed_seconds, "o/s")), - fmt::arg("bandwidthTotalOctets", - pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "o/s")), - fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / elapsed_seconds, "B/s")), - fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / elapsed_seconds, "B/s")), - fmt::arg("bandwidthDownBytesCompact", - pow_format(bandwidth_down / elapsed_seconds, "B", false, 2)), - fmt::arg("bandwidthUpBytesCompact", - pow_format(bandwidth_up / elapsed_seconds, "B", false, 2)), - fmt::arg("bandwidthTotalBytes", - pow_format((bandwidth_up + bandwidth_down) / elapsed_seconds, "B/s"))); - if (label_.get_tooltip_text() != tooltip_text) { - label_.set_tooltip_markup(tooltip_text); - } - } else if (label_.get_tooltip_text() != text) { - label_.set_tooltip_markup(text); + setTooltipMarkup(fmt::vformat(tooltip_format, store)); + } else { + setTooltipMarkup(text); } } diff --git a/src/modules/power_profiles_daemon.cpp b/src/modules/power_profiles_daemon.cpp index d7eb5bcd..b897cd1c 100644 --- a/src/modules/power_profiles_daemon.cpp +++ b/src/modules/power_profiles_daemon.cpp @@ -187,18 +187,14 @@ void PowerProfilesDaemon::switchToProfile(std::string const& str) { auto PowerProfilesDaemon::update() -> void { if (connected_ && activeProfile_ != availableProfiles_.end()) { auto profile = (*activeProfile_); - // Set label - fmt::dynamic_format_arg_store store; - store.push_back(fmt::arg("profile", profile.name)); - // Legacy placeholder, kept for backward compatibility with existing configs. - store.push_back(fmt::arg("driver", profile.driver)); - store.push_back(fmt::arg("cpu_driver", profile.cpuDriver)); - store.push_back(fmt::arg("platform_driver", profile.platformDriver)); - store.push_back(fmt::arg("icon", getIcon(0, profile.name))); - label_.set_markup(fmt::vformat(format_, store)); - if (tooltipEnabled()) { - label_.set_tooltip_markup(fmt::vformat(tooltipFormat_, store)); - } + // Set label and tooltip + updateLabelAndTooltip(format_, tooltipFormat_, fmt::arg("profile", profile.name), + // Legacy placeholder, kept for backward compatibility with existing + // configs. + fmt::arg("driver", profile.driver), + fmt::arg("cpu_driver", profile.cpuDriver), + fmt::arg("platform_driver", profile.platformDriver), + fmt::arg("icon", getIcon(0, profile.name))); // Set CSS class if (!currentStyle_.empty()) { diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index 10fb74f9..874f5d97 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -72,7 +72,6 @@ const std::vector waybar::modules::Pulseaudio::getPulseIcon() const auto waybar::modules::Pulseaudio::update() -> void { auto format = format_; - std::string tooltip_format; auto sink_volume = backend->getSinkVolume(); if (!alt_) { std::string format_name = "format"; @@ -120,29 +119,29 @@ auto waybar::modules::Pulseaudio::update() -> void { auto source_desc = backend->getSourceDesc(); format_source = fmt::format(fmt::runtime(format_source), fmt::arg("volume", source_volume)); - auto text = fmt::format( - fmt::runtime(format), fmt::arg("desc", sink_desc), fmt::arg("volume", sink_volume), - fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume), - fmt::arg("source_desc", source_desc), fmt::arg("icon", getIcon(sink_volume, getPulseIcon()))); + + fmt::dynamic_format_arg_store store; + store.push_back(fmt::arg("desc", sink_desc)); + store.push_back(fmt::arg("volume", sink_volume)); + store.push_back(fmt::arg("format_source", format_source)); + store.push_back(fmt::arg("source_volume", source_volume)); + store.push_back(fmt::arg("source_desc", source_desc)); + store.push_back(fmt::arg("icon", getIcon(sink_volume, getPulseIcon()))); + + auto text = fmt::vformat(format, store); if (text.empty()) { label_.hide(); } else { - label_.set_markup(text); + setLabelMarkup(text); label_.show(); } if (tooltipEnabled()) { - if (tooltip_format.empty() && config_["tooltip-format"].isString()) { - tooltip_format = config_["tooltip-format"].asString(); - } + auto tooltip_format = resolveTooltipFormat(""); if (!tooltip_format.empty()) { - label_.set_tooltip_markup(fmt::format( - fmt::runtime(tooltip_format), fmt::arg("desc", sink_desc), - fmt::arg("volume", sink_volume), fmt::arg("format_source", format_source), - fmt::arg("source_volume", source_volume), fmt::arg("source_desc", source_desc), - fmt::arg("icon", getIcon(sink_volume, getPulseIcon())))); + setTooltipMarkup(fmt::vformat(tooltip_format, store)); } else { - label_.set_tooltip_markup(sink_desc); + setTooltipMarkup(sink_desc); } } diff --git a/src/modules/wireplumber.cpp b/src/modules/wireplumber.cpp index 751e885c..e6a3c64b 100644 --- a/src/modules/wireplumber.cpp +++ b/src/modules/wireplumber.cpp @@ -432,7 +432,6 @@ void waybar::modules::Wireplumber::asyncLoadRequiredApiModules() { auto waybar::modules::Wireplumber::update() -> void { auto format = format_; - std::string tooltipFormat; // Handle sink mute state if (muted_) { @@ -486,32 +485,28 @@ auto waybar::modules::Wireplumber::update() -> void { std::string formatted_source = fmt::format(fmt::runtime(format_source), fmt::arg("volume", source_vol)); - std::string markup = - fmt::format(fmt::runtime(format), fmt::arg("node_name", node_name_), fmt::arg("volume", vol), - fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source), - fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_), - fmt::arg("volume_linear", volume_), fmt::arg("volume_cubic", vol_cube), - fmt::arg("volume_db", vol_db), fmt::arg("source_volume_linear", source_volume_), - fmt::arg("source_volume_cubic", source_vol_cube), - fmt::arg("source_volume_db", source_vol_db)); - label_.set_markup(markup); + fmt::dynamic_format_arg_store store; + store.push_back(fmt::arg("node_name", node_name_)); + store.push_back(fmt::arg("volume", vol)); + store.push_back(fmt::arg("icon", getIcon(vol))); + store.push_back(fmt::arg("format_source", formatted_source)); + store.push_back(fmt::arg("source_volume", source_vol)); + store.push_back(fmt::arg("source_desc", source_name_)); + store.push_back(fmt::arg("volume_linear", volume_)); + store.push_back(fmt::arg("volume_cubic", vol_cube)); + store.push_back(fmt::arg("volume_db", vol_db)); + store.push_back(fmt::arg("source_volume_linear", source_volume_)); + store.push_back(fmt::arg("source_volume_cubic", source_vol_cube)); + store.push_back(fmt::arg("source_volume_db", source_vol_db)); + + setLabelMarkup(fmt::vformat(format, store)); if (tooltipEnabled()) { - if (tooltipFormat.empty() && config_["tooltip-format"].isString()) { - tooltipFormat = config_["tooltip-format"].asString(); - } - + auto tooltipFormat = resolveTooltipFormat(""); if (!tooltipFormat.empty()) { - label_.set_tooltip_markup(fmt::format( - fmt::runtime(tooltipFormat), fmt::arg("node_name", node_name_), fmt::arg("volume", vol), - fmt::arg("icon", getIcon(vol)), fmt::arg("format_source", formatted_source), - fmt::arg("source_volume", source_vol), fmt::arg("source_desc", source_name_), - fmt::arg("volume_linear", volume_), fmt::arg("volume_cubic", vol_cube), - fmt::arg("volume_db", vol_db), fmt::arg("source_volume_linear", source_volume_), - fmt::arg("source_volume_cubic", source_vol_cube), - fmt::arg("source_volume_db", source_vol_db))); + setTooltipMarkup(fmt::vformat(tooltipFormat, store)); } else { - label_.set_tooltip_markup(node_name_); + setTooltipMarkup(node_name_); } } From 8dbba448ce771ae3220c4ba5bf06958390d02120 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 4 Jul 2026 00:05:17 +0200 Subject: [PATCH 779/780] refactor(modules): migrate state modules to ALabel tooltip helper Migrate cpu, cpu_usage, cpu_frequency and memory to the generic updateLabelAndTooltip/ForState helper so label and tooltip rendering go through the dedup-aware setters and shared tooltip-format resolution. Add store-accepting overloads of the helper to ALabel for modules that build a dynamic fmt argument store (per-core cpu stats). cpu keeps its tooltip-format- selection via the state overload; the others keep their existing tooltip-format-only behavior. --- include/ALabel.hpp | 20 ++++++++++++ src/modules/cpu.cpp | 16 +--------- src/modules/cpu_frequency/common.cpp | 24 ++++---------- src/modules/cpu_usage/common.cpp | 11 +------ src/modules/memory/common.cpp | 47 +++++++++------------------- 5 files changed, 42 insertions(+), 76 deletions(-) diff --git a/include/ALabel.hpp b/include/ALabel.hpp index bb68768d..60850352 100644 --- a/include/ALabel.hpp +++ b/include/ALabel.hpp @@ -69,6 +69,26 @@ class ALabel : public AModule { updateLabelAndTooltipForState("", labelFormat, tooltipDefault, std::forward(args)...); } + // Overloads accepting a pre-built argument store, for modules that must + // assemble a dynamic set of format arguments (e.g. per-core CPU stats) that + // cannot be expressed through a fixed variadic call. + // A non-const reference is used so this overload is preferred over the + // variadic template above (which would otherwise bind the store as a single + // forwarded argument). + void updateLabelAndTooltipForState(const std::string& state, const std::string& labelFormat, + const std::string& tooltipDefault, + fmt::dynamic_format_arg_store& store) { + setLabelMarkup(fmt::vformat(labelFormat, store)); + if (tooltipEnabled()) { + setTooltipMarkup(fmt::vformat(resolveTooltipFormat(tooltipDefault, state), store)); + } + } + + void updateLabelAndTooltip(const std::string& labelFormat, const std::string& tooltipDefault, + fmt::dynamic_format_arg_store& store) { + updateLabelAndTooltipForState("", labelFormat, tooltipDefault, store); + } + bool handleToggle(GdkEventButton* const& e) override; void copyToClipboard(const std::string&); virtual std::string getState(uint8_t value, bool lesser = false); diff --git a/src/modules/cpu.cpp b/src/modules/cpu.cpp index 1a64b4a3..ef9a13b6 100644 --- a/src/modules/cpu.cpp +++ b/src/modules/cpu.cpp @@ -62,21 +62,7 @@ auto waybar::modules::Cpu::update() -> void { store.push_back(fmt::arg(arg_names.back().c_str(), core_icon)); } store.push_back(fmt::arg("icons", all_icons)); - label_.set_markup(fmt::vformat(format, store)); - - if (tooltipEnabled()) { - std::string tooltip_format; - if (!state.empty() && config_["tooltip-format-" + state].isString()) { - tooltip_format = config_["tooltip-format-" + state].asString(); - } else if (config_["tooltip-format"].isString()) { - tooltip_format = config_["tooltip-format"].asString(); - } - if (!tooltip_format.empty()) { - label_.set_tooltip_markup(fmt::vformat(tooltip_format, store)); - } else { - label_.set_tooltip_markup(tooltip); - } - } + updateLabelAndTooltipForState(state, format, tooltip, store); } // Call parent update diff --git a/src/modules/cpu_frequency/common.cpp b/src/modules/cpu_frequency/common.cpp index 05adc2b3..a4a9c7e9 100644 --- a/src/modules/cpu_frequency/common.cpp +++ b/src/modules/cpu_frequency/common.cpp @@ -32,24 +32,12 @@ auto waybar::modules::CpuFrequency::update() -> void { } else { event_box_.show(); auto icons = std::vector{state}; - fmt::dynamic_format_arg_store store; - store.push_back(fmt::arg("icon", getIcon(avg_frequency, icons))); - store.push_back(fmt::arg("max_frequency", max_frequency)); - store.push_back(fmt::arg("min_frequency", min_frequency)); - store.push_back(fmt::arg("avg_frequency", avg_frequency)); - label_.set_markup(fmt::vformat(format, store)); - - if (tooltipEnabled()) { - std::string tooltip; - if (config_["tooltip-format"].isString()) { - tooltip = config_["tooltip-format"].asString(); - label_.set_tooltip_markup(fmt::vformat(tooltip, store)); - } else { - tooltip = "Minimum frequency: {}\nAverage frequency: {}\nMaximum frequency: {}\n"; - label_.set_tooltip_markup( - fmt::format(fmt::runtime(tooltip), min_frequency, avg_frequency, max_frequency)); - } - } + updateLabelAndTooltip( + format, + "Minimum frequency: {min_frequency}\nAverage frequency: {avg_frequency}\nMaximum " + "frequency: {max_frequency}\n", + fmt::arg("icon", getIcon(avg_frequency, icons)), fmt::arg("max_frequency", max_frequency), + fmt::arg("min_frequency", min_frequency), fmt::arg("avg_frequency", avg_frequency)); } // Call parent update diff --git a/src/modules/cpu_usage/common.cpp b/src/modules/cpu_usage/common.cpp index 79ce6941..13e9cc4e 100644 --- a/src/modules/cpu_usage/common.cpp +++ b/src/modules/cpu_usage/common.cpp @@ -49,16 +49,7 @@ auto waybar::modules::CpuUsage::update() -> void { store.push_back(fmt::arg(arg_names.back().c_str(), core_icon)); } store.push_back(fmt::arg("icons", all_icons)); - label_.set_markup(fmt::vformat(format, store)); - - if (tooltipEnabled()) { - if (config_["tooltip-format"].isString()) { - tooltip = config_["tooltip-format"].asString(); - label_.set_tooltip_markup(fmt::vformat(tooltip, store)); - } else { - label_.set_tooltip_markup(tooltip); - } - } + updateLabelAndTooltip(format, tooltip, store); } // Call parent update diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp index d41b4fc2..626376e4 100644 --- a/src/modules/memory/common.cpp +++ b/src/modules/memory/common.cpp @@ -1,16 +1,14 @@ #include "modules/memory.hpp" namespace { -const std::unordered_map kUnits = { - {"kB", 1.000}, - {"kiB", 1.024}, - {"MB", 1.000 * 1000.0}, - {"MiB", 1.024 * 1024.0}, - {"GB", 1.000 * 1000.0 * 1000.0}, - {"GiB", 1.024 * 1024.0 * 1024.0}, - {"TB", 1.000 * 1000.0 * 1000.0 * 1000.0}, - {"TiB", 1.024 * 1024.0 * 1024.0 * 1024.0} -}; +const std::unordered_map kUnits = {{"kB", 1.000}, + {"kiB", 1.024}, + {"MB", 1.000 * 1000.0}, + {"MiB", 1.024 * 1024.0}, + {"GB", 1.000 * 1000.0 * 1000.0}, + {"GiB", 1.024 * 1024.0 * 1024.0}, + {"TB", 1.000 * 1000.0 * 1000.0 * 1000.0}, + {"TiB", 1.024 * 1024.0 * 1024.0 * 1024.0}}; } waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config) @@ -54,7 +52,7 @@ auto waybar::modules::Memory::update() -> void { if (memtotal > 0 && memfree >= 0) { int used_ram_percentage = 100 * (memtotal - memfree) / memtotal; int used_swap_percentage = 0; - if ((bool) swaptotal) { + if ((bool)swaptotal) { used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal; } @@ -77,31 +75,14 @@ auto waybar::modules::Memory::update() -> void { } else { event_box_.show(); auto icons = std::vector{state}; - label_.set_markup(fmt::format( - fmt::runtime(format), used_ram_percentage, - fmt::arg("icon", getIcon(used_ram_percentage, icons)), - fmt::arg("total", total_ram), fmt::arg("swapTotal", total_swap), - fmt::arg("percentage", used_ram_percentage), + updateLabelAndTooltip( + format, fmt::format("{:.{}f}{} used", used_ram, 1, unit_), used_ram_percentage, + fmt::arg("icon", getIcon(used_ram_percentage, icons)), fmt::arg("total", total_ram), + fmt::arg("swapTotal", total_swap), fmt::arg("percentage", used_ram_percentage), fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"), fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram), fmt::arg("swapUsed", used_swap), fmt::arg("avail", available_ram), - fmt::arg("swapAvail", available_swap))); - } - - if (tooltipEnabled()) { - if (config_["tooltip-format"].isString()) { - auto tooltip_format = config_["tooltip-format"].asString(); - label_.set_tooltip_markup(fmt::format( - fmt::runtime(tooltip_format), used_ram_percentage, - fmt::arg("total", total_ram), fmt::arg("swapTotal", total_swap), - fmt::arg("percentage", used_ram_percentage), - fmt::arg("swapState", swaptotal == 0 ? "Off" : "On"), - fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram), - fmt::arg("swapUsed", used_swap), fmt::arg("avail", available_ram), - fmt::arg("swapAvail", available_swap))); - } else { - label_.set_tooltip_markup(fmt::format("{:.{}f}{} used", used_ram, 1, unit_)); - } + fmt::arg("swapAvail", available_swap)); } } else { event_box_.hide(); From 003d531701cd20a43c7611ef6d90a03a5301e98f Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 4 Jul 2026 00:17:14 +0200 Subject: [PATCH 780/780] refactor(AModule): hoist format/tooltip resolution to the base module Move resolveTooltipFormat() (and add resolveFormat()) from ALabel down into AModule, and add a generic updateTooltip(Gtk::Widget&, ...) helper. This lets modules that are not ALabel-derived reuse the shared tooltip logic instead of re-implementing it. Migrate gamemode (the only non-ALabel module reading tooltip-format) to updateTooltip(box_, ...). ALabel inherits the resolvers. --- include/ALabel.hpp | 13 +------------ include/AModule.hpp | 39 +++++++++++++++++++++++++++++++++++++++ src/modules/gamemode.cpp | 5 +---- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/include/ALabel.hpp b/include/ALabel.hpp index 60850352..201d640c 100644 --- a/include/ALabel.hpp +++ b/include/ALabel.hpp @@ -34,18 +34,7 @@ class ALabel : public AModule { bool setLabelMarkup(const Glib::ustring& markup); bool setTooltipMarkup(const Glib::ustring& markup); - // Resolve the tooltip format string: prefers `tooltip-format-` (when a - // non-empty state is given), then `tooltip-format`, then `defaultFormat`. - std::string resolveTooltipFormat(const std::string& defaultFormat, - const std::string& state = "") const { - if (!state.empty() && config_["tooltip-format-" + state].isString()) { - return config_["tooltip-format-" + state].asString(); - } - if (config_["tooltip-format"].isString()) { - return config_["tooltip-format"].asString(); - } - return defaultFormat; - } + // resolveTooltipFormat() / resolveFormat() are inherited from AModule. // Combined label + tooltip helper. Builds a single fmt argument store from // `args`, renders `labelFormat` into the label and the resolved tooltip format diff --git a/include/AModule.hpp b/include/AModule.hpp index fedcc8fe..4aef1f8f 100644 --- a/include/AModule.hpp +++ b/include/AModule.hpp @@ -1,11 +1,16 @@ #pragma once +#include +#include #include #include #include #include #include +#include +#include + #include "IModule.hpp" namespace waybar { @@ -40,6 +45,40 @@ class AModule : public IModule { SCROLL_DIR getScrollDir(GdkEventScroll* e); bool tooltipEnabled() const; + // --- Generic format/tooltip resolution (config-only, usable by any module, + // ALabel-derived or not). Prefers `-`, then ``, then default. + std::string resolveFormat(const std::string& defaultFormat, const std::string& state = "") const { + if (!state.empty() && config_["format-" + state].isString()) { + return config_["format-" + state].asString(); + } + if (config_["format"].isString()) { + return config_["format"].asString(); + } + return defaultFormat; + } + std::string resolveTooltipFormat(const std::string& defaultFormat, + const std::string& state = "") const { + if (!state.empty() && config_["tooltip-format-" + state].isString()) { + return config_["tooltip-format-" + state].asString(); + } + if (config_["tooltip-format"].isString()) { + return config_["tooltip-format"].asString(); + } + return defaultFormat; + } + + // Generic tooltip for any widget: honors the `tooltip` toggle and + // `tooltip-format`, formats with the given args and applies it. Lets modules + // that are not ALabel-derived (e.g. gamemode) reuse the shared logic. + template + void updateTooltip(Gtk::Widget& widget, const std::string& defaultFormat, Args&&... args) { + if (!tooltipEnabled()) { + return; + } + widget.set_tooltip_markup( + fmt::format(fmt::runtime(resolveTooltipFormat(defaultFormat)), std::forward(args)...)); + } + std::vector pid_children_; const std::string name_; const Json::Value& config_; diff --git a/src/modules/gamemode.cpp b/src/modules/gamemode.cpp index 691a2844..40970f0c 100644 --- a/src/modules/gamemode.cpp +++ b/src/modules/gamemode.cpp @@ -210,10 +210,7 @@ auto Gamemode::update() -> void { lastStatus = status; // Tooltip - if (tooltip) { - std::string text = fmt::format(fmt::runtime(tooltip_format), fmt::arg("count", gameCount)); - box_.set_tooltip_markup(text); - } + updateTooltip(box_, tooltip_format, fmt::arg("count", gameCount)); // Label format std::string str = fmt::format(fmt::runtime(showAltText ? format_alt : format),