From 83f16a2092a899c35c696029b2e1bddbdffe3361 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Tue, 12 Aug 2025 16:43:31 +0200 Subject: [PATCH 1/4] Document newer config options of workspace-taskbar Adds some configs that were only documented in the GitHub wiki to the manpage. --- man/waybar-hyprland-workspaces.5.scd | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index 430a5134..e280ac25 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -50,6 +50,11 @@ This setting is ignored if *workspace-taskbar.enable* is set to true. default: false ++ Enables the workspace taskbar mode. + *update-active-window*: ++ + typeof: bool ++ + default: false ++ + If true, the active/focused window will have an 'active' class. Could cause higher CPU usage due to more frequent redraws. + *format*: ++ typeof: string ++ default: {icon} ++ @@ -70,6 +75,19 @@ This setting is ignored if *workspace-taskbar.enable* is set to true. default: horizontal ++ Direction in which the workspace taskbar is displayed. + *ignore-list*: ++ + typeof: array ++ + default: [] ++ + Regex patterns to match against window class or window title. If a window's class OR title matches any of the patterns, it will not be shown. + + *on-click-window*: ++ + typeof: string ++ + default: "" ++ + Command to run when a window is clicked. Available placeholders are: ++ + - {address} Hyprland address of the clicked window. ++ + - {button} Pressed button number, see https://api.gtkd.org/gdk.c.types.GdkEventButton.button.html. ++ + See https://github.com/Alexays/Waybar/wiki/Module:-Hyprland#workspace-taskbars-example for a full example. + *show-special*: ++ typeof: bool ++ default: false ++ @@ -216,4 +234,6 @@ Additional to workspace name matching, the following *format-icons* can be set. - *#workspaces button.special* - *#workspaces button.urgent* - *#workspaces button.hosting-monitor* (gets applied if workspace-monitor == waybar-monitor) -- *#workspaces .taskbar-window* (each window in the taskbar) +- *#workspaces .workspace-label* +- *#workspaces .taskbar-window* (each window in the taskbar, only if 'workspace-taskbar.enable' is true) +- *#workspaces .taskbar-window.active* (applied to the focused window, only if 'workspace-taskbar.update-active-window' is true) From 691b7d427b7b6980442bc149646a955b29646089 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Mon, 18 Aug 2025 20:46:02 +0200 Subject: [PATCH 2/4] Implement "reverse-direction" --- include/modules/hyprland/workspaces.hpp | 2 ++ man/waybar-hyprland-workspaces.5.scd | 5 +++++ src/modules/hyprland/workspace.cpp | 15 +++++++++++++-- src/modules/hyprland/workspaces.cpp | 1 + 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 76b3462d..ef35639d 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -51,6 +51,7 @@ class Workspaces : public AModule, public EventHandler { auto taskbarFormatAfter() const -> std::string { return m_taskbarFormatAfter; } auto taskbarIconSize() const -> int { return m_taskbarIconSize; } auto taskbarOrientation() const -> Gtk::Orientation { return m_taskbarOrientation; } + auto taskbarReverseDirection() const -> bool { return m_taskbarReverseDirection; } auto onClickWindow() const -> std::string { return m_onClickWindow; } auto getIgnoredWindows() const -> std::vector { return m_ignoreWindows; } @@ -183,6 +184,7 @@ class Workspaces : public AModule, public EventHandler { std::string m_taskbarFormatAfter; int m_taskbarIconSize = 16; Gtk::Orientation m_taskbarOrientation = Gtk::ORIENTATION_HORIZONTAL; + bool m_taskbarReverseDirection = false; std::string m_onClickWindow; std::string m_currentActiveWindowAddress; diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index e280ac25..f2b3fb6b 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -55,6 +55,11 @@ This setting is ignored if *workspace-taskbar.enable* is set to true. default: false ++ If true, the active/focused window will have an 'active' class. Could cause higher CPU usage due to more frequent redraws. + *reverse-direction*: ++ + typeof: bool ++ + default: false ++ + If true, the taskbar windows will be added in reverse order (right to left if orientation is horizontal, bottom to top if vertical). + *format*: ++ typeof: string ++ default: {icon} ++ diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 2c8a7b09..febb70c2 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -259,9 +259,9 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { } bool isFirst = true; - for (const auto &window_repr : m_windowMap) { + auto processWindow = [&](const WindowRepr &window_repr) { if (shouldSkipWindow(window_repr)) { - continue; + return; // skip } if (isFirst) { isFirst = false; @@ -270,6 +270,7 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { m_content.pack_start(*windowSeparator, false, false); windowSeparator->show(); } + auto window_box = Gtk::make_managed(Gtk::ORIENTATION_HORIZONTAL); window_box->set_tooltip_text(window_repr.window_title); window_box->get_style_context()->add_class("taskbar-window"); @@ -307,6 +308,16 @@ void Workspace::updateTaskbar(const std::string &workspace_icon) { m_content.pack_start(*event_box, true, false); event_box->show_all(); + }; + + if (m_workspaceManager.taskbarReverseDirection()) { + for (auto it = m_windowMap.rbegin(); it != m_windowMap.rend(); ++it) { + processWindow(*it); + } + } else { + for (const auto &window_repr : m_windowMap) { + processWindow(window_repr); + } } auto formatAfter = m_workspaceManager.formatAfter(); diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 5d2903dc..abfa03d3 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -728,6 +728,7 @@ auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> vo populateBoolConfig(workspaceTaskbar, "enable", m_enableTaskbar); populateBoolConfig(workspaceTaskbar, "update-active-window", m_updateActiveWindow); + populateBoolConfig(workspaceTaskbar, "reverse-direction", m_taskbarReverseDirection); if (workspaceTaskbar["format"].isString()) { /* The user defined a format string, use it */ From 45d01ce6e5e0b5d30dfe339c084e16954ae64179 Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Mon, 18 Aug 2025 21:17:26 +0200 Subject: [PATCH 3/4] Implement "active-window-position" --- include/modules/hyprland/workspaces.hpp | 10 ++++++++++ man/waybar-hyprland-workspaces.5.scd | 5 +++++ src/modules/hyprland/workspace.cpp | 21 +++++++++++++++++++-- src/modules/hyprland/workspaces.cpp | 12 ++++++++++++ src/util/enum.cpp | 1 + 5 files changed, 47 insertions(+), 2 deletions(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index ef35639d..a5d94bbf 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -55,6 +55,9 @@ class Workspaces : public AModule, public EventHandler { auto onClickWindow() const -> std::string { return m_onClickWindow; } auto getIgnoredWindows() const -> std::vector { return m_ignoreWindows; } + enum class ActiveWindowPosition { NONE, FIRST, LAST }; + auto activeWindowPosition() const -> ActiveWindowPosition { return m_activeWindowPosition; } + std::string getRewrite(std::string window_class, std::string window_title); std::string& getWindowSeparator() { return m_formatWindowSeparator; } bool isWorkspaceIgnored(std::string const& workspace_name); @@ -185,6 +188,13 @@ class Workspaces : public AModule, public EventHandler { int m_taskbarIconSize = 16; Gtk::Orientation m_taskbarOrientation = Gtk::ORIENTATION_HORIZONTAL; bool m_taskbarReverseDirection = false; + util::EnumParser m_activeWindowEnumParser; + ActiveWindowPosition m_activeWindowPosition = ActiveWindowPosition::NONE; + std::map m_activeWindowPositionMap = { + {"NONE", ActiveWindowPosition::NONE}, + {"FIRST", ActiveWindowPosition::FIRST}, + {"LAST", ActiveWindowPosition::LAST}, + }; std::string m_onClickWindow; std::string m_currentActiveWindowAddress; diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index f2b3fb6b..1d04157b 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -60,6 +60,11 @@ This setting is ignored if *workspace-taskbar.enable* is set to true. default: false ++ If true, the taskbar windows will be added in reverse order (right to left if orientation is horizontal, bottom to top if vertical). + *active-window-position*: ++ + typeof: "none" | "first" | "last" ++ + default: "none" ++ + If set to "first", the active window will be moved at the beginning of the taskbar. If set to "last", it will be moved at the end. It will only work if *update-active-window* is set to true. + *format*: ++ typeof: string ++ default: {icon} ++ diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index febb70c2..850b0397 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -104,8 +104,25 @@ void Workspace::initializeWindowMap(const Json::Value &clients_data) { } void Workspace::setActiveWindow(WindowAddress const &addr) { - for (auto &window : m_windowMap) { - window.setActive(window.address == addr); + std::optional activeIdx; + for (size_t i = 0; i < m_windowMap.size(); ++i) { + auto &window = m_windowMap[i]; + bool isActive = (window.address == addr); + window.setActive(isActive); + if (isActive) { + activeIdx = i; + } + } + + auto activeWindowPos = m_workspaceManager.activeWindowPosition(); + if (activeIdx.has_value() && activeWindowPos != Workspaces::ActiveWindowPosition::NONE) { + auto window = std::move(m_windowMap[*activeIdx]); + m_windowMap.erase(m_windowMap.begin() + *activeIdx); + if (activeWindowPos == Workspaces::ActiveWindowPosition::FIRST) { + m_windowMap.insert(m_windowMap.begin(), std::move(window)); + } else if (activeWindowPos == Workspaces::ActiveWindowPosition::LAST) { + m_windowMap.emplace_back(std::move(window)); + } } } diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index abfa03d3..ca37084a 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -776,6 +776,18 @@ auto Workspaces::populateWorkspaceTaskbarConfig(const Json::Value &config) -> vo } } } + + if (workspaceTaskbar["active-window-position"].isString()) { + auto posStr = workspaceTaskbar["active-window-position"].asString(); + try { + m_activeWindowPosition = + m_activeWindowEnumParser.parseStringToEnum(posStr, m_activeWindowPositionMap); + } catch (const std::invalid_argument &e) { + spdlog::warn( + "Invalid string representation for active-window-position. Falling back to 'none'."); + m_activeWindowPosition = ActiveWindowPosition::NONE; + } + } } void Workspaces::registerOrphanWindow(WindowCreationPayload create_window_payload) { diff --git a/src/util/enum.cpp b/src/util/enum.cpp index 1e28d66e..6b5d5562 100644 --- a/src/util/enum.cpp +++ b/src/util/enum.cpp @@ -41,6 +41,7 @@ EnumType EnumParser::parseStringToEnum(const std::string& str, // Explicit instantiations for specific EnumType types you intend to use // Add explicit instantiations for all relevant EnumType types template struct EnumParser; +template struct EnumParser; template struct EnumParser; } // namespace waybar::util From 5f1db15c2e76b368ea693afb2b1458b8ac3b1cbf Mon Sep 17 00:00:00 2001 From: Pol Rivero <65060696+pol-rivero@users.noreply.github.com> Date: Wed, 1 Oct 2025 08:06:11 +0200 Subject: [PATCH 4/4] Workspaces containing only ignored windows should be considered empty This fixes #4479 --- include/modules/hyprland/workspace.hpp | 2 +- src/modules/hyprland/workspace.cpp | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/include/modules/hyprland/workspace.hpp b/include/modules/hyprland/workspace.hpp index a06e3816..1d80d331 100644 --- a/include/modules/hyprland/workspace.hpp +++ b/include/modules/hyprland/workspace.hpp @@ -42,7 +42,6 @@ class Workspace { bool isPersistentConfig() const { return m_isPersistentConfig; }; bool isPersistentRule() const { return m_isPersistentRule; }; bool isVisible() const { return m_isVisible; }; - bool isEmpty() const { return m_windows == 0; }; bool isUrgent() const { return m_isUrgent; }; bool handleClicked(GdkEventButton* bt) const; @@ -88,6 +87,7 @@ class Workspace { Gtk::Label m_labelBefore; Gtk::Label m_labelAfter; + bool isEmpty() const; void updateTaskbar(const std::string& workspace_icon); bool handleClick(const GdkEventButton* event_button, WindowAddress const& addr) const; bool shouldSkipWindow(const WindowRepr& window_repr) const; diff --git a/src/modules/hyprland/workspace.cpp b/src/modules/hyprland/workspace.cpp index 850b0397..ae66bc84 100644 --- a/src/modules/hyprland/workspace.cpp +++ b/src/modules/hyprland/workspace.cpp @@ -268,6 +268,17 @@ void Workspace::update(const std::string &workspace_icon) { } } +bool Workspace::isEmpty() const { + auto ignore_list = m_workspaceManager.getIgnoredWindows(); + if (ignore_list.empty()) { + return m_windows == 0; + } + // If there are windows but they are all ignored, consider the workspace empty + return std::all_of( + m_windowMap.begin(), m_windowMap.end(), + [this, &ignore_list](const auto &window_repr) { return shouldSkipWindow(window_repr); }); +} + void Workspace::updateTaskbar(const std::string &workspace_icon) { for (auto child : m_content.get_children()) { if (child != &m_labelBefore) {