sway-window, Issue 1399: new style classes

Provides CSS classes empty, floating, tabbed, tiled, solo, stacked and
app_id.

Adds offscreen-css bool option (default false), only effective when
"all-outputs" is true. This adds styles on outputs without focused
node, according to its focused workspaces window situation.

Adds an "offscreen-css-text" string option (default empty), only
effective when "all-outputs" and "offscreen-style" are set. This
is shown as a text on outputs without a focused node.

Adds a "show-focused-workspace" bool option (default false) to indicate
the workspace name if the whole workspace is focused when nodes are
also present. If not set, empty text is shown, but css classes
according to nodes in the workspace are still applied.

Limitation:
When the top level layout changes, there is no sway event so the
module cannot react. Perhaps in the future recurring polling can
be added to go around this limitation.
This commit is contained in:
Rene D. Obermueller
2022-04-17 07:13:35 +02:00
parent f0bead34d4
commit 328573332f
3 changed files with 199 additions and 73 deletions

View File

@ -17,7 +17,7 @@
namespace waybar::modules::sway {
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: AIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), windowId_(-1) {
: AIconLabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) {
// Icon size
if (config_["icon-size"].isUInt()) {
app_icon_size_ = config["icon-size"].asUInt();
@ -35,6 +35,7 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
ipc_.handleEvent();
} catch (const std::exception& e) {
spdlog::error("Window: {}", e.what());
spdlog::trace("Window::Window exception");
}
});
}
@ -46,12 +47,13 @@ void Window::onCmd(const struct Ipc::ipc_response& res) {
std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload);
auto output = payload["output"].isString() ? payload["output"].asString() : "";
std::tie(app_nb_, windowId_, window_, app_id_, app_class_, shell_) =
std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_) =
getFocusedNode(payload["nodes"], output);
updateAppIconName();
dp.emit();
} catch (const std::exception& e) {
spdlog::error("Window: {}", e.what());
spdlog::trace("Window::onCmd exception");
}
}
@ -156,27 +158,52 @@ void Window::updateAppIcon() {
}
auto Window::update() -> void {
if (!old_app_id_.empty()) {
bar_.window.get_style_context()->remove_class(old_app_id_);
}
spdlog::trace("workspace layout {}, tiled count {}, floating count {}", layout_, app_nb_,
floating_count_);
int mode = 0;
if (app_nb_ == 0) {
bar_.window.get_style_context()->remove_class("solo");
if (!bar_.window.get_style_context()->has_class("empty")) {
bar_.window.get_style_context()->add_class("empty");
if (floating_count_ == 0) {
mode += 1;
} else {
mode += 4;
}
} else if (app_nb_ == 1) {
bar_.window.get_style_context()->remove_class("empty");
if (!bar_.window.get_style_context()->has_class("solo")) {
bar_.window.get_style_context()->add_class("solo");
mode += 2;
} else {
if (layout_ == "tabbed") {
mode += 8;
} else if (layout_ == "stacked") {
mode += 16;
} else {
mode += 32;
}
if (!app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) {
bar_.window.get_style_context()->add_class(app_id_);
old_app_id_ = app_id_;
}
} else {
bar_.window.get_style_context()->remove_class("solo");
bar_.window.get_style_context()->remove_class("empty");
}
if (!old_app_id_.empty() && ((mode & 2) == 0 || old_app_id_ != app_id_) &&
bar_.window.get_style_context()->has_class(old_app_id_)) {
spdlog::trace("Removing app_id class: {}", old_app_id_);
bar_.window.get_style_context()->remove_class(old_app_id_);
old_app_id_ = "";
}
setClass("empty", ((mode & 1) > 0));
setClass("solo", ((mode & 2) > 0));
setClass("floating", ((mode & 4) > 0));
setClass("tabbed", ((mode & 8) > 0));
setClass("stacked", ((mode & 16) > 0));
setClass("tiled", ((mode & 32) > 0));
if ((mode & 2) > 0 && !app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) {
spdlog::trace("Adding app_id class: {}", app_id_);
bar_.window.get_style_context()->add_class(app_id_);
old_app_id_ = app_id_;
}
label_.set_markup(fmt::format(
format_, fmt::arg("title", waybar::util::rewriteTitle(window_, config_["rewrite"])),
fmt::arg("app_id", app_id_), fmt::arg("shell", shell_)));
@ -190,71 +217,143 @@ auto Window::update() -> void {
AIconLabel::update();
}
int leafNodesInWorkspace(const Json::Value& node) {
void Window::setClass(std::string classname, bool enable) {
if (enable) {
if (!bar_.window.get_style_context()->has_class(classname)) {
bar_.window.get_style_context()->add_class(classname);
}
} else {
bar_.window.get_style_context()->remove_class(classname);
}
}
std::pair<int, int> leafNodesInWorkspace(const Json::Value& node) {
auto const& nodes = node["nodes"];
auto const& floating_nodes = node["floating_nodes"];
if (nodes.empty() && floating_nodes.empty()) {
if (node["type"] == "workspace")
return 0;
else
return 1;
if (node["type"].asString() == "workspace")
return {0, 0};
else if (node["type"].asString() == "floating_con") {
return {0, 1};
} else {
return {1, 0};
}
}
int sum = 0;
if (!nodes.empty()) {
for (auto const& node : nodes) sum += leafNodesInWorkspace(node);
}
if (!floating_nodes.empty()) {
for (auto const& node : floating_nodes) sum += leafNodesInWorkspace(node);
}
return sum;
}
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string> gfnWithWorkspace(
const Json::Value& nodes, std::string& output, const Json::Value& config_, const Bar& bar_,
Json::Value& parentWorkspace) {
int floating_sum = 0;
for (auto const& node : nodes) {
if (node["output"].isString()) {
output = node["output"].asString();
}
// found node
if (node["focused"].asBool() && (node["type"] == "con" || node["type"] == "floating_con")) {
if ((!config_["all-outputs"].asBool() && output == bar_.output->name) ||
config_["all-outputs"].asBool()) {
auto app_id = node["app_id"].isString() ? node["app_id"].asString()
: node["window_properties"]["instance"].asString();
const auto app_class = node["window_properties"]["class"].isString()
? node["window_properties"]["class"].asString()
: "";
const auto shell = node["shell"].isString() ? node["shell"].asString() : "";
int nb = node.size();
if (parentWorkspace != 0) nb = leafNodesInWorkspace(parentWorkspace);
return {nb, node["id"].asInt(), Glib::Markup::escape_text(node["name"].asString()),
app_id, app_class, shell};
}
}
// iterate
if (node["type"] == "workspace") parentWorkspace = node;
auto [nb, id, name, app_id, app_class, shell] =
gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace);
if (id > -1 && !name.empty()) {
return {nb, id, name, app_id, app_class, shell};
}
// Search for floating node
std::tie(nb, id, name, app_id, app_class, shell) =
gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace);
if (id > -1 && !name.empty()) {
return {nb, id, name, app_id, app_class, shell};
}
std::pair all_leaf_nodes = leafNodesInWorkspace(node);
sum += all_leaf_nodes.first;
floating_sum += all_leaf_nodes.second;
}
return {0, -1, "", "", "", ""};
for (auto const& node : floating_nodes) {
std::pair all_leaf_nodes = leafNodesInWorkspace(node);
sum += all_leaf_nodes.first;
floating_sum += all_leaf_nodes.second;
}
return {sum, floating_sum};
}
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string>
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Value& config_,
const Bar& bar_, Json::Value& parentWorkspace,
const Json::Value& immediateParent) {
for (auto const& node : nodes) {
if (node["type"].asString() == "output") {
if ((!config_["all-outputs"].asBool() || config_["offscreen-css"].asBool()) &&
(node["name"].asString() != bar_.output->name)) {
continue;
}
output = node["name"].asString();
} else if (node["type"].asString() == "workspace") {
// needs to be a string comparison, because filterWorkspace is the current_workspace
if (node["name"].asString() != immediateParent["current_workspace"].asString()) {
continue;
}
if (node["focused"].asBool()) {
std::pair all_leaf_nodes = leafNodesInWorkspace(node);
return {all_leaf_nodes.first,
all_leaf_nodes.second,
node["id"].asInt(),
(((all_leaf_nodes.first > 0) || (all_leaf_nodes.second > 0)) &&
(config_["show-focused-workspace-name"].asBool()))
? node["name"].asString()
: "",
"",
"",
"",
node["layout"].asString()};
}
parentWorkspace = node;
} else if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") &&
(node["focused"].asBool())) {
// found node
spdlog::trace("actual output {}, output found {}, node (focused) found {}", bar_.output->name,
output, node["name"].asString());
auto app_id = node["app_id"].isString() ? node["app_id"].asString()
: node["window_properties"]["instance"].asString();
const auto app_class = node["window_properties"]["class"].isString()
? node["window_properties"]["class"].asString()
: "";
const auto shell = node["shell"].isString() ? node["shell"].asString() : "";
int nb = node.size();
int floating_count = 0;
std::string workspace_layout = "";
if (!parentWorkspace.isNull()) {
std::pair all_leaf_nodes = leafNodesInWorkspace(parentWorkspace);
nb = all_leaf_nodes.first;
floating_count = all_leaf_nodes.second;
workspace_layout = parentWorkspace["layout"].asString();
}
return {nb,
floating_count,
node["id"].asInt(),
Glib::Markup::escape_text(node["name"].asString()),
app_id,
app_class,
shell,
workspace_layout};
}
// iterate
auto [nb, f, id, name, app_id, app_class, shell, workspace_layout] =
gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace, node);
auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2] =
gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace, node);
// if ((id > 0 || ((id2 < 0 || name2.empty()) && id > -1)) && !name.empty()) {
if ((id > 0) || (id2 < 0 && id > -1)) {
return {nb, f, id, name, app_id, app_class, shell, workspace_layout};
} else if (id2 > 0 && !name2.empty()) {
return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2};
}
}
// this only comes into effect when no focused children are present
if (config_["all-outputs"].asBool() && config_["offscreen-css"].asBool() &&
immediateParent["type"].asString() == "workspace") {
std::pair all_leaf_nodes = leafNodesInWorkspace(immediateParent);
// using an empty string as default ensures that no window depending styles are set due to the
// checks above for !name.empty()
return {all_leaf_nodes.first,
all_leaf_nodes.second,
0,
(all_leaf_nodes.first > 0 || all_leaf_nodes.second > 0)
? config_["offscreen-css-text"].asString()
: "",
"",
"",
"",
immediateParent["layout"].asString()};
}
return {0, 0, -1, "", "", "", "", ""};
}
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
Window::getFocusedNode(const Json::Value& nodes, std::string& output) {
Json::Value placeholder = 0;
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder);
Json::Value placeholder = Json::Value::null;
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder, placeholder);
}
void Window::getTree() {
@ -262,6 +361,7 @@ void Window::getTree() {
ipc_.sendCmd(IPC_GET_TREE);
} catch (const std::exception& e) {
spdlog::error("Window: {}", e.what());
spdlog::trace("Window::getTree exception");
}
}