Merge pull request #2944 from grimpy/waybarwindows

Implement windows formating in sway/workspaces
pull/2949/head
Alexis Rouillard 2024-02-20 18:34:32 +01:00 committed by GitHub
commit 1606dce62e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 135 additions and 16 deletions

View File

@ -12,6 +12,7 @@
#include "client.hpp" #include "client.hpp"
#include "modules/sway/ipc/client.hpp" #include "modules/sway/ipc/client.hpp"
#include "util/json.hpp" #include "util/json.hpp"
#include "util/regex_collection.hpp"
namespace waybar::modules::sway { namespace waybar::modules::sway {
@ -27,10 +28,13 @@ class Workspaces : public AModule, public sigc::trackable {
R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")"; R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")";
static int convertWorkspaceNameToNum(std::string name); static int convertWorkspaceNameToNum(std::string name);
static int windowRewritePriorityFunction(std::string const& window_rule);
void onCmd(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&);
void onEvent(const struct Ipc::ipc_response&); void onEvent(const struct Ipc::ipc_response&);
bool filterButtons(); bool filterButtons();
static bool hasFlag(const Json::Value&, const std::string&);
void updateWindows(const Json::Value&, std::string&);
Gtk::Button& addButton(const Json::Value&); Gtk::Button& addButton(const Json::Value&);
void onButtonReady(const Json::Value&, Gtk::Button&); void onButtonReady(const Json::Value&, Gtk::Button&);
std::string getIcon(const std::string&, const Json::Value&); std::string getIcon(const std::string&, const Json::Value&);
@ -44,6 +48,9 @@ class Workspaces : public AModule, public sigc::trackable {
std::vector<std::string> high_priority_named_; std::vector<std::string> high_priority_named_;
std::vector<std::string> workspaces_order_; std::vector<std::string> workspaces_order_;
Gtk::Box box_; Gtk::Box box_;
std::string m_formatWindowSeperator;
std::string m_windowRewriteDefault;
util::RegexCollection m_windowRewriteRules;
util::JsonParser parser_; util::JsonParser parser_;
std::unordered_map<std::string, Gtk::Button> buttons_; std::unordered_map<std::string, Gtk::Button> buttons_;
std::mutex mutex_; std::mutex mutex_;

View File

@ -82,6 +82,23 @@ warp-on-scroll: ++
default: true ++ default: true ++
If set to false, you can scroll to cycle through workspaces without mouse warping being enabled. If set to true this behaviour is disabled. If set to false, you can scroll to cycle through workspaces without mouse warping being enabled. If set to true this behaviour is disabled.
*window-rewrite*: ++
typeof: object ++
Regex rules to map window class to an icon or preferred method of representation for a workspace's window.
Keys are the rules, while the values are the methods of representation.
Rules may specify `class<...>`, `title<...>`, or both in order to fine-tune the matching.
*window-rewrite-default*:
typeof: string ++
default: "?" ++
The default method of representation for a workspace's window. This will be used for windows whose classes do not match any of the rules in *window-rewrite*.
*format-window-separator*: ++
typeof: string ++
default: " " ++
The separator to be used between windows in a workspace.
# FORMAT REPLACEMENTS # FORMAT REPLACEMENTS
*{value}*: Name of the workspace, as defined by sway. *{value}*: Name of the workspace, as defined by sway.
@ -94,6 +111,8 @@ warp-on-scroll: ++
*{output}*: Output where the workspace is located. *{output}*: Output where the workspace is located.
*{windows}*: Result from window-rewrite
# ICONS # ICONS
Additional to workspace name matching, the following *format-icons* can be set. Additional to workspace name matching, the following *format-icons* can be set.
@ -143,6 +162,19 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge
} }
``` ```
```
"sway/workspaces": {
"format": "<span size='larger'>{name}</span> {windows}",
"format-window-separator": " | ",
"window-rewrite-default": "{name}",
"window-format": "<span color='#e0e0e0'>{name}</span>",
"window-rewrite": {
"class<firefox>": "",
"class<kitty>": "k",
}
}
```
# Style # Style
- *#workspaces button* - *#workspaces button*

View File

@ -24,6 +24,24 @@ int Workspaces::convertWorkspaceNameToNum(std::string name) {
return -1; return -1;
} }
int Workspaces::windowRewritePriorityFunction(std::string const &window_rule) {
// Rules that match against title are prioritized
// Rules that don't specify if they're matching against either title or class are deprioritized
bool const hasTitle = window_rule.find("title") != std::string::npos;
bool const hasClass = window_rule.find("class") != std::string::npos;
if (hasTitle && hasClass) {
return 3;
}
if (hasTitle) {
return 2;
}
if (hasClass) {
return 1;
}
return 0;
}
Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config) Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
: AModule(config, "workspaces", id, false, !config["disable-scroll"].asBool()), : AModule(config, "workspaces", id, false, !config["disable-scroll"].asBool()),
bar_(bar), bar_(bar),
@ -39,10 +57,25 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
} }
box_.get_style_context()->add_class(MODULE_CLASS); box_.get_style_context()->add_class(MODULE_CLASS);
event_box_.add(box_); event_box_.add(box_);
if (config_["format-window-separator"].isString()) {
m_formatWindowSeperator = config_["format-window-separator"].asString();
} else {
m_formatWindowSeperator = " ";
}
const Json::Value &windowRewrite = config["window-rewrite"];
const Json::Value &windowRewriteDefaultConfig = config["window-rewrite-default"];
m_windowRewriteDefault =
windowRewriteDefaultConfig.isString() ? windowRewriteDefaultConfig.asString() : "?";
m_windowRewriteRules = waybar::util::RegexCollection(
windowRewrite, m_windowRewriteDefault,
[this](std::string &window_rule) { return windowRewritePriorityFunction(window_rule); });
ipc_.subscribe(R"(["workspace"])"); ipc_.subscribe(R"(["workspace"])");
ipc_.subscribe(R"(["window"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &Workspaces::onEvent)); ipc_.signal_event.connect(sigc::mem_fun(*this, &Workspaces::onEvent));
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Workspaces::onCmd)); ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Workspaces::onCmd));
ipc_.sendCmd(IPC_GET_WORKSPACES); ipc_.sendCmd(IPC_GET_TREE);
if (config["enable-bar-scroll"].asBool()) { if (config["enable-bar-scroll"].asBool()) {
auto &window = const_cast<Bar &>(bar_).window; auto &window = const_cast<Bar &>(bar_).window;
window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); window.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
@ -60,26 +93,33 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
void Workspaces::onEvent(const struct Ipc::ipc_response &res) { void Workspaces::onEvent(const struct Ipc::ipc_response &res) {
try { try {
ipc_.sendCmd(IPC_GET_WORKSPACES); ipc_.sendCmd(IPC_GET_TREE);
} catch (const std::exception &e) { } catch (const std::exception &e) {
spdlog::error("Workspaces: {}", e.what()); spdlog::error("Workspaces: {}", e.what());
} }
} }
void Workspaces::onCmd(const struct Ipc::ipc_response &res) { void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
if (res.type == IPC_GET_WORKSPACES) { if (res.type == IPC_GET_TREE) {
try { try {
{ {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload); auto payload = parser_.parse(res.payload);
workspaces_.clear(); workspaces_.clear();
std::copy_if(payload.begin(), payload.end(), std::back_inserter(workspaces_), std::vector<Json::Value> outputs;
std::copy_if(payload["nodes"].begin(), payload["nodes"].end(), std::back_inserter(outputs),
[&](const auto &workspace) { [&](const auto &workspace) {
return !config_["all-outputs"].asBool() return !config_["all-outputs"].asBool()
? workspace["output"].asString() == bar_.output->name ? workspace["name"].asString() == bar_.output->name
: true; : true;
}); });
for (auto &output : outputs) {
std::copy(output["nodes"].begin(), output["nodes"].end(),
std::back_inserter(workspaces_));
std::copy(output["floating_nodes"].begin(), output["floating_nodes"].end(),
std::back_inserter(workspaces_));
}
if (config_["persistent_workspaces"].isObject()) { if (config_["persistent_workspaces"].isObject()) {
spdlog::warn( spdlog::warn(
"persistent_workspaces is deprecated. Please change config to use " "persistent_workspaces is deprecated. Please change config to use "
@ -204,6 +244,40 @@ bool Workspaces::filterButtons() {
return needReorder; return needReorder;
} }
bool Workspaces::hasFlag(const Json::Value &node, const std::string &flag) {
if (node[flag].asBool()) {
return true;
}
if (std::any_of(node["nodes"].begin(), node["nodes"].end(),
[&](auto const &e) { return hasFlag(e, flag); })) {
return true;
}
return false;
}
void Workspaces::updateWindows(const Json::Value &node, std::string &windows) {
auto format = config_["window-format"].asString();
if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") &&
node["name"].isString()) {
std::string title = g_markup_escape_text(node["name"].asString().c_str(), -1);
std::string windowClass = node["app_id"].asString();
std::string windowReprKey = fmt::format("class<{}> title<{}>", windowClass, title);
std::string window = m_windowRewriteRules.get(windowReprKey);
// allow result to have formatting
window =
fmt::format(fmt::runtime(window), fmt::arg("name", title), fmt::arg("class", windowClass));
windows.append(window);
windows.append(m_formatWindowSeperator);
}
for (const Json::Value &child : node["nodes"]) {
updateWindows(child, windows);
}
for (const Json::Value &child : node["floating_nodes"]) {
updateWindows(child, windows);
}
}
auto Workspaces::update() -> void { auto Workspaces::update() -> void {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
bool needReorder = filterButtons(); bool needReorder = filterButtons();
@ -213,22 +287,25 @@ auto Workspaces::update() -> void {
needReorder = true; needReorder = true;
} }
auto &button = bit == buttons_.end() ? addButton(*it) : bit->second; auto &button = bit == buttons_.end() ? addButton(*it) : bit->second;
if ((*it)["focused"].asBool()) { if (needReorder) {
box_.reorder_child(button, it - workspaces_.begin());
}
if (hasFlag((*it), "focused")) {
button.get_style_context()->add_class("focused"); button.get_style_context()->add_class("focused");
} else { } else {
button.get_style_context()->remove_class("focused"); button.get_style_context()->remove_class("focused");
} }
if ((*it)["visible"].asBool()) { if (hasFlag((*it), "visible")) {
button.get_style_context()->add_class("visible"); button.get_style_context()->add_class("visible");
} else { } else {
button.get_style_context()->remove_class("visible"); button.get_style_context()->remove_class("visible");
} }
if ((*it)["urgent"].asBool()) { if (hasFlag((*it), "urgent")) {
button.get_style_context()->add_class("urgent"); button.get_style_context()->add_class("urgent");
} else { } else {
button.get_style_context()->remove_class("urgent"); button.get_style_context()->remove_class("urgent");
} }
if ((*it)["target_output"].isString()) { if (hasFlag((*it), "target_output")) {
button.get_style_context()->add_class("persistent"); button.get_style_context()->add_class("persistent");
} else { } else {
button.get_style_context()->remove_class("persistent"); button.get_style_context()->remove_class("persistent");
@ -242,16 +319,19 @@ auto Workspaces::update() -> void {
} else { } else {
button.get_style_context()->remove_class("current_output"); button.get_style_context()->remove_class("current_output");
} }
if (needReorder) {
box_.reorder_child(button, it - workspaces_.begin());
}
std::string output = (*it)["name"].asString(); std::string output = (*it)["name"].asString();
std::string windows = "";
if (config_["window-format"].isString()) {
updateWindows((*it), windows);
}
if (config_["format"].isString()) { if (config_["format"].isString()) {
auto format = config_["format"].asString(); auto format = config_["format"].asString();
output = fmt::format(fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)), output = fmt::format(
fmt::arg("value", output), fmt::arg("name", trimWorkspaceName(output)), fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)), fmt::arg("value", output),
fmt::arg("index", (*it)["num"].asString()), fmt::arg("name", trimWorkspaceName(output)), fmt::arg("index", (*it)["num"].asString()),
fmt::arg("output", (*it)["output"].asString())); fmt::arg("windows",
windows.substr(0, windows.length() - m_formatWindowSeperator.length())),
fmt::arg("output", (*it)["output"].asString()));
} }
if (!config_["disable-markup"].asBool()) { if (!config_["disable-markup"].asBool()) {
static_cast<Gtk::Label *>(button.get_children()[0])->set_markup(output); static_cast<Gtk::Label *>(button.get_children()[0])->set_markup(output);