From 626c52cc79f4aa4798e026f6250e8c61943a95cc Mon Sep 17 00:00:00 2001 From: lilly-lizard Date: Sat, 13 Jul 2024 14:08:48 +1200 Subject: [PATCH 1/6] add "output" class to river tags module when monitor is focused --- include/modules/river/tags.hpp | 4 +++ src/modules/river/tags.cpp | 58 ++++++++++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/include/modules/river/tags.hpp b/include/modules/river/tags.hpp index fb3eefaa..5927ac59 100644 --- a/include/modules/river/tags.hpp +++ b/include/modules/river/tags.hpp @@ -20,6 +20,8 @@ class Tags : public waybar::AModule { void handle_focused_tags(uint32_t tags); void handle_view_tags(struct wl_array *tags); void handle_urgent_tags(uint32_t tags); + void handle_focused_output(struct wl_output *output); + void handle_unfocused_output(struct wl_output *output); void handle_primary_clicked(uint32_t tag); bool handle_button_press(GdkEventButton *event_button, uint32_t tag); @@ -30,9 +32,11 @@ class Tags : public waybar::AModule { private: const waybar::Bar &bar_; + struct wl_output *output_; // stores the output this module belongs to Gtk::Box box_; std::vector buttons_; struct zriver_output_status_v1 *output_status_; + struct zriver_seat_status_v1 *seat_status_; }; } /* namespace waybar::modules::river */ diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index 9e7cd5aa..cf0adbae 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -27,12 +27,39 @@ static void listen_urgent_tags(void *data, struct zriver_output_status_v1 *zrive static_cast(data)->handle_urgent_tags(tags); } +static void listen_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, + const char *title) { + // This module doesn't care +} + +static void listen_mode(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, + const char *mode) { + // This module doesn't care +} + +static void listen_focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, + struct wl_output *output) { + static_cast(data)->handle_focused_output(output); +} + +static void listen_unfocused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, + struct wl_output *output) { + static_cast(data)->handle_unfocused_output(output); +} + static const zriver_output_status_v1_listener output_status_listener_impl{ .focused_tags = listen_focused_tags, .view_tags = listen_view_tags, .urgent_tags = listen_urgent_tags, }; +static const zriver_seat_status_v1_listener seat_status_listener_impl{ + .focused_output = listen_focused_output, + .unfocused_output = listen_unfocused_output, + .focused_view = listen_focused_view, + .mode = listen_mode, +}; + static void listen_command_success(void *data, struct zriver_command_callback_v1 *zriver_command_callback_v1, const char *output) { @@ -88,12 +115,15 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con seat_{nullptr}, bar_(bar), box_{bar.orientation, 0}, - output_status_{nullptr} { + output_status_{nullptr}, + seat_status_{nullptr} { struct wl_display *display = Client::inst()->wl_display; struct wl_registry *registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener_impl, this); wl_display_roundtrip(display); + output_ = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + if (!status_manager_) { spdlog::error("river_status_manager_v1 not advertised"); return; @@ -150,10 +180,12 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con button.show(); } - struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); - output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output); + output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output_); zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this); + seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_); + zriver_seat_status_v1_add_listener(seat_status_, &seat_status_listener_impl, this); + zriver_status_manager_v1_destroy(status_manager_); } @@ -162,6 +194,10 @@ Tags::~Tags() { zriver_output_status_v1_destroy(output_status_); } + if (seat_status_) { + zriver_seat_status_v1_destroy(seat_status_); + } + if (control_) { zriver_control_v1_destroy(control_); } @@ -224,4 +260,20 @@ void Tags::handle_urgent_tags(uint32_t tags) { } } +void Tags::handle_focused_output(struct wl_output *output) { + if (output_ == output) { + for (size_t i = 0; i < buttons_.size(); ++i) { + buttons_[i].get_style_context()->add_class("output"); + } + } +} + +void Tags::handle_unfocused_output(struct wl_output *output) { + if (output_ == output) { + for (size_t i = 0; i < buttons_.size(); ++i) { + buttons_[i].get_style_context()->remove_class("output"); + } + } +} + } /* namespace waybar::modules::river */ From 79ce23a9a77f874d14fd14070a46cf9d2dba9499 Mon Sep 17 00:00:00 2001 From: lilly-lizard Date: Sat, 13 Jul 2024 14:12:54 +1200 Subject: [PATCH 2/6] docs for river tags output style --- man/waybar-river-tags.5.scd | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/man/waybar-river-tags.5.scd b/man/waybar-river-tags.5.scd index f0b2b84e..d629f73d 100644 --- a/man/waybar-river-tags.5.scd +++ b/man/waybar-river-tags.5.scd @@ -40,10 +40,13 @@ Addressed by *river/tags* - *#tags button.occupied* - *#tags button.focused* - *#tags button.urgent* +- *#tags button.output* -Note that occupied/focused/urgent status may overlap. That is, a tag may be +Note that occupied/focused/urgent/output status may overlap. That is, a tag may be both occupied and focused at the same time. +The *output* style is applied when the river output (e.g. monitor) of the current bar is focused. + # SEE ALSO waybar(5), river(1) From a96f1caa197f5f83e6e98c47669885bf6fbfe0c1 Mon Sep 17 00:00:00 2001 From: lilly-lizard Date: Sat, 11 Oct 2025 10:25:50 +1300 Subject: [PATCH 3/6] fixed bug after merge with master duplicating code in handle_show() --- src/modules/river/tags.cpp | 57 ++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index c50fcdeb..5712e9a0 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -27,15 +27,11 @@ static void listen_urgent_tags(void *data, struct zriver_output_status_v1 *zrive static_cast(data)->handle_urgent_tags(tags); } -static void listen_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, - const char *title) { - // This module doesn't care -} - -static void listen_mode(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, - const char *mode) { - // This module doesn't care -} +static const zriver_output_status_v1_listener output_status_listener_impl{ + .focused_tags = listen_focused_tags, + .view_tags = listen_view_tags, + .urgent_tags = listen_urgent_tags, +}; static void listen_focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, struct wl_output *output) { @@ -47,11 +43,15 @@ static void listen_unfocused_output(void *data, struct zriver_seat_status_v1 *zr static_cast(data)->handle_unfocused_output(output); } -static const zriver_output_status_v1_listener output_status_listener_impl{ - .focused_tags = listen_focused_tags, - .view_tags = listen_view_tags, - .urgent_tags = listen_urgent_tags, -}; +static void listen_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, + const char *title) { + // This module doesn't care +} + +static void listen_mode(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, + const char *mode) { + // This module doesn't care +} static const zriver_seat_status_v1_listener seat_status_listener_impl{ .focused_output = listen_focused_output, @@ -115,6 +115,7 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con seat_{nullptr}, bar_(bar), box_{bar.orientation, 0}, + output_{nullptr}, output_status_{nullptr}, seat_status_{nullptr} { struct wl_display *display = Client::inst()->wl_display; @@ -122,8 +123,6 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con wl_registry_add_listener(registry, ®istry_listener_impl, this); wl_display_roundtrip(display); - output_ = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); - if (!status_manager_) { spdlog::error("river_status_manager_v1 not advertised"); return; @@ -137,6 +136,10 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con spdlog::error("wl_seat not advertised"); } + output_ = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output_); + seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_); + box_.set_name("tags"); if (!id.empty()) { box_.get_style_context()->add_class(id); @@ -180,14 +183,6 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con button.show(); } - output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output_); - zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this); - - seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_); - zriver_seat_status_v1_add_listener(seat_status_, &seat_status_listener_impl, this); - - zriver_status_manager_v1_destroy(status_manager_); - box_.signal_show().connect(sigc::mem_fun(*this, &Tags::handle_show)); } @@ -196,10 +191,6 @@ Tags::~Tags() { zriver_output_status_v1_destroy(output_status_); } - if (seat_status_) { - zriver_seat_status_v1_destroy(seat_status_); - } - if (control_) { zriver_control_v1_destroy(control_); } @@ -210,12 +201,18 @@ Tags::~Tags() { } void Tags::handle_show() { - struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); - output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output); + spdlog::info("bruh1"); + + output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output_); zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this); + seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_); + zriver_seat_status_v1_add_listener(seat_status_, &seat_status_listener_impl, this); + zriver_status_manager_v1_destroy(status_manager_); status_manager_ = nullptr; + + spdlog::info("bruh2"); } void Tags::handle_primary_clicked(uint32_t tag) { From 1ab1d41e4dd5e2c2aced0354b5c19c4348fbe2e4 Mon Sep 17 00:00:00 2001 From: lilly-lizard Date: Tue, 14 Apr 2026 08:25:04 +1200 Subject: [PATCH 4/6] removed residual debug logging --- src/modules/river/tags.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index 5712e9a0..a9f14faf 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -201,8 +201,6 @@ Tags::~Tags() { } void Tags::handle_show() { - spdlog::info("bruh1"); - output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output_); zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this); @@ -211,8 +209,6 @@ void Tags::handle_show() { zriver_status_manager_v1_destroy(status_manager_); status_manager_ = nullptr; - - spdlog::info("bruh2"); } void Tags::handle_primary_clicked(uint32_t tag) { From 0ee4fd4aa25948483e1704d5c4a35bd8d9a32292 Mon Sep 17 00:00:00 2001 From: lilly-lizard Date: Tue, 14 Apr 2026 13:47:53 +1200 Subject: [PATCH 5/6] added output class to dwl/mangowm allowing tags to display differently when the output is focused (for multi-monitor setups) --- include/modules/dwl/tags.hpp | 2 ++ man/waybar-dwl-tags.5.scd | 3 ++- src/modules/dwl/tags.cpp | 16 ++++++++++++++-- src/modules/river/tags.cpp | 5 ++--- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/include/modules/dwl/tags.hpp b/include/modules/dwl/tags.hpp index 856a35dd..45a6f01f 100644 --- a/include/modules/dwl/tags.hpp +++ b/include/modules/dwl/tags.hpp @@ -21,6 +21,8 @@ class Tags : public waybar::AModule { void handle_primary_clicked(uint32_t tag); bool handle_button_press(GdkEventButton* event_button, uint32_t tag); + void handle_active_output(zdwl_ipc_output_v2* zdwl_output_v2, uint32_t active); + struct zdwl_ipc_manager_v2* status_manager_; struct wl_seat* seat_; diff --git a/man/waybar-dwl-tags.5.scd b/man/waybar-dwl-tags.5.scd index 39d15b7a..f775b742 100644 --- a/man/waybar-dwl-tags.5.scd +++ b/man/waybar-dwl-tags.5.scd @@ -46,8 +46,9 @@ Addressed by *dwl/tags* - *#tags button.empty* - *#tags button.focused* - *#tags button.urgent* +- *#tags button.output* -Note that occupied/focused/urgent status may overlap. That is, a tag may be +Note that occupied/focused/urgent/output status may overlap. That is, a tag may be both occupied and focused at the same time. # SEE ALSO diff --git a/src/modules/dwl/tags.cpp b/src/modules/dwl/tags.cpp index 243aaa92..52554707 100644 --- a/src/modules/dwl/tags.cpp +++ b/src/modules/dwl/tags.cpp @@ -26,7 +26,7 @@ static void toggle_visibility(void* data, zdwl_ipc_output_v2* zdwl_output_v2) { } static void active(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t active) { - // Intentionally empty + static_cast(data)->handle_active_output(zdwl_output_v2, active); } static void set_tag(void* data, zdwl_ipc_output_v2* zdwl_output_v2, uint32_t tag, uint32_t state, @@ -163,7 +163,7 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con i <<= 1; } - struct wl_output* output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); output_status_ = zdwl_ipc_manager_v2_get_output(status_manager_, output); zdwl_ipc_output_v2_add_listener(output_status_, &output_status_listener_impl, this); @@ -223,4 +223,16 @@ void Tags::handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint } } +void Tags::handle_active_output(zdwl_ipc_output_v2* zdwl_output_v2, uint32_t active) { + if (output_status_ == zdwl_output_v2) { + for (size_t i = 0; i < buttons_.size(); ++i) { + if (active == 0) { + buttons_[i].get_style_context()->remove_class("output"); + } else { + buttons_[i].get_style_context()->add_class("output"); + } + } + } +} + } /* namespace waybar::modules::dwl */ diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index f8faf60c..a864daea 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -114,8 +114,8 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con control_{nullptr}, seat_{nullptr}, bar_(bar), - box_{bar.orientation, 0}, output_{nullptr}, + box_{bar.orientation, 0}, output_status_{nullptr}, seat_status_{nullptr} { struct wl_display* display = Client::inst()->wl_display; @@ -204,8 +204,7 @@ Tags::~Tags() { void Tags::handle_show() { if (!status_manager_) return; - struct wl_output* output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); - output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output); + output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output_); zriver_output_status_v1_add_listener(output_status_, &output_status_listener_impl, this); seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_); From 6dc168c4f2635bc62013ea418f39210fc0fae989 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 3 Jul 2026 22:10:46 +0200 Subject: [PATCH 6/6] river/tags: fix memory leak of output_status_/seat_status_ The constructor created output_status_ and seat_status_ without adding any listener, then handle_show() recreated both (leaking the constructor-created objects) before destroying status_manager_. Additionally seat_status_ was never destroyed in ~Tags(). Create these objects lazily (with listeners) only in handle_show(), and destroy seat_status_ in the destructor. --- src/modules/river/tags.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/modules/river/tags.cpp b/src/modules/river/tags.cpp index a864daea..aac304ed 100644 --- a/src/modules/river/tags.cpp +++ b/src/modules/river/tags.cpp @@ -138,9 +138,10 @@ Tags::Tags(const std::string& id, const waybar::Bar& bar, const Json::Value& con return; } + // Store the output this module belongs to; the river_output_status and + // river_seat_status objects (and their listeners) are created lazily in + // handle_show() to avoid leaking objects without listeners here. output_ = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); - output_status_ = zriver_status_manager_v1_get_river_output_status(status_manager_, output_); - seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_); box_.set_name("tags"); if (!id.empty()) { @@ -193,6 +194,10 @@ Tags::~Tags() { zriver_output_status_v1_destroy(output_status_); } + if (seat_status_) { + zriver_seat_status_v1_destroy(seat_status_); + } + if (control_) { zriver_control_v1_destroy(control_); }