-
-
Couldn't load subscription status.
- Fork 916
feat(niri/workspaces): Niri per-workspace taskbar feature #4581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
Related #4164 |
Looked at it briefly. Seems like an over-complication. |
|
I am not a coder so sorry for any technical error, but I found two problems in
I haven't solved this problem yet. |
I have solved the problem. #4530, this change:
In brief, the implementation in After merge the above commit and my suggested changes in registration of IPC, Note I don't know if the null check is necessary since I am not familiar with C++ |
|
Glad you figured it out. I am using pretty recent niri build(instead of the release version). Maybe that is the reason I did not notice the issue.
As for this one, I think you are right to be thorough. But I check the source code of JsonCPP here. nullvalue will be returned as 0. So it should be fine~ |
|
But there is another problem now, can you try to track the memory usage of waybar while switching focus between windows many times? There seems to be a memory leak in the updateTaskbar function! |
|
I have solved it again, :P. Waybar/src/modules/privacy/privacy_item.cpp Lines 98 to 105 in 161367d
#4092, this PR solved the similar memory leak in privacy module. From gtk3 documentation:
It seems to be some gtk3 quirks. So the solution is simply delete the child... |
|
Thanks! Pushed with the fix. |
|
By the way the implementation in hyprland workspace taskbar which we use as the reference seems to have the same memory leak problem but nobody has come up with an issue yet, so I am not sure what under the earth the memory management of gtk3 does... |
|
Exactly. I actually referred to the code from hyprland here. |
|
What about other changes I suggested, like the overlooked events in the registration of IPC and the missing implementation in |
|
I have not observed any problems with "closing window" or "changing layout" with the current implementation... |
I think you do. But I am working on other things... You can add your changes here(or anywhere else)~ The format thing is pretty cool.
For me, they are working fine. I'm not sure about the reasons. |
A late thank for you and your creating this elegant and amazing PR 🎉 👍 , without your PR, I would wait for that over-complicated PR for whoever knows how long. I am trying to complete the configuration to match up with hyprland's workspace taskbar. I find it may be difficult for the coming people who want to work on this module to add some feature since the GTK seems to be cursed and some time-consuming groping is necessary for add even a small feature. When I am done, I will send a diff file here based on your PR, you can take any part as you like, after all the credits is yours. |
|
I was just trying to add things that I like. So I might be over-simplifying things sometimes. Glad I can help~😁 |
diff --git a/include/modules/niri/workspaces.hpp b/include/modules/niri/workspaces.hpp
index 328586c3..19414177 100644
--- a/include/modules/niri/workspaces.hpp
+++ b/include/modules/niri/workspaces.hpp
@@ -23,7 +23,7 @@ class Workspace {
private:
std::string getIcon(const std::string& value, const Json::Value& ws);
- void updateTaskbar(const std::vector<Json::Value>& windows_data);
+ void updateTaskbar(const std::vector<Json::Value>& windows_data, const uint64_t active_window_id);
IconLoader iconLoader_;
uint64_t id_;
diff --git a/man/waybar-niri-workspaces.5.scd b/man/waybar-niri-workspaces.5.scd
index 230181c8..f2dec6d6 100644
--- a/man/waybar-niri-workspaces.5.scd
+++ b/man/waybar-niri-workspaces.5.scd
@@ -67,7 +67,17 @@ Addressed by *niri/workspaces*
*separator*: ++
typeof: string ++
default: " " ++
- The separator to be used between windows in a workspace. ++
+ The separator to be used between windows in a workspace.
+
+ *format*: ++
+ typeof: string ++
+ default: "{icon}" ++
+ Format to use for each window in the workspace taskbar. Available placeholders are {icon}, {title} and {app_id}.
+
+ *tooltip-format*: ++
+ typeof: string ++
+ default: "{title}" ++
+ The format, how information in the tooltip should be displayed. Available placeholders are {title} and {app_id}. ++
This setting is ignored if *workspace-taskbar.enable* is set to true.
# FORMAT REPLACEMENTS
@@ -127,3 +137,5 @@ Additional to workspace name matching, the following *format-icons* can be set.
- *#workspaces .taskbar-window* (each window in the taskbar, only if 'workspace-taskbar.enable' is true)
- *#workspaces .taskbar-window.focused* (applied to the focused window)
- *#workspaces .taskbar-window.urgent* (applied to urgent windows)
+- *#workspaces .taskbar-window.floating* (applied to floating windows)
+- *#workspaces .taskbar-window.active* (applied to the active window within every workspace)
diff --git a/src/modules/niri/backend.cpp b/src/modules/niri/backend.cpp
index fa4dc287..528b7b5c 100644
--- a/src/modules/niri/backend.cpp
+++ b/src/modules/niri/backend.cpp
@@ -202,6 +202,18 @@ void IPC::parseIPC(const std::string &line) {
for (auto &win : windows_) {
win["is_focused"] = focused && win["id"].asUInt64() == id;
}
+ } else if (const auto &payload = ev["WindowLayoutsChanged"]) {
+ const auto &values = payload["changes"];
+ for (const auto &changed : values) {
+ const auto id = changed[0].asUInt64();
+ const auto &change = changed[1];
+ for (auto &win : windows_) {
+ if (win["id"].asUInt64() == id) {
+ win["layout"] = change;
+ break;
+ }
+ }
+ }
}
}
diff --git a/src/modules/niri/workspaces.cpp b/src/modules/niri/workspaces.cpp
index 6f1a0150..4e8f7ba0 100644
--- a/src/modules/niri/workspaces.cpp
+++ b/src/modules/niri/workspaces.cpp
@@ -71,23 +71,53 @@ std::string Workspace::getIcon(const std::string& value, const Json::Value& ws)
return value;
}
-void Workspace::updateTaskbar(const std::vector<Json::Value>& windows_data) {
+void Workspace::updateTaskbar(const std::vector<Json::Value>& windows_data,
+ const uint64_t active_window_id) {
if (!taskBarConfig_.get("enable", false).asBool()) return;
for (auto child : content_.get_children()) {
if (child != &label_) {
content_.remove(*child);
+ // despite the remove, still needs a delete to prevent memory leak. Speculating that this
+ // might work differently in GTK4.
delete child;
}
}
auto separator = taskBarConfig_.get("separator", " ").asString();
+ auto format = taskBarConfig_.get("format", "{icon}").asString();
+ bool taskbarWithIcon = false;
+ std::string taskbarFormatBefore, taskbarFormatAfter;
+
+ if (format != "") {
+ auto parts = split(format, "{icon}", 1);
+ taskbarFormatBefore = parts[0];
+ if (parts.size() > 1) {
+ taskbarWithIcon = true;
+ taskbarFormatAfter = parts[1];
+ }
+ } else {
+ taskbarWithIcon = true; // default to icon-only
+ }
+
+ auto format_tooltip = taskBarConfig_.get("tooltip-format", "{title}").asString();
+
auto sorted_windows_data = windows_data;
std::sort(sorted_windows_data.begin(), sorted_windows_data.end(),
[](const Json::Value& a, const Json::Value& b) {
auto layoutA = a["layout"];
auto layoutB = b["layout"];
+
+ // Handle null positions (floating windows)
+ if (layoutA["pos_in_scrolling_layout"].isNull()) {
+ return false; // Floating windows go to the end
+ }
+ if (layoutB["pos_in_scrolling_layout"].isNull()) {
+ return true; // Tiled windows before floating
+ }
+
+ // Both are tiled windows - sort by position
return layoutA["pos_in_scrolling_layout"][0].asInt() <
layoutB["pos_in_scrolling_layout"][0].asInt();
});
@@ -100,10 +130,18 @@ void Workspace::updateTaskbar(const std::vector<Json::Value>& windows_data) {
}
auto window_box = Gtk::make_managed<Gtk::Box>(Gtk::ORIENTATION_HORIZONTAL);
- window_box->set_tooltip_text(window["title"].asString());
+ if (!format_tooltip.empty()) {
+ auto txt =
+ fmt::format(fmt::runtime(format_tooltip), fmt::arg("title", window["title"].asString()),
+ fmt::arg("app_id", window["app_id"].asString()));
+ window_box->set_tooltip_text(txt);
+ }
window_box->get_style_context()->add_class("taskbar-window");
if (window["is_focused"].asBool()) window_box->get_style_context()->add_class("focused");
+ if (window["is_floating"].asBool()) window_box->get_style_context()->add_class("floating");
if (window["is_urgent"].asBool()) window_box->get_style_context()->add_class("urgent");
+ if (window["id"].asUInt64() == active_window_id)
+ window_box->get_style_context()->add_class("active");
auto event_box = Gtk::make_managed<Gtk::EventBox>();
event_box->add(*window_box);
if (!config_["disable-click"].asBool()) {
@@ -128,11 +166,31 @@ void Workspace::updateTaskbar(const std::vector<Json::Value>& windows_data) {
event_box->signal_button_press_event().connect(
sigc::bind(sigc::ptr_fun(func_ptr), window["id"].asUInt64()));
}
- auto window_icon = Gtk::make_managed<Gtk::Image>();
- iconLoader_.image_load_icon(
- *window_icon, IconLoader::get_app_info_from_app_id_list(window["app_id"].asString()),
- taskBarConfig_.get("icon-size", 16).asInt());
- window_box->pack_start(*window_icon, false, false);
+
+ auto text_before = fmt::format(fmt::runtime(taskbarFormatBefore),
+ fmt::arg("title", window["title"].asString()),
+ fmt::arg("app_id", window["app_id"].asString()));
+ if (!text_before.empty()) {
+ auto window_label_before = Gtk::make_managed<Gtk::Label>(text_before);
+ window_box->pack_start(*window_label_before, true, true);
+ }
+
+ if (taskbarWithIcon) {
+ auto window_icon = Gtk::make_managed<Gtk::Image>();
+ iconLoader_.image_load_icon(
+ *window_icon, IconLoader::get_app_info_from_app_id_list(window["app_id"].asString()),
+ taskBarConfig_.get("icon-size", 16).asInt());
+ window_box->pack_start(*window_icon, false, false);
+ }
+
+ auto text_after =
+ fmt::format(fmt::runtime(taskbarFormatAfter), fmt::arg("title", window["title"].asString()),
+ fmt::arg("app_id", window["app_id"].asString()));
+ if (!text_after.empty()) {
+ auto window_label_after = Gtk::make_managed<Gtk::Label>(text_after);
+ window_box->pack_start(*window_label_after, true, true);
+ }
+
content_.pack_start(*event_box, false, false);
}
}
@@ -183,7 +241,7 @@ void Workspace::update(const Json::Value& workspace_data,
else
label_.set_text(name);
- updateTaskbar(windows_data);
+ updateTaskbar(windows_data, workspace_data["active_window_id"].asUInt64());
if (config_["current-only"].asBool()) {
const auto* property = config_["all-outputs"].asBool() ? "is_focused" : "is_active";
@@ -212,6 +270,8 @@ Workspaces::Workspaces(const std::string& id, const Bar& bar, const Json::Value&
gIPC->registerForIPC("WorkspaceUrgencyChanged", this);
gIPC->registerForIPC("WindowFocusChanged", this);
gIPC->registerForIPC("WindowOpenedOrChanged", this);
+ gIPC->registerForIPC("WindowClosed", this);
+ gIPC->registerForIPC("WindowLayoutsChanged", this);
dp.emit();
}The change of I add two configuration fields, All other changes I have explained in the comments above. Let me explain my reason behind adding the CSS class What makes Niri different from other compositors, in my humble opinion, is that Imagine the situation when you move up and down between workspace, can you In fact, in niri, there are two independent variables to describe the position Warning I only have one laptop and have never tried multi-monitor PC, so I don't know In fact there is the third dimension, the floating and the scrolling layouts. So screenrecord.mp4"niri/workspaces": {
"format": "{index} ",
"workspace-taskbar": {
"enable": true,
"icon-size": 16,
"separator": "",
"format": "{icon} {title:10.10}",
"tooltip-format": "{title} | {app_id}"
}
},#workspaces {
color: transparent;
margin-right: 2px;
margin-left: 2px;
}
#workspaces button {
padding: 0;
margin-right: 2px;
margin-left: 2px;
min-width: 0;
color: @content_inactive;
opacity: 0.4;
}
#workspaces button.active {
color: @content_main;
opacity: 0.8;
}
#workspaces button.focused {
color: @content_main;
opacity: 1.0;
}
#workspaces button.urgent {
background: rgba(255, 200, 0, 0.35);
color: @warning_color;
opacity: 1.0;
}
#workspaces button:hover {
background: @bg_hover;
color: @content_main;
}
#workspaces .workspace-label {
font-size: 12px;
padding-left: 3px;
margin-right: 4px;
margin-top: 4px;
margin-bottom: 4px;
border-radius: 5px;
border-width: 1px;
border-style: solid;
border-color: @border_floating;
}
#workspaces .taskbar-window {
padding-top: 1px;
padding-bottom: 1px;
padding-left: 3px;
padding-right: 3px;
color: @content_main;
}
#workspaces .taskbar-window.floating {
border-left: 1px solid @border_floating;
}
#workspaces .taskbar-window.active {
border-bottom: 2px solid @content_main;
}
#workspaces .taskbar-window.focused {
background: @bg_active;
} |

Per #3745