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>
|
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)
|
2021-10-17 18:08:16 +00:00
|
|
|
: AIconLabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) {
|
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() : "";
|
2019-06-14 09:27:40 +00:00
|
|
|
std::tie(app_nb_, windowId_, window_, app_id_) = getFocusedNode(payload["nodes"], output);
|
2021-10-17 18:08:16 +00:00
|
|
|
updateAppIcon();
|
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
|
|
|
}
|
|
|
|
|
2021-10-17 18:08:16 +00:00
|
|
|
std::optional<std::string> getDesktopFilePath(const std::string& app_id) {
|
|
|
|
const auto data_dirs = Glib::get_system_data_dirs();
|
|
|
|
for (const auto& data_dir : data_dirs) {
|
|
|
|
const auto desktop_file_path = data_dir + "applications/" + app_id + ".desktop";
|
|
|
|
if (std::filesystem::exists(desktop_file_path)) {
|
|
|
|
return desktop_file_path;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<Glib::ustring> getIconName(const std::string& app_id) {
|
|
|
|
const auto desktop_file_path = getDesktopFilePath(app_id);
|
|
|
|
if (!desktop_file_path.has_value()) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
Glib::KeyFile desktop_file;
|
|
|
|
desktop_file.load_from_file(desktop_file_path.value());
|
|
|
|
const auto icon_name = desktop_file.get_string("Desktop Entry", "Icon");
|
|
|
|
if (icon_name.empty()) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
return icon_name;
|
|
|
|
} 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 {};
|
|
|
|
}
|
|
|
|
|
|
|
|
void Window::updateAppIcon() {
|
|
|
|
if (!iconEnabled()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const auto icon_name = getIconName(app_id_);
|
|
|
|
if (icon_name.has_value()) {
|
|
|
|
image_.set_from_icon_name(icon_name.value(), Gtk::ICON_SIZE_LARGE_TOOLBAR);
|
|
|
|
image_.set_visible(true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
image_.set_visible(false);
|
|
|
|
}
|
|
|
|
|
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-04-06 06:37:19 +00:00
|
|
|
label_.set_markup(
|
|
|
|
fmt::format(format_, fmt::arg("title", rewriteTitle(window_)), fmt::arg("app_id", app_id_)));
|
2019-02-22 10:35:26 +00:00
|
|
|
if (tooltipEnabled()) {
|
|
|
|
label_.set_tooltip_text(window_);
|
|
|
|
}
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::tuple<std::size_t, int, 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();
|
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);
|
|
|
|
return {nb, node["id"].asInt(), Glib::Markup::escape_text(node["name"].asString()), app_id};
|
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;
|
|
|
|
auto [nb, id, name, app_id] =
|
|
|
|
gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace);
|
2019-06-14 09:27:40 +00:00
|
|
|
if (id > -1 && !name.empty()) {
|
|
|
|
return {nb, id, name, app_id};
|
|
|
|
}
|
|
|
|
// Search for floating node
|
2022-04-06 06:37:19 +00:00
|
|
|
std::tie(nb, id, name, app_id) =
|
|
|
|
gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace);
|
2018-09-18 19:16:35 +00:00
|
|
|
if (id > -1 && !name.empty()) {
|
2019-04-25 14:47:51 +00:00
|
|
|
return {nb, id, name, app_id};
|
2018-08-16 12:29:41 +00:00
|
|
|
}
|
2018-08-15 18:17:17 +00:00
|
|
|
}
|
2019-04-25 14:47:51 +00:00
|
|
|
return {0, -1, "", ""};
|
2018-08-15 18:17:17 +00:00
|
|
|
}
|
|
|
|
|
2020-12-18 23:14:14 +00:00
|
|
|
std::tuple<std::size_t, int, 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
|