Merge pull request #2563 from Syndelis/feat/hyprland-window-rename-by-title
This commit is contained in:
commit
7645ae1907
|
@ -17,8 +17,10 @@
|
|||
#include "bar.hpp"
|
||||
#include "modules/hyprland/backend.hpp"
|
||||
#include "util/enum.hpp"
|
||||
#include "util/regex_collection.hpp"
|
||||
|
||||
using WindowAddress = std::string;
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
class Workspaces;
|
||||
|
@ -47,8 +49,8 @@ class Workspace {
|
|||
void set_visible(bool value = true) { is_visible_ = value; };
|
||||
void set_windows(uint value) { windows_ = value; };
|
||||
void set_name(std::string value) { name_ = value; };
|
||||
bool contains_window(WindowAddress addr) { return window_map_.contains(addr); }
|
||||
void insert_window(WindowAddress addr, std::string window_repr);
|
||||
bool contains_window(WindowAddress addr) const { return window_map_.contains(addr); }
|
||||
void insert_window(WindowAddress addr, std::string window_class, std::string window_title);
|
||||
std::string remove_window(WindowAddress addr);
|
||||
void initialize_window_map(const Json::Value& clients_data);
|
||||
|
||||
|
@ -93,10 +95,12 @@ class Workspaces : public AModule, public EventHandler {
|
|||
|
||||
auto get_bar_output() const -> std::string { return bar_.output->name; }
|
||||
|
||||
std::string get_rewrite(std::string window_class);
|
||||
std::string get_rewrite(std::string window_class, std::string window_title);
|
||||
std::string& get_window_separator() { return format_window_separator_; }
|
||||
bool is_workspace_ignored(std::string& workspace_name);
|
||||
|
||||
bool window_rewrite_config_uses_title() const { return any_window_rewrite_rule_uses_title_; }
|
||||
|
||||
private:
|
||||
void onEvent(const std::string&) override;
|
||||
void update_window_count();
|
||||
|
@ -113,6 +117,8 @@ class Workspaces : public AModule, public EventHandler {
|
|||
void on_window_closed(std::string payload);
|
||||
void on_window_moved(std::string payload);
|
||||
|
||||
int window_rewrite_priority_function(std::string& window_rule);
|
||||
|
||||
bool all_outputs_ = false;
|
||||
bool show_special_ = false;
|
||||
bool active_only_ = false;
|
||||
|
@ -131,11 +137,12 @@ class Workspaces : public AModule, public EventHandler {
|
|||
bool persistent_created_ = false;
|
||||
|
||||
std::string format_;
|
||||
|
||||
std::map<std::string, std::string> icons_map_;
|
||||
Json::Value window_rewrite_rules_;
|
||||
std::map<std::string, std::string> regex_cache_;
|
||||
util::RegexCollection window_rewrite_rules_;
|
||||
bool any_window_rewrite_rule_uses_title_ = false;
|
||||
std::string format_window_separator_;
|
||||
std::string window_rewrite_default_;
|
||||
|
||||
bool with_icon_;
|
||||
uint64_t monitor_id_;
|
||||
std::string active_workspace_name_;
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include <json/json.h>
|
||||
|
||||
#include <functional>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
||||
namespace waybar::util {
|
||||
|
||||
struct Rule {
|
||||
std::regex rule;
|
||||
std::string repr;
|
||||
int priority;
|
||||
|
||||
// Fix for Clang < 16
|
||||
// See https://en.cppreference.com/w/cpp/compiler_support/20 "Parenthesized initialization of
|
||||
// aggregates"
|
||||
Rule(std::regex rule, std::string repr, int priority)
|
||||
: rule(rule), repr(repr), priority(priority) {}
|
||||
};
|
||||
|
||||
int default_priority_function(std::string& key);
|
||||
|
||||
/* A collection of regexes and strings, with a default string to return if no regexes.
|
||||
* When a regex is matched, the corresponding string is returned.
|
||||
* All regexes that are matched are cached, so that the regexes are only
|
||||
* evaluated once against a given string.
|
||||
* Regexes may be given a higher priority than others, so that they are matched
|
||||
* first. The priority function is given the regex string, and should return a
|
||||
* higher number for higher priority regexes.
|
||||
*/
|
||||
class RegexCollection {
|
||||
private:
|
||||
std::vector<Rule> rules;
|
||||
std::map<std::string, std::string> regex_cache;
|
||||
std::string default_repr;
|
||||
|
||||
std::string& find_match(std::string& value, bool& matched_any);
|
||||
|
||||
public:
|
||||
RegexCollection() = default;
|
||||
RegexCollection(const Json::Value& map, std::string default_repr = "",
|
||||
std::function<int(std::string&)> priority_function = default_priority_function);
|
||||
~RegexCollection() = default;
|
||||
|
||||
std::string& get(std::string& value, bool& matched_any);
|
||||
std::string& get(std::string& value);
|
||||
};
|
||||
|
||||
} // namespace waybar::util
|
|
@ -25,6 +25,7 @@ Addressed by *hyprland/workspaces*
|
|||
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 ++
|
||||
|
@ -129,8 +130,10 @@ Additional to workspace name matching, the following *format-icons* can be set.
|
|||
"format-window-separator": "\n",
|
||||
"window-rewrite-default": "",
|
||||
"window-rewrite": {
|
||||
"firefox": "",
|
||||
"foot": "",
|
||||
"title<.*youtube.*>": "", // Windows whose titles contain "youtube"
|
||||
"class<firefox>": "", // Windows whose classes are "firefox"
|
||||
"class<firefox> title<.*github.*>": "", // Windows whose class is "firefox" and title contains "github". Note that "class" always comes first.
|
||||
"foot": "", // Windows that contain "foot" in either class or title. For optimization reasons, it will only match against a title if at least one other window explicitly matches against a title.
|
||||
"code": "",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -177,7 +177,8 @@ src_files = files(
|
|||
'src/util/ustring_clen.cpp',
|
||||
'src/util/sanitize_str.cpp',
|
||||
'src/util/rewrite_string.cpp',
|
||||
'src/util/gtk_icon.cpp'
|
||||
'src/util/gtk_icon.cpp',
|
||||
'src/util/regex_collection.cpp'
|
||||
)
|
||||
|
||||
inc_dirs = ['include']
|
||||
|
|
|
@ -49,9 +49,9 @@ auto waybar::modules::Disk::update() -> void {
|
|||
float specific_free, specific_used, specific_total, divisor;
|
||||
|
||||
divisor = calc_specific_divisor(unit_);
|
||||
specific_free = (stats.f_bavail * stats.f_frsize)/divisor;
|
||||
specific_used = ((stats.f_blocks - stats.f_bfree) * stats.f_frsize)/divisor;
|
||||
specific_total = (stats.f_blocks * stats.f_frsize)/divisor;
|
||||
specific_free = (stats.f_bavail * stats.f_frsize) / divisor;
|
||||
specific_used = ((stats.f_blocks - stats.f_bfree) * stats.f_frsize) / divisor;
|
||||
specific_total = (stats.f_blocks * stats.f_frsize) / divisor;
|
||||
|
||||
auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true);
|
||||
auto used = pow_format((stats.f_blocks - stats.f_bfree) * stats.f_frsize, "B", true);
|
||||
|
@ -72,7 +72,7 @@ auto waybar::modules::Disk::update() -> void {
|
|||
fmt::runtime(format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
|
||||
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used),
|
||||
fmt::arg("percentage_used", percentage_used), fmt::arg("total", total),
|
||||
fmt::arg("path", path_), fmt::arg("specific_free", specific_free),
|
||||
fmt::arg("path", path_), fmt::arg("specific_free", specific_free),
|
||||
fmt::arg("specific_used", specific_used), fmt::arg("specific_total", specific_total)));
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@ auto waybar::modules::Disk::update() -> void {
|
|||
fmt::runtime(tooltip_format), stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
|
||||
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used),
|
||||
fmt::arg("percentage_used", percentage_used), fmt::arg("total", total),
|
||||
fmt::arg("path", path_), fmt::arg("specific_free", specific_free),
|
||||
fmt::arg("path", path_), fmt::arg("specific_free", specific_free),
|
||||
fmt::arg("specific_used", specific_used), fmt::arg("specific_total", specific_total)));
|
||||
}
|
||||
// Call parent update
|
||||
|
@ -109,7 +109,7 @@ float waybar::modules::Disk::calc_specific_divisor(std::string divisor) {
|
|||
return 1000.0 * 1000.0 * 1000.0 * 1000.0;
|
||||
} else if (divisor == "TiB") {
|
||||
return 1024.0 * 1024.0 * 1024.0 * 1024.0;
|
||||
} else { //default to Bytes if it is anything that we don't recongnise
|
||||
} else { // default to Bytes if it is anything that we don't recongnise
|
||||
return 1.0;
|
||||
}
|
||||
}
|
|
@ -1,19 +1,36 @@
|
|||
#include "modules/hyprland/workspaces.hpp"
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
#include <json/value.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <charconv>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "util/rewrite_string.hpp"
|
||||
#include "util/regex_collection.hpp"
|
||||
|
||||
namespace waybar::modules::hyprland {
|
||||
|
||||
int Workspaces::window_rewrite_priority_function(std::string &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 has_title = window_rule.find("title") != std::string::npos;
|
||||
bool has_class = window_rule.find("class") != std::string::npos;
|
||||
|
||||
if (has_title && has_class) {
|
||||
any_window_rewrite_rule_uses_title_ = true;
|
||||
return 3;
|
||||
} else if (has_title) {
|
||||
any_window_rewrite_rule_uses_title_ = true;
|
||||
return 2;
|
||||
} else if (has_class) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
|
||||
: AModule(config, "workspaces", id, false, false),
|
||||
bar_(bar),
|
||||
|
@ -94,11 +111,16 @@ auto Workspaces::parse_config(const Json::Value &config) -> void {
|
|||
format_window_separator_ =
|
||||
format_window_separator.isString() ? format_window_separator.asString() : " ";
|
||||
|
||||
window_rewrite_rules_ = config["window-rewrite"];
|
||||
Json::Value window_rewrite = config["window-rewrite"];
|
||||
|
||||
Json::Value window_rewrite_default = config["window-rewrite-default"];
|
||||
window_rewrite_default_ =
|
||||
window_rewrite_default.isString() ? window_rewrite_default.asString() : "?";
|
||||
Json::Value window_rewrite_default_config = config["window-rewrite-default"];
|
||||
std::string window_rewrite_default =
|
||||
window_rewrite_default_config.isString() ? window_rewrite_default_config.asString() : "?";
|
||||
|
||||
window_rewrite_rules_ = util::RegexCollection(
|
||||
window_rewrite, window_rewrite_default, [this](std::string &window_rule) {
|
||||
return this->window_rewrite_priority_function(window_rule);
|
||||
});
|
||||
}
|
||||
|
||||
auto Workspaces::register_ipc() -> void {
|
||||
|
@ -118,6 +140,13 @@ auto Workspaces::register_ipc() -> void {
|
|||
gIPC->registerForIPC("closewindow", this);
|
||||
gIPC->registerForIPC("movewindow", this);
|
||||
gIPC->registerForIPC("urgent", this);
|
||||
|
||||
if (window_rewrite_config_uses_title()) {
|
||||
spdlog::info(
|
||||
"Registering for Hyprland's 'windowtitle' events because a user-defined window "
|
||||
"rewrite rule uses the 'title' field.");
|
||||
gIPC->registerForIPC("windowtitle", this);
|
||||
}
|
||||
}
|
||||
|
||||
auto Workspaces::update() -> void {
|
||||
|
@ -252,6 +281,25 @@ void Workspaces::onEvent(const std::string &ev) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
} else if (eventName == "windowtitle") {
|
||||
auto window_workspace =
|
||||
std::find_if(workspaces_.begin(), workspaces_.end(),
|
||||
[payload](auto &workspace) { return workspace->contains_window(payload); });
|
||||
|
||||
if (window_workspace != workspaces_.end()) {
|
||||
Json::Value clients_data = gIPC->getSocket1JsonReply("clients");
|
||||
std::string json_window_address = fmt::format("0x{}", payload);
|
||||
|
||||
auto client = std::find_if(clients_data.begin(), clients_data.end(),
|
||||
[json_window_address](auto &client) {
|
||||
return client["address"].asString() == json_window_address;
|
||||
});
|
||||
|
||||
if (!client->empty()) {
|
||||
(*window_workspace)
|
||||
->insert_window(payload, (*client)["class"].asString(), (*client)["title"].asString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dp.emit();
|
||||
|
@ -353,15 +401,22 @@ void Workspace::initialize_window_map(const Json::Value &clients_data) {
|
|||
// {ADDR}
|
||||
WindowAddress client_address = client["address"].asString();
|
||||
client_address = client_address.substr(2, client_address.length() - 2);
|
||||
insert_window(client_address, client["class"].asString());
|
||||
insert_window(client_address, client["class"].asString(), client["title"].asString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Workspace::insert_window(WindowAddress addr, std::string window_class) {
|
||||
auto window_repr = workspace_manager_.get_rewrite(window_class);
|
||||
void Workspace::insert_window(WindowAddress addr, std::string window_class,
|
||||
std::string window_title) {
|
||||
if (window_class.empty() &&
|
||||
(!workspace_manager_.window_rewrite_config_uses_title() || window_title.empty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto window_repr = workspace_manager_.get_rewrite(window_class, window_title);
|
||||
|
||||
if (!window_repr.empty()) {
|
||||
window_map_.emplace(addr, window_repr);
|
||||
window_map_[addr] = window_repr;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -375,7 +430,7 @@ std::string Workspace::remove_window(WindowAddress addr) {
|
|||
bool Workspace::on_window_opened(WindowAddress &addr, std::string &workspace_name,
|
||||
std::string window_repr) {
|
||||
if (workspace_name == name()) {
|
||||
window_map_.emplace(addr, window_repr);
|
||||
window_map_[addr] = window_repr;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
@ -385,7 +440,7 @@ bool Workspace::on_window_opened(WindowAddress &addr, std::string &workspace_nam
|
|||
bool Workspace::on_window_opened(WindowAddress &addr, std::string &workspace_name,
|
||||
std::string &window_class, std::string &window_title) {
|
||||
if (workspace_name == name()) {
|
||||
insert_window(addr, window_class);
|
||||
insert_window(addr, window_class, window_title);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
@ -798,23 +853,14 @@ void Workspaces::set_urgent_workspace(std::string windowaddress) {
|
|||
}
|
||||
}
|
||||
|
||||
std::string Workspaces::get_rewrite(std::string window_class) {
|
||||
if (regex_cache_.contains(window_class)) {
|
||||
return regex_cache_[window_class];
|
||||
std::string Workspaces::get_rewrite(std::string window_class, std::string window_title) {
|
||||
std::string window_repr_key;
|
||||
if (window_rewrite_config_uses_title()) {
|
||||
window_repr_key = fmt::format("class<{}> title<{}>", window_class, window_title);
|
||||
} else {
|
||||
window_repr_key = fmt::format("class<{}>", window_class);
|
||||
}
|
||||
|
||||
bool matched_any;
|
||||
|
||||
std::string window_class_rewrite =
|
||||
waybar::util::rewriteStringOnce(window_class, window_rewrite_rules_, matched_any);
|
||||
|
||||
if (!matched_any) {
|
||||
window_class_rewrite = window_rewrite_default_;
|
||||
}
|
||||
|
||||
regex_cache_.emplace(window_class, window_class_rewrite);
|
||||
|
||||
return window_class_rewrite;
|
||||
return window_rewrite_rules_.get(window_repr_key);
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::hyprland
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
#include "util/regex_collection.hpp"
|
||||
|
||||
#include <json/value.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace waybar::util {
|
||||
|
||||
int default_priority_function(std::string& key) { return 0; }
|
||||
|
||||
RegexCollection::RegexCollection(const Json::Value& map, std::string default_repr,
|
||||
std::function<int(std::string&)> priority_function)
|
||||
: default_repr(default_repr) {
|
||||
if (!map.isObject()) {
|
||||
spdlog::warn("Mapping is not an object");
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto it = map.begin(); it != map.end(); ++it) {
|
||||
if (it.key().isString() && it->isString()) {
|
||||
std::string key = it.key().asString();
|
||||
int priority = priority_function(key);
|
||||
try {
|
||||
const std::regex rule{key, std::regex_constants::icase};
|
||||
rules.emplace_back(rule, it->asString(), priority);
|
||||
} catch (const std::regex_error& e) {
|
||||
spdlog::error("Invalid rule '{}': {}", key, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(rules.begin(), rules.end(), [](Rule& a, Rule& b) { return a.priority > b.priority; });
|
||||
}
|
||||
|
||||
std::string& RegexCollection::find_match(std::string& value, bool& matched_any) {
|
||||
for (auto& rule : rules) {
|
||||
if (std::regex_search(value, rule.rule)) {
|
||||
matched_any = true;
|
||||
return rule.repr;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
std::string& RegexCollection::get(std::string& value, bool& matched_any) {
|
||||
if (regex_cache.contains(value)) {
|
||||
return regex_cache[value];
|
||||
}
|
||||
|
||||
// std::string repr =
|
||||
// waybar::util::find_match(value, window_rewrite_rules_, matched_any);
|
||||
|
||||
std::string repr = find_match(value, matched_any);
|
||||
|
||||
if (!matched_any) {
|
||||
repr = default_repr;
|
||||
}
|
||||
|
||||
regex_cache.emplace(value, repr);
|
||||
|
||||
return regex_cache[value]; // Necessary in order to return a reference to the heap
|
||||
}
|
||||
|
||||
std::string& RegexCollection::get(std::string& value) {
|
||||
bool matched_any = false;
|
||||
return get(value, matched_any);
|
||||
}
|
||||
|
||||
} // namespace waybar::util
|
|
@ -1,6 +1,5 @@
|
|||
#include "util/rewrite_string.hpp"
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <regex>
|
||||
|
@ -30,31 +29,4 @@ std::string rewriteString(const std::string& value, const Json::Value& rules) {
|
|||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string rewriteStringOnce(const std::string& value, const Json::Value& rules,
|
||||
bool& matched_any) {
|
||||
if (!rules.isObject()) {
|
||||
return value;
|
||||
}
|
||||
|
||||
matched_any = false;
|
||||
|
||||
std::string res = value;
|
||||
|
||||
for (auto it = rules.begin(); it != rules.end(); ++it) {
|
||||
if (it.key().isString() && it->isString()) {
|
||||
try {
|
||||
const std::regex rule{it.key().asString(), std::regex_constants::icase};
|
||||
if (std::regex_match(value, rule)) {
|
||||
matched_any = true;
|
||||
return std::regex_replace(res, rule, it->asString());
|
||||
}
|
||||
} catch (const std::regex_error& e) {
|
||||
spdlog::error("Invalid rule {}: {}", it.key().asString(), e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
} // namespace waybar::util
|
||||
|
|
Loading…
Reference in New Issue