// Display images inside a terminal
// Copyright (C) 2023 JustKidding
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
#include "util/x11.hpp"
#include "flags.hpp"
#include "os.hpp"
#include "util.hpp"
#include "util/ptr.hpp"
#include
#include
#include
#include
#include
#include
#include
X11Util::X11Util()
: connection(xcb_connect(nullptr, nullptr))
{
const auto flags = Flags::instance();
const auto xdg_session = os::getenv("XDG_SESSION_TYPE").value_or("");
if (xcb_connection_has_error(connection) == 0) {
screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data;
if (xdg_session != "wayland" || flags->output == "x11") {
connected = true;
}
}
}
X11Util::X11Util(xcb_connection_t *connection)
: connection(connection),
screen(xcb_setup_roots_iterator(xcb_get_setup(connection)).data),
owns_connection(false)
{
}
X11Util::~X11Util()
{
if (owns_connection) {
xcb_disconnect(connection);
}
}
auto X11Util::get_server_window_ids() const -> std::vector
{
const int num_clients = 256;
std::vector windows;
std::stack cookies_st;
windows.reserve(num_clients);
cookies_st.push(xcb_query_tree_unchecked(connection, screen->root));
while (!cookies_st.empty()) {
const auto cookie = cookies_st.top();
cookies_st.pop();
const auto reply = unique_C_ptr{xcb_query_tree_reply(connection, cookie, nullptr)};
if (!reply) {
continue;
}
const auto num_children = xcb_query_tree_children_length(reply.get());
if (num_children == 0) {
continue;
}
const auto *children = xcb_query_tree_children(reply.get());
for (int i = 0; i < num_children; ++i) {
const auto child = children[i];
const bool is_complete_window = window_has_properties(child, {XCB_ATOM_WM_CLASS, XCB_ATOM_WM_NAME});
if (is_complete_window) {
windows.push_back(child);
}
cookies_st.push(xcb_query_tree_unchecked(connection, child));
}
}
return windows;
}
auto X11Util::get_pid_window_map() const -> std::unordered_map
{
const auto windows = get_server_window_ids();
std::unordered_map res;
std::vector cookies;
res.reserve(windows.size());
cookies.reserve(windows.size());
struct xcb_res_client_id_spec_t spec;
spec.mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID;
// bulk request pids
for (const auto window : windows) {
spec.client = window;
cookies.push_back(xcb_res_query_client_ids_unchecked(connection, 1, &spec));
}
// process replies
auto win_iter = windows.cbegin();
for (const auto cookie : cookies) {
const auto reply =
unique_C_ptr{xcb_res_query_client_ids_reply(connection, cookie, nullptr)};
if (!reply) {
continue;
}
const auto iter = xcb_res_query_client_ids_ids_iterator(reply.get());
const auto pid = *xcb_res_client_id_value_value(iter.data);
res.insert_or_assign(pid, *win_iter);
std::advance(win_iter, 1);
}
return res;
}
auto X11Util::window_has_properties(xcb_window_t window, std::initializer_list properties) const -> bool
{
std::vector cookies;
cookies.reserve(properties.size());
for (const auto prop : properties) {
cookies.push_back(xcb_get_property_unchecked(connection, 0, window, prop, XCB_ATOM_ANY, 0, 4));
}
return ranges::any_of(cookies, [this](xcb_get_property_cookie_t cookie) -> bool {
const auto reply = unique_C_ptr{xcb_get_property_reply(connection, cookie, nullptr)};
return reply && xcb_get_property_value_length(reply.get()) != 0;
});
}
auto X11Util::get_window_dimensions(xcb_window_t window) const -> std::pair
{
const auto cookie = xcb_get_geometry_unchecked(connection, window);
const auto reply = unique_C_ptr{xcb_get_geometry_reply(connection, cookie, nullptr)};
if (!reply) {
return std::make_pair(0, 0);
}
return std::make_pair(reply->width, reply->height);
}
auto X11Util::get_parent_window(int pid) const -> xcb_window_t
{
const auto wid = os::getenv("WINDOWID");
if (wid.has_value()) {
try {
return std::stoi(wid.value());
} catch (const std::out_of_range &oor) {
return 0;
}
}
const auto pid_window_map = get_pid_window_map();
const auto tree = util::get_process_tree(pid);
for (const auto spid : tree) {
const auto win = pid_window_map.find(spid);
if (win != pid_window_map.end()) {
return win->second;
}
}
return 0;
}