Skip to content
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

feat: hyprland workspaces add sort-by #2486

Merged
merged 10 commits into from
Sep 11, 2023
11 changes: 11 additions & 0 deletions include/modules/hyprland/workspaces.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "AModule.hpp"
#include "bar.hpp"
#include "modules/hyprland/backend.hpp"
#include "util/enum.hpp"

namespace waybar::modules::hyprland {

Expand Down Expand Up @@ -80,11 +81,21 @@ class Workspaces : public AModule, public EventHandler {
void create_workspace(Json::Value& value);
void remove_workspace(std::string name);
void set_urgent_workspace(std::string windowaddress);
void parse_config(const Json::Value& config);
void register_ipc();

bool all_outputs_ = false;
bool show_special_ = false;
bool active_only_ = false;

enum class SORT_METHOD { ID, NAME, NUMBER, DEFAULT };
util::EnumParser<SORT_METHOD> enum_parser_;
SORT_METHOD sort_by_ = SORT_METHOD::DEFAULT;
std::map<std::string, SORT_METHOD> sort_map_ = {{"ID", SORT_METHOD::ID},
{"NAME", SORT_METHOD::NAME},
{"NUMBER", SORT_METHOD::NUMBER},
{"DEFAULT", SORT_METHOD::DEFAULT}};

void fill_persistent_workspaces();
void create_persistent_workspaces();
std::vector<std::string> persistent_workspaces_to_create_;
Expand Down
46 changes: 46 additions & 0 deletions include/util/enum.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#pragma once

#include <cctype>
#include <iostream>
#include <map>
#include <stdexcept>
#include <string>

namespace waybar::util {

template <typename EnumType>
struct EnumParser {
khaneliman marked this conversation as resolved.
Show resolved Hide resolved
EnumParser() {}

// Helper function to capitalize a string
khaneliman marked this conversation as resolved.
Show resolved Hide resolved
std::string capitalizeString(const std::string& str) {
std::string result = str;
std::transform(result.begin(), result.end(), result.begin(),
[](unsigned char c) { return std::toupper(c); });
return result;
}

EnumType parseStringToEnum(const std::string& str,
const std::map<std::string, EnumType>& enumMap) {
// Convert the input string to uppercase
std::string uppercaseStr = capitalizeString(str);

// Capitalize the map keys before searching
std::map<std::string, EnumType> capitalizedEnumMap;
std::transform(enumMap.begin(), enumMap.end(),
std::inserter(capitalizedEnumMap, capitalizedEnumMap.end()),
[this](const auto& pair) {
return std::make_pair(capitalizeString(pair.first), pair.second);
});

// Return enum match of string
auto it = enumMap.find(uppercaseStr);
if (it != enumMap.end()) return it->second;

// Throw error if it doesnt return
throw std::invalid_argument("Invalid string representation for enum");
}

~EnumParser() = default;
};
} // namespace waybar::util
8 changes: 8 additions & 0 deletions man/waybar-hyprland-workspaces.5.scd
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ Addressed by *hyprland/workspaces*
default: false ++
If set to true, only the active workspace will be shown.

*sort-by*: ++
typeof: string ++
default: "default" ++
If set to number, workspaces will sort by number.
If set to name, workspaces will sort by name.
If set to id, workspaces will sort by id.
If none of those, workspaces will sort with default behavior.

# FORMAT REPLACEMENTS

*{id}*: id of workspace assigned by compositor
Expand Down
17 changes: 14 additions & 3 deletions man/waybar.5.scd.in
Original file line number Diff line number Diff line change
Expand Up @@ -273,28 +273,39 @@ Valid options for the (optional) "orientation" property are: "horizontal", "vert
- *waybar-cpu(5)*
- *waybar-custom(5)*
- *waybar-disk(5)*
- *waybar-dwl-tags(5)*
- *waybar-gamemode(5)*
- *waybar-hyprland-language(5)*
- *waybar-hyprland-submap(5)*
- *waybar-hyprland-window(5)*
- *waybar-hyprland-workspaces(5)*
- *waybar-idle-inhibitor(5)*
- *waybar-image(5)*
- *waybar-inhibitor(5)*
- *waybar-jack(5)*
- *waybar-keyboard-state(5)*
- *waybar-memory(5)*
- *waybar-mpd(5)*
- *waybar-mpris(5)*
- *waybar-network(5)*
- *waybar-pulseaudio(5)*
- *waybar-river-layout(5)*
- *waybar-river-mode(5)*
- *waybar-river-tags(5)*
- *waybar-river-window(5)*
- *waybar-river-layout(5)*
- *waybar-sndio(5)*
- *waybar-states(5)*
- *waybar-sway-language(5)*
- *waybar-sway-mode(5)*
- *waybar-sway-scratchpad(5)*
- *waybar-sway-window(5)*
- *waybar-sway-workspaces(5)*
- *waybar-temperature(5)*
- *waybar-tray(5)*
- *waybar-upower(5)*
- *waybar-wireplumber(5)*
- *waybar-wlr-taskbar(5)*
- *waybar-wlr-workspaces(5)*
- *waybar-temperature(5)*
- *waybar-tray(5)*

# SEE ALSO

Expand Down
116 changes: 82 additions & 34 deletions src/modules/hyprland/workspaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
: AModule(config, "workspaces", id, false, false),
bar_(bar),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) {
parse_config(config);
khaneliman marked this conversation as resolved.
Show resolved Hide resolved

box_.set_name("workspaces");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
event_box_.add(box_);

register_ipc();

init();
}

auto Workspaces::parse_config(const Json::Value &config) -> void {
Json::Value config_format = config["format"];

format_ = config_format.isString() ? config_format.asString() : "{name}";
Expand Down Expand Up @@ -43,18 +57,26 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
active_only_ = config_active_only.asBool();
}

box_.set_name("workspaces");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
auto config_sort_by = config_["sort-by"];
if (config_sort_by.isString()) {
auto sort_by_str = config_sort_by.asString();
try {
sort_by_ = enum_parser_.parseStringToEnum(sort_by_str, sort_map_);
} catch (const std::invalid_argument &e) {
// Handle the case where the string is not a valid enum representation.
sort_by_ = SORT_METHOD::DEFAULT;
g_warning("Invalid string representation for sort-by. Falling back to default sort method.");
}
}
event_box_.add(box_);
}

auto Workspaces::register_ipc() -> void {
modulesReady = true;

if (!gIPC) {
gIPC = std::make_unique<IPC>();
}

init();

gIPC->registerForIPC("workspace", this);
gIPC->registerForIPC("createworkspace", this);
gIPC->registerForIPC("destroyworkspace", this);
Expand Down Expand Up @@ -406,36 +428,62 @@ void Workspace::update(const std::string &format, const std::string &icon) {

void Workspaces::sort_workspaces() {
std::sort(workspaces_.begin(), workspaces_.end(),
[](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) {
// normal -> named persistent -> named -> special -> named special

// both normal (includes numbered persistent) => sort by ID
if (a->id() > 0 && b->id() > 0) {
return a->id() < b->id();
}

// one normal, one special => normal first
if ((a->is_special()) ^ (b->is_special())) {
return b->is_special();
}

// only one normal, one named
if ((a->id() > 0) ^ (b->id() > 0)) {
return a->id() > 0;
}

// both special
if (a->is_special() && b->is_special()) {
// if one is -99 => put it last
if (a->id() == -99 || b->id() == -99) {
return b->id() == -99;
}
// both are 0 (not yet named persistents) / both are named specials (-98 <= ID <=-1)
return a->name() < b->name();
[&](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) {
// Helper comparisons
auto is_id_less = a->id() < b->id();
auto is_name_less = a->name() < b->name();
auto is_number_less = std::stoi(a->name()) < std::stoi(b->name());

switch (sort_by_) {
case SORT_METHOD::ID:
return is_id_less;
case SORT_METHOD::NAME:
return is_name_less;
case SORT_METHOD::NUMBER:
try {
return is_number_less;
} catch (const std::invalid_argument &) {
// Handle the exception if necessary.
break;
}
case SORT_METHOD::DEFAULT:
default:
// Handle the default case here.
// normal -> named persistent -> named -> special -> named special

// both normal (includes numbered persistent) => sort by ID
if (a->id() > 0 && b->id() > 0) {
return is_id_less;
}

// one normal, one special => normal first
if ((a->is_special()) ^ (b->is_special())) {
return b->is_special();
}

// only one normal, one named
if ((a->id() > 0) ^ (b->id() > 0)) {
return a->id() > 0;
}

// both special
if (a->is_special() && b->is_special()) {
// if one is -99 => put it last
if (a->id() == -99 || b->id() == -99) {
return b->id() == -99;
}
// both are 0 (not yet named persistents) / both are named specials (-98 <= ID
// <=-1)
return is_name_less;
}

// sort non-special named workspaces by name (ID <= -1377)
return is_name_less;
break;
}

// sort non-special named workspaces by name (ID <= -1377)
return a->name() < b->name();
// Return a default value if none of the cases match.
return is_name_less; // You can adjust this to your specific needs.
});

for (size_t i = 0; i < workspaces_.size(); ++i) {
Expand Down