feat: allow sorting niri workspaces

This commit is contained in:
Raphael Kimmig
2025-12-07 12:18:24 +01:00
parent 161367d961
commit 85a8fca0de
3 changed files with 105 additions and 2 deletions
+6
View File
@@ -2,6 +2,7 @@
#include <gtkmm/button.h> #include <gtkmm/button.h>
#include <json/value.h> #include <json/value.h>
#include <vector>
#include "AModule.hpp" #include "AModule.hpp"
#include "bar.hpp" #include "bar.hpp"
@@ -18,6 +19,7 @@ class Workspaces : public AModule, public EventHandler {
private: private:
void onEvent(const Json::Value &ev) override; void onEvent(const Json::Value &ev) override;
void doUpdate(); void doUpdate();
void sortWorkspaces(std::vector<Json::Value> &workspaces) const;
Gtk::Button &addButton(const Json::Value &ws); Gtk::Button &addButton(const Json::Value &ws);
std::string getIcon(const std::string &value, const Json::Value &ws); std::string getIcon(const std::string &value, const Json::Value &ws);
@@ -25,6 +27,10 @@ class Workspaces : public AModule, public EventHandler {
Gtk::Box box_; Gtk::Box box_;
// Map from niri workspace id to button. // Map from niri workspace id to button.
std::unordered_map<uint64_t, Gtk::Button> buttons_; std::unordered_map<uint64_t, Gtk::Button> buttons_;
bool sort_by_id_ = false;
bool sort_by_name_ = false;
bool sort_by_coordinates_ = false;
}; };
} // namespace waybar::modules::niri } // namespace waybar::modules::niri
+17
View File
@@ -17,6 +17,23 @@ Addressed by *niri/workspaces*
default: false ++ default: false ++
If set to false, workspaces will only be shown on the output they are on. If set to true all workspaces will be shown on every output. If set to false, workspaces will only be shown on the output they are on. If set to true all workspaces will be shown on every output.
*sort-by-name*: ++
typeof: bool ++
default: false ++
Sort workspaces by name (numeric sort when all names are numbers). Unnamed workspaces fall back to their index on the output.
*sort-by-coordinates*: ++
typeof: bool ++
default: false ++
Sort workspaces by output and index. If both *sort-by-name* and *sort-by-coordinates* are true, sorting by name is applied.
*sort-by-id*: ++
typeof: bool ++
default: false ++
Sort workspaces by id, taking precedence over other sort options.
If none of the sorting options are enabled, workspaces keep their output/index order.
*format*: ++ *format*: ++
typeof: string ++ typeof: string ++
default: {value} ++ default: {value} ++
+82 -2
View File
@@ -4,10 +4,34 @@
#include <gtkmm/label.h> #include <gtkmm/label.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <algorithm>
#include <cctype>
namespace waybar::modules::niri { namespace waybar::modules::niri {
Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
: AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) { : AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.orientation, 0) {
const auto config_sort_by_number = config_["sort-by-number"];
if (config_sort_by_number.isBool()) {
spdlog::warn("[niri/workspaces]: Prefer sort-by-id instead of sort-by-number");
sort_by_id_ = config_sort_by_number.asBool();
}
const auto config_sort_by_id = config_["sort-by-id"];
if (config_sort_by_id.isBool()) {
sort_by_id_ = config_sort_by_id.asBool();
}
const auto config_sort_by_name = config_["sort-by-name"];
if (config_sort_by_name.isBool()) {
sort_by_name_ = config_sort_by_name.asBool();
}
const auto config_sort_by_coordinates = config_["sort-by-coordinates"];
if (config_sort_by_coordinates.isBool()) {
sort_by_coordinates_ = config_sort_by_coordinates.asBool();
}
box_.set_name("workspaces"); box_.set_name("workspaces");
if (!id.empty()) { if (!id.empty()) {
box_.get_style_context()->add_class(id); box_.get_style_context()->add_class(id);
@@ -41,6 +65,8 @@ void Workspaces::doUpdate() {
return ws["output"].asString() == bar_.output->name; return ws["output"].asString() == bar_.output->name;
}); });
sortWorkspaces(my_workspaces);
// Remove buttons for removed workspaces. // Remove buttons for removed workspaces.
for (auto it = buttons_.begin(); it != buttons_.end();) { for (auto it = buttons_.begin(); it != buttons_.end();) {
auto ws = std::find_if(my_workspaces.begin(), my_workspaces.end(), auto ws = std::find_if(my_workspaces.begin(), my_workspaces.end(),
@@ -123,8 +149,7 @@ void Workspaces::doUpdate() {
for (auto it = my_workspaces.cbegin(); it != my_workspaces.cend(); ++it) { for (auto it = my_workspaces.cbegin(); it != my_workspaces.cend(); ++it) {
const auto &ws = *it; const auto &ws = *it;
auto pos = ws["idx"].asUInt() - 1; const auto pos = static_cast<int>(std::distance(my_workspaces.cbegin(), it));
if (alloutputs) pos = it - my_workspaces.cbegin();
auto &button = buttons_[ws["id"].asUInt64()]; auto &button = buttons_[ws["id"].asUInt64()];
box_.reorder_child(button, pos); box_.reorder_child(button, pos);
@@ -193,4 +218,59 @@ std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws)
return value; return value;
} }
void Workspaces::sortWorkspaces(std::vector<Json::Value> &workspaces) const {
auto get_name = [](const Json::Value &ws) -> std::string {
if (ws["name"]) return ws["name"].asString();
return std::to_string(ws["idx"].asUInt());
};
auto is_numeric = [](const std::string &value) {
return !value.empty() &&
std::all_of(value.begin(), value.end(), [](unsigned char c) { return std::isdigit(c); });
};
const bool names_are_numeric =
std::all_of(workspaces.begin(), workspaces.end(),
[&](const auto &ws) { return is_numeric(get_name(ws)); });
auto compare_numeric_strings = [](const std::string &a, const std::string &b) {
if (a.size() != b.size()) return a.size() < b.size();
return a < b;
};
std::sort(workspaces.begin(), workspaces.end(), [&](const auto &a, const auto &b) {
if (sort_by_id_) {
return a["id"].asUInt64() < b["id"].asUInt64();
}
if (sort_by_name_) {
const auto a_name = get_name(a);
const auto b_name = get_name(b);
if (a_name == b_name) return a["id"].asUInt64() < b["id"].asUInt64();
if (names_are_numeric) return compare_numeric_strings(a_name, b_name);
return a_name < b_name;
}
if (sort_by_coordinates_) {
const auto &a_output = a["output"].asString();
const auto &b_output = b["output"].asString();
if (a_output == b_output) {
const auto a_idx = a["idx"].asUInt();
const auto b_idx = b["idx"].asUInt();
if (a_idx == b_idx) return a["id"].asUInt64() < b["id"].asUInt64();
return a_idx < b_idx;
}
return a_output < b_output;
}
// Default to sorting by workspace index on each output.
const auto &a_output = a["output"].asString();
const auto &b_output = b["output"].asString();
const auto a_idx = a["idx"].asUInt();
const auto b_idx = b["idx"].asUInt();
if (a_output == b_output) return a_idx < b_idx;
return a_output < b_output;
});
}
} // namespace waybar::modules::niri } // namespace waybar::modules::niri