diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index 500bbe36..b32dc41d 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -9,27 +9,30 @@ namespace waybar::modules::hyprland { -struct WorkspaceDto { - int id; - - static WorkspaceDto parse(const Json::Value& value); -}; - class Workspace { public: - Workspace(int id); - Workspace(WorkspaceDto dto); - int id() const { return id_; }; - int active() const { return active_; }; + Workspace(const Json::Value& workspace_data); std::string& select_icon(std::map& icons_map); - void set_active(bool value = true) { active_ = value; }; Gtk::Button& button() { return button_; }; + int id() const { return id_; }; + std::string name() const { return name_; }; + std::string output() const { return output_; }; + int active() const { return active_; }; + bool is_special() const { return is_special_; }; + + auto handle_clicked(GdkEventButton* bt) -> bool; + void set_active(bool value = true) { active_ = value; }; + void update(const std::string& format, const std::string& icon); private: int id_; + std::string name_; + std::string output_; + int windows_; bool active_; + bool is_special_; Gtk::Button button_; Gtk::Box content_; @@ -39,23 +42,31 @@ class Workspace { class Workspaces : public AModule, public EventHandler { public: Workspaces(const std::string&, const waybar::Bar&, const Json::Value&); - virtual ~Workspaces(); + ~Workspaces() override; void update() override; void init(); + auto all_outputs() const -> bool { return all_outputs_; } + auto show_special() const -> bool { return show_special_; } + + auto get_bar_output() const -> std::string { return bar_.output->name; } + private: void onEvent(const std::string&) override; void sort_workspaces(); - void create_workspace(int id); - void remove_workspace(int id); + void create_workspace(Json::Value& value); + void remove_workspace(std::string name); + + bool all_outputs_ = false; + bool show_special_ = false; std::string format_; std::map icons_map_; bool with_icon_; - int active_workspace_id; + std::string active_workspace_name; std::vector> workspaces_; - std::vector workspaces_to_create_; - std::vector workspaces_to_remove_; + std::vector workspaces_to_create_; + std::vector workspaces_to_remove_; std::mutex mutex_; const Bar& bar_; Gtk::Box box_; diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd index 0678fb22..6e67e188 100644 --- a/man/waybar-hyprland-workspaces.5.scd +++ b/man/waybar-hyprland-workspaces.5.scd @@ -21,10 +21,22 @@ Addressed by *hyprland/workspaces* typeof: array ++ Based on the workspace id and state, the corresponding icon gets selected. See *icons*. +*show-special*: ++ + typeof: bool ++ + default: false ++ + If set to true special workspaces will be shown. + +*all-outputs*: ++ + typeof: bool ++ + default: false ++ + If set to false workspaces group will be shown only in assigned output. Otherwise all workspace groups are shown. + # FORMAT REPLACEMENTS *{id}*: id of workspace assigned by compositor +*{name}*: workspace name assigned by compositor + *{icon}*: Icon, as defined in *format-icons*. # ICONS @@ -48,7 +60,8 @@ Additional to workspace name matching, the following *format-icons* can be set. "active": "", "default": "" }, - "sort-by-number": true + "all-outputs": false, + "show-special": false } ``` diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index e169f916..f7b87bfa 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -28,13 +28,23 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value icons_map_.emplace("", ""); } + auto config_all_outputs = config_["all-outputs"]; + if (config_all_outputs.isBool()) { + all_outputs_ = config_all_outputs.asBool(); + } + + auto config_show_special = config_["show-special"]; + if (config_show_special.isBool()) { + show_special_ = config_show_special.asBool(); + } + box_.set_name("workspaces"); if (!id.empty()) { box_.get_style_context()->add_class(id); } event_box_.add(box_); modulesReady = true; - if (!gIPC.get()) { + if (!gIPC) { gIPC = std::make_unique(); } @@ -43,32 +53,31 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value gIPC->registerForIPC("workspace", this); gIPC->registerForIPC("createworkspace", this); gIPC->registerForIPC("destroyworkspace", this); + gIPC->registerForIPC("focusedmon", this); + gIPC->registerForIPC("moveworkspace", this); } auto Workspaces::update() -> void { - for (int &workspace_to_remove : workspaces_to_remove_) { + for (std::string workspace_to_remove : workspaces_to_remove_) { remove_workspace(workspace_to_remove); } workspaces_to_remove_.clear(); - for (int &workspace_to_create : workspaces_to_create_) { + for (Json::Value &workspace_to_create : workspaces_to_create_) { create_workspace(workspace_to_create); } workspaces_to_create_.clear(); - for (std::unique_ptr &workspace : workspaces_) { - workspace->set_active(workspace->id() == active_workspace_id); - + for (auto &workspace : workspaces_) { + workspace->set_active(workspace->name() == active_workspace_name); std::string &workspace_icon = icons_map_[""]; if (with_icon_) { workspace_icon = workspace->select_icon(icons_map_); } - workspace->update(format_, workspace_icon); } - AModule::update(); } @@ -76,35 +85,60 @@ void Workspaces::onEvent(const std::string &ev) { std::lock_guard lock(mutex_); 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); + active_workspace_name = payload; + } else if (eventName == "destroyworkspace") { - int deleted_workspace_id; - std::from_chars(payload.data(), payload.data() + payload.size(), deleted_workspace_id); - workspaces_to_remove_.push_back(deleted_workspace_id); + workspaces_to_remove_.push_back(payload); + } else if (eventName == "createworkspace") { - int new_workspace_id; - std::from_chars(payload.data(), payload.data() + payload.size(), new_workspace_id); - workspaces_to_create_.push_back(new_workspace_id); + const Json::Value workspaces_json = gIPC->getSocket1JsonReply("workspaces"); + for (Json::Value workspace_json : workspaces_json) { + if (workspace_json["name"].asString() == payload && + (all_outputs() || bar_.output->name == workspace_json["monitor"].asString()) && + (show_special() || workspace_json["name"].asString().find("special:") != 0)) { + workspaces_to_create_.push_back(workspace_json); + break; + } + } + + } else if (eventName == "focusedmon") { + active_workspace_name = payload.substr(payload.find(',') + 1); + + } else if (eventName == "moveworkspace" && !all_outputs()) { + std::string workspace = payload.substr(0, payload.find(',')); + std::string new_output = payload.substr(payload.find(',') + 1); + if (bar_.output->name == new_output) { // TODO: implement this better + const Json::Value workspaces_json = gIPC->getSocket1JsonReply("workspaces"); + for (Json::Value workspace_json : workspaces_json) { + if (workspace_json["name"].asString() == workspace && + bar_.output->name == workspace_json["monitor"].asString()) { + workspaces_to_create_.push_back(workspace_json); + break; + } + } + } else { + workspaces_to_remove_.push_back(workspace); + } } dp.emit(); } -void Workspaces::create_workspace(int id) { - workspaces_.push_back(std::make_unique(id)); +void Workspaces::create_workspace(Json::Value &value) { + workspaces_.push_back(std::make_unique(value)); Gtk::Button &new_workspace_button = workspaces_.back()->button(); box_.pack_start(new_workspace_button, false, false); sort_workspaces(); new_workspace_button.show_all(); } -void Workspaces::remove_workspace(int id) { +void Workspaces::remove_workspace(std::string name) { auto workspace = std::find_if(workspaces_.begin(), workspaces_.end(), - [&](std::unique_ptr &x) { return x->id() == id; }); + [&](std::unique_ptr &x) { return x->name() == name; }); if (workspace == workspaces_.end()) { - spdlog::warn("Can't find workspace with id {}", id); return; } @@ -113,16 +147,13 @@ void Workspaces::remove_workspace(int id) { } void Workspaces::init() { - 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( - std::make_unique(Workspace(WorkspaceDto::parse(workspace_json)))); - } + active_workspace_name = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString(); - for (auto &workspace : workspaces_) { - box_.pack_start(workspace->button(), false, false); + const Json::Value workspaces_json = gIPC->getSocket1JsonReply("workspaces"); + for (Json::Value workspace_json : workspaces_json) { + if ((all_outputs() || bar_.output->name == workspace_json["monitor"].asString()) && + (workspace_json["name"].asString().find("special") != 0 || show_special())) + create_workspace(workspace_json); } sort_workspaces(); @@ -136,19 +167,30 @@ Workspaces::~Workspaces() { std::lock_guard lg(mutex_); } -WorkspaceDto WorkspaceDto::parse(const Json::Value &value) { - return WorkspaceDto{value["id"].asInt()}; -} +Workspace::Workspace(const Json::Value &workspace_data) + : id_(workspace_data["id"].asInt()), + name_(workspace_data["name"].asString()), + output_(workspace_data["monitor"].asString()), // TODO:allow using monitor desc + windows_(workspace_data["id"].asInt()), + active_(true), + is_special_(false) { + if (name_.find("name:") == 0) { + name_ = name_.substr(5); + } else if (name_.find("special") == 0) { + name_ = id_ == -99 ? name_ : name_.substr(13); + is_special_ = true; + } -Workspace::Workspace(WorkspaceDto dto) : Workspace(dto.id){}; + button_.add_events(Gdk::BUTTON_PRESS_MASK); + button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked), + false); -Workspace::Workspace(int id) : id_(id) { button_.set_relief(Gtk::RELIEF_NONE); content_.set_center_widget(label_); button_.add(content_); }; -void add_or_remove_class(Glib::RefPtr context, bool condition, +void add_or_remove_class(const Glib::RefPtr &context, bool condition, const std::string &class_name) { if (condition) { context->add_class(class_name); @@ -158,17 +200,30 @@ void add_or_remove_class(Glib::RefPtr context, bool condition } void Workspace::update(const std::string &format, const std::string &icon) { - Glib::RefPtr style_context = button_.get_style_context(); + auto 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))); + label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg("id", id()), + fmt::arg("name", name()), fmt::arg("icon", icon))); } void Workspaces::sort_workspaces() { std::sort(workspaces_.begin(), workspaces_.end(), - [](std::unique_ptr &lhs, std::unique_ptr &rhs) { - return lhs->id() < rhs->id(); + [](std::unique_ptr &a, std::unique_ptr &b) { + // normal -> named -> special -> named special + if (a->id() > 0 && b->id() > 0) { + return a->id() < b->id(); + } + if (a->id() < 0 && b->id() < 0) { + if ((a->is_special()) ^ (a->is_special())) { + return a->id() > b->id(); + } + return a->id() < b->id(); + } + if ((a->id() > 0) ^ (b->id() > 0)) { + return a->id() > b->id(); + } + return false; }); for (size_t i = 0; i < workspaces_.size(); ++i) { @@ -193,7 +248,25 @@ std::string &Workspace::select_icon(std::map &icons_ma if (default_icon_it != icons_map.end()) { return default_icon_it->second; } - return icons_map[""]; } + +auto Workspace::handle_clicked(GdkEventButton *bt) -> bool { + try { + if (id() > 0) { // normal + gIPC->getSocket1Reply("dispatch workspace " + std::to_string(id())); + } else if (!is_special()) { // named normal + gIPC->getSocket1Reply("dispatch workspace name" + name()); + } else if (id() != -99) { // named special + gIPC->getSocket1Reply("dispatch togglespecialworkspace name" + name()); + } else { // special + gIPC->getSocket1Reply("dispatch togglespecialworkspace"); + } + return true; + } catch (const std::exception &e) { + spdlog::error("Failed to dispatch workspace: {}", e.what()); + } + return false; +} + } // namespace waybar::modules::hyprland