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 01/22] 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 02/22] 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 03/22] 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 04/22] 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 05/22] 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 06/22] 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 07/22] 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 08/22] 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 09/22] 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 10/22] 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 11/22] 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 12/22] 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 13/22] 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 14/22] 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 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 15/22] 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 16/22] 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 17/22] 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 18/22] 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 19/22] 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 20/22] 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 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 21/22] 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 22/22] 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) {