Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/modules/niri/workspaces.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <gtkmm/button.h>
#include <json/value.h>
#include <vector>

#include "AModule.hpp"
#include "bar.hpp"
Expand All @@ -18,13 +19,18 @@ class Workspaces : public AModule, public EventHandler {
private:
void onEvent(const Json::Value &ev) override;
void doUpdate();
void sortWorkspaces(std::vector<Json::Value> &workspaces) const;
Gtk::Button &addButton(const Json::Value &ws);
std::string getIcon(const std::string &value, const Json::Value &ws);

const Bar &bar_;
Gtk::Box box_;
// Map from niri workspace id to button.
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
17 changes: 17 additions & 0 deletions man/waybar-niri-workspaces.5.scd
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@ Addressed by *niri/workspaces*
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.

*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*: ++
typeof: string ++
default: {value} ++
Expand Down
84 changes: 82 additions & 2 deletions src/modules/niri/workspaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,34 @@
#include <gtkmm/label.h>
#include <spdlog/spdlog.h>

#include <algorithm>
#include <cctype>

namespace waybar::modules::niri {

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) {
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");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
Expand Down Expand Up @@ -41,6 +65,8 @@ void Workspaces::doUpdate() {
return ws["output"].asString() == bar_.output->name;
});

sortWorkspaces(my_workspaces);

// Remove buttons for removed workspaces.
for (auto it = buttons_.begin(); it != buttons_.end();) {
auto ws = std::find_if(my_workspaces.begin(), my_workspaces.end(),
Expand Down Expand Up @@ -123,8 +149,7 @@ void Workspaces::doUpdate() {
for (auto it = my_workspaces.cbegin(); it != my_workspaces.cend(); ++it) {
const auto &ws = *it;

auto pos = ws["idx"].asUInt() - 1;
if (alloutputs) pos = it - my_workspaces.cbegin();
const auto pos = static_cast<int>(std::distance(my_workspaces.cbegin(), it));

auto &button = buttons_[ws["id"].asUInt64()];
box_.reorder_child(button, pos);
Expand Down Expand Up @@ -193,4 +218,59 @@ std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws)
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