2018-08-15 18:17:17 +00:00
|
|
|
#include "modules/sway/window.hpp"
|
2021-10-17 18:08:16 +00:00
|
|
|
|
|
|
|
#include <gdkmm/pixbuf.h>
|
|
|
|
#include <glibmm/fileutils.h>
|
|
|
|
#include <glibmm/keyfile.h>
|
|
|
|
#include <glibmm/miscutils.h>
|
|
|
|
#include <gtkmm/enums.h>
|
2022-04-18 01:09:25 +00:00
|
|
|
#include <gtkmm/icontheme.h>
|
2019-05-18 23:44:45 +00:00
|
|
|
#include <spdlog/spdlog.h>
|
2021-10-17 18:08:16 +00:00
|
|
|
|
|
|
|
#include <filesystem>
|
2021-03-21 10:42:25 +00:00
|
|
|
#include <regex>
|
2021-10-17 18:08:16 +00:00
|
|
|
#include <string>
|
2018-08-15 18:17:17 +00:00
|
|
|
|
2019-04-19 09:09:06 +00:00
|
|
|
namespace waybar::modules::sway {
|
|
|
|
|
|
|
|
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
|
2022-08-18 13:21:50 +00:00
|
|
|
: AIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), windowId_(-1) {
|
2022-04-17 21:54:27 +00:00
|
|
|
// Icon size
|
|
|
|
if (config_["icon-size"].isUInt()) {
|
|
|
|
app_icon_size_ = config["icon-size"].asUInt();
|
|
|
|
}
|
|
|
|
image_.set_pixel_size(app_icon_size_);
|
|
|
|
|
2019-06-11 12:01:55 +00:00
|
|
|
ipc_.subscribe(R"(["window","workspace"])");
|
2019-04-19 09:09:06 +00:00
|
|
|
ipc_.signal_event.connect(sigc::mem_fun(*this, &Window::onEvent));
|
|
|
|
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Window::onCmd));
|
2019-04-25 14:47:51 +00:00
|
|
|
// Get Initial focused window
|
|
|
|
getTree();
|
2018-08-20 12:50:45 +00:00
|
|
|
// Launch worker
|
2019-08-28 02:43:03 +00:00
|
|
|
ipc_.setWorker([this] {
|
|
|
|
try {
|
|
|
|
ipc_.handleEvent();
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
spdlog::error("Window: {}", e.what());
|
|
|
|
}
|
|
|
|
});
|
2018-08-20 12:50:45 +00:00
|
|
|
}
|
|
|
|
|
2019-05-07 11:43:48 +00:00
|
|
|
void Window::onEvent(const struct Ipc::ipc_response& res) { getTree(); }
|
2019-04-19 09:09:06 +00:00
|
|
|
|
2019-04-24 10:37:24 +00:00
|
|
|
void Window::onCmd(const struct Ipc::ipc_response& res) {
|
2019-05-09 08:30:54 +00:00
|
|
|
try {
|
2019-06-17 09:39:45 +00:00
|
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
2019-06-15 12:57:52 +00:00
|
|
|
auto payload = parser_.parse(res.payload);
|
2019-08-31 17:50:56 +00:00
|
|
|
auto output = payload["output"].isString() ? payload["output"].asString() : "";
|
2022-08-10 08:34:51 +00:00
|
|
|
std::tie(app_nb_, windowId_, window_, app_id_, app_class_, shell_) =
|
2022-04-18 01:09:25 +00:00
|
|
|
getFocusedNode(payload["nodes"], output);
|
2022-04-17 20:55:58 +00:00
|
|
|
updateAppIconName();
|
2019-06-04 15:34:00 +00:00
|
|
|
dp.emit();
|
2019-05-09 08:30:54 +00:00
|
|
|
} catch (const std::exception& e) {
|
2019-05-18 23:44:45 +00:00
|
|
|
spdlog::error("Window: {}", e.what());
|
2019-04-25 14:47:51 +00:00
|
|
|
}
|
2019-04-19 09:09:06 +00:00
|
|
|
}
|
|
|
|
|
2022-04-18 01:09:25 +00:00
|
|
|
std::optional<std::string> getDesktopFilePath(const std::string& app_id,
|
|
|
|
const std::string& app_class) {
|
2021-10-17 18:08:16 +00:00
|
|
|
const auto data_dirs = Glib::get_system_data_dirs();
|
|
|
|
for (const auto& data_dir : data_dirs) {
|
2022-04-18 01:09:25 +00:00
|
|
|
const auto data_app_dir = data_dir + "applications/";
|
|
|
|
auto desktop_file_path = data_app_dir + app_id + ".desktop";
|
2021-10-17 18:08:16 +00:00
|
|
|
if (std::filesystem::exists(desktop_file_path)) {
|
|
|
|
return desktop_file_path;
|
|
|
|
}
|
2022-04-18 01:09:25 +00:00
|
|
|
if (!app_class.empty()) {
|
|
|
|
desktop_file_path = data_app_dir + app_class + ".desktop";
|
|
|
|
if (std::filesystem::exists(desktop_file_path)) {
|
|
|
|
return desktop_file_path;
|
|
|
|
}
|
|
|
|
}
|
2021-10-17 18:08:16 +00:00
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-04-18 01:09:25 +00:00
|
|
|
std::optional<Glib::ustring> getIconName(const std::string& app_id, const std::string& app_class) {
|
|
|
|
const auto desktop_file_path = getDesktopFilePath(app_id, app_class);
|
2021-10-17 18:08:16 +00:00
|
|
|
if (!desktop_file_path.has_value()) {
|
2022-04-18 01:09:25 +00:00
|
|
|
// Try some heuristics to find a matching icon
|
|
|
|
|
|
|
|
const auto default_icon_theme = Gtk::IconTheme::get_default();
|
|
|
|
if (default_icon_theme->has_icon(app_id)) {
|
|
|
|
return app_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto app_id_desktop = app_id + "-desktop";
|
|
|
|
if (default_icon_theme->has_icon(app_id_desktop)) {
|
|
|
|
return app_id_desktop;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto to_lower = [](const std::string& str) {
|
|
|
|
auto str_cpy = str;
|
|
|
|
std::transform(str_cpy.begin(), str_cpy.end(), str_cpy.begin(),
|
|
|
|
[](unsigned char c) { return std::tolower(c); });
|
|
|
|
return str;
|
|
|
|
};
|
|
|
|
|
|
|
|
const auto first_space = app_id.find_first_of(' ');
|
|
|
|
if (first_space != std::string::npos) {
|
|
|
|
const auto first_word = to_lower(app_id.substr(0, first_space));
|
|
|
|
if (default_icon_theme->has_icon(first_word)) {
|
|
|
|
return first_word;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto first_dash = app_id.find_first_of('-');
|
|
|
|
if (first_dash != std::string::npos) {
|
|
|
|
const auto first_word = to_lower(app_id.substr(0, first_dash));
|
|
|
|
if (default_icon_theme->has_icon(first_word)) {
|
|
|
|
return first_word;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-17 18:08:16 +00:00
|
|
|
return {};
|
|
|
|
}
|
2022-04-18 01:09:25 +00:00
|
|
|
|
2021-10-17 18:08:16 +00:00
|
|
|
try {
|
|
|
|
Glib::KeyFile desktop_file;
|
|
|
|
desktop_file.load_from_file(desktop_file_path.value());
|
2022-04-18 01:09:25 +00:00
|
|
|
return desktop_file.get_string("Desktop Entry", "Icon");
|
2021-10-17 18:08:16 +00:00
|
|
|
} catch (Glib::FileError& error) {
|
2022-04-06 06:37:19 +00:00
|
|
|
spdlog::warn("Error while loading desktop file {}: {}", desktop_file_path.value(),
|
|
|
|
error.what().c_str());
|
2021-10-17 18:08:16 +00:00
|
|
|
} catch (Glib::KeyFileError& error) {
|
2022-04-06 06:37:19 +00:00
|
|
|
spdlog::warn("Error while loading desktop file {}: {}", desktop_file_path.value(),
|
|
|
|
error.what().c_str());
|
2021-10-17 18:08:16 +00:00
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-04-17 20:55:58 +00:00
|
|
|
void Window::updateAppIconName() {
|
2021-10-17 18:08:16 +00:00
|
|
|
if (!iconEnabled()) {
|
|
|
|
return;
|
|
|
|
}
|
2022-04-17 20:55:58 +00:00
|
|
|
|
2022-04-18 01:09:25 +00:00
|
|
|
const auto icon_name = getIconName(app_id_, app_class_);
|
2021-10-17 18:08:16 +00:00
|
|
|
if (icon_name.has_value()) {
|
2022-04-17 20:55:58 +00:00
|
|
|
app_icon_name_ = icon_name.value();
|
|
|
|
} else {
|
|
|
|
app_icon_name_ = "";
|
|
|
|
}
|
|
|
|
update_app_icon_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Window::updateAppIcon() {
|
|
|
|
if (update_app_icon_) {
|
|
|
|
update_app_icon_ = false;
|
|
|
|
if (app_icon_name_.empty()) {
|
|
|
|
image_.set_visible(false);
|
|
|
|
} else {
|
2022-04-17 21:54:27 +00:00
|
|
|
image_.set_from_icon_name(app_icon_name_, Gtk::ICON_SIZE_INVALID);
|
2022-04-17 20:55:58 +00:00
|
|
|
image_.set_visible(true);
|
|
|
|
}
|
2021-10-17 18:08:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-19 09:09:06 +00:00
|
|
|
auto Window::update() -> void {
|
2019-06-04 15:34:00 +00:00
|
|
|
if (!old_app_id_.empty()) {
|
|
|
|
bar_.window.get_style_context()->remove_class(old_app_id_);
|
|
|
|
}
|
|
|
|
if (app_nb_ == 0) {
|
|
|
|
bar_.window.get_style_context()->remove_class("solo");
|
|
|
|
if (!bar_.window.get_style_context()->has_class("empty")) {
|
|
|
|
bar_.window.get_style_context()->add_class("empty");
|
|
|
|
}
|
|
|
|
} else if (app_nb_ == 1) {
|
|
|
|
bar_.window.get_style_context()->remove_class("empty");
|
|
|
|
if (!bar_.window.get_style_context()->has_class("solo")) {
|
|
|
|
bar_.window.get_style_context()->add_class("solo");
|
|
|
|
}
|
|
|
|
if (!app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) {
|
|
|
|
bar_.window.get_style_context()->add_class(app_id_);
|
|
|
|
old_app_id_ = app_id_;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
bar_.window.get_style_context()->remove_class("solo");
|
|
|
|
bar_.window.get_style_context()->remove_class("empty");
|
|
|
|
}
|
2022-08-10 08:41:18 +00:00
|
|
|
label_.set_markup(fmt::format(format_, fmt::arg("title", rewriteTitle(window_)),
|
|
|
|
fmt::arg("app_id", app_id_), fmt::arg("shell", shell_)));
|
2019-02-22 10:35:26 +00:00
|
|
|
if (tooltipEnabled()) {
|
|
|
|
label_.set_tooltip_text(window_);
|
|
|
|
}
|
2022-04-17 20:55:58 +00:00
|
|
|
|
|
|
|
updateAppIcon();
|
|
|
|
|
2020-04-12 16:30:21 +00:00
|
|
|
// Call parent update
|
2021-10-17 18:08:16 +00:00
|
|
|
AIconLabel::update();
|
2018-08-15 18:17:17 +00:00
|
|
|
}
|
|
|
|
|
2020-12-18 23:14:14 +00:00
|
|
|
int leafNodesInWorkspace(const Json::Value& node) {
|
|
|
|
auto const& nodes = node["nodes"];
|
2021-10-02 16:35:38 +00:00
|
|
|
auto const& floating_nodes = node["floating_nodes"];
|
2022-04-06 06:37:19 +00:00
|
|
|
if (nodes.empty() && floating_nodes.empty()) {
|
|
|
|
if (node["type"] == "workspace")
|
2020-12-18 23:14:14 +00:00
|
|
|
return 0;
|
|
|
|
else
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
int sum = 0;
|
2021-10-02 16:35:38 +00:00
|
|
|
if (!nodes.empty()) {
|
2022-04-06 06:37:19 +00:00
|
|
|
for (auto const& node : nodes) sum += leafNodesInWorkspace(node);
|
2021-10-02 16:35:38 +00:00
|
|
|
}
|
|
|
|
if (!floating_nodes.empty()) {
|
2022-04-06 06:37:19 +00:00
|
|
|
for (auto const& node : floating_nodes) sum += leafNodesInWorkspace(node);
|
2021-10-02 16:35:38 +00:00
|
|
|
}
|
2020-12-18 23:14:14 +00:00
|
|
|
return sum;
|
|
|
|
}
|
|
|
|
|
2022-08-10 08:34:51 +00:00
|
|
|
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string> gfnWithWorkspace(
|
2022-04-06 06:37:19 +00:00
|
|
|
const Json::Value& nodes, std::string& output, const Json::Value& config_, const Bar& bar_,
|
|
|
|
Json::Value& parentWorkspace) {
|
|
|
|
for (auto const& node : nodes) {
|
2019-06-14 08:57:22 +00:00
|
|
|
if (node["output"].isString()) {
|
|
|
|
output = node["output"].asString();
|
|
|
|
}
|
2020-12-18 23:14:14 +00:00
|
|
|
// found node
|
2019-06-14 09:27:40 +00:00
|
|
|
if (node["focused"].asBool() && (node["type"] == "con" || node["type"] == "floating_con")) {
|
2019-06-14 08:57:22 +00:00
|
|
|
if ((!config_["all-outputs"].asBool() && output == bar_.output->name) ||
|
2019-04-25 14:47:51 +00:00
|
|
|
config_["all-outputs"].asBool()) {
|
|
|
|
auto app_id = node["app_id"].isString() ? node["app_id"].asString()
|
2022-04-06 06:37:19 +00:00
|
|
|
: node["window_properties"]["instance"].asString();
|
2022-04-18 01:09:25 +00:00
|
|
|
const auto app_class = node["window_properties"]["class"].isString()
|
|
|
|
? node["window_properties"]["class"].asString()
|
|
|
|
: "";
|
2022-08-10 08:34:51 +00:00
|
|
|
|
2022-08-10 08:41:18 +00:00
|
|
|
const auto shell = node["shell"].isString() ? node["shell"].asString() : "";
|
2022-08-10 08:34:51 +00:00
|
|
|
|
2020-12-18 23:14:14 +00:00
|
|
|
int nb = node.size();
|
2022-04-06 06:37:19 +00:00
|
|
|
if (parentWorkspace != 0) nb = leafNodesInWorkspace(parentWorkspace);
|
2022-08-18 13:22:25 +00:00
|
|
|
return {nb, node["id"].asInt(), Glib::Markup::escape_text(node["name"].asString()),
|
|
|
|
app_id, app_class, shell};
|
2019-04-25 14:47:51 +00:00
|
|
|
}
|
2018-08-16 12:29:41 +00:00
|
|
|
}
|
2020-12-18 23:14:14 +00:00
|
|
|
// iterate
|
2022-04-06 06:37:19 +00:00
|
|
|
if (node["type"] == "workspace") parentWorkspace = node;
|
2022-08-10 08:34:51 +00:00
|
|
|
auto [nb, id, name, app_id, app_class, shell] =
|
2022-04-06 06:37:19 +00:00
|
|
|
gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace);
|
2019-06-14 09:27:40 +00:00
|
|
|
if (id > -1 && !name.empty()) {
|
2022-08-10 08:34:51 +00:00
|
|
|
return {nb, id, name, app_id, app_class, shell};
|
2019-06-14 09:27:40 +00:00
|
|
|
}
|
|
|
|
// Search for floating node
|
2022-08-10 08:34:51 +00:00
|
|
|
std::tie(nb, id, name, app_id, app_class, shell) =
|
2022-04-06 06:37:19 +00:00
|
|
|
gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace);
|
2018-09-18 19:16:35 +00:00
|
|
|
if (id > -1 && !name.empty()) {
|
2022-08-10 08:34:51 +00:00
|
|
|
return {nb, id, name, app_id, app_class, shell};
|
2018-08-16 12:29:41 +00:00
|
|
|
}
|
2018-08-15 18:17:17 +00:00
|
|
|
}
|
2022-08-10 08:34:51 +00:00
|
|
|
return {0, -1, "", "", "", ""};
|
2018-08-15 18:17:17 +00:00
|
|
|
}
|
|
|
|
|
2022-08-10 08:41:18 +00:00
|
|
|
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string>
|
|
|
|
Window::getFocusedNode(const Json::Value& nodes, std::string& output) {
|
2020-12-18 23:17:17 +00:00
|
|
|
Json::Value placeholder = 0;
|
2020-12-18 23:14:14 +00:00
|
|
|
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder);
|
|
|
|
}
|
|
|
|
|
2019-04-25 14:47:51 +00:00
|
|
|
void Window::getTree() {
|
2018-08-15 18:17:17 +00:00
|
|
|
try {
|
2019-04-19 09:09:06 +00:00
|
|
|
ipc_.sendCmd(IPC_GET_TREE);
|
2019-04-18 15:52:00 +00:00
|
|
|
} catch (const std::exception& e) {
|
2019-05-18 23:44:45 +00:00
|
|
|
spdlog::error("Window: {}", e.what());
|
2018-08-15 18:17:17 +00:00
|
|
|
}
|
|
|
|
}
|
2019-04-19 09:09:06 +00:00
|
|
|
|
2021-04-26 18:26:43 +00:00
|
|
|
std::string Window::rewriteTitle(const std::string& title) {
|
2021-03-21 10:42:25 +00:00
|
|
|
const auto& rules = config_["rewrite"];
|
|
|
|
if (!rules.isObject()) {
|
|
|
|
return title;
|
|
|
|
}
|
|
|
|
|
2021-04-26 18:26:43 +00:00
|
|
|
std::string res = title;
|
|
|
|
|
2021-03-21 10:42:25 +00:00
|
|
|
for (auto it = rules.begin(); it != rules.end(); ++it) {
|
|
|
|
if (it.key().isString() && it->isString()) {
|
2021-04-21 10:15:25 +00:00
|
|
|
try {
|
|
|
|
// malformated regexes will cause an exception.
|
|
|
|
// in this case, log error and try the next rule.
|
|
|
|
const std::regex rule{it.key().asString()};
|
|
|
|
if (std::regex_match(title, rule)) {
|
2021-04-26 18:26:43 +00:00
|
|
|
res = std::regex_replace(res, rule, it->asString());
|
2021-04-21 10:15:25 +00:00
|
|
|
}
|
|
|
|
} catch (const std::regex_error& e) {
|
|
|
|
spdlog::error("Invalid rule {}: {}", it.key().asString(), e.what());
|
2021-03-21 10:42:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-26 18:26:43 +00:00
|
|
|
return res;
|
2021-03-21 10:42:25 +00:00
|
|
|
}
|
|
|
|
|
2019-05-11 18:36:10 +00:00
|
|
|
} // namespace waybar::modules::sway
|