#pragma once #include #include #include #include namespace xcb { struct ReplyDeleter { template void operator()(T* ptr) const { free(const_cast*>(ptr)); } }; template using Reply = std::unique_ptr; static std::optional getAtom(xcb_connection_t* connection, std::string_view name) { xcb_intern_atom_cookie_t cookie = xcb_intern_atom(connection, false, name.length(), name.data()); auto reply = Reply{ xcb_intern_atom_reply(connection, cookie, nullptr) }; if (!reply) { fprintf(stderr, "[Gamescope WSI] Failed to get xcb atom.\n"); return std::nullopt; } xcb_atom_t atom = reply->atom; return atom; } template static std::optional getPropertyValue(xcb_connection_t* connection, xcb_atom_t atom) { static_assert(sizeof(T) % 4 == 0); xcb_screen_t* screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data; xcb_get_property_cookie_t cookie = xcb_get_property(connection, false, screen->root, atom, XCB_ATOM_CARDINAL, 0, sizeof(T) / sizeof(uint32_t)); auto reply = Reply{ xcb_get_property_reply(connection, cookie, nullptr) }; if (!reply) { fprintf(stderr, "[Gamescope WSI] Failed to read T root window property.\n"); return std::nullopt; } if (reply->type != XCB_ATOM_CARDINAL) { fprintf(stderr, "[Gamescope WSI] Atom of T was wrong type. Expected XCB_ATOM_CARDINAL.\n"); return std::nullopt; } T value = *reinterpret_cast(xcb_get_property_value(reply.get())); return value; } template static std::optional getPropertyValue(xcb_connection_t* connection, std::string_view name) { std::optional atom = getAtom(connection, name); if (!atom) return std::nullopt; return getPropertyValue(connection, *atom); } static std::optional getToplevelWindow(xcb_connection_t* connection, xcb_window_t window) { for (;;) { xcb_query_tree_cookie_t cookie = xcb_query_tree(connection, window); auto reply = Reply{ xcb_query_tree_reply(connection, cookie, nullptr) }; if (!reply) { fprintf(stderr, "[Gamescope WSI] getToplevelWindow: xcb_query_tree failed for window 0x%x.\n", window); return std::nullopt; } if (reply->root == reply->parent) return window; window = reply->parent; } } static std::optional getWindowRect(xcb_connection_t* connection, xcb_window_t window) { xcb_get_geometry_cookie_t cookie = xcb_get_geometry(connection, window); auto reply = Reply{ xcb_get_geometry_reply(connection, cookie, nullptr) }; if (!reply) { fprintf(stderr, "[Gamescope WSI] getWindowRect: xcb_get_geometry failed for window 0x%x.\n", window); return std::nullopt; } VkRect2D rect = { .offset = { reply->x, reply->y }, .extent = { reply->width, reply->height }, }; return rect; } static VkRect2D clip(VkRect2D parent, VkRect2D child) { return VkRect2D { .offset = child.offset, .extent = VkExtent2D { .width = std::min(child.extent.width, std::max(parent.extent.width - child.offset.x, 0)), .height = std::min(child.extent.height, std::max(parent.extent.height - child.offset.y, 0)), }, }; } static VkExtent2D max(VkExtent2D a, VkExtent2D b) { return VkExtent2D { .width = std::max(a.width, b.width), .height = std::max(a.height, b.height), }; } static std::optional getLargestObscuringChildWindowSize(xcb_connection_t* connection, xcb_window_t window) { VkExtent2D largestExtent = {}; xcb_query_tree_cookie_t cookie = xcb_query_tree(connection, window); auto reply = Reply{ xcb_query_tree_reply(connection, cookie, nullptr) }; if (!reply) { fprintf(stderr, "[Gamescope WSI] getLargestObscuringWindowSize: xcb_query_tree failed for window 0x%x.\n", window); return std::nullopt; } auto ourRect = getWindowRect(connection, window); if (!ourRect) { fprintf(stderr, "[Gamescope WSI] getLargestObscuringWindowSize: getWindowRect failed for main window 0x%x.\n", window); return std::nullopt; } xcb_window_t* children = xcb_query_tree_children(reply.get()); for (uint32_t i = 0; i < reply->children_len; i++) { xcb_window_t child = children[i]; xcb_get_window_attributes_cookie_t attributeCookie = xcb_get_window_attributes(connection, child); auto attributeReply = Reply{ xcb_get_window_attributes_reply(connection, attributeCookie, nullptr) }; const bool obscuring = attributeReply && attributeReply->map_state == XCB_MAP_STATE_VIEWABLE && !attributeReply->override_redirect; if (obscuring) { if (auto childRect = getWindowRect(connection, child)) { VkRect2D clippedRect = clip(*ourRect, *childRect); largestExtent = max(largestExtent, clippedRect.extent); } } } return largestExtent; } } inline int32_t iabs(int32_t a) { if (a < 0) return -a; return a; }