2018-08-15 18:17:17 +00:00
|
|
|
#include "modules/sway/workspaces.hpp"
|
2018-08-08 21:54:58 +00:00
|
|
|
|
2019-04-19 09:09:06 +00:00
|
|
|
namespace waybar::modules::sway {
|
|
|
|
|
|
|
|
Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
|
2019-04-18 15:43:16 +00:00
|
|
|
: bar_(bar),
|
|
|
|
config_(config),
|
|
|
|
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
|
|
|
|
scrolling_(false) {
|
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-04-24 10:37:24 +00:00
|
|
|
ipc_.subscribe(R"(["workspace"])");
|
2019-04-19 14:48:02 +00:00
|
|
|
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));
|
2019-04-19 14:48:02 +00:00
|
|
|
ipc_.sendCmd(IPC_GET_WORKSPACES);
|
2019-05-17 16:42:11 +00:00
|
|
|
if (!config["disable-bar-scroll"].asBool()) {
|
2019-05-20 11:21:22 +00:00
|
|
|
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
|
|
|
|
worker();
|
|
|
|
}
|
|
|
|
|
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) {
|
|
|
|
std::cerr << "Workspaces: " << e.what() << std::endl;
|
|
|
|
}
|
|
|
|
}
|
2019-04-19 14:48:02 +00:00
|
|
|
|
2019-04-24 10:37:24 +00:00
|
|
|
void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
|
2019-04-19 14:48:02 +00:00
|
|
|
if (res.type == IPC_GET_WORKSPACES) {
|
2019-05-09 08:30:54 +00:00
|
|
|
try {
|
|
|
|
auto payload = parser_.parse(res.payload);
|
|
|
|
if (payload.isArray()) {
|
|
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
|
|
workspaces_.clear();
|
|
|
|
std::copy_if(payload.begin(),
|
|
|
|
payload.end(),
|
|
|
|
std::back_inserter(workspaces_),
|
|
|
|
[&](const auto &workspace) {
|
|
|
|
return !config_["all-outputs"].asBool()
|
|
|
|
? workspace["output"].asString() == bar_.output->name
|
|
|
|
: true;
|
|
|
|
});
|
2019-05-18 14:58:55 +00:00
|
|
|
|
|
|
|
// adding persistant workspaces (as per the config file)
|
2019-05-20 11:21:22 +00:00
|
|
|
const Json::Value & p_workspaces = config_["persistant_workspaces"];
|
2019-05-18 14:58:55 +00:00
|
|
|
const std::vector<std::string> p_workspaces_names = p_workspaces.getMemberNames();
|
2019-05-18 16:04:09 +00:00
|
|
|
for (const std::string &p_w_name : p_workspaces_names) {
|
|
|
|
const Json::Value &p_w = p_workspaces[p_w_name];
|
2019-05-20 11:21:22 +00:00
|
|
|
auto it =
|
2019-05-18 16:04:09 +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-18 14:58:55 +00:00
|
|
|
if (it != payload.end()) {
|
2019-05-18 16:04:09 +00:00
|
|
|
continue; // already displayed by some bar
|
2019-05-18 14:58:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (p_w.isArray() && !p_w.empty()) {
|
2019-05-18 16:04:09 +00:00
|
|
|
// Adding to target outputs
|
|
|
|
for (const Json::Value &output : p_w) {
|
2019-05-18 14:58:55 +00:00
|
|
|
if (output.asString() == bar_.output->name) {
|
|
|
|
Json::Value v;
|
|
|
|
v["name"] = p_w_name;
|
2019-05-20 11:21:22 +00:00
|
|
|
v["target_output"] = bar_.output->name;
|
2019-05-18 14:58:55 +00:00
|
|
|
workspaces_.emplace_back(std::move(v));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2019-05-18 16:04:09 +00:00
|
|
|
// Adding to all outputs
|
2019-05-18 14:58:55 +00:00
|
|
|
Json::Value v;
|
|
|
|
v["name"] = p_w_name;
|
|
|
|
workspaces_.emplace_back(std::move(v));
|
|
|
|
}
|
|
|
|
}
|
2019-05-18 15:58:01 +00:00
|
|
|
|
|
|
|
if (workspaces_order_.empty()) {
|
2019-05-18 16:04:09 +00:00
|
|
|
// Saving starting order
|
2019-05-20 11:31:02 +00:00
|
|
|
workspaces_order_.reserve(payload.size());
|
|
|
|
for (const Json::Value &workspace : payload) {
|
2019-05-18 16:04:09 +00:00
|
|
|
workspaces_order_.emplace_back(workspace["name"].asString());
|
|
|
|
}
|
2019-05-18 15:58:01 +00:00
|
|
|
} else {
|
2019-05-18 16:04:09 +00:00
|
|
|
// Ordering workspaces as it was before for current output
|
|
|
|
std::vector<Json::Value> sorted_workspaces;
|
|
|
|
sorted_workspaces.reserve(workspaces_.size());
|
|
|
|
auto ws_end = workspaces_.end();
|
|
|
|
for (const std::string &name_by_order : workspaces_order_) {
|
|
|
|
auto it =
|
|
|
|
std::find_if(workspaces_.begin(), ws_end, [&name_by_order](const Json::Value &ws) {
|
|
|
|
return ws["name"].asString() == name_by_order;
|
|
|
|
});
|
|
|
|
if (it != ws_end) {
|
|
|
|
sorted_workspaces.emplace_back(*it);
|
|
|
|
--ws_end;
|
2019-05-20 11:21:22 +00:00
|
|
|
if (ws_end == workspaces_.begin()) { // we've extracted everything
|
2019-05-18 16:09:30 +00:00
|
|
|
break;
|
|
|
|
}
|
2019-05-18 16:04:09 +00:00
|
|
|
ws_end->swap(*it);
|
|
|
|
}
|
|
|
|
}
|
2019-05-18 15:58:01 +00:00
|
|
|
|
2019-05-18 22:21:01 +00:00
|
|
|
// Adding new workspaces to the output (those were never showed in this output before)
|
2019-05-18 16:04:09 +00:00
|
|
|
for (int i = 0; workspaces_.size() > sorted_workspaces.size(); ++i) {
|
|
|
|
workspaces_order_.emplace_back(workspaces_[i]["name"].asString());
|
|
|
|
sorted_workspaces.emplace_back(workspaces_[i]);
|
|
|
|
}
|
|
|
|
workspaces_.swap(sorted_workspaces);
|
2019-05-18 15:58:01 +00:00
|
|
|
}
|
2019-05-18 14:58:55 +00:00
|
|
|
|
2019-05-09 08:30:54 +00:00
|
|
|
dp.emit();
|
|
|
|
}
|
|
|
|
} catch (const std::exception &e) {
|
|
|
|
std::cerr << "Workspaces: " << e.what() << std::endl;
|
2019-04-23 09:41:49 +00:00
|
|
|
}
|
2019-04-19 14:48:02 +00:00
|
|
|
} else {
|
|
|
|
if (scrolling_) {
|
|
|
|
scrolling_ = false;
|
|
|
|
}
|
2019-04-19 09:09:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Workspaces::worker() {
|
2018-08-16 12:29:41 +00:00
|
|
|
thread_ = [this] {
|
2018-08-13 19:23:43 +00:00
|
|
|
try {
|
2019-04-19 14:48:02 +00:00
|
|
|
ipc_.handleEvent();
|
2019-04-18 15:43:16 +00:00
|
|
|
} catch (const std::exception &e) {
|
2018-12-09 09:49:28 +00:00
|
|
|
std::cerr << "Workspaces: " << e.what() << std::endl;
|
2018-08-13 19:23:43 +00:00
|
|
|
}
|
2018-08-09 18:22:01 +00:00
|
|
|
};
|
2018-08-08 21:54:58 +00:00
|
|
|
}
|
|
|
|
|
2019-04-19 14:48:02 +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();) {
|
2019-04-19 14:48:02 +00:00
|
|
|
auto ws = std::find_if(workspaces_.begin(), workspaces_.end(), [it](const auto &node) {
|
2019-04-18 15:43:16 +00:00
|
|
|
return node["name"].asString() == it->first;
|
|
|
|
});
|
2019-04-19 14:48:02 +00:00
|
|
|
if (ws == workspaces_.end() ||
|
2019-04-18 15:43:16 +00:00
|
|
|
(!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
|
|
|
}
|
2019-04-19 14:48:02 +00:00
|
|
|
return needReorder;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto Workspaces::update() -> void {
|
2019-04-23 09:41:49 +00:00
|
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
|
|
bool needReorder = filterButtons();
|
2019-04-19 14:48:02 +00:00
|
|
|
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;
|
2019-04-19 14:48:02 +00:00
|
|
|
}
|
|
|
|
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 {
|
2019-04-19 14:48:02 +00:00
|
|
|
button.get_style_context()->remove_class("focused");
|
2018-08-08 21:54:58 +00:00
|
|
|
}
|
2019-04-19 14:48:02 +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 (needReorder) {
|
|
|
|
box_.reorder_child(button, it - workspaces_.begin());
|
|
|
|
}
|
|
|
|
std::string output = getIcon((*it)["name"].asString(), *it);
|
|
|
|
if (config_["format"].isString()) {
|
|
|
|
auto format = config_["format"].asString();
|
|
|
|
output = fmt::format(format,
|
|
|
|
fmt::arg("icon", output),
|
|
|
|
fmt::arg("name", trimWorkspaceName((*it)["name"].asString())),
|
|
|
|
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
|
|
|
}
|
2018-08-08 21:54:58 +00:00
|
|
|
}
|
|
|
|
|
2019-04-19 14:48:02 +00:00
|
|
|
Gtk::Button &Workspaces::addButton(const Json::Value &node) {
|
|
|
|
auto pair = buttons_.emplace(node["name"].asString(), node["name"].asString());
|
2018-08-08 21:54:58 +00:00
|
|
|
auto &button = pair.first->second;
|
2018-08-16 12:29:41 +00:00
|
|
|
box_.pack_start(button, false, false, 0);
|
2018-08-08 21:54:58 +00:00
|
|
|
button.set_relief(Gtk::RELIEF_NONE);
|
2019-05-20 11:21:22 +00:00
|
|
|
button.signal_clicked().connect([this, node] {
|
2018-08-11 00:09:39 +00:00
|
|
|
try {
|
2019-05-20 11:21:22 +00:00
|
|
|
if (node["target_output"].isString()) {
|
|
|
|
ipc_.sendCmd(
|
|
|
|
IPC_COMMAND,
|
|
|
|
fmt::format("workspace \"{}\"; move workspace to output \"{}\"; workspace \"{}\"",
|
|
|
|
node["name"].asString(),
|
|
|
|
node["target_output"].asString(),
|
|
|
|
node["name"].asString()));
|
|
|
|
} else {
|
|
|
|
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\""));
|
|
|
|
}
|
2019-04-18 15:43:16 +00:00
|
|
|
} catch (const std::exception &e) {
|
2018-08-11 00:09:39 +00:00
|
|
|
std::cerr << e.what() << std::endl;
|
|
|
|
}
|
2018-08-08 21:54:58 +00:00
|
|
|
});
|
2018-08-16 15:12:45 +00:00
|
|
|
if (!config_["disable-scroll"].asBool()) {
|
2019-03-18 13:44:07 +00:00
|
|
|
button.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
|
2019-04-18 15:43:16 +00:00
|
|
|
button.signal_scroll_event().connect(sigc::mem_fun(*this, &Workspaces::handleScroll));
|
2018-08-16 15:12:45 +00:00
|
|
|
}
|
2019-04-19 14:48:02 +00:00
|
|
|
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) {
|
2019-04-18 15:43:16 +00:00
|
|
|
std::vector<std::string> keys = {name, "urgent", "focused", "visible", "default"};
|
|
|
|
for (auto const &key : keys) {
|
2018-09-04 22:16:56 +00:00
|
|
|
if (key == "focused" || key == "visible" || key == "urgent") {
|
2018-10-26 07:27:16 +00:00
|
|
|
if (config_["format-icons"][key].isString() && node[key].asBool()) {
|
2018-09-04 22:16:56 +00:00
|
|
|
return config_["format-icons"][key].asString();
|
|
|
|
}
|
2018-10-26 07:27:16 +00:00
|
|
|
} else if (config_["format-icons"][key].isString()) {
|
2018-09-04 22:16:56 +00:00
|
|
|
return config_["format-icons"][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) {
|
2018-08-14 09:26:06 +00:00
|
|
|
// Avoid concurrent scroll event
|
2018-08-16 12:29:41 +00:00
|
|
|
if (scrolling_) {
|
2018-08-14 09:26:06 +00:00
|
|
|
return false;
|
2018-08-16 12:29:41 +00:00
|
|
|
}
|
2019-04-19 14:48:02 +00:00
|
|
|
std::string name;
|
2019-04-23 09:41:49 +00:00
|
|
|
scrolling_ = true;
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
|
|
auto it = std::find_if(workspaces_.begin(), workspaces_.end(), [](const auto &workspace) {
|
|
|
|
return workspace["focused"].asBool();
|
|
|
|
});
|
|
|
|
if (it == workspaces_.end()) {
|
|
|
|
scrolling_ = false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
switch (e->direction) {
|
|
|
|
case GDK_SCROLL_DOWN:
|
|
|
|
case GDK_SCROLL_RIGHT:
|
2019-04-19 14:48:02 +00:00
|
|
|
name = getCycleWorkspace(it, false);
|
2019-04-23 09:41:49 +00:00
|
|
|
break;
|
|
|
|
case GDK_SCROLL_UP:
|
|
|
|
case GDK_SCROLL_LEFT:
|
|
|
|
name = getCycleWorkspace(it, true);
|
|
|
|
break;
|
|
|
|
case GDK_SCROLL_SMOOTH:
|
|
|
|
gdouble delta_x, delta_y;
|
|
|
|
gdk_event_get_scroll_deltas(reinterpret_cast<const GdkEvent *>(e), &delta_x, &delta_y);
|
|
|
|
if (delta_y < 0) {
|
|
|
|
name = getCycleWorkspace(it, true);
|
|
|
|
} else if (delta_y > 0) {
|
|
|
|
name = getCycleWorkspace(it, false);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (name.empty() || name == (*it)["name"].asString()) {
|
|
|
|
scrolling_ = false;
|
|
|
|
return false;
|
|
|
|
}
|
2018-08-14 09:26:06 +00:00
|
|
|
}
|
2019-05-13 13:15:50 +00:00
|
|
|
try {
|
|
|
|
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace \"{}\"", name));
|
|
|
|
} catch (const std::exception &e) {
|
|
|
|
std::cerr << "Workspaces: " << e.what() << std::endl;
|
|
|
|
}
|
2018-08-14 09:26:06 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-04-19 14:48:02 +00:00
|
|
|
const std::string Workspaces::getCycleWorkspace(std::vector<Json::Value>::iterator it,
|
|
|
|
bool prev) const {
|
2019-05-17 16:53:38 +00:00
|
|
|
if (prev && it == workspaces_.begin() && !config_["disable-scroll-wraparound"].asBool()) {
|
2019-04-19 14:48:02 +00:00
|
|
|
return (*(--workspaces_.end()))["name"].asString();
|
2018-08-16 12:29:41 +00:00
|
|
|
}
|
2019-04-19 14:48:02 +00:00
|
|
|
if (prev && it != workspaces_.begin())
|
|
|
|
--it;
|
|
|
|
else if (!prev && it != workspaces_.end())
|
|
|
|
++it;
|
|
|
|
if (!prev && it == workspaces_.end()) {
|
2019-05-17 16:53:38 +00:00
|
|
|
if (config_["disable-scroll-wraparound"].asBool()) {
|
|
|
|
--it;
|
|
|
|
} else {
|
|
|
|
return (*(workspaces_.begin()))["name"].asString();
|
|
|
|
}
|
2019-02-01 23:36:52 +00:00
|
|
|
}
|
2019-04-19 14:48:02 +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(':');
|
2019-04-18 15:43:16 +00:00
|
|
|
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) {
|
2019-04-18 15:43:16 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2019-04-19 09:09:06 +00:00
|
|
|
Workspaces::operator Gtk::Widget &() { return box_; }
|
|
|
|
|
2019-05-17 16:37:24 +00:00
|
|
|
} // namespace waybar::modules::sway
|