From 83d5ba42f8a98fc0df177efef4333f74a0481f61 Mon Sep 17 00:00:00 2001 From: pants Date: Mon, 21 Sep 2026 14:18:06 -0700 Subject: init --- src/util/dbus.cpp | 15 +++ src/util/egl.cpp | 228 +++++++++++++++++++++++++++++++++++++++++++++ src/util/socket.cpp | 169 +++++++++++++++++++++++++++++++++ src/util/util.cpp | 262 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/util/x11.cpp | 171 ++++++++++++++++++++++++++++++++++ 5 files changed, 845 insertions(+) create mode 100644 src/util/dbus.cpp create mode 100644 src/util/egl.cpp create mode 100644 src/util/socket.cpp create mode 100644 src/util/util.cpp create mode 100644 src/util/x11.cpp (limited to 'src/util') diff --git a/src/util/dbus.cpp b/src/util/dbus.cpp new file mode 100644 index 0000000..1cc78e5 --- /dev/null +++ b/src/util/dbus.cpp @@ -0,0 +1,15 @@ +#include "util/dbus.hpp" +#include + +DbusUtil::DbusUtil(const std::string &address) + : connection(dbus_connection_open(address.c_str(), nullptr)) +{ + if (connection == nullptr) { + throw std::invalid_argument("Could not connect to dbus address"); + } +} + +DbusUtil::~DbusUtil() +{ + dbus_connection_unref(connection); +} diff --git a/src/util/egl.cpp b/src/util/egl.cpp new file mode 100644 index 0000000..5b64459 --- /dev/null +++ b/src/util/egl.cpp @@ -0,0 +1,228 @@ +// 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/egl.hpp" +#include "image.hpp" + +#include +#include + +#include +#include + +constexpr EGLint egl_major_version = 1; +constexpr EGLint egl_minor_version = 5; +constexpr EGLint opengl_major_version = 4; +constexpr EGLint opengl_minor_version = 6; + +constexpr auto config_attrs = std::to_array({EGL_SURFACE_TYPE, + EGL_WINDOW_BIT, + EGL_CONFORMANT, + EGL_OPENGL_BIT, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_BIT, + EGL_COLOR_BUFFER_TYPE, + EGL_RGB_BUFFER, + + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_ALPHA_SIZE, + 8, + + EGL_DEPTH_SIZE, + 24, + EGL_STENCIL_SIZE, + 8, + + EGL_NONE}); + +const auto context_attrs = std::to_array( + {EGL_CONTEXT_MAJOR_VERSION, opengl_major_version, EGL_CONTEXT_MINOR_VERSION, opengl_minor_version, +#ifdef DEBUG + EGL_CONTEXT_OPENGL_DEBUG, EGL_TRUE, +#endif + EGL_CONTEXT_OPENGL_ROBUST_ACCESS, EGL_TRUE, EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + EGL_NONE}); + +void GLAPIENTRY debug_callback([[maybe_unused]] GLenum source, [[maybe_unused]] GLenum type, + [[maybe_unused]] GLuint gl_id, GLenum severity, [[maybe_unused]] GLsizei length, + const GLchar *message, [[maybe_unused]] const void *user) +{ + if (type != GL_DEBUG_TYPE_ERROR) { + return; + } + const auto logger = spdlog::get("opengl"); + logger->error("Type: {:#X}, Severity: {:#X}, Message: {}", type, severity, message); +} + +template +EGLUtil::EGLUtil(EGLenum platform, T *native_display, const EGLAttrib *attrib) + : display(eglGetPlatformDisplay(platform, native_display, attrib)) +{ + logger = spdlog::get("opengl"); + + if (display == EGL_NO_DISPLAY) { + const auto err = error_to_string(); + logger->error("Could not obtain display, error {}", err); + throw std::runtime_error(""); + } + + EGLint egl_major = 0; + EGLint egl_minor = 0; + EGLBoolean eglres = eglInitialize(display, &egl_major, &egl_minor); + if (eglres != EGL_TRUE) { + const auto err = error_to_string(); + logger->error("Could not initialize display, error {}", err); + throw std::runtime_error(""); + } + if (egl_major != egl_major_version && egl_minor != egl_minor_version) { + logger->error("EGL {}.{} is not available", egl_major_version, egl_minor_version); + throw std::runtime_error(""); + } + + eglres = eglBindAPI(EGL_OPENGL_API); + if (eglres != EGL_TRUE) { + const auto err = error_to_string(); + logger->error("Could not bind to OpenGL API, error {}", err); + throw std::runtime_error(""); + } + + int num_config = 0; + eglres = eglChooseConfig(display, config_attrs.data(), &config, 1, &num_config); + if (eglres != EGL_TRUE || num_config != 1) { + const auto err = error_to_string(); + logger->error("Could not create config, error {}", err); + throw std::runtime_error(""); + } + + logger->info("Using EGL {}.{} and OpenGL {}.{}", egl_major_version, egl_minor_version, opengl_major_version, + opengl_minor_version); +} + +template +EGLUtil::~EGLUtil() +{ + eglTerminate(display); +} + +template +auto EGLUtil::error_to_string() const -> std::string +{ + using pair_type = std::pair; + constexpr auto error_codes = std::to_array({ + {EGL_SUCCESS, "EGL_SUCCESS"}, + {EGL_BAD_ACCESS, "EGL_BAD_ACCESS"}, + {EGL_BAD_ALLOC, "EGL_BAD_ALLOC"}, + {EGL_BAD_ATTRIBUTE, "EGL_BAD_ATTRIBUTE"}, + {EGL_BAD_CONFIG, "EGL_BAD_CONFIG"}, + {EGL_BAD_CONTEXT, "EGL_BAD_CONTEXT"}, + {EGL_BAD_CURRENT_SURFACE, "EGL_BAD_CURRENT_SURFACE"}, + {EGL_BAD_DISPLAY, "EGL_BAD_DISPLAY"}, + {EGL_BAD_MATCH, "EGL_BAD_MATCH"}, + {EGL_BAD_NATIVE_PIXMAP, "EGL_BAD_NATIVE_PIXMAP"}, + {EGL_BAD_NATIVE_WINDOW, "EGL_BAD_NATIVE_WINDOW"}, + {EGL_BAD_PARAMETER, "EGL_BAD_PARAMETER"}, + {EGL_BAD_SURFACE, "EGL_BAD_SURFACE"}, + }); + + const auto current_error = eglGetError(); + + const auto found = + ranges::find_if(error_codes, [current_error](const pair_type &pair) { return pair.first == current_error; }); + + if (found == error_codes.end()) { + return "EGL_UNKNOWN_ERROR"; + } + + return std::string(found->second); +} + +template +auto EGLUtil::create_context(EGLSurface surface) const -> EGLContext +{ + EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attrs.data()); + if (context == EGL_NO_CONTEXT) { + const auto err = error_to_string(); + logger->error("Could not create context, error {}", err); + return context; + } + +#ifdef DEBUG + run_contained(surface, context, [] { + glDebugMessageCallback(debug_callback, nullptr); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + }); +#endif + + return context; +} + +template +auto EGLUtil::create_surface(V *native_window) const -> EGLSurface +{ + EGLSurface surface = eglCreatePlatformWindowSurface(display, config, native_window, nullptr); + if (surface == EGL_NO_SURFACE) { + const auto err = error_to_string(); + logger->error("Could not create surface, error {}", err); + } + return surface; +} + +template +void EGLUtil::make_current(EGLSurface surface, EGLContext context) const +{ + eglMakeCurrent(display, surface, surface, context); +} + +template +void EGLUtil::restore() const +{ + eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); +} + +template +void EGLUtil::run_contained(EGLSurface surface, EGLContext context, const std::function &func) const +{ + make_current(surface, context); + func(); + restore(); +} + +template +void EGLUtil::get_texture_from_image(const Image &image, GLuint texture) const +{ + glBindTexture(GL_TEXTURE_2D, texture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.width(), image.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, image.data()); +} + +#ifdef ENABLE_X11 +# include +template class EGLUtil; +#endif + +#ifdef ENABLE_WAYLAND +# include +# include +template class EGLUtil; +#endif diff --git a/src/util/socket.cpp b/src/util/socket.cpp new file mode 100644 index 0000000..d770270 --- /dev/null +++ b/src/util/socket.cpp @@ -0,0 +1,169 @@ +// 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/socket.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "os.hpp" +#include "util.hpp" + +namespace fs = std::filesystem; + +UnixSocket::UnixSocket(const std::string_view endpoint) + : fd(socket(AF_UNIX, SOCK_STREAM, 0)) +{ + if (fd == -1) { + throw std::system_error(errno, std::generic_category()); + } + connect_to_endpoint(endpoint); +} + +UnixSocket::UnixSocket() + : fd(socket(AF_UNIX, SOCK_STREAM, 0)) +{ + if (fd == -1) { + throw std::system_error(errno, std::generic_category()); + } + const int bufsize = 8192; + buffer.reserve(bufsize); +} + +void UnixSocket::connect_to_endpoint(const std::string_view endpoint) +{ + if (!fs::exists(endpoint)) { + connected = false; + return; + } + struct sockaddr_un sock; + std::memset(&sock, 0, sizeof(sockaddr_un)); + sock.sun_family = AF_UNIX; + endpoint.copy(sock.sun_path, endpoint.size()); + + int res = connect(fd, reinterpret_cast(&sock), sizeof(struct sockaddr_un)); + if (res == -1) { + throw std::system_error(errno, std::generic_category()); + } +} + +void UnixSocket::bind_to_endpoint(const std::string_view endpoint) const +{ + struct sockaddr_un sock; + std::memset(&sock, 0, sizeof(sockaddr_un)); + sock.sun_family = AF_UNIX; + endpoint.copy(sock.sun_path, endpoint.size()); + + int res = bind(fd, reinterpret_cast(&sock), sizeof(struct sockaddr_un)); + if (res == -1) { + throw std::system_error(errno, std::generic_category()); + } + res = listen(fd, SOMAXCONN); + if (res == -1) { + throw std::system_error(errno, std::generic_category()); + } +} + +auto UnixSocket::wait_for_connections(int waitms) const -> int +{ + const auto in_event = os::wait_for_data_on_fd(fd, waitms); + if (in_event) { + return accept(fd, nullptr, nullptr); + } + return -1; +} + +auto UnixSocket::read_data_from_connection(int filde) -> std::vector +{ + // a single connection could send multiples commands at once + // each command should end with a '\n' character + const int read_buffer_size = 4096; + std::array read_buffer; + while (true) { + const auto status = recv(filde, read_buffer.data(), read_buffer_size, 0); + if (status <= 0) { + break; + } + buffer.append(read_buffer.data(), status); + } + auto cmds = util::str_split(buffer, "\n"); + buffer.clear(); + close(filde); + return cmds; +} + +void UnixSocket::write(const void *data, std::size_t len) const +{ + if (!connected) { + return; + } + const auto *runner = static_cast(data); + while (len != 0) { + const auto status = send(fd, runner, len, MSG_NOSIGNAL); + if (status == -1) { + throw std::system_error(errno, std::generic_category()); + } + len -= status; + runner += status; + } +} + +void UnixSocket::read(void *data, std::size_t len) const +{ + if (!connected) { + return; + } + auto *runner = static_cast(data); + while (len != 0) { + const auto status = recv(fd, runner, len, 0); + if (status == 0) { + return; // no data + } + if (status == -1) { + throw std::system_error(errno, std::generic_category()); + } + len -= status; + runner += status; + } +} + +auto UnixSocket::read_until_empty() const -> std::string +{ + std::string result; + const int read_buffer_size = 4096; + std::array read_buffer; + result.reserve(read_buffer_size); + while (true) { + const auto status = recv(fd, read_buffer.data(), read_buffer_size, 0); + if (status <= 0) { + break; + } + result.append(read_buffer.data(), status); + } + return result; +} + +UnixSocket::~UnixSocket() +{ + close(fd); +} diff --git a/src/util/util.cpp b/src/util/util.cpp new file mode 100644 index 0000000..2d3e99f --- /dev/null +++ b/src/util/util.cpp @@ -0,0 +1,262 @@ +// 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.hpp" +#include "flags.hpp" +#include "os.hpp" +#include "process.hpp" +#include "util/ptr.hpp" +#include "util/socket.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#if OPENSSL_VERSION_NUMBER < 0x10100000L +# define EVP_MD_CTX_new EVP_MD_CTX_create +# define EVP_MD_CTX_free EVP_MD_CTX_destroy +#endif +#ifdef ENABLE_TURBOBASE64 +# ifdef WITH_SYSTEM_TURBOBASE64 +# include +# else +# include "turbob64.h" +# endif +#endif +#include + +#include + +namespace fs = std::filesystem; +using njson = nlohmann::json; + +auto util::str_split(std::string_view str, std::string_view delim) -> std::vector +{ + std::vector result; + for (const auto word : ranges::views::split(str, delim)) { + result.emplace_back(ranges::to(word)); + } + return result; +} + +auto util::get_process_tree(int pid) -> std::vector +{ + std::vector res; + Process runner(pid); + while (runner.pid > 1) { + res.push_back(runner.pid); + runner = Process(runner.ppid); + } + return res; +} + +auto util::get_process_tree_v2(int pid) -> std::vector +{ + std::vector tree; + Process runner(pid); + while (runner.pid > 1) { + tree.push_back(runner); + runner = Process(runner.ppid); + } + return tree; +} + +auto util::get_cache_path() -> std::string +{ + const auto home = os::getenv("HOME").value_or(util::temp_directory_path()); + const auto cache_home = os::getenv("XDG_CACHE_HOME").value_or(fmt::format("{}/.cache", home)); + return fmt::format("{}/ueberzugpp/", cache_home); +} + +auto util::get_log_filename() -> std::string +{ + const auto user = os::getenv("USER").value_or("NOUSER"); + return fmt::format("ueberzugpp-{}.log", user); +} + +auto util::get_socket_path(int pid) -> std::string +{ + return fmt::format("{}/ueberzugpp-{}.socket", util::temp_directory_path().string(), pid); +} + +void util::send_socket_message(const std::string_view msg, const std::string_view endpoint) +{ + try { + UnixSocket socket; + socket.connect_to_endpoint(endpoint); + socket.write(msg.data(), msg.size()); + } catch (const std::system_error &err) { + return; + } +} + +auto util::base64_encode(const unsigned char *input, size_t length) -> std::string +{ + const size_t bufsize = 4 * ((length + 2) / 3) + 1; + std::vector res(bufsize, 0); + base64_encode_v2(input, length, reinterpret_cast(res.data())); + return {res.data()}; +} + +void util::base64_encode_v2(const unsigned char *input, size_t length, unsigned char *out) +{ +#ifdef ENABLE_TURBOBASE64 + tb64enc(input, length, out); +#else + EVP_EncodeBlock(out, input, static_cast(length)); +#endif +} + +auto util::get_b2_hash_ssl(const std::string_view str) -> std::string +{ + std::stringstream sstream; + const auto mdctx = c_unique_ptr{EVP_MD_CTX_new()}; +#ifdef LIBRESSL_VERSION_NUMBER + const auto *evp = EVP_sha256(); +#else + const auto *evp = EVP_blake2b512(); +#endif + auto digest = std::array(); + + EVP_DigestInit_ex(mdctx.get(), evp, nullptr); + EVP_DigestUpdate(mdctx.get(), str.data(), str.size()); + unsigned int digest_len = 0; + EVP_DigestFinal_ex(mdctx.get(), digest.data(), &digest_len); + + sstream << std::hex << std::setw(2) << std::setfill('0'); + for (unsigned int i = 0; i < digest_len; ++i) { + sstream << static_cast(digest[i]); + } + return sstream.str(); +} + +void util::move_cursor(int row, int col) +{ + std::cout << "\033[" << row << ";" << col << "f" << std::flush; +} + +void util::save_cursor_position() +{ + std::cout << "\0337" << std::flush; +} + +void util::restore_cursor_position() +{ + std::cout << "\0338" << std::flush; +} + +auto util::get_cache_file_save_location(const fs::path &path) -> std::string +{ + return fmt::format("{}{}{}", get_cache_path(), get_b2_hash_ssl(path.string()), path.extension().string()); +} + +void util::benchmark(const std::function &func) +{ + using std::chrono::duration; + using std::chrono::duration_cast; + using std::chrono::high_resolution_clock; + using std::chrono::milliseconds; + + const auto ti1 = high_resolution_clock::now(); + func(); + const auto ti2 = high_resolution_clock::now(); + const duration ms_double = ti2 - ti1; + + std::cout << ms_double.count() << "ms\n"; +} + +void util::send_command(const Flags &flags) +{ + if (flags.cmd_action == "exit") { + util::send_socket_message("EXIT", flags.cmd_socket); + return; + } + + if (flags.cmd_action == "remove") { + const njson json = {{"action", "remove"}, {"identifier", flags.cmd_id}}; + util::send_socket_message(json.dump(), flags.cmd_socket); + return; + } + + const njson json = {{"action", flags.cmd_action}, + {"identifier", flags.cmd_id}, + {"max_width", std::stoi(flags.cmd_max_width)}, + {"max_height", std::stoi(flags.cmd_max_height)}, + {"x", std::stoi(flags.cmd_x)}, + {"y", std::stoi(flags.cmd_y)}, + {"path", flags.cmd_file_path}}; + util::send_socket_message(json.dump(), flags.cmd_socket); +} + +void util::clear_terminal_area(int xcoord, int ycoord, int width, int height) +{ + save_cursor_position(); + const auto line_clear = std::string(width, ' '); + for (int i = ycoord; i <= height + 2; ++i) { + util::move_cursor(i, xcoord); + std::cout << line_clear; + } + std::cout << std::flush; + restore_cursor_position(); +} + +auto util::generate_random_string(size_t length) -> std::string +{ + constexpr auto chars = + std::to_array({'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}); + auto rng_dev = std::random_device(); + auto rng = std::mt19937(rng_dev()); + auto dist = std::uniform_int_distribution{{}, chars.size() - 1}; + std::string result(length, 0); + std::generate_n(std::begin(result), length, [&chars, &dist, &rng] { return chars[dist(rng)]; }); + return result; +} + +auto util::read_exif_rotation(const fs::path &path) -> std::optional +{ + try { + auto image = vips::VImage::new_from_file(path.c_str()); + return image.get_int("orientation"); + } catch (const vips::VError &) { + return {}; + } +} + +auto util::round_up(int num_to_round, int multiple) -> int +{ + if (multiple == 0) { + return num_to_round; + } + + int remainder = num_to_round % multiple; + if (remainder == 0) { + return num_to_round; + } + + return num_to_round + multiple - remainder; +} + +auto util::temp_directory_path() -> fs::path +{ + return os::getenv("UEBERZUGPP_TMPDIR").value_or(fs::temp_directory_path()); +} 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