aboutsummaryrefslogtreecommitdiff
path: root/src/x.c
diff options
context:
space:
mode:
authorallusive-dev <[email protected]>2023-09-19 17:47:33 +1000
committerallusive-dev <[email protected]>2023-09-19 17:47:33 +1000
commita93aba600b1c5d019b680b9f4ff3fa85d5d43a60 (patch)
tree77f8152222655657472a70e0bfa413a0495dd555 /src/x.c
parentreset (diff)
downloadcompfy-a93aba600b1c5d019b680b9f4ff3fa85d5d43a60.tar.xz
compfy-a93aba600b1c5d019b680b9f4ff3fa85d5d43a60.zip
Fixed broken files/code and other errors
Diffstat (limited to 'src/x.c')
-rw-r--r--src/x.c748
1 files changed, 748 insertions, 0 deletions
diff --git a/src/x.c b/src/x.c
new file mode 100644
index 0000000..c146f48
--- /dev/null
+++ b/src/x.c
@@ -0,0 +1,748 @@
+// SPDX-License-Identifier: MPL-2.0
+// Copyright (c) 2018 Yuxuan Shui <[email protected]>
+#include <stdalign.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <X11/Xutil.h>
+#include <pixman.h>
+#include <xcb/composite.h>
+#include <xcb/damage.h>
+#include <xcb/glx.h>
+#include <xcb/render.h>
+#include <xcb/sync.h>
+#include <xcb/xcb.h>
+#include <xcb/xcb_renderutil.h>
+#include <xcb/xfixes.h>
+
+#include "atom.h"
+#ifdef CONFIG_OPENGL
+#include "backend/gl/glx.h"
+#endif
+#include "common.h"
+#include "compiler.h"
+#include "kernel.h"
+#include "log.h"
+#include "region.h"
+#include "utils.h"
+#include "x.h"
+
+/**
+ * Get a specific attribute of a window.
+ *
+ * Returns a blank structure if the returned type and format does not
+ * match the requested type and format.
+ *
+ * @param ps current session
+ * @param w window
+ * @param atom atom of attribute to fetch
+ * @param length length to read
+ * @param rtype atom of the requested type
+ * @param rformat requested format
+ * @return a <code>winprop_t</code> structure containing the attribute
+ * and number of items. A blank one on failure.
+ */
+winprop_t x_get_prop_with_offset(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom,
+ int offset, int length, xcb_atom_t rtype, int rformat) {
+ xcb_get_property_reply_t *r = xcb_get_property_reply(
+ c,
+ xcb_get_property(c, 0, w, atom, rtype, to_u32_checked(offset),
+ to_u32_checked(length)),
+ NULL);
+
+ if (r && xcb_get_property_value_length(r) &&
+ (rtype == XCB_GET_PROPERTY_TYPE_ANY || r->type == rtype) &&
+ (!rformat || r->format == rformat) &&
+ (r->format == 8 || r->format == 16 || r->format == 32)) {
+ auto len = xcb_get_property_value_length(r);
+ return (winprop_t){
+ .ptr = xcb_get_property_value(r),
+ .nitems = (ulong)(len / (r->format / 8)),
+ .type = r->type,
+ .format = r->format,
+ .r = r,
+ };
+ }
+
+ free(r);
+ return (winprop_t){
+ .ptr = NULL, .nitems = 0, .type = XCB_GET_PROPERTY_TYPE_ANY, .format = 0};
+}
+
+/// Get the type, format and size in bytes of a window's specific attribute.
+winprop_info_t x_get_prop_info(xcb_connection_t *c, xcb_window_t w, xcb_atom_t atom) {
+ xcb_generic_error_t *e = NULL;
+ auto r = xcb_get_property_reply(
+ c, xcb_get_property(c, 0, w, atom, XCB_ATOM_ANY, 0, 0), &e);
+ if (!r) {
+ log_debug_x_error(e, "Failed to get property info for window %#010x", w);
+ free(e);
+ return (winprop_info_t){
+ .type = XCB_GET_PROPERTY_TYPE_ANY, .format = 0, .length = 0};
+ }
+
+ winprop_info_t winprop_info = {
+ .type = r->type, .format = r->format, .length = r->bytes_after};
+ free(r);
+
+ return winprop_info;
+}
+
+/**
+ * Get the value of a type-<code>xcb_window_t</code> property of a window.
+ *
+ * @return the value if successful, 0 otherwise
+ */
+xcb_window_t wid_get_prop_window(xcb_connection_t *c, xcb_window_t wid, xcb_atom_t aprop) {
+ // Get the attribute
+ xcb_window_t p = XCB_NONE;
+ winprop_t prop = x_get_prop(c, wid, aprop, 1L, XCB_ATOM_WINDOW, 32);
+
+ // Return it
+ if (prop.nitems) {
+ p = (xcb_window_t)*prop.p32;
+ }
+
+ free_winprop(&prop);
+
+ return p;
+}
+
+/**
+ * Get the value of a text property of a window.
+ */
+bool wid_get_text_prop(session_t *ps, xcb_window_t wid, xcb_atom_t prop, char ***pstrlst,
+ int *pnstr) {
+ assert(ps->server_grabbed);
+ auto prop_info = x_get_prop_info(ps->c, wid, prop);
+ auto type = prop_info.type;
+ auto format = prop_info.format;
+ auto length = prop_info.length;
+
+ if (type == XCB_ATOM_NONE) {
+ return false;
+ }
+
+ if (type != XCB_ATOM_STRING && type != ps->atoms->aUTF8_STRING &&
+ type != ps->atoms->aC_STRING) {
+ log_warn("Text property %d of window %#010x has unsupported type: %d",
+ prop, wid, type);
+ return false;
+ }
+
+ if (format != 8) {
+ log_warn("Text property %d of window %#010x has unexpected format: %d",
+ prop, wid, format);
+ return false;
+ }
+
+ xcb_generic_error_t *e = NULL;
+ auto word_count = (length + 4 - 1) / 4;
+ auto r = xcb_get_property_reply(
+ ps->c, xcb_get_property(ps->c, 0, wid, prop, type, 0, word_count), &e);
+ if (!r) {
+ log_debug_x_error(e, "Failed to get window property for %#010x", wid);
+ free(e);
+ return false;
+ }
+
+ assert(length == (uint32_t)xcb_get_property_value_length(r));
+
+ void *data = xcb_get_property_value(r);
+ unsigned int nstr = 0;
+ uint32_t current_offset = 0;
+ while (current_offset < length) {
+ current_offset +=
+ (uint32_t)strnlen(data + current_offset, length - current_offset) + 1;
+ nstr += 1;
+ }
+
+ if (nstr == 0) {
+ // The property is set to an empty string, in that case, we return one
+ // string
+ char **strlst = malloc(sizeof(char *));
+ strlst[0] = "";
+ *pnstr = 1;
+ *pstrlst = strlst;
+ free(r);
+ return true;
+ }
+
+ // Allocate the pointers and the strings together
+ void *buf = NULL;
+ if (posix_memalign(&buf, alignof(char *), length + sizeof(char *) * nstr + 1) != 0) {
+ abort();
+ }
+
+ char *strlst = buf + sizeof(char *) * nstr;
+ memcpy(strlst, xcb_get_property_value(r), length);
+ strlst[length] = '\0'; // X strings aren't guaranteed to be null terminated
+
+ char **ret = buf;
+ current_offset = 0;
+ nstr = 0;
+ while (current_offset < length) {
+ ret[nstr] = strlst + current_offset;
+ current_offset += (uint32_t)strlen(strlst + current_offset) + 1;
+ nstr += 1;
+ }
+
+ *pnstr = to_int_checked(nstr);
+ *pstrlst = ret;
+ free(r);
+ return true;
+}
+
+// A cache of pict formats. We assume they don't change during the lifetime
+// of this program
+static thread_local xcb_render_query_pict_formats_reply_t *g_pictfmts = NULL;
+
+static inline void x_get_server_pictfmts(xcb_connection_t *c) {
+ if (g_pictfmts) {
+ return;
+ }
+ xcb_generic_error_t *e = NULL;
+ // Get window picture format
+ g_pictfmts =
+ xcb_render_query_pict_formats_reply(c, xcb_render_query_pict_formats(c), &e);
+ if (e || !g_pictfmts) {
+ log_fatal("failed to get pict formats\n");
+ abort();
+ }
+}
+
+const xcb_render_pictforminfo_t *
+x_get_pictform_for_visual(xcb_connection_t *c, xcb_visualid_t visual) {
+ x_get_server_pictfmts(c);
+
+ xcb_render_pictvisual_t *pv = xcb_render_util_find_visual_format(g_pictfmts, visual);
+ for (xcb_render_pictforminfo_iterator_t i =
+ xcb_render_query_pict_formats_formats_iterator(g_pictfmts);
+ i.rem; xcb_render_pictforminfo_next(&i)) {
+ if (i.data->id == pv->format) {
+ return i.data;
+ }
+ }
+ return NULL;
+}
+
+static xcb_visualid_t attr_pure x_get_visual_for_pictfmt(xcb_render_query_pict_formats_reply_t *r,
+ xcb_render_pictformat_t fmt) {
+ for (auto screen = xcb_render_query_pict_formats_screens_iterator(r); screen.rem;
+ xcb_render_pictscreen_next(&screen)) {
+ for (auto depth = xcb_render_pictscreen_depths_iterator(screen.data);
+ depth.rem; xcb_render_pictdepth_next(&depth)) {
+ for (auto pv = xcb_render_pictdepth_visuals_iterator(depth.data);
+ pv.rem; xcb_render_pictvisual_next(&pv)) {
+ if (pv.data->format == fmt) {
+ return pv.data->visual;
+ }
+ }
+ }
+ }
+ return XCB_NONE;
+}
+
+xcb_visualid_t x_get_visual_for_standard(xcb_connection_t *c, xcb_pict_standard_t std) {
+ x_get_server_pictfmts(c);
+
+ auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, std);
+
+ return x_get_visual_for_pictfmt(g_pictfmts, pictfmt->id);
+}
+
+xcb_render_pictformat_t
+x_get_pictfmt_for_standard(xcb_connection_t *c, xcb_pict_standard_t std) {
+ x_get_server_pictfmts(c);
+
+ auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, std);
+
+ return pictfmt->id;
+}
+
+int x_get_visual_depth(xcb_connection_t *c, xcb_visualid_t visual) {
+ auto setup = xcb_get_setup(c);
+ for (auto screen = xcb_setup_roots_iterator(setup); screen.rem;
+ xcb_screen_next(&screen)) {
+ for (auto depth = xcb_screen_allowed_depths_iterator(screen.data);
+ depth.rem; xcb_depth_next(&depth)) {
+ const int len = xcb_depth_visuals_length(depth.data);
+ const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data);
+ for (int i = 0; i < len; i++) {
+ if (visual == visuals[i].visual_id) {
+ return depth.data->depth;
+ }
+ }
+ }
+ }
+ return -1;
+}
+
+xcb_render_picture_t
+x_create_picture_with_pictfmt_and_pixmap(xcb_connection_t *c,
+ const xcb_render_pictforminfo_t *pictfmt,
+ xcb_pixmap_t pixmap, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr) {
+ void *buf = NULL;
+ if (attr) {
+ xcb_render_create_picture_value_list_serialize(&buf, valuemask, attr);
+ if (!buf) {
+ log_error("failed to serialize picture attributes");
+ return XCB_NONE;
+ }
+ }
+
+ xcb_render_picture_t tmp_picture = x_new_id(c);
+ xcb_generic_error_t *e =
+ xcb_request_check(c, xcb_render_create_picture_checked(
+ c, tmp_picture, pixmap, pictfmt->id, valuemask, buf));
+ free(buf);
+ if (e) {
+ log_error_x_error(e, "failed to create picture");
+ return XCB_NONE;
+ }
+ return tmp_picture;
+}
+
+xcb_render_picture_t
+x_create_picture_with_visual_and_pixmap(xcb_connection_t *c, xcb_visualid_t visual,
+ xcb_pixmap_t pixmap, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr) {
+ const xcb_render_pictforminfo_t *pictfmt = x_get_pictform_for_visual(c, visual);
+ return x_create_picture_with_pictfmt_and_pixmap(c, pictfmt, pixmap, valuemask, attr);
+}
+
+xcb_render_picture_t
+x_create_picture_with_standard_and_pixmap(xcb_connection_t *c, xcb_pict_standard_t standard,
+ xcb_pixmap_t pixmap, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr) {
+ x_get_server_pictfmts(c);
+
+ auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, standard);
+ assert(pictfmt);
+ return x_create_picture_with_pictfmt_and_pixmap(c, pictfmt, pixmap, valuemask, attr);
+}
+
+xcb_render_picture_t
+x_create_picture_with_standard(xcb_connection_t *c, xcb_drawable_t d, int w, int h,
+ xcb_pict_standard_t standard, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr) {
+ x_get_server_pictfmts(c);
+
+ auto pictfmt = xcb_render_util_find_standard_format(g_pictfmts, standard);
+ assert(pictfmt);
+ return x_create_picture_with_pictfmt(c, d, w, h, pictfmt, valuemask, attr);
+}
+
+/**
+ * Create an picture.
+ */
+xcb_render_picture_t
+x_create_picture_with_pictfmt(xcb_connection_t *c, xcb_drawable_t d, int w, int h,
+ const xcb_render_pictforminfo_t *pictfmt, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr) {
+ uint8_t depth = pictfmt->depth;
+
+ xcb_pixmap_t tmp_pixmap = x_create_pixmap(c, depth, d, w, h);
+ if (!tmp_pixmap) {
+ return XCB_NONE;
+ }
+
+ xcb_render_picture_t picture = x_create_picture_with_pictfmt_and_pixmap(
+ c, pictfmt, tmp_pixmap, valuemask, attr);
+
+ xcb_free_pixmap(c, tmp_pixmap);
+
+ return picture;
+}
+
+xcb_render_picture_t
+x_create_picture_with_visual(xcb_connection_t *c, xcb_drawable_t d, int w, int h,
+ xcb_visualid_t visual, uint32_t valuemask,
+ const xcb_render_create_picture_value_list_t *attr) {
+ auto pictfmt = x_get_pictform_for_visual(c, visual);
+ return x_create_picture_with_pictfmt(c, d, w, h, pictfmt, valuemask, attr);
+}
+
+bool x_fetch_region(xcb_connection_t *c, xcb_xfixes_region_t r, pixman_region32_t *res) {
+ xcb_generic_error_t *e = NULL;
+ xcb_xfixes_fetch_region_reply_t *xr =
+ xcb_xfixes_fetch_region_reply(c, xcb_xfixes_fetch_region(c, r), &e);
+ if (!xr) {
+ log_error_x_error(e, "Failed to fetch rectangles");
+ return false;
+ }
+
+ int nrect = xcb_xfixes_fetch_region_rectangles_length(xr);
+ auto b = ccalloc(nrect, pixman_box32_t);
+ xcb_rectangle_t *xrect = xcb_xfixes_fetch_region_rectangles(xr);
+ for (int i = 0; i < nrect; i++) {
+ b[i] = (pixman_box32_t){.x1 = xrect[i].x,
+ .y1 = xrect[i].y,
+ .x2 = xrect[i].x + xrect[i].width,
+ .y2 = xrect[i].y + xrect[i].height};
+ }
+ bool ret = pixman_region32_init_rects(res, b, nrect);
+ free(b);
+ free(xr);
+ return ret;
+}
+
+void x_set_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict,
+ int16_t clip_x_origin, int16_t clip_y_origin,
+ const region_t *reg) {
+ int nrects;
+ const rect_t *rects = pixman_region32_rectangles((region_t *)reg, &nrects);
+ auto xrects = ccalloc(nrects, xcb_rectangle_t);
+ for (int i = 0; i < nrects; i++) {
+ xrects[i] = (xcb_rectangle_t){
+ .x = to_i16_checked(rects[i].x1),
+ .y = to_i16_checked(rects[i].y1),
+ .width = to_u16_checked(rects[i].x2 - rects[i].x1),
+ .height = to_u16_checked(rects[i].y2 - rects[i].y1),
+ };
+ }
+
+ xcb_generic_error_t *e = xcb_request_check(
+ c, xcb_render_set_picture_clip_rectangles_checked(
+ c, pict, clip_x_origin, clip_y_origin, to_u32_checked(nrects), xrects));
+ if (e) {
+ log_error_x_error(e, "Failed to set clip region");
+ free(e);
+ }
+ free(xrects);
+}
+
+void x_clear_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict) {
+ xcb_render_change_picture_value_list_t v = {.clipmask = XCB_NONE};
+ xcb_generic_error_t *e = xcb_request_check(
+ c, xcb_render_change_picture(c, pict, XCB_RENDER_CP_CLIP_MASK, &v));
+ if (e) {
+ log_error_x_error(e, "failed to clear clip region");
+ free(e);
+ }
+}
+
+enum {
+ XSyncBadCounter = 0,
+ XSyncBadAlarm = 1,
+ XSyncBadFence = 2,
+};
+
+/**
+ * Convert a X11 error to string
+ *
+ * @return a pointer to a string. this pointer shouldn NOT be freed, same buffer is used
+ * for multiple calls to this function,
+ */
+static const char *
+_x_strerror(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code) {
+ session_t *const ps = ps_g;
+
+ int o = 0;
+ const char *name = "Unknown";
+
+#define CASESTRRET(s) \
+ case s: \
+ name = #s; \
+ break
+
+#define CASESTRRET2(s) \
+ case XCB_##s: name = #s; break
+
+ // TODO(yshui) separate error code out from session_t
+ o = error_code - ps->xfixes_error;
+ switch (o) { CASESTRRET2(XFIXES_BAD_REGION); }
+
+ o = error_code - ps->damage_error;
+ switch (o) { CASESTRRET2(DAMAGE_BAD_DAMAGE); }
+
+ o = error_code - ps->render_error;
+ switch (o) {
+ CASESTRRET2(RENDER_PICT_FORMAT);
+ CASESTRRET2(RENDER_PICTURE);
+ CASESTRRET2(RENDER_PICT_OP);
+ CASESTRRET2(RENDER_GLYPH_SET);
+ CASESTRRET2(RENDER_GLYPH);
+ }
+
+ if (ps->glx_exists) {
+ o = error_code - ps->glx_error;
+ switch (o) {
+ CASESTRRET2(GLX_BAD_CONTEXT);
+ CASESTRRET2(GLX_BAD_CONTEXT_STATE);
+ CASESTRRET2(GLX_BAD_DRAWABLE);
+ CASESTRRET2(GLX_BAD_PIXMAP);
+ CASESTRRET2(GLX_BAD_CONTEXT_TAG);
+ CASESTRRET2(GLX_BAD_CURRENT_WINDOW);
+ CASESTRRET2(GLX_BAD_RENDER_REQUEST);
+ CASESTRRET2(GLX_BAD_LARGE_REQUEST);
+ CASESTRRET2(GLX_UNSUPPORTED_PRIVATE_REQUEST);
+ CASESTRRET2(GLX_BAD_FB_CONFIG);
+ CASESTRRET2(GLX_BAD_PBUFFER);
+ CASESTRRET2(GLX_BAD_CURRENT_DRAWABLE);
+ CASESTRRET2(GLX_BAD_WINDOW);
+ CASESTRRET2(GLX_GLX_BAD_PROFILE_ARB);
+ }
+ }
+
+ if (ps->xsync_exists) {
+ o = error_code - ps->xsync_error;
+ switch (o) {
+ CASESTRRET(XSyncBadCounter);
+ CASESTRRET(XSyncBadAlarm);
+ CASESTRRET(XSyncBadFence);
+ }
+ }
+
+ switch (error_code) {
+ CASESTRRET2(ACCESS);
+ CASESTRRET2(ALLOC);
+ CASESTRRET2(ATOM);
+ CASESTRRET2(COLORMAP);
+ CASESTRRET2(CURSOR);
+ CASESTRRET2(DRAWABLE);
+ CASESTRRET2(FONT);
+ CASESTRRET2(G_CONTEXT);
+ CASESTRRET2(ID_CHOICE);
+ CASESTRRET2(IMPLEMENTATION);
+ CASESTRRET2(LENGTH);
+ CASESTRRET2(MATCH);
+ CASESTRRET2(NAME);
+ CASESTRRET2(PIXMAP);
+ CASESTRRET2(REQUEST);
+ CASESTRRET2(VALUE);
+ CASESTRRET2(WINDOW);
+ }
+
+#undef CASESTRRET
+#undef CASESTRRET2
+
+ thread_local static char buffer[256];
+ snprintf(buffer, sizeof(buffer), "X error %d %s request %d minor %d serial %lu",
+ error_code, name, major, minor, serial);
+ return buffer;
+}
+
+/**
+ * Log a X11 error
+ */
+void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code) {
+ log_debug("%s", _x_strerror(serial, major, minor, error_code));
+}
+
+/*
+ * Convert a xcb_generic_error_t to a string that describes the error
+ *
+ * @return a pointer to a string. this pointer shouldn NOT be freed, same buffer is used
+ * for multiple calls to this function,
+ */
+const char *x_strerror(xcb_generic_error_t *e) {
+ if (!e) {
+ return "No error";
+ }
+ return _x_strerror(e->full_sequence, e->major_code, e->minor_code, e->error_code);
+}
+
+/**
+ * Create a pixmap and check that creation succeeded.
+ */
+xcb_pixmap_t x_create_pixmap(xcb_connection_t *c, uint8_t depth, xcb_drawable_t drawable,
+ int width, int height) {
+ xcb_pixmap_t pix = x_new_id(c);
+ xcb_void_cookie_t cookie = xcb_create_pixmap_checked(
+ c, depth, pix, drawable, to_u16_checked(width), to_u16_checked(height));
+ xcb_generic_error_t *err = xcb_request_check(c, cookie);
+ if (err == NULL) {
+ return pix;
+ }
+
+ log_error_x_error(err, "Failed to create pixmap");
+ free(err);
+ return XCB_NONE;
+}
+
+/**
+ * Validate a pixmap.
+ *
+ * Detect whether the pixmap is valid with XGetGeometry. Well, maybe there
+ * are better ways.
+ */
+bool x_validate_pixmap(xcb_connection_t *c, xcb_pixmap_t pixmap) {
+ if (pixmap == XCB_NONE) {
+ return false;
+ }
+
+ auto r = xcb_get_geometry_reply(c, xcb_get_geometry(c, pixmap), NULL);
+ if (!r) {
+ return false;
+ }
+
+ bool ret = r->width && r->height;
+ free(r);
+ return ret;
+}
+/// Names of root window properties that could point to a pixmap of
+/// background.
+static const char *background_props_str[] = {
+ "_XROOTPMAP_ID",
+ "_XSETROOT_ID",
+ 0,
+};
+
+xcb_pixmap_t
+x_get_root_back_pixmap(xcb_connection_t *c, xcb_window_t root, struct atom *atoms) {
+ xcb_pixmap_t pixmap = XCB_NONE;
+
+ // Get the values of background attributes
+ for (int p = 0; background_props_str[p]; p++) {
+ xcb_atom_t prop_atom = get_atom(atoms, background_props_str[p]);
+ winprop_t prop = x_get_prop(c, root, prop_atom, 1, XCB_ATOM_PIXMAP, 32);
+ if (prop.nitems) {
+ pixmap = (xcb_pixmap_t)*prop.p32;
+ free_winprop(&prop);
+ break;
+ }
+ free_winprop(&prop);
+ }
+
+ return pixmap;
+}
+
+bool x_is_root_back_pixmap_atom(struct atom *atoms, xcb_atom_t atom) {
+ for (int p = 0; background_props_str[p]; p++) {
+ xcb_atom_t prop_atom = get_atom(atoms, background_props_str[p]);
+ if (prop_atom == atom) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Synchronizes a X Render drawable to ensure all pending painting requests
+ * are completed.
+ */
+bool x_fence_sync(xcb_connection_t *c, xcb_sync_fence_t f) {
+ // TODO(richardgv): If everybody just follows the rules stated in X Sync
+ // prototype, we need only one fence per screen, but let's stay a bit
+ // cautious right now
+
+ auto e = xcb_request_check(c, xcb_sync_trigger_fence_checked(c, f));
+ if (e) {
+ log_error_x_error(e, "Failed to trigger the fence");
+ goto err;
+ }
+
+ e = xcb_request_check(c, xcb_sync_await_fence_checked(c, 1, &f));
+ if (e) {
+ log_error_x_error(e, "Failed to await on a fence");
+ goto err;
+ }
+
+ e = xcb_request_check(c, xcb_sync_reset_fence_checked(c, f));
+ if (e) {
+ log_error_x_error(e, "Failed to reset the fence");
+ goto err;
+ }
+ return true;
+
+err:
+ free(e);
+ return false;
+}
+
+// xcb-render specific macros
+#define XFIXED_TO_DOUBLE(value) (((double)(value)) / 65536)
+#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536))
+
+/**
+ * Convert a struct conv to a X picture convolution filter, normalizing the kernel
+ * in the process. Allow the caller to specify the element at the center of the kernel,
+ * for compatibility with legacy code.
+ *
+ * @param[in] kernel the convolution kernel
+ * @param[in] center the element to put at the center of the matrix
+ * @param[inout] ret pointer to an array of `size`, if `size` is too small, more space
+ * will be allocated, and `*ret` will be updated
+ * @param[inout] size size of the array pointed to by `ret`, in number of elements
+ * @return number of elements filled into `*ret`
+ */
+void x_create_convolution_kernel(const conv *kernel, double center,
+ struct x_convolution_kernel **ret) {
+ assert(ret);
+ if (!*ret || (*ret)->capacity < kernel->w * kernel->h + 2) {
+ free(*ret);
+ *ret =
+ cvalloc(sizeof(struct x_convolution_kernel) +
+ (size_t)(kernel->w * kernel->h + 2) * sizeof(xcb_render_fixed_t));
+ (*ret)->capacity = kernel->w * kernel->h + 2;
+ }
+
+ (*ret)->size = kernel->w * kernel->h + 2;
+
+ auto buf = (*ret)->kernel;
+ buf[0] = DOUBLE_TO_XFIXED(kernel->w);
+ buf[1] = DOUBLE_TO_XFIXED(kernel->h);
+
+ double sum = center;
+ for (int i = 0; i < kernel->w * kernel->h; i++) {
+ if (i == kernel->w * kernel->h / 2) {
+ continue;
+ }
+ sum += kernel->data[i];
+ }
+
+ // Note for floating points a / b != a * (1 / b), but this shouldn't have any real
+ // impact on the result
+ double factor = sum != 0 ? 1.0 / sum : 1;
+ for (int i = 0; i < kernel->w * kernel->h; i++) {
+ buf[i + 2] = DOUBLE_TO_XFIXED(kernel->data[i] * factor);
+ }
+
+ buf[kernel->h / 2 * kernel->w + kernel->w / 2 + 2] =
+ DOUBLE_TO_XFIXED(center * factor);
+}
+
+/// Generate a search criteria for fbconfig from a X visual.
+/// Returns {-1, -1, -1, -1, -1, 0} on failure
+struct xvisual_info x_get_visual_info(xcb_connection_t *c, xcb_visualid_t visual) {
+ auto pictfmt = x_get_pictform_for_visual(c, visual);
+ auto depth = x_get_visual_depth(c, visual);
+ if (!pictfmt || depth == -1) {
+ log_error("Invalid visual %#03x", visual);
+ return (struct xvisual_info){-1, -1, -1, -1, -1, 0};
+ }
+ if (pictfmt->type != XCB_RENDER_PICT_TYPE_DIRECT) {
+ log_error("We cannot handle non-DirectColor visuals. Report an "
+ "issue if you see this error message.");
+ return (struct xvisual_info){-1, -1, -1, -1, -1, 0};
+ }
+
+ int red_size = popcntul(pictfmt->direct.red_mask),
+ blue_size = popcntul(pictfmt->direct.blue_mask),
+ green_size = popcntul(pictfmt->direct.green_mask),
+ alpha_size = popcntul(pictfmt->direct.alpha_mask);
+
+ return (struct xvisual_info){
+ .red_size = red_size,
+ .green_size = green_size,
+ .blue_size = blue_size,
+ .alpha_size = alpha_size,
+ .visual_depth = depth,
+ .visual = visual,
+ };
+}
+
+xcb_screen_t *x_screen_of_display(xcb_connection_t *c, int screen) {
+ xcb_screen_iterator_t iter;
+
+ iter = xcb_setup_roots_iterator(xcb_get_setup(c));
+ for (; iter.rem; --screen, xcb_screen_next(&iter)) {
+ if (screen == 0) {
+ return iter.data;
+ }
+ }
+
+ return NULL;
+}