Implement windows formating in sway/workspaces
This implementation mimics to some extend the implementation of hyprland Signed-off-by: Jo De Boeck <deboeck.jo@gmail.com>pull/2773/head
parent
4c5ff80bbd
commit
c641d52e06
|
@ -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_;
|
||||||
|
|
|
@ -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*
|
||||||
|
|
|
@ -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),
|
||||||
|
@ -38,10 +56,25 @@ Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value
|
||||||
box_.get_style_context()->add_class(id);
|
box_.get_style_context()->add_class(id);
|
||||||
}
|
}
|
||||||
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);
|
||||||
|
@ -59,26 +92,31 @@ 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_));
|
||||||
|
}
|
||||||
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 "
|
||||||
|
@ -203,6 +241,35 @@ 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::ranges::any_of(node["nodes"], [&](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["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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
@ -212,22 +279,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");
|
||||||
|
@ -241,15 +311,18 @@ 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("windows",
|
||||||
|
windows.substr(0, windows.length() - m_formatWindowSeperator.length())),
|
||||||
fmt::arg("output", (*it)["output"].asString()));
|
fmt::arg("output", (*it)["output"].asString()));
|
||||||
}
|
}
|
||||||
if (!config_["disable-markup"].asBool()) {
|
if (!config_["disable-markup"].asBool()) {
|
||||||
|
|
Loading…
Reference in New Issue