From 83d5ba42f8a98fc0df177efef4333f74a0481f61 Mon Sep 17 00:00:00 2001 From: pants Date: Mon, 21 Sep 2026 14:18:06 -0700 Subject: init --- src/util/x11.cpp | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 src/util/x11.cpp (limited to 'src/util/x11.cpp') diff --git a/src/util/x11.cpp b/src/util/x11.cpp new file mode 100644 index 0000000..5250997 --- /dev/null +++ b/src/util/x11.cpp @@ -0,0 +1,171 @@ +// 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; +} -- cgit v1.2.3