Merge pull request #4861 from eonphi/master
feat(hyprland/workspaces): grouping icons
This commit is contained in:
@@ -64,6 +64,8 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
|
|
||||||
std::string getRewrite(const std::string& window_class, const std::string& window_title);
|
std::string getRewrite(const std::string& window_class, const std::string& window_title);
|
||||||
std::string& getWindowSeparator() { return m_formatWindowSeparator; }
|
std::string& getWindowSeparator() { return m_formatWindowSeparator; }
|
||||||
|
auto windowRewriteGroupThreshold() const -> int { return m_windowRewriteGroupThreshold; }
|
||||||
|
auto const& getWindowRewriteGroupFormat() const { return m_windowRewriteGroupFormat; }
|
||||||
bool isWorkspaceIgnored(std::string const& workspace_name);
|
bool isWorkspaceIgnored(std::string const& workspace_name);
|
||||||
|
|
||||||
bool windowRewriteConfigUsesTitle() const { return m_anyWindowRewriteRuleUsesTitle; }
|
bool windowRewriteConfigUsesTitle() const { return m_anyWindowRewriteRuleUsesTitle; }
|
||||||
@@ -177,6 +179,8 @@ class Workspaces : public AModule, public EventHandler {
|
|||||||
util::RegexCollection m_windowRewriteRules;
|
util::RegexCollection m_windowRewriteRules;
|
||||||
bool m_anyWindowRewriteRuleUsesTitle = false;
|
bool m_anyWindowRewriteRuleUsesTitle = false;
|
||||||
std::string m_formatWindowSeparator;
|
std::string m_formatWindowSeparator;
|
||||||
|
int m_windowRewriteGroupThreshold = 0;
|
||||||
|
std::string m_windowRewriteGroupFormat = "{icon}×{count}";
|
||||||
|
|
||||||
bool m_withIcon;
|
bool m_withIcon;
|
||||||
uint64_t m_monitorId;
|
uint64_t m_monitorId;
|
||||||
|
|||||||
@@ -41,6 +41,19 @@ This setting is ignored if *workspace-taskbar.enable* is set to true.
|
|||||||
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.
|
This setting is ignored if *workspace-taskbar.enable* is set to true.
|
||||||
|
|
||||||
|
*window-rewrite-group-threshold*: ++
|
||||||
|
typeof: int ++
|
||||||
|
default: 0 ++
|
||||||
|
When a workspace contains at least this many windows with the same rewrite result, they are collapsed into a single one using *window-rewrite-group-format*. ++
|
||||||
|
Set to 0 to disable grouping. ++
|
||||||
|
This setting is ignored if *workspace-taskbar.enable* is set to true.
|
||||||
|
|
||||||
|
*window-rewrite-group-format*: ++
|
||||||
|
typeof: string ++
|
||||||
|
default: "{icon}×{count}" ++
|
||||||
|
The format used to represent a group of collapsed windows. Available placeholders are {icon} (the icon being grouped) and {count} (how many windows share it). ++
|
||||||
|
This setting is ignored if *workspace-taskbar.enable* is set to true.
|
||||||
|
|
||||||
*workspace-taskbar*: ++
|
*workspace-taskbar*: ++
|
||||||
typeof: object ++
|
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.
|
Contains settings for the workspace taskbar, an alternative mode for the workspaces module which displays the window icons as images instead of text.
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
#include <glibmm/main.h>
|
||||||
#include <json/value.h>
|
#include <json/value.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <glibmm/main.h>
|
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -152,8 +153,7 @@ bool Workspace::pointerInsideButton() {
|
|||||||
const int buttonHeight = allocation.get_height();
|
const int buttonHeight = allocation.get_height();
|
||||||
|
|
||||||
return pointerRootX >= buttonRootX && pointerRootY >= buttonRootY &&
|
return pointerRootX >= buttonRootX && pointerRootY >= buttonRootY &&
|
||||||
pointerRootX < buttonRootX + buttonWidth &&
|
pointerRootX < buttonRootX + buttonWidth && pointerRootY < buttonRootY + buttonHeight;
|
||||||
pointerRootY < buttonRootY + buttonHeight;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Workspace::syncHoverClass() {
|
bool Workspace::syncHoverClass() {
|
||||||
@@ -174,9 +174,8 @@ void Workspace::startHoverCheck() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_hoverCheckConnection = Glib::signal_timeout().connect(
|
m_hoverCheckConnection =
|
||||||
sigc::mem_fun(*this, &Workspace::syncHoverClass),
|
Glib::signal_timeout().connect(sigc::mem_fun(*this, &Workspace::syncHoverClass), 50);
|
||||||
50);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Workspace::stopHoverCheck() {
|
void Workspace::stopHoverCheck() {
|
||||||
@@ -252,8 +251,7 @@ void Workspace::setActiveWindow(WindowAddress const& addr) {
|
|||||||
|
|
||||||
auto activeWindowPos = m_workspaceManager.activeWindowPosition();
|
auto activeWindowPos = m_workspaceManager.activeWindowPosition();
|
||||||
const bool has_active_window =
|
const bool has_active_window =
|
||||||
activeIdx.has_value() &&
|
activeIdx.has_value() && activeWindowPos != Workspaces::ActiveWindowPosition::NONE;
|
||||||
activeWindowPos != Workspaces::ActiveWindowPosition::NONE;
|
|
||||||
|
|
||||||
if (has_active_window) {
|
if (has_active_window) {
|
||||||
auto window = std::move(m_windowMap[*activeIdx]);
|
auto window = std::move(m_windowMap[*activeIdx]);
|
||||||
@@ -270,8 +268,7 @@ void Workspace::insertWindow(WindowCreationPayload create_window_payload) {
|
|||||||
if (!create_window_payload.isEmpty(m_workspaceManager)) {
|
if (!create_window_payload.isEmpty(m_workspaceManager)) {
|
||||||
auto repr = create_window_payload.repr(m_workspaceManager);
|
auto repr = create_window_payload.repr(m_workspaceManager);
|
||||||
|
|
||||||
const bool should_display =
|
const bool should_display = !repr.empty() || m_workspaceManager.enableTaskbar();
|
||||||
!repr.empty() || m_workspaceManager.enableTaskbar();
|
|
||||||
|
|
||||||
if (should_display) {
|
if (should_display) {
|
||||||
auto addr = create_window_payload.getAddress();
|
auto addr = create_window_payload.getAddress();
|
||||||
@@ -337,7 +334,7 @@ std::string& Workspace::selectIcon(std::map<std::string, std::string>& icons_map
|
|||||||
if (specialNamedIconIt != icons_map.end()) {
|
if (specialNamedIconIt != icons_map.end()) {
|
||||||
return specialNamedIconIt->second;
|
return specialNamedIconIt->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto specialIconIt = icons_map.find("special");
|
auto specialIconIt = icons_map.find("special");
|
||||||
if (specialIconIt != icons_map.end()) {
|
if (specialIconIt != icons_map.end()) {
|
||||||
return specialIconIt->second;
|
return specialIconIt->second;
|
||||||
@@ -378,7 +375,6 @@ std::string& Workspace::selectIcon(std::map<std::string, std::string>& icons_map
|
|||||||
return m_name;
|
return m_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Workspace::update(const std::string& workspace_icon) {
|
void Workspace::update(const std::string& workspace_icon) {
|
||||||
if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {
|
if (this->m_workspaceManager.persistentOnly() && !this->isPersistent()) {
|
||||||
m_button.hide();
|
m_button.hide();
|
||||||
@@ -436,16 +432,56 @@ void Workspace::update(const std::string& workspace_icon) {
|
|||||||
// need to compute this if enableTaskbar() is true
|
// need to compute this if enableTaskbar() is true
|
||||||
if (!m_workspaceManager.enableTaskbar()) {
|
if (!m_workspaceManager.enableTaskbar()) {
|
||||||
auto windowSeparator = m_workspaceManager.getWindowSeparator();
|
auto windowSeparator = m_workspaceManager.getWindowSeparator();
|
||||||
|
auto groupThreshold = m_workspaceManager.windowRewriteGroupThreshold();
|
||||||
|
|
||||||
bool isNotFirst = false;
|
auto end_it = m_workspaceManager.maxWindows() == 0
|
||||||
auto end_it = m_workspaceManager.maxWindows() == 0 ? m_windowMap.end() : m_windowMap.begin() + m_workspaceManager.maxWindows();
|
? m_windowMap.end()
|
||||||
|
: m_windowMap.begin() + m_workspaceManager.maxWindows();
|
||||||
|
|
||||||
for (auto it = m_windowMap.begin(); it != end_it; ++it) {
|
if (groupThreshold > 0) {
|
||||||
if (isNotFirst) {
|
// Build ordered counts of each unique icon (including singular ones when threshold set to 1)
|
||||||
windows.append(windowSeparator);
|
std::vector<std::pair<std::string, int>> iconCounts;
|
||||||
|
for (auto it = m_windowMap.begin(); it != end_it; ++it) {
|
||||||
|
const auto& window_repr = *it;
|
||||||
|
auto found = std::ranges::find_if(
|
||||||
|
iconCounts, [&](const auto& p) { return p.first == window_repr.repr_rewrite; });
|
||||||
|
if (found != iconCounts.end()) {
|
||||||
|
found->second++;
|
||||||
|
} else {
|
||||||
|
iconCounts.emplace_back(window_repr.repr_rewrite, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the group string
|
||||||
|
auto groupFormat = m_workspaceManager.getWindowRewriteGroupFormat();
|
||||||
|
bool isNotFirst = false;
|
||||||
|
for (const auto& [icon, count] : iconCounts) {
|
||||||
|
if (count >= groupThreshold) {
|
||||||
|
if (isNotFirst) windows.append(windowSeparator);
|
||||||
|
isNotFirst = true;
|
||||||
|
try {
|
||||||
|
windows.append(fmt::format(fmt::runtime(groupFormat), fmt::arg("icon", icon),
|
||||||
|
fmt::arg("count", count)));
|
||||||
|
} catch (const fmt::format_error& e) {
|
||||||
|
spdlog::warn("Formatting window-rewrite-group-format error: {}", e.what());
|
||||||
|
windows.append(icon);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
if (isNotFirst) windows.append(windowSeparator);
|
||||||
|
isNotFirst = true;
|
||||||
|
windows.append(icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not grouping icons
|
||||||
|
bool isNotFirst = false;
|
||||||
|
for (auto it = m_windowMap.begin(); it != end_it; ++it) {
|
||||||
|
if (isNotFirst) windows.append(windowSeparator);
|
||||||
|
isNotFirst = true;
|
||||||
|
windows.append(it->repr_rewrite);
|
||||||
}
|
}
|
||||||
isNotFirst = true;
|
|
||||||
windows.append(it->repr_rewrite);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,8 +541,7 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) {
|
|||||||
}
|
}
|
||||||
if (m_workspaceManager.onClickWindow() != "") {
|
if (m_workspaceManager.onClickWindow() != "") {
|
||||||
button->signal_button_press_event().connect(
|
button->signal_button_press_event().connect(
|
||||||
sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address),
|
sigc::bind(sigc::mem_fun(*this, &Workspace::handleClick), window_repr.address), false);
|
||||||
false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()),
|
auto text_before = fmt::format(fmt::runtime(m_workspaceManager.taskbarFormatBefore()),
|
||||||
@@ -536,13 +571,17 @@ void Workspace::updateTaskbar(const std::string& workspace_icon) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (m_workspaceManager.taskbarReverseDirection()) {
|
if (m_workspaceManager.taskbarReverseDirection()) {
|
||||||
auto rend_it = m_workspaceManager.maxWindows() == 0 ? m_windowMap.rend() : m_windowMap.rbegin() + m_workspaceManager.maxWindows();
|
auto rend_it = m_workspaceManager.maxWindows() == 0
|
||||||
|
? m_windowMap.rend()
|
||||||
|
: m_windowMap.rbegin() + m_workspaceManager.maxWindows();
|
||||||
|
|
||||||
for (auto it = m_windowMap.rbegin(); it != rend_it; ++it) {
|
for (auto it = m_windowMap.rbegin(); it != rend_it; ++it) {
|
||||||
processWindow(*it);
|
processWindow(*it);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
auto end_it = m_workspaceManager.maxWindows() == 0 ? m_windowMap.end() : m_windowMap.begin() + m_workspaceManager.maxWindows();
|
auto end_it = m_workspaceManager.maxWindows() == 0
|
||||||
|
? m_windowMap.end()
|
||||||
|
: m_windowMap.begin() + m_workspaceManager.maxWindows();
|
||||||
|
|
||||||
for (auto it = m_windowMap.begin(); it != end_it; ++it) {
|
for (auto it = m_windowMap.begin(); it != end_it; ++it) {
|
||||||
processWindow(*it);
|
processWindow(*it);
|
||||||
|
|||||||
@@ -670,6 +670,16 @@ auto Workspaces::parseConfig(const Json::Value& config) -> void {
|
|||||||
populateSortByConfig(config);
|
populateSortByConfig(config);
|
||||||
populateIgnoreWorkspacesConfig(config);
|
populateIgnoreWorkspacesConfig(config);
|
||||||
populateFormatWindowSeparatorConfig(config);
|
populateFormatWindowSeparatorConfig(config);
|
||||||
|
|
||||||
|
const auto& groupThreshold = config["window-rewrite-group-threshold"];
|
||||||
|
if (groupThreshold.isInt()) {
|
||||||
|
m_windowRewriteGroupThreshold = groupThreshold.asInt();
|
||||||
|
}
|
||||||
|
const auto& groupFormat = config["window-rewrite-group-format"];
|
||||||
|
if (groupFormat.isString()) {
|
||||||
|
m_windowRewriteGroupFormat = groupFormat.asString();
|
||||||
|
}
|
||||||
|
|
||||||
populateWindowRewriteConfig(config);
|
populateWindowRewriteConfig(config);
|
||||||
populateMaxWindowsConfig(config);
|
populateMaxWindowsConfig(config);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user