Skip to content

Commit

Permalink
sway-window, Issue 1399: new style classes
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
RobertMueller2 committed Aug 28, 2022
1 parent fd24d7b commit bbc3601
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 66 deletions.
5 changes: 4 additions & 1 deletion include/modules/sway/window.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ class Window : public AIconLabel, public sigc::trackable {
auto update() -> void;

private:
void setClass(std::string classname, bool enable);
void onEvent(const struct Ipc::ipc_response&);
void onCmd(const struct Ipc::ipc_response&);
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string> getFocusedNode(
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string> getFocusedNode(
const Json::Value& nodes, std::string& output);
void getTree();
std::string rewriteTitle(const std::string& title);
Expand All @@ -33,12 +34,14 @@ class Window : public AIconLabel, public sigc::trackable {
int windowId_;
std::string app_id_;
std::string app_class_;
std::string layout_;
std::string old_app_id_;
std::size_t app_nb_;
std::string shell_;
unsigned app_icon_size_{24};
bool update_app_icon_{true};
std::string app_icon_name_;
int floating_count_;
util::JsonParser parser_;
std::mutex mutex_;
Ipc ipc_;
Expand Down
27 changes: 25 additions & 2 deletions man/waybar-sway-window.5.scd
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,25 @@ Addressed by *sway/window*
default: true ++
Option to disable tooltip on hover.

*all-outputs*: ++
typeof: bool ++
default: false ++
Option to show the focused window along with its workspace styles on all outputs.

*offscreen-css*: ++
typeof: bool ++
default: false ++
Only effective when all-outputs is true. Adds style according to present windows on unfocused outputs instead of showing the focused window and style.

*offscreen-css-text*: ++
typeof: string ++
Only effective when both all-outputs and offscreen-style are true. On screens currently not focused, show the given text along with that workspaces styles.

*show-focused-workspace-name*: ++
typeof: bool ++
default: false ++
If the workspace itself is focused and the workspace contains nodes or floating_nodes, show the workspace name. If not set, text remains empty but styles according to nodes in the workspace are still applied.

*rewrite*: ++
typeof: object ++
Rules to rewrite window title. See *rewrite rules*.
Expand Down Expand Up @@ -117,6 +136,10 @@ Invalid expressions (e.g., mismatched parentheses) are skipped.
# STYLE

- *#window*
- *window#waybar.empty* When no windows is in the workspace
- *window#waybar.solo* When one window is in the workspace
- *window#waybar.empty* When no windows are in the workspace, or screen is not focused and offscreen-text option is not set
- *window#waybar.solo* When one tiled window is in the workspace
- *window#waybar.floating* When there are only floating windows in the workspace
- *window#waybar.stacked* When there is more than one window in the workspace and the workspace layout is stacked
- *window#waybar.tabbed* When there is more than one window in the workspace and the workspace layout is tabbed
- *window#waybar.tiled* When there is more than one window in the workspace and the workspace layout is splith or splitv
- *window#waybar.<app_id>* Where *app_id* is the app_id or *instance* name like (*chromium*) of the only window in the workspace
226 changes: 163 additions & 63 deletions src/modules/sway/window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,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();
Expand All @@ -33,6 +33,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");
}
});
}
Expand All @@ -44,12 +45,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");
}
}

Expand Down Expand Up @@ -154,29 +156,54 @@ 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");
}
label_.set_markup(fmt::format(format_, fmt::arg("title", rewriteTitle(window_)),
fmt::arg("app_id", app_id_), fmt::arg("shell", shell_)));

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", rewriteTitle(window_)), fmt::arg("app_id", app_id_), fmt::arg("shell", shell_)));
if (tooltipEnabled()) {
label_.set_tooltip_text(window_);
}
Expand All @@ -187,78 +214,151 @@ 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);
int floating_sum = 0;
for (auto const& node : nodes) {
std::pair all_leaf_nodes = leafNodesInWorkspace(node);
sum += all_leaf_nodes.first;
floating_sum += all_leaf_nodes.second;
}
if (!floating_nodes.empty()) {
for (auto const& node : floating_nodes) sum += leafNodesInWorkspace(node);
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;
return {sum, floating_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) {
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["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};
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
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};
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};
}
}
return {0, -1, "", "", "", ""};

// 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, 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>
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() {
try {
ipc_.sendCmd(IPC_GET_TREE);
} catch (const std::exception& e) {
spdlog::error("Window: {}", e.what());
spdlog::trace("Window::getTree exception");
}
}

Expand Down

0 comments on commit bbc3601

Please sign in to comment.