Skip to content

Commit 2091395

Browse files
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.
1 parent fd24d7b commit 2091395

File tree

3 files changed

+191
-65
lines changed

3 files changed

+191
-65
lines changed

include/modules/sway/window.hpp

+5-2
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ class Window : public AIconLabel, public sigc::trackable {
1919
auto update() -> void;
2020

2121
private:
22+
void setClass(std::string classname, bool enable);
2223
void onEvent(const struct Ipc::ipc_response&);
2324
void onCmd(const struct Ipc::ipc_response&);
24-
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string> getFocusedNode(
25-
const Json::Value& nodes, std::string& output);
25+
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
26+
getFocusedNode(const Json::Value& nodes, std::string& output);
2627
void getTree();
2728
std::string rewriteTitle(const std::string& title);
2829
void updateAppIconName();
@@ -33,12 +34,14 @@ class Window : public AIconLabel, public sigc::trackable {
3334
int windowId_;
3435
std::string app_id_;
3536
std::string app_class_;
37+
std::string layout_;
3638
std::string old_app_id_;
3739
std::size_t app_nb_;
3840
std::string shell_;
3941
unsigned app_icon_size_{24};
4042
bool update_app_icon_{true};
4143
std::string app_icon_name_;
44+
int floating_count_;
4245
util::JsonParser parser_;
4346
std::mutex mutex_;
4447
Ipc ipc_;

man/waybar-sway-window.5.scd

+25-2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,25 @@ Addressed by *sway/window*
6666
default: true ++
6767
Option to disable tooltip on hover.
6868

69+
*all-outputs*: ++
70+
typeof: bool ++
71+
default: false ++
72+
Option to show the focused window along with its workspace styles on all outputs.
73+
74+
*offscreen-css*: ++
75+
typeof: bool ++
76+
default: false ++
77+
Only effective when all-outputs is true. Adds style according to present windows on unfocused outputs instead of showing the focused window and style.
78+
79+
*offscreen-css-text*: ++
80+
typeof: string ++
81+
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.
82+
83+
*show-focused-workspace-name*: ++
84+
typeof: bool ++
85+
default: false ++
86+
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.
87+
6988
*rewrite*: ++
7089
typeof: object ++
7190
Rules to rewrite window title. See *rewrite rules*.
@@ -117,6 +136,10 @@ Invalid expressions (e.g., mismatched parentheses) are skipped.
117136
# STYLE
118137

119138
- *#window*
120-
- *window#waybar.empty* When no windows is in the workspace
121-
- *window#waybar.solo* When one window is in the workspace
139+
- *window#waybar.empty* When no windows are in the workspace, or screen is not focused and offscreen-text option is not set
140+
- *window#waybar.solo* When one tiled window is in the workspace
141+
- *window#waybar.floating* When there are only floating windows in the workspace
142+
- *window#waybar.stacked* When there is more than one window in the workspace and the workspace layout is stacked
143+
- *window#waybar.tabbed* When there is more than one window in the workspace and the workspace layout is tabbed
144+
- *window#waybar.tiled* When there is more than one window in the workspace and the workspace layout is splith or splitv
122145
- *window#waybar.<app_id>* Where *app_id* is the app_id or *instance* name like (*chromium*) of the only window in the workspace

src/modules/sway/window.cpp

+161-61
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
namespace waybar::modules::sway {
1616

1717
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
18-
: AIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), windowId_(-1) {
18+
: AIconLabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) {
1919
// Icon size
2020
if (config_["icon-size"].isUInt()) {
2121
app_icon_size_ = config["icon-size"].asUInt();
@@ -33,6 +33,7 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
3333
ipc_.handleEvent();
3434
} catch (const std::exception& e) {
3535
spdlog::error("Window: {}", e.what());
36+
spdlog::trace("Window::Window exception");
3637
}
3738
});
3839
}
@@ -44,12 +45,13 @@ void Window::onCmd(const struct Ipc::ipc_response& res) {
4445
std::lock_guard<std::mutex> lock(mutex_);
4546
auto payload = parser_.parse(res.payload);
4647
auto output = payload["output"].isString() ? payload["output"].asString() : "";
47-
std::tie(app_nb_, windowId_, window_, app_id_, app_class_, shell_) =
48+
std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_) =
4849
getFocusedNode(payload["nodes"], output);
4950
updateAppIconName();
5051
dp.emit();
5152
} catch (const std::exception& e) {
5253
spdlog::error("Window: {}", e.what());
54+
spdlog::trace("Window::onCmd exception");
5355
}
5456
}
5557

@@ -154,27 +156,52 @@ void Window::updateAppIcon() {
154156
}
155157

156158
auto Window::update() -> void {
157-
if (!old_app_id_.empty()) {
158-
bar_.window.get_style_context()->remove_class(old_app_id_);
159-
}
159+
spdlog::trace("workspace layout {}, tiled count {}, floating count {}", layout_, app_nb_,
160+
floating_count_);
161+
162+
int mode = 0;
160163
if (app_nb_ == 0) {
161-
bar_.window.get_style_context()->remove_class("solo");
162-
if (!bar_.window.get_style_context()->has_class("empty")) {
163-
bar_.window.get_style_context()->add_class("empty");
164+
if (floating_count_ == 0) {
165+
mode += 1;
166+
} else {
167+
mode += 4;
164168
}
165169
} else if (app_nb_ == 1) {
166-
bar_.window.get_style_context()->remove_class("empty");
167-
if (!bar_.window.get_style_context()->has_class("solo")) {
168-
bar_.window.get_style_context()->add_class("solo");
170+
mode += 2;
171+
} else {
172+
if (layout_ == "tabbed") {
173+
mode += 8;
174+
} else if (layout_ == "stacked") {
175+
mode += 16;
176+
} else {
177+
mode += 32;
169178
}
170179
if (!app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) {
171180
bar_.window.get_style_context()->add_class(app_id_);
172181
old_app_id_ = app_id_;
173182
}
174-
} else {
175-
bar_.window.get_style_context()->remove_class("solo");
176-
bar_.window.get_style_context()->remove_class("empty");
177183
}
184+
185+
if (!old_app_id_.empty() && ((mode & 2) == 0 || old_app_id_ != app_id_) &&
186+
bar_.window.get_style_context()->has_class(old_app_id_)) {
187+
spdlog::trace("Removing app_id class: {}", old_app_id_);
188+
bar_.window.get_style_context()->remove_class(old_app_id_);
189+
old_app_id_ = "";
190+
}
191+
192+
setClass("empty", ((mode & 1) > 0));
193+
setClass("solo", ((mode & 2) > 0));
194+
setClass("floating", ((mode & 4) > 0));
195+
setClass("tabbed", ((mode & 8) > 0));
196+
setClass("stacked", ((mode & 16) > 0));
197+
setClass("tiled", ((mode & 32) > 0));
198+
199+
if ((mode & 2) > 0 && !app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) {
200+
spdlog::trace("Adding app_id class: {}", app_id_);
201+
bar_.window.get_style_context()->add_class(app_id_);
202+
old_app_id_ = app_id_;
203+
}
204+
178205
label_.set_markup(fmt::format(format_, fmt::arg("title", rewriteTitle(window_)),
179206
fmt::arg("app_id", app_id_), fmt::arg("shell", shell_)));
180207
if (tooltipEnabled()) {
@@ -187,78 +214,151 @@ auto Window::update() -> void {
187214
AIconLabel::update();
188215
}
189216

190-
int leafNodesInWorkspace(const Json::Value& node) {
217+
void Window::setClass(std::string classname, bool enable) {
218+
if (enable) {
219+
if (!bar_.window.get_style_context()->has_class(classname)) {
220+
bar_.window.get_style_context()->add_class(classname);
221+
}
222+
} else {
223+
bar_.window.get_style_context()->remove_class(classname);
224+
}
225+
}
226+
227+
std::pair<int, int> leafNodesInWorkspace(const Json::Value& node) {
191228
auto const& nodes = node["nodes"];
192229
auto const& floating_nodes = node["floating_nodes"];
193230
if (nodes.empty() && floating_nodes.empty()) {
194-
if (node["type"] == "workspace")
195-
return 0;
196-
else
197-
return 1;
231+
if (node["type"].asString() == "workspace")
232+
return {0, 0};
233+
else if (node["type"].asString() == "floating_con") {
234+
return {0, 1};
235+
} else {
236+
return {1, 0};
237+
}
198238
}
199239
int sum = 0;
200-
if (!nodes.empty()) {
201-
for (auto const& node : nodes) sum += leafNodesInWorkspace(node);
240+
int floating_sum = 0;
241+
for (auto const& node : nodes) {
242+
std::pair all_leaf_nodes = leafNodesInWorkspace(node);
243+
sum += all_leaf_nodes.first;
244+
floating_sum += all_leaf_nodes.second;
202245
}
203-
if (!floating_nodes.empty()) {
204-
for (auto const& node : floating_nodes) sum += leafNodesInWorkspace(node);
246+
for (auto const& node : floating_nodes) {
247+
std::pair all_leaf_nodes = leafNodesInWorkspace(node);
248+
sum += all_leaf_nodes.first;
249+
floating_sum += all_leaf_nodes.second;
205250
}
206-
return sum;
251+
return {sum, floating_sum};
207252
}
208253

209-
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string> gfnWithWorkspace(
210-
const Json::Value& nodes, std::string& output, const Json::Value& config_, const Bar& bar_,
211-
Json::Value& parentWorkspace) {
254+
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
255+
gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Value& config_,
256+
const Bar& bar_, Json::Value& parentWorkspace,
257+
const Json::Value& immediateParent) {
212258
for (auto const& node : nodes) {
213-
if (node["output"].isString()) {
214-
output = node["output"].asString();
215-
}
216-
// found node
217-
if (node["focused"].asBool() && (node["type"] == "con" || node["type"] == "floating_con")) {
218-
if ((!config_["all-outputs"].asBool() && output == bar_.output->name) ||
219-
config_["all-outputs"].asBool()) {
220-
auto app_id = node["app_id"].isString() ? node["app_id"].asString()
221-
: node["window_properties"]["instance"].asString();
222-
const auto app_class = node["window_properties"]["class"].isString()
223-
? node["window_properties"]["class"].asString()
224-
: "";
225-
226-
const auto shell = node["shell"].isString() ? node["shell"].asString() : "";
227-
228-
int nb = node.size();
229-
if (parentWorkspace != 0) nb = leafNodesInWorkspace(parentWorkspace);
230-
return {nb, node["id"].asInt(), Glib::Markup::escape_text(node["name"].asString()),
231-
app_id, app_class, shell};
259+
if (node["type"].asString() == "output") {
260+
if ((!config_["all-outputs"].asBool() || config_["offscreen-css"].asBool()) &&
261+
(node["name"].asString() != bar_.output->name)) {
262+
continue;
263+
}
264+
output = node["name"].asString();
265+
} else if (node["type"].asString() == "workspace") {
266+
// needs to be a string comparison, because filterWorkspace is the current_workspace
267+
if (node["name"].asString() != immediateParent["current_workspace"].asString()) {
268+
continue;
232269
}
270+
if (node["focused"].asBool()) {
271+
std::pair all_leaf_nodes = leafNodesInWorkspace(node);
272+
return {all_leaf_nodes.first,
273+
all_leaf_nodes.second,
274+
node["id"].asInt(),
275+
(((all_leaf_nodes.first > 0) || (all_leaf_nodes.second > 0)) &&
276+
(config_["show-focused-workspace-name"].asBool()))
277+
? node["name"].asString()
278+
: "",
279+
"",
280+
"",
281+
"",
282+
node["layout"].asString()};
283+
}
284+
parentWorkspace = node;
285+
} else if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") &&
286+
(node["focused"].asBool())) {
287+
// found node
288+
spdlog::trace("actual output {}, output found {}, node (focused) found {}", bar_.output->name,
289+
output, node["name"].asString());
290+
auto app_id = node["app_id"].isString() ? node["app_id"].asString()
291+
: node["window_properties"]["instance"].asString();
292+
const auto app_class = node["window_properties"]["class"].isString()
293+
? node["window_properties"]["class"].asString()
294+
: "";
295+
const auto shell = node["shell"].isString() ? node["shell"].asString() : "";
296+
int nb = node.size();
297+
int floating_count = 0;
298+
std::string workspace_layout = "";
299+
if (!parentWorkspace.isNull()) {
300+
std::pair all_leaf_nodes = leafNodesInWorkspace(parentWorkspace);
301+
nb = all_leaf_nodes.first;
302+
floating_count = all_leaf_nodes.second;
303+
workspace_layout = parentWorkspace["layout"].asString();
304+
}
305+
return {nb,
306+
floating_count,
307+
node["id"].asInt(),
308+
Glib::Markup::escape_text(node["name"].asString()),
309+
app_id,
310+
app_class,
311+
shell,
312+
workspace_layout};
233313
}
314+
234315
// iterate
235-
if (node["type"] == "workspace") parentWorkspace = node;
236-
auto [nb, id, name, app_id, app_class, shell] =
237-
gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace);
238-
if (id > -1 && !name.empty()) {
239-
return {nb, id, name, app_id, app_class, shell};
240-
}
241-
// Search for floating node
242-
std::tie(nb, id, name, app_id, app_class, shell) =
243-
gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace);
244-
if (id > -1 && !name.empty()) {
245-
return {nb, id, name, app_id, app_class, shell};
316+
auto [nb, f, id, name, app_id, app_class, shell, workspace_layout] =
317+
gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace, node);
318+
auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2] =
319+
gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace, node);
320+
321+
// if ((id > 0 || ((id2 < 0 || name2.empty()) && id > -1)) && !name.empty()) {
322+
if ((id > 0) || (id2 < 0 && id > -1)) {
323+
return {nb, f, id, name, app_id, app_class, shell, workspace_layout};
324+
} else if (id2 > 0 && !name2.empty()) {
325+
return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2};
246326
}
247327
}
248-
return {0, -1, "", "", "", ""};
328+
329+
// this only comes into effect when no focused children are present
330+
if (config_["all-outputs"].asBool() && config_["offscreen-css"].asBool() &&
331+
immediateParent["type"].asString() == "workspace") {
332+
std::pair all_leaf_nodes = leafNodesInWorkspace(immediateParent);
333+
// using an empty string as default ensures that no window depending styles are set due to the
334+
// checks above for !name.empty()
335+
return {all_leaf_nodes.first,
336+
all_leaf_nodes.second,
337+
0,
338+
(all_leaf_nodes.first > 0 || all_leaf_nodes.second > 0)
339+
? config_["offscreen-css-text"].asString()
340+
: "",
341+
"",
342+
"",
343+
"",
344+
immediateParent["layout"].asString()};
345+
}
346+
347+
return {0, 0, -1, "", "", "", "", ""};
249348
}
250349

251-
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string>
350+
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
252351
Window::getFocusedNode(const Json::Value& nodes, std::string& output) {
253-
Json::Value placeholder = 0;
254-
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder);
352+
Json::Value placeholder = Json::Value::null;
353+
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder, placeholder);
255354
}
256355

257356
void Window::getTree() {
258357
try {
259358
ipc_.sendCmd(IPC_GET_TREE);
260359
} catch (const std::exception& e) {
261360
spdlog::error("Window: {}", e.what());
361+
spdlog::trace("Window::getTree exception");
262362
}
263363
}
264364

0 commit comments

Comments
 (0)