Waybar/src/modules/sway/workspaces.cpp

372 lines
14 KiB
C++
Raw Normal View History

2018-08-15 18:17:17 +00:00
#include "modules/sway/workspaces.hpp"
2019-05-18 23:44:45 +00:00
#include <spdlog/spdlog.h>
2018-08-08 21:54:58 +00:00
#include <algorithm>
#include <cctype>
#include <string>
2019-04-19 09:09:06 +00:00
namespace waybar::modules::sway {
// Helper function to to assign a number to a workspace, just like sway. In fact
// this is taken quite verbatim from `sway/ipc-json.c`.
int Workspaces::convertWorkspaceNameToNum(std::string name) {
if (isdigit(name[0])) {
errno = 0;
2022-04-06 06:37:19 +00:00
char *endptr = NULL;
long long parsed_num = strtoll(name.c_str(), &endptr, 10);
if (errno != 0 || parsed_num > INT32_MAX || parsed_num < 0 || endptr == name.c_str()) {
return -1;
} else {
return (int)parsed_num;
}
}
return -1;
}
2019-04-19 09:09:06 +00:00
Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
2019-06-15 12:57:52 +00:00
: AModule(config, "workspaces", id, false, !config["disable-scroll"].asBool()),
bar_(bar),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) {
2018-08-16 12:29:41 +00:00
box_.set_name("workspaces");
2018-12-18 16:30:54 +00:00
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
2019-06-15 12:57:52 +00:00
event_box_.add(box_);
2019-04-24 10:37:24 +00:00
ipc_.subscribe(R"(["workspace"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &Workspaces::onEvent));
2019-04-19 09:09:06 +00:00
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Workspaces::onCmd));
ipc_.sendCmd(IPC_GET_WORKSPACES);
if (config["enable-bar-scroll"].asBool()) {
auto &window = const_cast<Bar &>(bar_).window;
2019-05-17 07:59:37 +00:00
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
window.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
}
2018-08-20 12:50:45 +00:00
// Launch worker
ipc_.setWorker([this] {
try {
ipc_.handleEvent();
} catch (const std::exception &e) {
spdlog::error("Workspaces: {}", e.what());
}
});
2018-08-20 12:50:45 +00:00
}
2019-05-13 13:15:50 +00:00
void Workspaces::onEvent(const struct Ipc::ipc_response &res) {
try {
ipc_.sendCmd(IPC_GET_WORKSPACES);
} catch (const std::exception &e) {
2019-05-18 23:44:45 +00:00
spdlog::error("Workspaces: {}", e.what());
2019-05-13 13:15:50 +00:00
}
}
2019-04-24 10:37:24 +00:00
void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
if (res.type == IPC_GET_WORKSPACES) {
2019-05-09 08:30:54 +00:00
try {
2019-06-17 09:39:45 +00:00
{
std::lock_guard<std::mutex> lock(mutex_);
2022-04-06 06:37:19 +00:00
auto payload = parser_.parse(res.payload);
2019-05-09 08:30:54 +00:00
workspaces_.clear();
2022-04-06 06:37:19 +00:00
std::copy_if(payload.begin(), payload.end(), std::back_inserter(workspaces_),
2019-05-09 08:30:54 +00:00
[&](const auto &workspace) {
return !config_["all-outputs"].asBool()
? workspace["output"].asString() == bar_.output->name
: true;
});
2019-08-31 17:57:44 +00:00
// adding persistent workspaces (as per the config file)
if (config_["persistent_workspaces"].isObject()) {
2022-04-06 06:37:19 +00:00
const Json::Value &p_workspaces = config_["persistent_workspaces"];
2019-05-20 18:46:44 +00:00
const std::vector<std::string> p_workspaces_names = p_workspaces.getMemberNames();
2019-05-20 18:46:44 +00:00
for (const std::string &p_w_name : p_workspaces_names) {
const Json::Value &p_w = p_workspaces[p_w_name];
2022-04-06 06:37:19 +00:00
auto it =
2019-05-20 18:46:44 +00:00
std::find_if(payload.begin(), payload.end(), [&p_w_name](const Json::Value &node) {
return node["name"].asString() == p_w_name;
});
2019-05-20 18:46:44 +00:00
if (it != payload.end()) {
continue; // already displayed by some bar
}
2019-05-18 15:58:01 +00:00
2019-05-20 18:46:44 +00:00
if (p_w.isArray() && !p_w.empty()) {
// Adding to target outputs
for (const Json::Value &output : p_w) {
if (output.asString() == bar_.output->name) {
Json::Value v;
v["name"] = p_w_name;
v["target_output"] = bar_.output->name;
v["num"] = convertWorkspaceNameToNum(p_w_name);
2019-05-20 18:46:44 +00:00
workspaces_.emplace_back(std::move(v));
break;
}
}
2019-05-20 18:46:44 +00:00
} else {
// Adding to all outputs
Json::Value v;
v["name"] = p_w_name;
v["target_output"] = "";
v["num"] = convertWorkspaceNameToNum(p_w_name);
2019-05-20 18:46:44 +00:00
workspaces_.emplace_back(std::move(v));
2019-05-18 16:04:09 +00:00
}
}
}
2019-05-18 15:58:01 +00:00
// sway has a defined ordering of workspaces that should be preserved in
// the representation displayed by waybar to ensure that commands such
// as "workspace prev" or "workspace next" make sense when looking at
// the workspace representation in the bar.
// Due to waybar's own feature of persistent workspaces unknown to sway,
// custom sorting logic is necessary to make these workspaces appear
// naturally in the list of workspaces without messing up sway's
// sorting. For this purpose, a custom numbering property is created
// that preserves the order provided by sway while inserting numbered
// persistent workspaces at their natural positions.
//
// All of this code assumes that sway provides numbered workspaces first
// and other workspaces are sorted by their creation time.
//
// In a first pass, the maximum "num" value is computed to enqueue
// unnumbered workspaces behind numbered ones when computing the sort
// attribute.
int max_num = -1;
2022-04-06 06:37:19 +00:00
for (auto &workspace : workspaces_) {
max_num = std::max(workspace["num"].asInt(), max_num);
}
2022-04-06 06:37:19 +00:00
for (auto &workspace : workspaces_) {
auto workspace_num = workspace["num"].asInt();
if (workspace_num > -1) {
workspace["sort"] = workspace_num;
} else {
workspace["sort"] = ++max_num;
}
}
2022-04-06 06:37:19 +00:00
std::sort(workspaces_.begin(), workspaces_.end(),
[](const Json::Value &lhs, const Json::Value &rhs) {
auto lname = lhs["name"].asString();
auto rname = rhs["name"].asString();
2022-04-06 06:37:19 +00:00
int l = lhs["sort"].asInt();
int r = rhs["sort"].asInt();
if (l == r) {
// In case both integers are the same, lexicographical
// sort. The code above already ensure that this will only
// happend in case of explicitly numbered workspaces.
return lname < rname;
}
return l < r;
});
2019-05-09 08:30:54 +00:00
}
2019-06-17 09:39:45 +00:00
dp.emit();
2019-05-09 08:30:54 +00:00
} catch (const std::exception &e) {
2019-05-18 23:44:45 +00:00
spdlog::error("Workspaces: {}", e.what());
}
2019-04-19 09:09:06 +00:00
}
}
bool Workspaces::filterButtons() {
2019-04-19 10:11:55 +00:00
bool needReorder = false;
2018-08-16 12:29:41 +00:00
for (auto it = buttons_.begin(); it != buttons_.end();) {
2022-04-06 06:37:19 +00:00
auto ws = std::find_if(workspaces_.begin(), workspaces_.end(),
[it](const auto &node) { return node["name"].asString() == it->first; });
if (ws == workspaces_.end() ||
(!config_["all-outputs"].asBool() && (*ws)["output"].asString() != bar_.output->name)) {
2019-04-18 15:52:00 +00:00
it = buttons_.erase(it);
2018-08-10 16:02:12 +00:00
needReorder = true;
2018-08-16 12:29:41 +00:00
} else {
2018-08-15 12:30:01 +00:00
++it;
2018-08-16 12:29:41 +00:00
}
2018-08-08 21:54:58 +00:00
}
return needReorder;
}
auto Workspaces::update() -> void {
2019-06-17 09:39:45 +00:00
std::lock_guard<std::mutex> lock(mutex_);
2022-04-06 06:37:19 +00:00
bool needReorder = filterButtons();
for (auto it = workspaces_.begin(); it != workspaces_.end(); ++it) {
auto bit = buttons_.find((*it)["name"].asString());
if (bit == buttons_.end()) {
2018-08-10 16:02:12 +00:00
needReorder = true;
}
auto &button = bit == buttons_.end() ? addButton(*it) : bit->second;
if ((*it)["focused"].asBool()) {
button.get_style_context()->add_class("focused");
2018-08-08 21:54:58 +00:00
} else {
button.get_style_context()->remove_class("focused");
2018-08-08 21:54:58 +00:00
}
if ((*it)["visible"].asBool()) {
button.get_style_context()->add_class("visible");
} else {
button.get_style_context()->remove_class("visible");
}
if ((*it)["urgent"].asBool()) {
button.get_style_context()->add_class("urgent");
} else {
button.get_style_context()->remove_class("urgent");
}
if ((*it)["target_output"].isString()) {
2019-08-31 17:57:44 +00:00
button.get_style_context()->add_class("persistent");
} else {
2019-08-31 17:57:44 +00:00
button.get_style_context()->remove_class("persistent");
}
if ((*it)["output"].isString()) {
if (((*it)["output"].asString()) == bar_.output->name) {
button.get_style_context()->add_class("current_output");
} else {
button.get_style_context()->remove_class("current_output");
}
} else {
button.get_style_context()->remove_class("current_output");
}
if (needReorder) {
box_.reorder_child(button, it - workspaces_.begin());
}
std::string output = (*it)["name"].asString();
if (config_["format"].isString()) {
auto format = config_["format"].asString();
2022-04-06 06:37:19 +00:00
output = fmt::format(format, fmt::arg("icon", getIcon(output, *it)),
fmt::arg("value", output), fmt::arg("name", trimWorkspaceName(output)),
fmt::arg("index", (*it)["num"].asString()));
}
if (!config_["disable-markup"].asBool()) {
static_cast<Gtk::Label *>(button.get_children()[0])->set_markup(output);
} else {
button.set_label(output);
}
onButtonReady(*it, button);
2018-08-16 12:29:41 +00:00
}
2020-04-12 16:30:21 +00:00
// Call parent update
AModule::update();
2018-08-08 21:54:58 +00:00
}
Gtk::Button &Workspaces::addButton(const Json::Value &node) {
2022-04-06 06:37:19 +00:00
auto pair = buttons_.emplace(node["name"].asString(), node["name"].asString());
2019-06-15 12:57:52 +00:00
auto &&button = pair.first->second;
2018-08-16 12:29:41 +00:00
box_.pack_start(button, false, false, 0);
button.set_name("sway-workspace-" + node["name"].asString());
2018-08-08 21:54:58 +00:00
button.set_relief(Gtk::RELIEF_NONE);
if (!config_["disable-click"].asBool()) {
button.signal_pressed().connect([this, node] {
try {
if (node["target_output"].isString()) {
2022-04-06 06:37:19 +00:00
ipc_.sendCmd(IPC_COMMAND,
fmt::format(workspace_switch_cmd_ + "; move workspace to output \"{}\"; " +
workspace_switch_cmd_,
"--no-auto-back-and-forth", node["name"].asString(),
node["target_output"].asString(), "--no-auto-back-and-forth",
node["name"].asString()));
} else {
2022-04-06 06:37:19 +00:00
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace {} \"{}\"",
config_["disable-auto-back-and-forth"].asBool()
? "--no-auto-back-and-forth"
: "",
node["name"].asString()));
}
} catch (const std::exception &e) {
spdlog::error("Workspaces: {}", e.what());
}
});
}
return button;
2018-08-08 21:54:58 +00:00
}
2019-04-19 09:09:06 +00:00
std::string Workspaces::getIcon(const std::string &name, const Json::Value &node) {
std::vector<std::string> keys = {name, "urgent", "focused", "visible", "default"};
for (auto const &key : keys) {
if (key == "focused" || key == "visible" || key == "urgent") {
2018-10-26 07:27:16 +00:00
if (config_["format-icons"][key].isString() && node[key].asBool()) {
return config_["format-icons"][key].asString();
}
} else if (config_["format_icons"]["persistent"].isString() &&
node["target_output"].isString()) {
return config_["format-icons"]["persistent"].asString();
2018-10-26 07:27:16 +00:00
} else if (config_["format-icons"][key].isString()) {
return config_["format-icons"][key].asString();
} else if (config_["format-icons"][trimWorkspaceName(key)].isString()) {
return config_["format-icons"][trimWorkspaceName(key)].asString();
}
2018-08-16 12:29:41 +00:00
}
2018-08-15 12:48:08 +00:00
return name;
}
2019-04-19 09:09:06 +00:00
bool Workspaces::handleScroll(GdkEventScroll *e) {
if (gdk_event_get_pointer_emulated((GdkEvent *)e)) {
/**
* Ignore emulated scroll events on window
*/
return false;
}
2019-06-15 12:57:52 +00:00
auto dir = AModule::getScrollDir(e);
if (dir == SCROLL_DIR::NONE) {
return true;
}
std::string name;
{
std::lock_guard<std::mutex> lock(mutex_);
2022-04-06 06:37:19 +00:00
auto it = std::find_if(workspaces_.begin(), workspaces_.end(),
[](const auto &workspace) { return workspace["focused"].asBool(); });
if (it == workspaces_.end()) {
return true;
}
if (dir == SCROLL_DIR::DOWN || dir == SCROLL_DIR::RIGHT) {
name = getCycleWorkspace(it, false);
} else if (dir == SCROLL_DIR::UP || dir == SCROLL_DIR::LEFT) {
name = getCycleWorkspace(it, true);
} else {
return true;
}
if (name == (*it)["name"].asString()) {
return true;
}
2018-08-14 09:26:06 +00:00
}
2019-05-13 13:15:50 +00:00
try {
2022-04-06 06:37:19 +00:00
ipc_.sendCmd(IPC_COMMAND, fmt::format(workspace_switch_cmd_, "--no-auto-back-and-forth", name));
2019-05-13 13:15:50 +00:00
} catch (const std::exception &e) {
2019-05-18 23:44:45 +00:00
spdlog::error("Workspaces: {}", e.what());
2019-05-13 13:15:50 +00:00
}
2018-08-14 09:26:06 +00:00
return true;
}
const std::string Workspaces::getCycleWorkspace(std::vector<Json::Value>::iterator it,
2022-04-06 06:37:19 +00:00
bool prev) const {
if (prev && it == workspaces_.begin() && !config_["disable-scroll-wraparound"].asBool()) {
return (*(--workspaces_.end()))["name"].asString();
2018-08-16 12:29:41 +00:00
}
if (prev && it != workspaces_.begin())
--it;
else if (!prev && it != workspaces_.end())
++it;
if (!prev && it == workspaces_.end()) {
if (config_["disable-scroll-wraparound"].asBool()) {
--it;
} else {
return (*(workspaces_.begin()))["name"].asString();
}
2019-02-01 23:36:52 +00:00
}
return (*it)["name"].asString();
2018-08-08 21:54:58 +00:00
}
2018-12-28 17:35:21 +00:00
2019-04-19 09:09:06 +00:00
std::string Workspaces::trimWorkspaceName(std::string name) {
2019-04-24 10:37:24 +00:00
std::size_t found = name.find(':');
if (found != std::string::npos) {
return name.substr(found + 1);
2018-12-28 17:35:21 +00:00
}
return name;
}
2019-02-01 23:36:52 +00:00
2019-04-19 09:09:06 +00:00
void Workspaces::onButtonReady(const Json::Value &node, Gtk::Button &button) {
if (config_["current-only"].asBool()) {
if (node["focused"].asBool()) {
button.show();
} else {
button.hide();
}
} else {
button.show();
}
2019-03-03 10:35:32 +00:00
}
} // namespace waybar::modules::sway