Merge pull request #2341 from zjeffer/hyprland/workspaces
Persistent workspaces in hyprland/workspaces
This commit is contained in:
commit
86b3e456e1
|
@ -18,11 +18,15 @@ class Workspace {
|
||||||
int id() const { return id_; };
|
int id() const { return id_; };
|
||||||
std::string name() const { return name_; };
|
std::string name() const { return name_; };
|
||||||
std::string output() const { return output_; };
|
std::string output() const { return output_; };
|
||||||
int active() const { return active_; };
|
bool active() const { return active_; };
|
||||||
bool is_special() const { return is_special_; };
|
bool is_special() const { return is_special_; };
|
||||||
|
bool is_persistent() const { return is_persistent_; };
|
||||||
|
bool is_empty() const { return windows_ == 0; };
|
||||||
|
|
||||||
auto handle_clicked(GdkEventButton* bt) -> bool;
|
auto handle_clicked(GdkEventButton* bt) -> bool;
|
||||||
void set_active(bool value = true) { active_ = value; };
|
void set_active(bool value = true) { active_ = value; };
|
||||||
|
void set_persistent(bool value = true) { is_persistent_ = value; };
|
||||||
|
void set_windows(uint value) { windows_ = value; };
|
||||||
|
|
||||||
void update(const std::string& format, const std::string& icon);
|
void update(const std::string& format, const std::string& icon);
|
||||||
|
|
||||||
|
@ -30,9 +34,10 @@ class Workspace {
|
||||||
int id_;
|
int id_;
|
||||||
std::string name_;
|
std::string name_;
|
||||||
std::string output_;
|
std::string output_;
|
||||||
int windows_;
|
uint windows_;
|
||||||
bool active_;
|
bool active_ = false;
|
||||||
bool is_special_;
|
bool is_special_ = false;
|
||||||
|
bool is_persistent_ = false;
|
||||||
|
|
||||||
Gtk::Button button_;
|
Gtk::Button button_;
|
||||||
Gtk::Box content_;
|
Gtk::Box content_;
|
||||||
|
@ -53,6 +58,7 @@ 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 sort_workspaces();
|
void sort_workspaces();
|
||||||
void create_workspace(Json::Value& value);
|
void create_workspace(Json::Value& value);
|
||||||
void remove_workspace(std::string name);
|
void remove_workspace(std::string name);
|
||||||
|
@ -60,10 +66,16 @@ class Workspaces : public AModule, public EventHandler {
|
||||||
bool all_outputs_ = false;
|
bool all_outputs_ = false;
|
||||||
bool show_special_ = false;
|
bool show_special_ = false;
|
||||||
|
|
||||||
|
void fill_persistent_workspaces();
|
||||||
|
void create_persistent_workspaces();
|
||||||
|
std::vector<std::string> persistent_workspaces_to_create_;
|
||||||
|
bool persistent_created_ = false;
|
||||||
|
|
||||||
std::string format_;
|
std::string format_;
|
||||||
std::map<std::string, std::string> icons_map_;
|
std::map<std::string, std::string> icons_map_;
|
||||||
bool with_icon_;
|
bool with_icon_;
|
||||||
std::string active_workspace_name;
|
uint64_t monitor_id_;
|
||||||
|
std::string active_workspace_name_;
|
||||||
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_;
|
||||||
|
|
|
@ -55,6 +55,9 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
|
||||||
gIPC->registerForIPC("destroyworkspace", this);
|
gIPC->registerForIPC("destroyworkspace", this);
|
||||||
gIPC->registerForIPC("focusedmon", this);
|
gIPC->registerForIPC("focusedmon", this);
|
||||||
gIPC->registerForIPC("moveworkspace", this);
|
gIPC->registerForIPC("moveworkspace", this);
|
||||||
|
gIPC->registerForIPC("openwindow", this);
|
||||||
|
gIPC->registerForIPC("closewindow", this);
|
||||||
|
gIPC->registerForIPC("movewindow", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Workspaces::update() -> void {
|
auto Workspaces::update() -> void {
|
||||||
|
@ -71,7 +74,7 @@ auto Workspaces::update() -> void {
|
||||||
workspaces_to_create_.clear();
|
workspaces_to_create_.clear();
|
||||||
|
|
||||||
for (auto &workspace : workspaces_) {
|
for (auto &workspace : workspaces_) {
|
||||||
workspace->set_active(workspace->name() == active_workspace_name);
|
workspace->set_active(workspace->name() == active_workspace_name_);
|
||||||
std::string &workspace_icon = icons_map_[""];
|
std::string &workspace_icon = icons_map_[""];
|
||||||
if (with_icon_) {
|
if (with_icon_) {
|
||||||
workspace_icon = workspace->select_icon(icons_map_);
|
workspace_icon = workspace->select_icon(icons_map_);
|
||||||
|
@ -87,7 +90,7 @@ void Workspaces::onEvent(const std::string &ev) {
|
||||||
std::string payload = ev.substr(eventName.size() + 2);
|
std::string payload = ev.substr(eventName.size() + 2);
|
||||||
|
|
||||||
if (eventName == "workspace") {
|
if (eventName == "workspace") {
|
||||||
active_workspace_name = payload;
|
active_workspace_name_ = payload;
|
||||||
|
|
||||||
} else if (eventName == "destroyworkspace") {
|
} else if (eventName == "destroyworkspace") {
|
||||||
workspaces_to_remove_.push_back(payload);
|
workspaces_to_remove_.push_back(payload);
|
||||||
|
@ -97,14 +100,14 @@ void Workspaces::onEvent(const std::string &ev) {
|
||||||
for (Json::Value workspace_json : workspaces_json) {
|
for (Json::Value workspace_json : workspaces_json) {
|
||||||
if (workspace_json["name"].asString() == payload &&
|
if (workspace_json["name"].asString() == payload &&
|
||||||
(all_outputs() || bar_.output->name == workspace_json["monitor"].asString()) &&
|
(all_outputs() || bar_.output->name == workspace_json["monitor"].asString()) &&
|
||||||
(show_special() || workspace_json["name"].asString().find("special") != 0)) {
|
(show_special() || workspace_json["name"].asString().starts_with("special"))) {
|
||||||
workspaces_to_create_.push_back(workspace_json);
|
workspaces_to_create_.push_back(workspace_json);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (eventName == "focusedmon") {
|
} else if (eventName == "focusedmon") {
|
||||||
active_workspace_name = payload.substr(payload.find(',') + 1);
|
active_workspace_name_ = payload.substr(payload.find(',') + 1);
|
||||||
|
|
||||||
} else if (eventName == "moveworkspace" && !all_outputs()) {
|
} else if (eventName == "moveworkspace" && !all_outputs()) {
|
||||||
std::string workspace = payload.substr(0, payload.find(','));
|
std::string workspace = payload.substr(0, payload.find(','));
|
||||||
|
@ -121,13 +124,47 @@ void Workspaces::onEvent(const std::string &ev) {
|
||||||
} else {
|
} else {
|
||||||
workspaces_to_remove_.push_back(workspace);
|
workspaces_to_remove_.push_back(workspace);
|
||||||
}
|
}
|
||||||
|
} else if (eventName == "openwindow" || eventName == "closewindow" || eventName == "movewindow") {
|
||||||
|
update_window_count();
|
||||||
}
|
}
|
||||||
|
|
||||||
dp.emit();
|
dp.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Workspaces::update_window_count() {
|
||||||
|
const Json::Value workspaces_json = gIPC->getSocket1JsonReply("workspaces");
|
||||||
|
for (auto &workspace : workspaces_) {
|
||||||
|
auto workspace_json = std::find_if(
|
||||||
|
workspaces_json.begin(), workspaces_json.end(),
|
||||||
|
[&](Json::Value const &x) { return x["name"].asString() == workspace->name(); });
|
||||||
|
if (workspace_json != workspaces_json.end()) {
|
||||||
|
try {
|
||||||
|
workspace->set_windows((*workspace_json)["windows"].asUInt());
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
spdlog::error("Failed to update window count: {}", e.what());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
workspace->set_windows(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Workspaces::create_workspace(Json::Value &value) {
|
void Workspaces::create_workspace(Json::Value &value) {
|
||||||
workspaces_.push_back(std::make_unique<Workspace>(value));
|
// replace the existing persistent workspace if it exists
|
||||||
|
auto workspace = std::find_if(
|
||||||
|
workspaces_.begin(), workspaces_.end(), [&](std::unique_ptr<Workspace> const &x) {
|
||||||
|
auto name = value["name"].asString();
|
||||||
|
return x->is_persistent() &&
|
||||||
|
((name.starts_with("special:") && name.substr(8) == x->name()) || name == x->name());
|
||||||
|
});
|
||||||
|
if (workspace != workspaces_.end()) {
|
||||||
|
// replace workspace, but keep persistent flag
|
||||||
|
workspaces_.erase(workspace);
|
||||||
|
value["persistent"] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new workspace
|
||||||
|
workspaces_.emplace_back(std::make_unique<Workspace>(value));
|
||||||
Gtk::Button &new_workspace_button = workspaces_.back()->button();
|
Gtk::Button &new_workspace_button = workspaces_.back()->button();
|
||||||
box_.pack_start(new_workspace_button, false, false);
|
box_.pack_start(new_workspace_button, false, false);
|
||||||
sort_workspaces();
|
sort_workspaces();
|
||||||
|
@ -139,6 +176,12 @@ void Workspaces::remove_workspace(std::string name) {
|
||||||
[&](std::unique_ptr<Workspace> &x) { return x->name() == name; });
|
[&](std::unique_ptr<Workspace> &x) { return x->name() == name; });
|
||||||
|
|
||||||
if (workspace == workspaces_.end()) {
|
if (workspace == workspaces_.end()) {
|
||||||
|
// happens when a workspace on another monitor is destroyed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((*workspace)->is_persistent()) {
|
||||||
|
// don't remove persistent workspaces, create_workspace will take care of replacement
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,16 +189,89 @@ void Workspaces::remove_workspace(std::string name) {
|
||||||
workspaces_.erase(workspace);
|
workspaces_.erase(workspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Workspaces::fill_persistent_workspaces() {
|
||||||
|
if (config_["persistent_workspaces"].isObject() && !all_outputs()) {
|
||||||
|
const Json::Value persistent_workspaces = config_["persistent_workspaces"];
|
||||||
|
const std::vector<std::string> keys = persistent_workspaces.getMemberNames();
|
||||||
|
|
||||||
|
for (const std::string &key : keys) {
|
||||||
|
const Json::Value &value = persistent_workspaces[key];
|
||||||
|
if (value.isInt()) {
|
||||||
|
// value is a number => create that many workspaces for this monitor
|
||||||
|
// only add if either:
|
||||||
|
// 1. key is "*" and this monitor is not already defined in the config
|
||||||
|
// 2. key is the current monitor name
|
||||||
|
if ((key == "*" && std::find(keys.begin(), keys.end(), bar_.output->name) == keys.end()) ||
|
||||||
|
key == bar_.output->name) {
|
||||||
|
int amount = value.asInt();
|
||||||
|
spdlog::debug("Creating {} persistent workspaces for monitor {}", amount,
|
||||||
|
bar_.output->name);
|
||||||
|
for (int i = 0; i < amount; i++) {
|
||||||
|
persistent_workspaces_to_create_.emplace_back(
|
||||||
|
std::to_string(monitor_id_ * amount + i + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (value.isArray() && !value.empty()) {
|
||||||
|
// value is an array => key is a workspace name
|
||||||
|
// values are monitor names this workspace should be shown on
|
||||||
|
for (const Json::Value &monitor : value) {
|
||||||
|
if (monitor.isString() && monitor.asString() == bar_.output->name) {
|
||||||
|
persistent_workspaces_to_create_.emplace_back(key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Workspaces::create_persistent_workspaces() {
|
||||||
|
for (const std::string &workspace_name : persistent_workspaces_to_create_) {
|
||||||
|
Json::Value new_workspace;
|
||||||
|
try {
|
||||||
|
// numbered persistent workspaces get the name as ID
|
||||||
|
new_workspace["id"] = workspace_name == "special" ? -99 : std::stoi(workspace_name);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
// named persistent workspaces start with ID=0
|
||||||
|
new_workspace["id"] = 0;
|
||||||
|
}
|
||||||
|
new_workspace["name"] = workspace_name;
|
||||||
|
new_workspace["monitor"] = bar_.output->name;
|
||||||
|
new_workspace["windows"] = 0;
|
||||||
|
new_workspace["persistent"] = true;
|
||||||
|
|
||||||
|
create_workspace(new_workspace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Workspaces::init() {
|
void Workspaces::init() {
|
||||||
active_workspace_name = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString();
|
active_workspace_name_ = (gIPC->getSocket1JsonReply("activeworkspace"))["name"].asString();
|
||||||
|
|
||||||
|
// get monitor ID from name (used by persistent workspaces)
|
||||||
|
monitor_id_ = 0;
|
||||||
|
auto monitors = gIPC->getSocket1JsonReply("monitors");
|
||||||
|
auto current_monitor = std::find_if(
|
||||||
|
monitors.begin(), monitors.end(),
|
||||||
|
[this](const Json::Value &m) { return m["name"].asString() == bar_.output->name; });
|
||||||
|
if (current_monitor == monitors.end()) {
|
||||||
|
spdlog::error("Monitor '{}' does not have an ID? Using 0", bar_.output->name);
|
||||||
|
} else {
|
||||||
|
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");
|
||||||
for (Json::Value workspace_json : workspaces_json) {
|
for (Json::Value workspace_json : workspaces_json) {
|
||||||
if ((all_outputs() || bar_.output->name == workspace_json["monitor"].asString()) &&
|
if ((all_outputs() || bar_.output->name == workspace_json["monitor"].asString()) &&
|
||||||
(workspace_json["name"].asString().find("special") != 0 || show_special()))
|
(workspace_json["name"].asString().starts_with("special") || show_special()))
|
||||||
create_workspace(workspace_json);
|
create_workspace(workspace_json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_window_count();
|
||||||
|
|
||||||
sort_workspaces();
|
sort_workspaces();
|
||||||
|
|
||||||
dp.emit();
|
dp.emit();
|
||||||
|
@ -171,16 +287,19 @@ Workspace::Workspace(const Json::Value &workspace_data)
|
||||||
: id_(workspace_data["id"].asInt()),
|
: id_(workspace_data["id"].asInt()),
|
||||||
name_(workspace_data["name"].asString()),
|
name_(workspace_data["name"].asString()),
|
||||||
output_(workspace_data["monitor"].asString()), // TODO:allow using monitor desc
|
output_(workspace_data["monitor"].asString()), // TODO:allow using monitor desc
|
||||||
windows_(workspace_data["id"].asInt()),
|
windows_(workspace_data["windows"].asInt()),
|
||||||
active_(true),
|
active_(true) {
|
||||||
is_special_(false) {
|
if (name_.starts_with("name:")) {
|
||||||
if (name_.find("name:") == 0) {
|
|
||||||
name_ = name_.substr(5);
|
name_ = name_.substr(5);
|
||||||
} else if (name_.find("special") == 0) {
|
} else if (name_.starts_with("special")) {
|
||||||
name_ = id_ == -99 ? name_ : name_.substr(13);
|
name_ = id_ == -99 ? name_ : name_.substr(8);
|
||||||
is_special_ = true;
|
is_special_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (workspace_data.isMember("persistent")) {
|
||||||
|
is_persistent_ = workspace_data["persistent"].asBool();
|
||||||
|
}
|
||||||
|
|
||||||
button_.add_events(Gdk::BUTTON_PRESS_MASK);
|
button_.add_events(Gdk::BUTTON_PRESS_MASK);
|
||||||
button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked),
|
button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked),
|
||||||
false);
|
false);
|
||||||
|
@ -202,28 +321,45 @@ void add_or_remove_class(const Glib::RefPtr<Gtk::StyleContext> &context, bool co
|
||||||
void Workspace::update(const std::string &format, const std::string &icon) {
|
void Workspace::update(const std::string &format, const std::string &icon) {
|
||||||
auto style_context = button_.get_style_context();
|
auto style_context = button_.get_style_context();
|
||||||
add_or_remove_class(style_context, active(), "active");
|
add_or_remove_class(style_context, active(), "active");
|
||||||
|
add_or_remove_class(style_context, is_special(), "special");
|
||||||
|
add_or_remove_class(style_context, is_empty(), "persistent");
|
||||||
|
|
||||||
label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg("id", id()),
|
label_.set_markup(
|
||||||
fmt::arg("name", name()), fmt::arg("icon", icon)));
|
fmt::format(fmt::runtime(format), fmt::arg("name", name()), fmt::arg("icon", icon)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Workspaces::sort_workspaces() {
|
void Workspaces::sort_workspaces() {
|
||||||
std::sort(workspaces_.begin(), workspaces_.end(),
|
std::sort(workspaces_.begin(), workspaces_.end(),
|
||||||
[](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) {
|
[](std::unique_ptr<Workspace> &a, std::unique_ptr<Workspace> &b) {
|
||||||
// normal -> named -> special -> named special
|
// normal -> named persistent -> named -> special -> named special
|
||||||
|
|
||||||
|
// both normal (includes numbered persistent) => sort by ID
|
||||||
if (a->id() > 0 && b->id() > 0) {
|
if (a->id() > 0 && b->id() > 0) {
|
||||||
return a->id() < b->id();
|
return a->id() < b->id();
|
||||||
}
|
}
|
||||||
if (a->id() < 0 && b->id() < 0) {
|
|
||||||
if ((a->is_special()) ^ (a->is_special())) {
|
// one normal, one special => normal first
|
||||||
return a->id() > b->id();
|
if ((a->is_special()) ^ (b->is_special())) {
|
||||||
}
|
return b->is_special();
|
||||||
return a->id() < b->id();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only one normal, one named
|
||||||
if ((a->id() > 0) ^ (b->id() > 0)) {
|
if ((a->id() > 0) ^ (b->id() > 0)) {
|
||||||
return a->id() > b->id();
|
return a->id() > 0;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort non-special named workspaces by name (ID <= -1377)
|
||||||
|
return a->name() < b->name();
|
||||||
});
|
});
|
||||||
|
|
||||||
for (size_t i = 0; i < workspaces_.size(); ++i) {
|
for (size_t i = 0; i < workspaces_.size(); ++i) {
|
||||||
|
@ -239,11 +375,25 @@ std::string &Workspace::select_icon(std::map<std::string, std::string> &icons_ma
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_special()) {
|
||||||
|
auto special_icon_it = icons_map.find("special");
|
||||||
|
if (special_icon_it != icons_map.end()) {
|
||||||
|
return special_icon_it->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto named_icon_it = icons_map.find(std::to_string(id()));
|
auto named_icon_it = icons_map.find(std::to_string(id()));
|
||||||
if (named_icon_it != icons_map.end()) {
|
if (named_icon_it != icons_map.end()) {
|
||||||
return named_icon_it->second;
|
return named_icon_it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_persistent()) {
|
||||||
|
auto persistent_icon_it = icons_map.find("persistent");
|
||||||
|
if (persistent_icon_it != icons_map.end()) {
|
||||||
|
return persistent_icon_it->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto default_icon_it = icons_map.find("default");
|
auto default_icon_it = icons_map.find("default");
|
||||||
if (default_icon_it != icons_map.end()) {
|
if (default_icon_it != icons_map.end()) {
|
||||||
return default_icon_it->second;
|
return default_icon_it->second;
|
||||||
|
@ -253,12 +403,12 @@ std::string &Workspace::select_icon(std::map<std::string, std::string> &icons_ma
|
||||||
|
|
||||||
auto Workspace::handle_clicked(GdkEventButton *bt) -> bool {
|
auto Workspace::handle_clicked(GdkEventButton *bt) -> bool {
|
||||||
try {
|
try {
|
||||||
if (id() > 0) { // normal
|
if (id() > 0) { // normal or numbered persistent
|
||||||
gIPC->getSocket1Reply("dispatch workspace " + std::to_string(id()));
|
gIPC->getSocket1Reply("dispatch workspace " + std::to_string(id()));
|
||||||
} else if (!is_special()) { // named normal
|
} else if (!is_special()) { // named
|
||||||
gIPC->getSocket1Reply("dispatch workspace name" + name());
|
gIPC->getSocket1Reply("dispatch workspace name:" + name());
|
||||||
} else if (id() != -99) { // named special
|
} else if (id() != -99) { // named special
|
||||||
gIPC->getSocket1Reply("dispatch togglespecialworkspace name" + name());
|
gIPC->getSocket1Reply("dispatch togglespecialworkspace " + name());
|
||||||
} else { // special
|
} else { // special
|
||||||
gIPC->getSocket1Reply("dispatch togglespecialworkspace");
|
gIPC->getSocket1Reply("dispatch togglespecialworkspace");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue