Merge pull request #2578 from Syndelis/fix/hyprland-windows-disappearing
commit
eefd6e8336
|
@ -11,6 +11,7 @@
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "AModule.hpp"
|
#include "AModule.hpp"
|
||||||
|
@ -25,6 +26,37 @@ namespace waybar::modules::hyprland {
|
||||||
|
|
||||||
class Workspaces;
|
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<Repr>(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::string, std::string>;
|
||||||
|
|
||||||
|
std::variant<Repr, ClassAndTitle> window_;
|
||||||
|
|
||||||
|
WindowAddress window_address_;
|
||||||
|
std::string workspace_name_;
|
||||||
|
int time_spent_uncreated_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
class Workspace {
|
class Workspace {
|
||||||
public:
|
public:
|
||||||
explicit Workspace(const Json::Value& workspace_data, Workspaces& workspace_manager,
|
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_windows(uint value) { windows_ = value; };
|
||||||
void set_name(std::string value) { name_ = value; };
|
void set_name(std::string value) { name_ = value; };
|
||||||
bool contains_window(WindowAddress addr) const { return window_map_.contains(addr); }
|
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);
|
std::string remove_window(WindowAddress addr);
|
||||||
void initialize_window_map(const Json::Value& clients_data);
|
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(CreateWindow create_window_paylod);
|
||||||
bool on_window_opened(WindowAddress& addr, std::string& workspace_name, std::string& window_class,
|
|
||||||
std::string& window_title);
|
|
||||||
|
|
||||||
std::optional<std::string> on_window_closed(WindowAddress& addr);
|
std::optional<std::string> on_window_closed(WindowAddress& addr);
|
||||||
|
|
||||||
|
@ -104,7 +134,6 @@ class Workspaces : public AModule, public EventHandler {
|
||||||
private:
|
private:
|
||||||
void onEvent(const std::string&) override;
|
void onEvent(const std::string&) override;
|
||||||
void update_window_count();
|
void update_window_count();
|
||||||
void initialize_window_maps();
|
|
||||||
void sort_workspaces();
|
void sort_workspaces();
|
||||||
void create_workspace(Json::Value& workspace_data,
|
void create_workspace(Json::Value& workspace_data,
|
||||||
const Json::Value& clients_data = Json::Value::nullRef);
|
const Json::Value& clients_data = Json::Value::nullRef);
|
||||||
|
@ -149,6 +178,7 @@ class Workspaces : public AModule, public EventHandler {
|
||||||
std::vector<std::unique_ptr<Workspace>> workspaces_;
|
std::vector<std::unique_ptr<Workspace>> workspaces_;
|
||||||
std::vector<Json::Value> workspaces_to_create_;
|
std::vector<Json::Value> workspaces_to_create_;
|
||||||
std::vector<std::string> workspaces_to_remove_;
|
std::vector<std::string> workspaces_to_remove_;
|
||||||
|
std::vector<CreateWindow> windows_to_create_;
|
||||||
|
|
||||||
std::vector<std::regex> ignore_workspaces_;
|
std::vector<std::regex> ignore_workspaces_;
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
#include "util/regex_collection.hpp"
|
#include "util/regex_collection.hpp"
|
||||||
|
|
||||||
|
@ -191,6 +193,34 @@ auto Workspaces::update() -> void {
|
||||||
}
|
}
|
||||||
workspace->update(format_, workspace_icon);
|
workspace->update(format_, workspace_icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool any_window_created = false;
|
||||||
|
std::vector<CreateWindow> 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();
|
AModule::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,8 +326,7 @@ void Workspaces::onEvent(const std::string &ev) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!client->empty()) {
|
if (!client->empty()) {
|
||||||
(*window_workspace)
|
(*window_workspace)->insert_window({*client});
|
||||||
->insert_window(payload, (*client)["class"].asString(), (*client)["title"].asString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
std::string window_title = payload.substr(next_comma_idx + 1, payload.length() - next_comma_idx);
|
||||||
|
|
||||||
for (auto &workspace : workspaces_) {
|
windows_to_create_.emplace_back(
|
||||||
if (workspace->on_window_opened(window_address, workspace_name, window_class, window_title)) {
|
CreateWindow(workspace_name, window_address, window_class, window_title));
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Workspaces::on_window_closed(std::string addr) {
|
void Workspaces::on_window_closed(std::string addr) {
|
||||||
|
@ -347,6 +373,15 @@ void Workspaces::on_window_moved(std::string payload) {
|
||||||
|
|
||||||
std::string window_repr;
|
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...
|
// Take the window's representation from the old workspace...
|
||||||
for (auto &workspace : workspaces_) {
|
for (auto &workspace : workspaces_) {
|
||||||
try {
|
try {
|
||||||
|
@ -359,10 +394,8 @@ void Workspaces::on_window_moved(std::string payload) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...and add it to the new workspace
|
// ...and add it to the new workspace
|
||||||
for (auto &workspace : workspaces_) {
|
if (!window_repr.empty()) {
|
||||||
if (workspace->on_window_opened(window_address, workspace_name, window_repr)) {
|
windows_to_create_.emplace_back(CreateWindow(workspace_name, window_address, window_repr));
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
void Workspace::initialize_window_map(const Json::Value &clients_data) {
|
||||||
window_map_.clear();
|
window_map_.clear();
|
||||||
for (auto client : clients_data) {
|
for (auto client : clients_data) {
|
||||||
if (client["workspace"]["id"].asInt() == id()) {
|
if (client["workspace"]["id"].asInt() == id()) {
|
||||||
// substr(2, ...) is necessary because Hyprland's JSON follows this format:
|
insert_window({client});
|
||||||
// 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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Workspace::insert_window(WindowAddress addr, std::string window_class,
|
void Workspace::insert_window(CreateWindow create_window_paylod) {
|
||||||
std::string window_title) {
|
if (!create_window_paylod.is_empty(workspace_manager_)) {
|
||||||
if (window_class.empty() &&
|
window_map_[create_window_paylod.addr()] = create_window_paylod.repr(workspace_manager_);
|
||||||
(!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;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -427,20 +439,9 @@ std::string Workspace::remove_window(WindowAddress addr) {
|
||||||
return window_repr;
|
return window_repr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Workspace::on_window_opened(WindowAddress &addr, std::string &workspace_name,
|
bool Workspace::on_window_opened(CreateWindow create_window_paylod) {
|
||||||
std::string window_repr) {
|
if (create_window_paylod.workspace_name() == name()) {
|
||||||
if (workspace_name == name()) {
|
insert_window(create_window_paylod);
|
||||||
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);
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
@ -456,17 +457,16 @@ std::optional<std::string> Workspace::on_window_closed(WindowAddress &addr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Workspaces::create_workspace(Json::Value &workspace_data, const Json::Value &clients_data) {
|
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(
|
auto workspace = std::find_if(
|
||||||
workspaces_.begin(), workspaces_.end(), [&](std::unique_ptr<Workspace> const &x) {
|
workspaces_.begin(), workspaces_.end(), [&](std::unique_ptr<Workspace> const &w) {
|
||||||
auto name = workspace_data["name"].asString();
|
return (workspace_name.starts_with("special:") && workspace_name.substr(8) == w->name()) ||
|
||||||
return x->is_persistent() &&
|
workspace_name == w->name();
|
||||||
((name.starts_with("special:") && name.substr(8) == x->name()) || name == x->name());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (workspace != workspaces_.end()) {
|
if (workspace != workspaces_.end()) {
|
||||||
// replace workspace, but keep persistent flag
|
return;
|
||||||
workspaces_.erase(workspace);
|
|
||||||
workspace_data["persistent"] = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new workspace
|
// create new workspace
|
||||||
|
@ -589,9 +589,6 @@ void Workspaces::init() {
|
||||||
monitor_id_ = (*current_monitor)["id"].asInt();
|
monitor_id_ = (*current_monitor)["id"].asInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
fill_persistent_workspaces();
|
|
||||||
create_persistent_workspaces();
|
|
||||||
|
|
||||||
const Json::Value workspaces_json = gIPC->getSocket1JsonReply("workspaces");
|
const Json::Value workspaces_json = gIPC->getSocket1JsonReply("workspaces");
|
||||||
const Json::Value clients_json = gIPC->getSocket1JsonReply("clients");
|
const Json::Value clients_json = gIPC->getSocket1JsonReply("clients");
|
||||||
|
|
||||||
|
@ -604,6 +601,9 @@ void Workspaces::init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fill_persistent_workspaces();
|
||||||
|
create_persistent_workspaces();
|
||||||
|
|
||||||
update_window_count();
|
update_window_count();
|
||||||
|
|
||||||
sort_workspaces();
|
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);
|
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<Repr>(window_)) {
|
||||||
|
return std::get<Repr>(window_);
|
||||||
|
} else if (std::holds_alternative<ClassAndTitle>(window_)) {
|
||||||
|
auto [window_class, window_title] = std::get<ClassAndTitle>(window_);
|
||||||
|
return workspace_manager.get_rewrite(window_class, window_title);
|
||||||
|
} else {
|
||||||
|
// Unreachable
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CreateWindow::is_empty(Workspaces &workspace_manager) {
|
||||||
|
if (std::holds_alternative<Repr>(window_)) {
|
||||||
|
return std::get<Repr>(window_).empty();
|
||||||
|
} else if (std::holds_alternative<ClassAndTitle>(window_)) {
|
||||||
|
auto [window_class, window_title] = std::get<ClassAndTitle>(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
|
} // namespace waybar::modules::hyprland
|
||||||
|
|
Loading…
Reference in New Issue