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/308] 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/308] 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 2de8a83d845c97bf6bb4848cf9d592bbb2439c62 Mon Sep 17 00:00:00 2001 From: Alessio Molinari Date: Sun, 4 Feb 2024 17:20:45 +0100 Subject: [PATCH 003/308] 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 004/308] 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 ea4b95fdd2434ea8cffcf24e9ff36e990815af15 Mon Sep 17 00:00:00 2001 From: Alessio Molinari Date: Mon, 4 Mar 2024 16:34:45 +0100 Subject: [PATCH 005/308] 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 f4608b3e312448b37a8f9d6351154026e67c680a Mon Sep 17 00:00:00 2001 From: schmop Date: Thu, 25 Jul 2024 01:40:49 +0200 Subject: [PATCH 006/308] 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 5c859bf520fe91d37127937e9a0af534b487d4ea Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen Date: Sat, 24 Aug 2024 00:06:24 +1000 Subject: [PATCH 007/308] 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 008/308] 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 009/308] 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 010/308] 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 011/308] 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 012/308] 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 013/308] 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 014/308] 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 015/308] 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 016/308] 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 017/308] 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 018/308] 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 019/308] 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 020/308] 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 021/308] 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 71a53eb79d78a98d6388678c29bf46baf32ae25c Mon Sep 17 00:00:00 2001 From: Rowan Leeder Date: Wed, 25 Sep 2024 03:16:14 +1000 Subject: [PATCH 022/308] 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 023/308] 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 ddb30164405b169cca3f726c1a0c2cb195a8c0f2 Mon Sep 17 00:00:00 2001 From: Ricardo Nogueira Date: Tue, 12 Nov 2024 17:10:05 -0300 Subject: [PATCH 024/308] 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 43af1b9ea00eceb32fec081445959c0e9b5dc94c Mon Sep 17 00:00:00 2001 From: twistedlogic Date: Wed, 11 Dec 2024 22:18:00 -0400 Subject: [PATCH 025/308] 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 026/308] 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 027/308] 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 028/308] 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 029/308] 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 030/308] 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 031/308] 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 032/308] 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 033/308] 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 034/308] 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 035/308] 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 036/308] 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 037/308] 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 038/308] 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 039/308] 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 040/308] 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 041/308] 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 042/308] 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 043/308] 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 044/308] 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 045/308] 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 046/308] 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 047/308] 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 048/308] 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 049/308] 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 050/308] 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 051/308] 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 052/308] 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 053/308] 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 054/308] 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 055/308] 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 056/308] 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 057/308] 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 058/308] 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 059/308] 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 060/308] 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 061/308] 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 062/308] 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 063/308] 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 cc94278c4ed83fa63c72d90eee8c16bf99358b7c Mon Sep 17 00:00:00 2001 From: Denis Kazimirov Date: Mon, 10 Feb 2025 18:32:57 +0500 Subject: [PATCH 064/308] 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 065/308] 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 066/308] 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 067/308] 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 068/308] 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 069/308] 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 070/308] 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 071/308] 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 072/308] 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 073/308] 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 074/308] 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 075/308] 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 076/308] 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 077/308] 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 078/308] 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 079/308] 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 080/308] 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 081/308] 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 082/308] 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 083/308] 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 084/308] 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 085/308] 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 086/308] 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 087/308] 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 088/308] 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 6fd859c0c4230cb45fe4503446ba548d09d64158 Mon Sep 17 00:00:00 2001 From: Angelo Dureghello Date: Sat, 22 Mar 2025 18:33:03 +0100 Subject: [PATCH 089/308] 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 090/308] 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 091/308] 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 092/308] 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 093/308] 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 094/308] 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 095/308] 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 096/308] .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 097/308] .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 098/308] 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 e92b0a86b5595a3c130f9c2fded724c71ac89d35 Mon Sep 17 00:00:00 2001 From: Clemens Horn Date: Mon, 7 Apr 2025 20:33:18 +0200 Subject: [PATCH 099/308] 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 100/308] 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 101/308] 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 102/308] 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 103/308] 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 104/308] 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 105/308] 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 106/308] 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 107/308] 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 108/308] 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 109/308] 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 110/308] 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 111/308] 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 112/308] 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 113/308] 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 114/308] 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 115/308] 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 116/308] 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 117/308] 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 118/308] 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 119/308] 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 120/308] 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 121/308] 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 122/308] 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 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 123/308] 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 124/308] 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 125/308] 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 126/308] 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 127/308] 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 128/308] 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 129/308] 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 130/308] 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 131/308] 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 132/308] 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 133/308] 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 134/308] 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 135/308] 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 0340760e12829239078c0d21fa6d227561e30ff2 Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Wed, 7 May 2025 16:57:54 +0800 Subject: [PATCH 136/308] 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 47e0f4252338cd51544a6229a495c24c35b442cf Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Tue, 13 May 2025 21:34:24 +0200 Subject: [PATCH 137/308] 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 138/308] 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 139/308] 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 140/308] 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 141/308] 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 142/308] 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 143/308] 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 144/308] 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 145/308] 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 146/308] 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 147/308] 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 148/308] 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 149/308] 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 150/308] 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 151/308] 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 152/308] 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 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 153/308] 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 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 154/308] 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 155/308] 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 156/308] 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 157/308] 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 158/308] 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 159/308] 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 160/308] 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 161/308] 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 162/308] 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 163/308] 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 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 164/308] 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 165/308] 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 166/308] 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 167/308] 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 168/308] 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 169/308] 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 170/308] 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 171/308] 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 172/308] 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 173/308] 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 174/308] 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 175/308] 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 176/308] 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 177/308] 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 178/308] 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 179/308] 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 180/308] 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 181/308] 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 182/308] 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 183/308] 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 184/308] 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 185/308] 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 186/308] 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 187/308] 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 188/308] 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 189/308] 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 190/308] 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 191/308] 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 192/308] 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 193/308] 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 194/308] 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 195/308] 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 196/308] 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 197/308] 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 198/308] 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 199/308] 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 200/308] 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 201/308] 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 202/308] 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 203/308] 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 204/308] 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 205/308] 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 206/308] 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 207/308] 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 208/308] 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 209/308] 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 210/308] 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 211/308] 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 212/308] 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 213/308] 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 214/308] 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 215/308] 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 216/308] 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 217/308] 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 218/308] 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 219/308] 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 220/308] 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 221/308] 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 222/308] 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 223/308] 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 224/308] 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 225/308] 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 226/308] 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 227/308] 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 228/308] 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 229/308] 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 230/308] 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 231/308] 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 232/308] 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 233/308] 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 234/308] 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 235/308] 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 236/308] 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 237/308] 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 238/308] 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 239/308] 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 240/308] 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 241/308] 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 242/308] 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 243/308] 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 244/308] 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 245/308] 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 246/308] 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 1e0037a873e5809ad91fea7f1881f415bca1ca56 Mon Sep 17 00:00:00 2001 From: Arkoniak Date: Thu, 31 Jul 2025 21:29:20 +0300 Subject: [PATCH 247/308] 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 248/308] 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 249/308] 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 250/308] 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 251/308] 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 64b64d03160a4552fcc4555802924c154d744302 Mon Sep 17 00:00:00 2001 From: Skylar Abruzese Date: Sat, 9 Aug 2025 18:33:35 -0400 Subject: [PATCH 252/308] Moved workspace id failing to parse from an error to part of the trace. With named persistent workspaces it is expected behavior that they have no id since their workspace may not have been created by hyprland yet. --- src/modules/hyprland/workspaces.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 5d2903dc..32f88c02 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -1129,9 +1129,9 @@ std::optional Workspaces::parseWorkspaceId(std::string const &workspaceIdSt try { return workspaceIdStr == "special" ? -99 : std::stoi(workspaceIdStr); } catch (std::exception const &e) { - spdlog::error("Failed to parse workspace ID: {}", e.what()); + spdlog::debug("Workspace \"{}\" is not bound to an id: {}", workspaceIdStr, e.what()); return std::nullopt; } } -} // namespace waybar::modules::hyprland \ No newline at end of file +} // namespace waybar::modules::hyprland From 83f16a2092a899c35c696029b2e1bddbdffe3361 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Tue, 12 Aug 2025 16:43:31 +0200 Subject: [PATCH 253/308] 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 254/308] 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 255/308] 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 256/308] 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 257/308] 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 258/308] 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 259/308] 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 260/308] 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 261/308] 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 262/308] refactor(makefile): allow build on test command meson will skip building, if not needed, but we want to make sure we're actually testing what we expect. Signed-off-by: Austin Horstman --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c362ee69..12af8e0e 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ debug-run: build-debug ./build/waybar --log-level debug test: - meson test -C build --no-rebuild --verbose --suite waybar + meson test -C build --verbose --suite waybar .PHONY: test test-detailed: From e8755b1a7f5c28180877e0a14793466d0654a8d2 Mon Sep 17 00:00:00 2001 From: Sebastian Schwarz Date: Sat, 16 Aug 2025 14:14:59 +0200 Subject: [PATCH 263/308] 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 264/308] 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 265/308] 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 266/308] 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 267/308] 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 268/308] 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 269/308] 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 270/308] 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 5a29473080cb63b372204d94e70cc9bd28f94fe4 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Fri, 22 Aug 2025 18:44:20 -0400 Subject: [PATCH 271/308] 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 272/308] 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 273/308] 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 274/308] 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 275/308] 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 276/308] 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 277/308] 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 278/308] 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 279/308] 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 280/308] Fix: style: show active workspace on hyprland The `hyprland/workspaces` module has the CSS class `active` rather than `focused` for the current workspace. Update the default CSS selector for the current workspace to match both `button.focused` and `button.active`. --- resources/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/style.css b/resources/style.css index 7e830285..bcfcb5c8 100644 --- a/resources/style.css +++ b/resources/style.css @@ -63,7 +63,7 @@ button:hover { background: rgba(0, 0, 0, 0.2); } -#workspaces button.focused { +#workspaces button.focused, #workspaces button.active { background-color: #64727D; box-shadow: inset 0 -3px #ffffff; } From e87ece4462f29ec4601c57c48518d037253aa66f Mon Sep 17 00:00:00 2001 From: winkelnp <68015877+winkelnp@users.noreply.github.com> Date: Mon, 15 Sep 2025 22:03:23 +0200 Subject: [PATCH 281/308] 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 282/308] 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 283/308] fix: close sni menu on item destruction --- include/modules/sni/item.hpp | 2 +- src/modules/sni/item.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/modules/sni/item.hpp b/include/modules/sni/item.hpp index c5e86d37..503ab637 100644 --- a/include/modules/sni/item.hpp +++ b/include/modules/sni/item.hpp @@ -26,7 +26,7 @@ struct ToolTip { class Item : public sigc::trackable { public: Item(const std::string&, const std::string&, const Json::Value&, const Bar&); - ~Item() = default; + ~Item(); std::string bus_name; std::string object_path; diff --git a/src/modules/sni/item.cpp b/src/modules/sni/item.cpp index 4e80eba7..30a6b834 100644 --- a/src/modules/sni/item.cpp +++ b/src/modules/sni/item.cpp @@ -74,6 +74,13 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf cancellable_, interface); } +Item::~Item() { + if (this->gtk_menu != nullptr) { + this->gtk_menu->popdown(); + this->gtk_menu->detach(); + } +} + bool Item::handleMouseEnter(GdkEventCrossing* const& e) { event_box.set_state_flags(Gtk::StateFlags::STATE_FLAG_PRELIGHT); return false; From 4a5358e8c58dd608c892514da5f7b51a63789411 Mon Sep 17 00:00:00 2001 From: Cole Leavitt Date: Wed, 17 Sep 2025 21:17:26 -0700 Subject: [PATCH 284/308] 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 285/308] 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 286/308] 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 287/308] 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 288/308] 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 289/308] 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 290/308] 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 291/308] 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 292/308] 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 293/308] 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 294/308] 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 295/308] fix: Correct the error in converting network speed units --- src/modules/network.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/modules/network.cpp b/src/modules/network.cpp index b0e01364..d0b7970c 100644 --- a/src/modules/network.cpp +++ b/src/modules/network.cpp @@ -333,18 +333,23 @@ auto waybar::modules::Network::update() -> void { fmt::arg("ipaddr", final_ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("cidr", cidr_), fmt::arg("cidr6", cidr6_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("icon", getIcon(signal_strength_, state_)), - fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), - fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), - fmt::arg("bandwidthTotalBits", - pow_format((bandwidth_up + bandwidth_down) * 8ull / interval_.count(), "b/s")), - fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")), - fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s")), + fmt::arg("bandwidthDownBits", + pow_format(bandwidth_down * 8ull / (interval_.count() / 1000.0), "b/s")), + fmt::arg("bandwidthUpBits", + pow_format(bandwidth_up * 8ull / (interval_.count() / 1000.0), "b/s")), + fmt::arg( + "bandwidthTotalBits", + pow_format((bandwidth_up + bandwidth_down) * 8ull / (interval_.count() / 1000.0), "b/s")), + fmt::arg("bandwidthDownOctets", + pow_format(bandwidth_down / (interval_.count() / 1000.0), "o/s")), + fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / (interval_.count() / 1000.0), "o/s")), fmt::arg("bandwidthTotalOctets", - pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "o/s")), - fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / interval_.count(), "B/s")), - fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")), + pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "o/s")), + fmt::arg("bandwidthDownBytes", + pow_format(bandwidth_down / (interval_.count() / 1000.0), "B/s")), + fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / (interval_.count() / 1000.0), "B/s")), fmt::arg("bandwidthTotalBytes", - pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s"))); + pow_format((bandwidth_up + bandwidth_down) / (interval_.count() / 1000.0), "B/s"))); if (text.compare(label_.get_label()) != 0) { label_.set_markup(text); if (text.empty()) { From d8e23924106c790c11a6b8d64367fa759a20c0c1 Mon Sep 17 00:00:00 2001 From: Pierre Lairez Date: Fri, 3 Oct 2025 11:24:18 +0200 Subject: [PATCH 296/308] 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 297/308] 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 298/308] 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 bef35e48fe8b38aa1cfb67bc25bf7ae42c2ffd4b Mon Sep 17 00:00:00 2001 From: Aaron Andersen Date: Thu, 9 Oct 2025 20:52:51 -0400 Subject: [PATCH 299/308] 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 7add8b2726d239c825be2eb0106e403a74821e89 Mon Sep 17 00:00:00 2001 From: Kosa Matyas Date: Fri, 10 Oct 2025 19:40:43 +0300 Subject: [PATCH 300/308] 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 966da11f3b0ee7157e6410d54c2f670b2209bae6 Mon Sep 17 00:00:00 2001 From: Viktar Lukashonak Date: Sun, 12 Oct 2025 14:53:49 +0300 Subject: [PATCH 301/308] 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 6021261383a209ddc3220652ea2caf95eca4bbee Mon Sep 17 00:00:00 2001 From: carljustineoyales Date: Wed, 15 Oct 2025 20:45:42 +0800 Subject: [PATCH 302/308] 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 303/308] 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 4beb7ddac7f3eaaaeb1364496c201f9304553637 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Sun, 19 Oct 2025 17:35:07 -0500 Subject: [PATCH 304/308] 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 305/308] 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 306/308] 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 307/308] 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 308/308] 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;