diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp index 3ad41c6e..c4e56b14 100644 --- a/include/modules/wlr/taskbar.hpp +++ b/include/modules/wlr/taskbar.hpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -69,6 +70,7 @@ class Task { Glib::RefPtr app_info_; bool button_visible_ = false; bool ignored_ = false; + bool squashed_ = false; bool with_icon_ = false; bool with_name_ = false; @@ -93,6 +95,7 @@ class Task { void set_minimize_hint(); void on_button_size_allocated(Gtk::Allocation& alloc); void hide_if_ignored(); + void hide_if_duplicate(); public: /* Getter functions */ @@ -164,6 +167,7 @@ class Taskbar : public waybar::AModule { IconLoader icon_loader_; std::unordered_set ignore_list_; + std::unordered_set squash_list_; std::map app_ids_replace_map_; struct zwlr_foreign_toplevel_manager_v1* manager_; @@ -202,7 +206,14 @@ class Taskbar : public waybar::AModule { const IconLoader& icon_loader() const; const std::unordered_set& ignore_list() const; + const std::unordered_set& squash_list() const; const std::map& app_ids_replace_map() const; + std::size_t task_id_count(std::string_view id) const; + std::size_t task_title_count(std::string_view title) const; + + auto tasks() { + return tasks_ | std::views::transform([](auto& task) -> Task& { return *task; }); + } private: void set_bar_css_class(const std::string&, bool); diff --git a/src/modules/wlr/taskbar.cpp b/src/modules/wlr/taskbar.cpp index e4935418..2cfe1736 100644 --- a/src/modules/wlr/taskbar.cpp +++ b/src/modules/wlr/taskbar.cpp @@ -234,6 +234,7 @@ void Task::handle_title(const char* title) { } title_ = title; hide_if_ignored(); + hide_if_duplicate(); if ((!with_icon_ && !with_name_) || app_info_) { return; @@ -258,6 +259,26 @@ void Task::set_minimize_hint() { minimize_hint.y, minimize_hint.w, minimize_hint.h); } +void Task::hide_if_duplicate() { + const auto& squash_list = tbar_->squash_list(); + bool contains_app = + squash_list.contains("*") || squash_list.contains(title_) || squash_list.contains(app_id_); + + // Squashes if the app is in the squash list and more than 1 instance is open + if (contains_app && (tbar_->task_id_count(app_id_) > 1 || tbar_->task_title_count(title_) > 1)) { + squashed_ = true; + if (button_visible_) { + auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + handle_output_leave(output); + } + } + + if (!squashed_ && !ignored_) { + auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); + handle_output_enter(output); + } +} + void Task::hide_if_ignored() { if (tbar_->ignore_list().count(app_id_) || tbar_->ignore_list().count(title_)) { ignored_ = true; @@ -265,7 +286,9 @@ void Task::hide_if_ignored() { auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); handle_output_leave(output); } - } else { + } + + if (!ignored_ && !squashed_) { bool is_was_ignored = ignored_; ignored_ = false; if (is_was_ignored) { @@ -283,6 +306,7 @@ void Task::handle_app_id(const char* app_id) { } app_id_ = app_id; hide_if_ignored(); + hide_if_duplicate(); auto ids_replace_map = tbar_->app_ids_replace_map(); if (ids_replace_map.count(app_id_)) { @@ -322,6 +346,10 @@ void Task::handle_output_enter(struct wl_output* output) { spdlog::debug("{} is ignored", repr()); return; } + if (squashed_) { + spdlog::debug("{} was squashed", repr()); + return; + } spdlog::debug("{} entered output {}", repr(), (void*)output); @@ -419,6 +447,28 @@ void Task::handle_closed() { tbar_->remove_button(button); button_visible_ = false; } + + const auto& squash_list = tbar_->squash_list(); + const bool in_squash_list = + squash_list.contains("*") || squash_list.contains(title_) || squash_list.contains(app_id_); + if (in_squash_list && !squashed_ && + (tbar_->task_id_count(app_id_) > 1 || tbar_->task_title_count(title_) > 1)) { + // Find next squashed task with same title or id (excluding ourselves) + auto tasks = tbar_->tasks(); + const auto it = std::ranges::find_if(tasks, [this](auto&& task) { + return &task != this && task.squashed_ && + (task.app_id() == app_id_ || task.title() == title_); + }); + + if (it != tasks.end() && !(*it).ignored_) { + Task& task = *it; + task.squashed_ = false; + tbar_->add_button(task.button); + task.button.show(); + task.button_visible_ = true; + } + } + tbar_->remove_task(id_); } @@ -672,6 +722,13 @@ Taskbar::Taskbar(const std::string& id, const waybar::Bar& bar, const Json::Valu } } + // Load squash-list + if (config_["squash-list"].isArray()) { + for (auto& app_name : config_["squash-list"]) { + squash_list_.emplace(app_name.asString()); + } + } + // Load app_id remappings if (config_["app_ids-mapping"].isObject()) { const Json::Value& mapping = config_["app_ids-mapping"]; @@ -1056,8 +1113,18 @@ const IconLoader& Taskbar::icon_loader() const { return icon_loader_; } const std::unordered_set& Taskbar::ignore_list() const { return ignore_list_; } +const std::unordered_set& Taskbar::squash_list() const { return squash_list_; } + const std::map& Taskbar::app_ids_replace_map() const { return app_ids_replace_map_; } +std::size_t Taskbar::task_id_count(std::string_view id) const { + return std::ranges::count_if(tasks_, [=](auto&& task) { return id == task->app_id(); }); +} + +std::size_t Taskbar::task_title_count(std::string_view title) const { + return std::ranges::count_if(tasks_, [=](auto&& task) { return title == task->title(); }); +} + } /* namespace waybar::modules::wlr */