From 33236c222f947d8d360b208c161181922445277a Mon Sep 17 00:00:00 2001 From: dmitry Date: Wed, 28 Jun 2023 02:52:01 +0300 Subject: [PATCH 1/5] save --- include/factory.hpp | 1 + include/modules/hyprland/backend.hpp | 3 ++ include/modules/hyprland/workspaces.hpp | 44 +++++++++++++++ meson.build | 10 +--- src/factory.cpp | 3 ++ src/modules/hyprland/backend.cpp | 4 ++ src/modules/hyprland/workspaces.cpp | 72 +++++++++++++++++++++++++ 7 files changed, 128 insertions(+), 9 deletions(-) create mode 100644 include/modules/hyprland/workspaces.hpp create mode 100644 src/modules/hyprland/workspaces.cpp diff --git a/include/factory.hpp b/include/factory.hpp index 7afc89f..90d0ac1 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -31,6 +31,7 @@ #include "modules/hyprland/language.hpp" #include "modules/hyprland/submap.hpp" #include "modules/hyprland/window.hpp" +#include "modules/hyprland/workspaces.hpp" #endif #if defined(__FreeBSD__) || (defined(__linux__) && !defined(NO_FILESYSTEM)) #include "modules/battery.hpp" diff --git a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp index d876781..179c82f 100644 --- a/include/modules/hyprland/backend.hpp +++ b/include/modules/hyprland/backend.hpp @@ -5,6 +5,7 @@ #include #include #include +#include "util/json.hpp" namespace waybar::modules::hyprland { @@ -22,12 +23,14 @@ class IPC { void unregisterForIPC(EventHandler*); std::string getSocket1Reply(const std::string& rq); + Json::Value getSocket1JsonReply(const std::string& rq); private: void startIPC(); void parseIPC(const std::string&); std::mutex callbackMutex; + util::JsonParser parser_; std::list> callbacks; }; diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp new file mode 100644 index 0000000..7903fef --- /dev/null +++ b/include/modules/hyprland/workspaces.hpp @@ -0,0 +1,44 @@ +#include +#include + +#include "AModule.hpp" +#include "bar.hpp" +#include "modules/hyprland/backend.hpp" + +namespace waybar::modules::hyprland { + +class Workspace { + public: + Workspace(int id); + int id() { return id_; }; + Gtk::Button& button() { return button_; }; + + static Workspace parse(const Json::Value&); + void update(); + + private: + int id_; + + Gtk::Button button_; + Gtk::Box content_; + Gtk::Label label_; +}; + +class Workspaces : public AModule, public EventHandler { + public: + Workspaces(const std::string&, const waybar::Bar&, const Json::Value&); + virtual ~Workspaces(); + void update() override; + void init(); + + private: + void onEvent(const std::string&) override; + + std::vector workspaces; + + std::mutex mutex_; + const Bar& bar_; + Gtk::Box box_; +}; + +} // namespace waybar::modules::hyprland diff --git a/meson.build b/meson.build index beee053..9ef4db0 100644 --- a/meson.build +++ b/meson.build @@ -240,6 +240,7 @@ if true src_files += 'src/modules/hyprland/window.cpp' src_files += 'src/modules/hyprland/language.cpp' src_files += 'src/modules/hyprland/submap.cpp' + src_files += 'src/modules/hyprland/workspaces.cpp' endif if libnl.found() and libnlgen.found() @@ -479,15 +480,6 @@ if scdoc.found() endforeach endif -catch2 = dependency( - 'catch2', - version: '>=2.0.0', - fallback: ['catch2', 'catch2_dep'], - required: get_option('tests'), -) -if catch2.found() - subdir('test') -endif clangtidy = find_program('clang-tidy', required: false) diff --git a/src/factory.cpp b/src/factory.cpp index bd2e7a2..1d7a00b 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -83,6 +83,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "hyprland/submap") { return new waybar::modules::hyprland::Submap(id, bar_, config_[name]); } + if (ref == "hyprland/workspaces") { + return new waybar::modules::hyprland::Workspaces(id, bar_, config_[name]); + } #endif if (ref == "idle_inhibitor") { return new waybar::modules::IdleInhibitor(id, bar_, config_[name]); diff --git a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp index 997fe11..79bc637 100644 --- a/src/modules/hyprland/backend.cpp +++ b/src/modules/hyprland/backend.cpp @@ -198,4 +198,8 @@ std::string IPC::getSocket1Reply(const std::string& rq) { return response; } +Json::Value IPC::getSocket1JsonReply(const std::string& rq) { + return parser_.parse(getSocket1Reply("j/" + rq)); +} + } // namespace waybar::modules::hyprland diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp new file mode 100644 index 0000000..4a19a59 --- /dev/null +++ b/src/modules/hyprland/workspaces.cpp @@ -0,0 +1,72 @@ +#include "modules/hyprland/workspaces.hpp" + +#include + +#include +#include + +namespace waybar::modules::hyprland { +Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) + : AModule(config, "workspaces", id, false, false), + bar_(bar), + box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) { + box_.set_name("workspaces"); + if (!id.empty()) { + box_.get_style_context()->add_class(id); + } + event_box_.add(box_); + modulesReady = true; + if (!gIPC.get()) { + gIPC = std::make_unique(); + } + + init(); + + gIPC->registerForIPC("createworkspace", this); + gIPC->registerForIPC("destroyworkspace", this); + gIPC->registerForIPC("urgent", this); +} + +auto Workspaces::update() -> void { + std::lock_guard lock(mutex_); + for (Workspace &workspace : workspaces) { + workspace.update(); + } + AModule::update(); +} + +void Workspaces::onEvent(const std::string &ev) { dp.emit(); } + +void Workspaces::init() { + const auto activeWorkspace = Workspace::parse(gIPC->getSocket1JsonReply("activeworkspace")); + const Json::Value workspaces_json = gIPC->getSocket1JsonReply("workspaces"); + for (const Json::Value &workspace_json : workspaces_json) { + workspaces.push_back(Workspace::parse(workspace_json)); + } + std::sort(workspaces.begin(), workspaces.end(), + [](Workspace &lhs, Workspace &rhs) { return lhs.id() < rhs.id(); }); + for (auto &workspace : workspaces) { + box_.pack_start(workspace.button(), false, false); + } + + dp.emit(); +} + +Workspaces::~Workspaces() { + gIPC->unregisterForIPC(this); + // wait for possible event handler to finish + std::lock_guard lg(mutex_); +} + +Workspace Workspace::Workspace::parse(const Json::Value &value) { + return Workspace{value["id"].asInt()}; +} + +Workspace::Workspace(int id) : id_(id) { + button_.set_relief(Gtk::RELIEF_NONE); + content_.set_center_widget(label_); + button_.add(content_); +}; + +void Workspace::update() { label_.set_text(std::to_string(id_)); } +} // namespace waybar::modules::hyprland From 887c44bf68fa7baab99b7f6960dc771fc3e261a5 Mon Sep 17 00:00:00 2001 From: dmitry Date: Sat, 1 Jul 2023 00:18:57 +0300 Subject: [PATCH 2/5] finish MVP --- include/modules/hyprland/workspaces.hpp | 24 ++++- src/modules/hyprland/workspaces.cpp | 119 +++++++++++++++++++++--- 2 files changed, 126 insertions(+), 17 deletions(-) diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 7903fef..0b8a452 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -7,17 +7,27 @@ namespace waybar::modules::hyprland { +struct WorkspaceDto { + int id; + + static WorkspaceDto parse(const Json::Value& value); +}; + class Workspace { public: Workspace(int id); - int id() { return id_; }; + Workspace(WorkspaceDto dto); + int id() const { return id_; }; + int active() const { return active_; }; + std::string& select_icon(std::map& icons_map); + void set_active(bool value = true) { active_ = value; }; Gtk::Button& button() { return button_; }; - static Workspace parse(const Json::Value&); - void update(); + void update(const std::string& format, const std::string& icon); private: int id_; + bool active_; Gtk::Button button_; Gtk::Box content_; @@ -33,9 +43,13 @@ class Workspaces : public AModule, public EventHandler { private: void onEvent(const std::string&) override; + void sort_workspaces(); - std::vector workspaces; - + std::string format_; + std::map icons_map_; + bool with_icon_; + int active_workspace_id; + std::vector workspaces_; std::mutex mutex_; const Bar& bar_; Gtk::Box box_; diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 4a19a59..54a425d 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -1,15 +1,32 @@ #include "modules/hyprland/workspaces.hpp" +#include #include #include +#include #include namespace waybar::modules::hyprland { + Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) : AModule(config, "workspaces", id, false, false), bar_(bar), box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) { + Json::Value config_format = config["format"]; + + format_ = config_format.isString() ? config_format.asString() : "{id}"; + with_icon_ = format_.find("{icon}") != std::string::npos; + + if (with_icon_ && icons_map_.empty()) { + Json::Value format_icons = config["format-icons"]; + for (std::string &name : format_icons.getMemberNames()) { + icons_map_.emplace(name, format_icons[name].asString()); + } + + icons_map_.emplace("", ""); + } + box_.set_name("workspaces"); if (!id.empty()) { box_.get_style_context()->add_class(id); @@ -22,33 +39,64 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value init(); + gIPC->registerForIPC("workspace", this); gIPC->registerForIPC("createworkspace", this); gIPC->registerForIPC("destroyworkspace", this); - gIPC->registerForIPC("urgent", this); } auto Workspaces::update() -> void { std::lock_guard lock(mutex_); - for (Workspace &workspace : workspaces) { - workspace.update(); + for (Workspace &workspace : workspaces_) { + workspace.set_active(workspace.id() == active_workspace_id); + + std::string &workspace_icon = icons_map_[""]; + if (with_icon_) { + workspace_icon = workspace.select_icon(icons_map_); + } + + workspace.update(format_, workspace_icon); } + AModule::update(); } -void Workspaces::onEvent(const std::string &ev) { dp.emit(); } +void Workspaces::onEvent(const std::string &ev) { + std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>')); + std::string payload = ev.substr(eventName.size() + 2); + if (eventName == "workspace") { + std::from_chars(payload.data(), payload.data() + payload.size(), active_workspace_id); + } else if (eventName == "destroyworkspace") { + int deleted_workspace_id; + std::from_chars(payload.data(), payload.data() + payload.size(), deleted_workspace_id); + workspaces_.erase(std::remove_if(workspaces_.begin(), workspaces_.end(), + [&](Workspace &x) { return x.id() == deleted_workspace_id; })); + } else if (eventName == "createworkspace") { + int new_workspace_id; + std::from_chars(payload.data(), payload.data() + payload.size(), new_workspace_id); + workspaces_.push_back(new_workspace_id); + Gtk::Button& new_workspace_button = workspaces_.back().button(); + box_.pack_end(new_workspace_button, false, false); + sort_workspaces(); + new_workspace_button.show_all(); + } + + dp.emit(); +} void Workspaces::init() { - const auto activeWorkspace = Workspace::parse(gIPC->getSocket1JsonReply("activeworkspace")); + const auto activeWorkspace = WorkspaceDto::parse(gIPC->getSocket1JsonReply("activeworkspace")); + active_workspace_id = activeWorkspace.id; const Json::Value workspaces_json = gIPC->getSocket1JsonReply("workspaces"); for (const Json::Value &workspace_json : workspaces_json) { - workspaces.push_back(Workspace::parse(workspace_json)); + workspaces_.push_back(Workspace(WorkspaceDto::parse(workspace_json))); } - std::sort(workspaces.begin(), workspaces.end(), - [](Workspace &lhs, Workspace &rhs) { return lhs.id() < rhs.id(); }); - for (auto &workspace : workspaces) { + + for (auto &workspace : workspaces_) { box_.pack_start(workspace.button(), false, false); } + sort_workspaces(); + dp.emit(); } @@ -58,15 +106,62 @@ Workspaces::~Workspaces() { std::lock_guard lg(mutex_); } -Workspace Workspace::Workspace::parse(const Json::Value &value) { - return Workspace{value["id"].asInt()}; +WorkspaceDto WorkspaceDto::parse(const Json::Value &value) { + return WorkspaceDto{value["id"].asInt()}; } +Workspace::Workspace(WorkspaceDto dto) : Workspace(dto.id){}; + Workspace::Workspace(int id) : id_(id) { button_.set_relief(Gtk::RELIEF_NONE); content_.set_center_widget(label_); button_.add(content_); }; -void Workspace::update() { label_.set_text(std::to_string(id_)); } +void add_or_remove_class(Glib::RefPtr context, bool condition, + const std::string &class_name) { + if (condition) { + context->add_class(class_name); + } else { + context->remove_class(class_name); + } +} + +void Workspace::update(const std::string &format, const std::string &icon) { + Glib::RefPtr style_context = button_.get_style_context(); + add_or_remove_class(style_context, active(), "active"); + + label_.set_markup( + fmt::format(fmt::runtime(format), fmt::arg("id", id()), fmt::arg("icon", icon))); +} + +void Workspaces::sort_workspaces() { + std::sort(workspaces_.begin(), workspaces_.end(), + [](Workspace &lhs, Workspace &rhs) { return lhs.id() < rhs.id(); }); + + for (size_t i = 0; i < workspaces_.size(); ++i) { + box_.reorder_child(workspaces_[i].button(), i); + } +} + +std::string &Workspace::select_icon(std::map &icons_map) { + if (active()) { + auto active_icon_it = icons_map.find("active"); + if (active_icon_it != icons_map.end()) { + return active_icon_it->second; + } + } + + auto named_icon_it = icons_map.find(std::to_string(id())); + if (named_icon_it != icons_map.end()) { + return named_icon_it->second; + } + + auto default_icon_it = icons_map.find("default"); + if (default_icon_it != icons_map.end()) { + return default_icon_it->second; + } + + return icons_map[""]; +} } // namespace waybar::modules::hyprland From dbc7471f83331a265367f908b8edeeaa391359cf Mon Sep 17 00:00:00 2001 From: dmitry Date: Sat, 1 Jul 2023 02:13:36 +0300 Subject: [PATCH 3/5] add docs --- man/waybar-hyprland-workspaces.5.scd | 59 ++++++++++++++++++++++++++++ man/waybar-wlr-workspaces.5.scd | 4 +- 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 man/waybar-hyprland-workspaces.5.scd diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd new file mode 100644 index 0000000..0678fb2 --- /dev/null +++ b/man/waybar-hyprland-workspaces.5.scd @@ -0,0 +1,59 @@ +waybar-wlr-workspaces(5) + +# NAME + +waybar - hyprland workspaces module + +# DESCRIPTION + +The *workspaces* module displays the currently used workspaces in hyprland compositor. + +# CONFIGURATION + +Addressed by *hyprland/workspaces* + +*format*: ++ + typeof: string ++ + default: {id} ++ + The format, how information should be displayed. + +*format-icons*: ++ + typeof: array ++ + Based on the workspace id and state, the corresponding icon gets selected. See *icons*. + +# FORMAT REPLACEMENTS + +*{id}*: id of workspace assigned by compositor + +*{icon}*: Icon, as defined in *format-icons*. + +# ICONS + +Additional to workspace name matching, the following *format-icons* can be set. + +- *default*: Will be shown, when no string match is found. +- *active*: Will be shown, when workspace is active + +# EXAMPLES + +``` +"wlr/workspaces": { + "format": "{name}: {icon}", + "format-icons": { + "1": "", + "2": "", + "3": "", + "4": "", + "5": "", + "active": "", + "default": "" + }, + "sort-by-number": true +} +``` + +# Style + +- *#workspaces* +- *#workspaces button* +- *#workspaces button.active* diff --git a/man/waybar-wlr-workspaces.5.scd b/man/waybar-wlr-workspaces.5.scd index 169112f..4a256f0 100644 --- a/man/waybar-wlr-workspaces.5.scd +++ b/man/waybar-wlr-workspaces.5.scd @@ -65,7 +65,7 @@ Addressed by *wlr/workspaces* Additional to workspace name matching, the following *format-icons* can be set. - *default*: Will be shown, when no string match is found. -- *focused*: Will be shown, when workspace is focused +- *active*: Will be shown, when workspace is active # EXAMPLES @@ -78,7 +78,7 @@ Additional to workspace name matching, the following *format-icons* can be set. "3": "", "4": "", "5": "", - "focused": "", + "active": "", "default": "" }, "sort-by-number": true From 0b602632f2e607fac2944bc71d791506943a42ca Mon Sep 17 00:00:00 2001 From: dmitry Date: Sat, 1 Jul 2023 02:23:37 +0300 Subject: [PATCH 4/5] return catch2 --- meson.build | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/meson.build b/meson.build index 9ef4db0..085dcd0 100644 --- a/meson.build +++ b/meson.build @@ -480,6 +480,15 @@ if scdoc.found() endforeach endif +catch2 = dependency( + 'catch2', + version: '>=2.0.0', + fallback: ['catch2', 'catch2_dep'], + required: get_option('tests'), +) +if catch2.found() + subdir('test') +endif clangtidy = find_program('clang-tidy', required: false) From 4f9fbbfa543c04b76e159391578c2ad25a7a4364 Mon Sep 17 00:00:00 2001 From: dmitry Date: Sat, 1 Jul 2023 02:25:15 +0300 Subject: [PATCH 5/5] fix format --- src/modules/hyprland/workspaces.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index 54a425d..cdcb54e 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -74,7 +74,7 @@ void Workspaces::onEvent(const std::string &ev) { int new_workspace_id; std::from_chars(payload.data(), payload.data() + payload.size(), new_workspace_id); workspaces_.push_back(new_workspace_id); - Gtk::Button& new_workspace_button = workspaces_.back().button(); + Gtk::Button &new_workspace_button = workspaces_.back().button(); box_.pack_end(new_workspace_button, false, false); sort_workspaces(); new_workspace_button.show_all();