Skip to content

Commit 3285733

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 f0bead3 commit 3285733

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
void updateAppIconName();
2829
void updateAppIcon();
@@ -32,12 +33,14 @@ class Window : public AIconLabel, public sigc::trackable {
3233
int windowId_;
3334
std::string app_id_;
3435
std::string app_class_;
36+
std::string layout_;
3537
std::string old_app_id_;
3638
std::size_t app_nb_;
3739
std::string shell_;
3840
unsigned app_icon_size_{24};
3941
bool update_app_icon_{true};
4042
std::string app_icon_name_;
43+
int floating_count_;
4144
util::JsonParser parser_;
4245
std::mutex mutex_;
4346
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
@@ -17,7 +17,7 @@
1717
namespace waybar::modules::sway {
1818

1919
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
20-
: AIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), windowId_(-1) {
20+
: AIconLabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) {
2121
// Icon size
2222
if (config_["icon-size"].isUInt()) {
2323
app_icon_size_ = config["icon-size"].asUInt();
@@ -35,6 +35,7 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
3535
ipc_.handleEvent();
3636
} catch (const std::exception& e) {
3737
spdlog::error("Window: {}", e.what());
38+
spdlog::trace("Window::Window exception");
3839
}
3940
});
4041
}
@@ -46,12 +47,13 @@ void Window::onCmd(const struct Ipc::ipc_response& res) {
4647
std::lock_guard<std::mutex> lock(mutex_);
4748
auto payload = parser_.parse(res.payload);
4849
auto output = payload["output"].isString() ? payload["output"].asString() : "";
49-
std::tie(app_nb_, windowId_, window_, app_id_, app_class_, shell_) =
50+
std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_) =
5051
getFocusedNode(payload["nodes"], output);
5152
updateAppIconName();
5253
dp.emit();
5354
} catch (const std::exception& e) {
5455
spdlog::error("Window: {}", e.what());
56+
spdlog::trace("Window::onCmd exception");
5557
}
5658
}
5759

@@ -156,27 +158,52 @@ void Window::updateAppIcon() {
156158
}
157159

158160
auto Window::update() -> void {
159-
if (!old_app_id_.empty()) {
160-
bar_.window.get_style_context()->remove_class(old_app_id_);
161-
}
161+
spdlog::trace("workspace layout {}, tiled count {}, floating count {}", layout_, app_nb_,
162+
floating_count_);
163+
164+
int mode = 0;
162165
if (app_nb_ == 0) {
163-
bar_.window.get_style_context()->remove_class("solo");
164-
if (!bar_.window.get_style_context()->has_class("empty")) {
165-
bar_.window.get_style_context()->add_class("empty");
166+
if (floating_count_ == 0) {
167+
mode += 1;
168+
} else {
169+
mode += 4;
166170
}
167171
} else if (app_nb_ == 1) {
168-
bar_.window.get_style_context()->remove_class("empty");
169-
if (!bar_.window.get_style_context()->has_class("solo")) {
170-
bar_.window.get_style_context()->add_class("solo");
172+
mode += 2;
173+
} else {
174+
if (layout_ == "tabbed") {
175+
mode += 8;
176+
} else if (layout_ == "stacked") {
177+
mode += 16;
178+
} else {
179+
mode += 32;
171180
}
172181
if (!app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) {
173182
bar_.window.get_style_context()->add_class(app_id_);
174183
old_app_id_ = app_id_;
175184
}
176-
} else {
177-
bar_.window.get_style_context()->remove_class("solo");
178-
bar_.window.get_style_context()->remove_class("empty");
179185
}
186+
187+
if (!old_app_id_.empty() && ((mode & 2) == 0 || old_app_id_ != app_id_) &&
188+
bar_.window.get_style_context()->has_class(old_app_id_)) {
189+
spdlog::trace("Removing app_id class: {}", old_app_id_);
190+
bar_.window.get_style_context()->remove_class(old_app_id_);
191+
old_app_id_ = "";
192+
}
193+
194+
setClass("empty", ((mode & 1) > 0));
195+
setClass("solo", ((mode & 2) > 0));
196+
setClass("floating", ((mode & 4) > 0));
197+
setClass("tabbed", ((mode & 8) > 0));
198+
setClass("stacked", ((mode & 16) > 0));
199+
setClass("tiled", ((mode & 32) > 0));
200+
201+
if ((mode & 2) > 0 && !app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) {
202+
spdlog::trace("Adding app_id class: {}", app_id_);
203+
bar_.window.get_style_context()->add_class(app_id_);
204+
old_app_id_ = app_id_;
205+
}
206+
180207
label_.set_markup(fmt::format(
181208
format_, fmt::arg("title", waybar::util::rewriteTitle(window_, config_["rewrite"])),
182209
fmt::arg("app_id", app_id_), fmt::arg("shell", shell_)));
@@ -190,78 +217,151 @@ auto Window::update() -> void {
190217
AIconLabel::update();
191218
}
192219

193-
int leafNodesInWorkspace(const Json::Value& node) {
220+
void Window::setClass(std::string classname, bool enable) {
221+
if (enable) {
222+
if (!bar_.window.get_style_context()->has_class(classname)) {
223+
bar_.window.get_style_context()->add_class(classname);
224+
}
225+
} else {
226+
bar_.window.get_style_context()->remove_class(classname);
227+
}
228+
}
229+
230+
std::pair<int, int> leafNodesInWorkspace(const Json::Value& node) {
194231
auto const& nodes = node["nodes"];
195232
auto const& floating_nodes = node["floating_nodes"];
196233
if (nodes.empty() && floating_nodes.empty()) {
197-
if (node["type"] == "workspace")
198-
return 0;
199-
else
200-
return 1;
234+
if (node["type"].asString() == "workspace")
235+
return {0, 0};
236+
else if (node["type"].asString() == "floating_con") {
237+
return {0, 1};
238+
} else {
239+
return {1, 0};
240+
}
201241
}
202242
int sum = 0;
203-
if (!nodes.empty()) {
204-
for (auto const& node : nodes) sum += leafNodesInWorkspace(node);
243+
int floating_sum = 0;
244+
for (auto const& node : nodes) {
245+
std::pair all_leaf_nodes = leafNodesInWorkspace(node);
246+
sum += all_leaf_nodes.first;
247+
floating_sum += all_leaf_nodes.second;
205248
}
206-
if (!floating_nodes.empty()) {
207-
for (auto const& node : floating_nodes) sum += leafNodesInWorkspace(node);
249+
for (auto const& node : floating_nodes) {
250+
std::pair all_leaf_nodes = leafNodesInWorkspace(node);
251+
sum += all_leaf_nodes.first;
252+
floating_sum += all_leaf_nodes.second;
208253
}
209-
return sum;
254+
return {sum, floating_sum};
210255
}
211256

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

254-
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string>
353+
std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
255354
Window::getFocusedNode(const Json::Value& nodes, std::string& output) {
256-
Json::Value placeholder = 0;
257-
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder);
355+
Json::Value placeholder = Json::Value::null;
356+
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder, placeholder);
258357
}
259358

260359
void Window::getTree() {
261360
try {
262361
ipc_.sendCmd(IPC_GET_TREE);
263362
} catch (const std::exception& e) {
264363
spdlog::error("Window: {}", e.what());
364+
spdlog::trace("Window::getTree exception");
265365
}
266366
}
267367

0 commit comments

Comments
 (0)