diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp index c5851cad..dde564df 100644 --- a/include/modules/hyprland/workspaces.hpp +++ b/include/modules/hyprland/workspaces.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "AModule.hpp" @@ -25,6 +26,37 @@ namespace waybar::modules::hyprland { class Workspaces; +class CreateWindow { + public: + CreateWindow(std::string workspace_name, WindowAddress window_address, std::string window_repr); + CreateWindow(std::string workspace_name, WindowAddress window_address, std::string window_class, + std::string window_title); + CreateWindow(Json::Value& client_data); + + int increment_time_spent_uncreated(); + bool is_empty(Workspaces& workspace_manager); + bool repr_is_ready() const { return std::holds_alternative(window_); } + std::string repr(Workspaces& workspace_manager); + + std::string workspace_name() const { return workspace_name_; } + WindowAddress addr() const { return window_address_; } + + void move_to_worksace(std::string& new_workspace_name); + + private: + void clear_addr(); + void clear_workspace_name(); + + using Repr = std::string; + using ClassAndTitle = std::pair; + + std::variant window_; + + WindowAddress window_address_; + std::string workspace_name_; + int time_spent_uncreated_ = 0; +}; + class Workspace { public: explicit Workspace(const Json::Value& workspace_data, Workspaces& workspace_manager, @@ -50,13 +82,11 @@ class Workspace { void set_windows(uint value) { windows_ = value; }; void set_name(std::string value) { name_ = value; }; bool contains_window(WindowAddress addr) const { return window_map_.contains(addr); } - void insert_window(WindowAddress addr, std::string window_class, std::string window_title); + void insert_window(CreateWindow create_window_paylod); std::string remove_window(WindowAddress addr); void initialize_window_map(const Json::Value& clients_data); - bool on_window_opened(WindowAddress& addr, std::string& workspace_name, std::string window_repr); - bool on_window_opened(WindowAddress& addr, std::string& workspace_name, std::string& window_class, - std::string& window_title); + bool on_window_opened(CreateWindow create_window_paylod); std::optional on_window_closed(WindowAddress& addr); @@ -104,7 +134,6 @@ class Workspaces : public AModule, public EventHandler { private: void onEvent(const std::string&) override; void update_window_count(); - void initialize_window_maps(); void sort_workspaces(); void create_workspace(Json::Value& workspace_data, const Json::Value& clients_data = Json::Value::nullRef); @@ -149,6 +178,7 @@ class Workspaces : public AModule, public EventHandler { std::vector> workspaces_; std::vector workspaces_to_create_; std::vector workspaces_to_remove_; + std::vector windows_to_create_; std::vector ignore_workspaces_; diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp index b3faeac1..4facacfb 100644 --- a/src/modules/hyprland/workspaces.cpp +++ b/src/modules/hyprland/workspaces.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "util/regex_collection.hpp" @@ -191,6 +193,34 @@ auto Workspaces::update() -> void { } workspace->update(format_, workspace_icon); } + + bool any_window_created = false; + std::vector not_created; + + for (auto &window_payload : windows_to_create_) { + bool created = false; + for (auto &workspace : workspaces_) { + if (workspace->on_window_opened(window_payload)) { + created = true; + any_window_created = true; + break; + } + } + if (!created) { + static const int WINDOW_CREATION_TIMEOUT = 2; + if (window_payload.increment_time_spent_uncreated() < WINDOW_CREATION_TIMEOUT) { + not_created.push_back(window_payload); + } + } + } + + if (any_window_created) { + dp.emit(); + } + + windows_to_create_.clear(); + windows_to_create_ = not_created; + AModule::update(); } @@ -296,8 +326,7 @@ void Workspaces::onEvent(const std::string &ev) { }); if (!client->empty()) { - (*window_workspace) - ->insert_window(payload, (*client)["class"].asString(), (*client)["title"].asString()); + (*window_workspace)->insert_window({*client}); } } } @@ -322,11 +351,8 @@ void Workspaces::on_window_opened(std::string payload) { std::string window_title = payload.substr(next_comma_idx + 1, payload.length() - next_comma_idx); - for (auto &workspace : workspaces_) { - if (workspace->on_window_opened(window_address, workspace_name, window_class, window_title)) { - break; - } - } + windows_to_create_.emplace_back( + CreateWindow(workspace_name, window_address, window_class, window_title)); } void Workspaces::on_window_closed(std::string addr) { @@ -347,6 +373,15 @@ void Workspaces::on_window_moved(std::string payload) { std::string window_repr; + // If the window was still queued to be created, just change its destination + // and exit + for (auto &window : windows_to_create_) { + if (window.addr() == window_address) { + window.move_to_worksace(workspace_name); + return; + } + } + // Take the window's representation from the old workspace... for (auto &workspace : workspaces_) { try { @@ -359,10 +394,8 @@ void Workspaces::on_window_moved(std::string payload) { } // ...and add it to the new workspace - for (auto &workspace : workspaces_) { - if (workspace->on_window_opened(window_address, workspace_name, window_repr)) { - break; - } + if (!window_repr.empty()) { + windows_to_create_.emplace_back(CreateWindow(workspace_name, window_address, window_repr)); } } @@ -384,39 +417,18 @@ void Workspaces::update_window_count() { } } -void Workspaces::initialize_window_maps() { - Json::Value clients_data = gIPC->getSocket1JsonReply("clients"); - for (auto &workspace : workspaces_) { - workspace->initialize_window_map(clients_data); - } -} - void Workspace::initialize_window_map(const Json::Value &clients_data) { window_map_.clear(); for (auto client : clients_data) { if (client["workspace"]["id"].asInt() == id()) { - // substr(2, ...) is necessary because Hyprland's JSON follows this format: - // 0x{ADDR} - // While Hyprland's IPC follows this format: - // {ADDR} - WindowAddress client_address = client["address"].asString(); - client_address = client_address.substr(2, client_address.length() - 2); - insert_window(client_address, client["class"].asString(), client["title"].asString()); + insert_window({client}); } } } -void Workspace::insert_window(WindowAddress addr, std::string window_class, - std::string window_title) { - if (window_class.empty() && - (!workspace_manager_.window_rewrite_config_uses_title() || window_title.empty())) { - return; - } - - auto window_repr = workspace_manager_.get_rewrite(window_class, window_title); - - if (!window_repr.empty()) { - window_map_[addr] = window_repr; +void Workspace::insert_window(CreateWindow create_window_paylod) { + if (!create_window_paylod.is_empty(workspace_manager_)) { + window_map_[create_window_paylod.addr()] = create_window_paylod.repr(workspace_manager_); } }; @@ -427,20 +439,9 @@ std::string Workspace::remove_window(WindowAddress addr) { return window_repr; } -bool Workspace::on_window_opened(WindowAddress &addr, std::string &workspace_name, - std::string window_repr) { - if (workspace_name == name()) { - window_map_[addr] = window_repr; - return true; - } else { - return false; - } -} - -bool Workspace::on_window_opened(WindowAddress &addr, std::string &workspace_name, - std::string &window_class, std::string &window_title) { - if (workspace_name == name()) { - insert_window(addr, window_class, window_title); +bool Workspace::on_window_opened(CreateWindow create_window_paylod) { + if (create_window_paylod.workspace_name() == name()) { + insert_window(create_window_paylod); return true; } else { return false; @@ -456,17 +457,16 @@ std::optional Workspace::on_window_closed(WindowAddress &addr) { } void Workspaces::create_workspace(Json::Value &workspace_data, const Json::Value &clients_data) { - // replace the existing persistent workspace if it exists + // avoid recreating existing workspaces + auto workspace_name = workspace_data["name"].asString(); auto workspace = std::find_if( - workspaces_.begin(), workspaces_.end(), [&](std::unique_ptr const &x) { - auto name = workspace_data["name"].asString(); - return x->is_persistent() && - ((name.starts_with("special:") && name.substr(8) == x->name()) || name == x->name()); + workspaces_.begin(), workspaces_.end(), [&](std::unique_ptr const &w) { + return (workspace_name.starts_with("special:") && workspace_name.substr(8) == w->name()) || + workspace_name == w->name(); }); + if (workspace != workspaces_.end()) { - // replace workspace, but keep persistent flag - workspaces_.erase(workspace); - workspace_data["persistent"] = true; + return; } // create new workspace @@ -589,9 +589,6 @@ void Workspaces::init() { monitor_id_ = (*current_monitor)["id"].asInt(); } - fill_persistent_workspaces(); - create_persistent_workspaces(); - const Json::Value workspaces_json = gIPC->getSocket1JsonReply("workspaces"); const Json::Value clients_json = gIPC->getSocket1JsonReply("clients"); @@ -604,6 +601,9 @@ void Workspaces::init() { } } + fill_persistent_workspaces(); + create_persistent_workspaces(); + update_window_count(); sort_workspaces(); @@ -863,4 +863,88 @@ std::string Workspaces::get_rewrite(std::string window_class, std::string window return window_rewrite_rules_.get(window_repr_key); } +CreateWindow::CreateWindow(std::string workspace_name, WindowAddress window_address, + std::string window_repr) + : window_(window_repr), window_address_(window_address), workspace_name_(workspace_name) { + clear_addr(); + clear_workspace_name(); +} + +CreateWindow::CreateWindow(std::string workspace_name, WindowAddress window_address, + std::string window_class, std::string window_title) + : window_(std::make_pair(window_class, window_title)), + window_address_(window_address), + workspace_name_(workspace_name) { + clear_addr(); + clear_workspace_name(); +} + +CreateWindow::CreateWindow(Json::Value &client_data) { + window_address_ = client_data["address"].asString(); + workspace_name_ = client_data["workspace"]["name"].asString(); + window_ = std::make_pair(client_data["class"].asString(), client_data["title"].asString()); + clear_addr(); + clear_workspace_name(); +} + +std::string CreateWindow::repr(Workspaces &workspace_manager) { + if (std::holds_alternative(window_)) { + return std::get(window_); + } else if (std::holds_alternative(window_)) { + auto [window_class, window_title] = std::get(window_); + return workspace_manager.get_rewrite(window_class, window_title); + } else { + // Unreachable + return ""; + } +} + +bool CreateWindow::is_empty(Workspaces &workspace_manager) { + if (std::holds_alternative(window_)) { + return std::get(window_).empty(); + } else if (std::holds_alternative(window_)) { + auto [window_class, window_title] = std::get(window_); + return (window_class.empty() && + (!workspace_manager.window_rewrite_config_uses_title() || window_title.empty())); + } else { + // Unreachable + return true; + } +} + +int CreateWindow::increment_time_spent_uncreated() { return time_spent_uncreated_++; } + +void CreateWindow::clear_addr() { + // substr(2, ...) is necessary because Hyprland's JSON follows this format: + // 0x{ADDR} + // While Hyprland's IPC follows this format: + // {ADDR} + static const std::string ADDR_PREFIX = "0x"; + static const int ADDR_PREFIX_LEN = ADDR_PREFIX.length(); + + if (window_address_.starts_with(ADDR_PREFIX)) { + window_address_ = + window_address_.substr(ADDR_PREFIX_LEN, window_address_.length() - ADDR_PREFIX_LEN); + } +} + +void CreateWindow::clear_workspace_name() { + // The workspace name may optionally feature "special:" at the beginning. + // If so, we need to remove it because the workspace is saved WITHOUT the + // special qualifier. The reasoning is that not all of Hyprland's IPC events + // use this qualifier, so it's better to be consistent about our uses. + + static const std::string SPECIAL_QUALIFIER_PREFIX = "special:"; + static const int SPECIAL_QUALIFIER_PREFIX_LEN = SPECIAL_QUALIFIER_PREFIX.length(); + + if (workspace_name_.starts_with(SPECIAL_QUALIFIER_PREFIX)) { + workspace_name_ = workspace_name_.substr( + SPECIAL_QUALIFIER_PREFIX_LEN, workspace_name_.length() - SPECIAL_QUALIFIER_PREFIX_LEN); + } +} + +void CreateWindow::move_to_worksace(std::string &new_workspace_name) { + workspace_name_ = new_workspace_name; +} + } // namespace waybar::modules::hyprland