Merge remote-tracking branch 'yshui/next' into next

This commit is contained in:
Arda Atci
2022-12-20 03:07:52 +03:00
12 changed files with 377 additions and 64 deletions

View File

@@ -197,6 +197,8 @@ void paint_all_new(session_t *ps, struct managed_win *t, bool ignore_damage) {
// region will be because of blur, we assume the worst case here.
// That is, the damaged window is at the bottom of the stack, and
// all other windows have semi-transparent background
//
// TODO(yshui): maybe we don't need to resize reg_damage, only reg_paint?
int resize_factor = 1;
if (t) {
resize_factor = t->stacking_rank;

View File

@@ -594,25 +594,28 @@ static void present(backend_t *base, const region_t *region) {
uint16_t region_width = to_u16_checked(extent->x2 - extent->x1),
region_height = to_u16_checked(extent->y2 - extent->y1);
// compose() sets clip region on the back buffer, so clear it first
x_clear_picture_clip_region(base->c, xd->back[xd->curr_back]);
// limit the region of update
x_set_picture_clip_region(base->c, xd->back[2], 0, 0, region);
if (xd->vsync) {
// compose() sets clip region on the back buffer, so clear it first
x_clear_picture_clip_region(base->c, xd->back[xd->curr_back]);
// Update the back buffer first, then present
xcb_render_composite(base->c, XCB_RENDER_PICT_OP_SRC, xd->back[2],
XCB_NONE, xd->back[xd->curr_back], orig_x, orig_y, 0,
0, orig_x, orig_y, region_width, region_height);
auto xregion = x_create_region(base->c, region);
// Make sure we got reply from PresentPixmap before waiting for events,
// to avoid deadlock
auto e = xcb_request_check(
base->c, xcb_present_pixmap_checked(
xd->base.c, xd->target_win,
xd->back_pixmap[xd->curr_back], 0, XCB_NONE, XCB_NONE, 0,
xd->back_pixmap[xd->curr_back], 0, XCB_NONE, xregion, 0,
0, XCB_NONE, XCB_NONE, XCB_NONE, 0, 0, 0, 0, 0, NULL));
x_destroy_region(base->c, xregion);
if (e) {
log_error("Failed to present pixmap");
free(e);

View File

@@ -36,9 +36,9 @@
#include <X11/Xlib.h>
#include <ev.h>
#include <pixman.h>
#include <xcb/xproto.h>
#include <xcb/render.h>
#include <xcb/sync.h>
#include <xcb/xproto.h>
#include "uthash_extra.h"
#ifdef CONFIG_OPENGL
@@ -55,11 +55,11 @@
#include "backend/driver.h"
#include "compiler.h"
#include "config.h"
#include "list.h"
#include "region.h"
#include "render.h"
#include "types.h"
#include "utils.h"
#include "list.h"
#include "render.h"
#include "win_defs.h"
#include "x.h"
@@ -83,10 +83,17 @@ struct glx_session;
struct atom;
struct conv;
typedef struct _ignore {
struct _ignore *next;
enum pending_reply_action {
PENDING_REPLY_ACTION_IGNORE,
PENDING_REPLY_ACTION_ABORT,
PENDING_REPLY_ACTION_DEBUG_ABORT,
};
typedef struct pending_reply {
struct pending_reply *next;
unsigned long sequence;
} ignore_t;
enum pending_reply_action action;
} pending_reply_t;
#ifdef CONFIG_OPENGL
#ifdef DEBUG_GLX_DEBUG_CONTEXT
@@ -262,18 +269,18 @@ typedef struct session {
/// Time of last window animation step. In milliseconds.
long animation_time; // TODO(dccsillag) turn into `long long`, like fade_time
/// Head pointer of the error ignore linked list.
ignore_t *ignore_head;
pending_reply_t *pending_reply_head;
/// Pointer to the <code>next</code> member of tail element of the error
/// ignore linked list.
ignore_t **ignore_tail;
pending_reply_t **pending_reply_tail;
// Cached blur convolution kernels.
struct x_convolution_kernel **blur_kerns_cache;
/// If we should quit
bool quit:1;
bool quit : 1;
// TODO(yshui) use separate flags for dfferent kinds of updates so we don't
// waste our time.
/// Whether there are pending updates, like window creation, etc.
bool pending_updates:1;
bool pending_updates : 1;
// === Expose event related ===
/// Pointer to an array of <code>XRectangle</code>-s of exposed region.
@@ -474,25 +481,33 @@ static inline bool bkend_use_glx(session_t *ps) {
return BKEND_GLX == ps->o.backend || BKEND_XR_GLX_HYBRID == ps->o.backend;
}
static void set_ignore(session_t *ps, unsigned long sequence) {
if (ps->o.show_all_xerrors)
return;
auto i = cmalloc(ignore_t);
if (!i)
return;
static void
set_reply_action(session_t *ps, uint32_t sequence, enum pending_reply_action action) {
auto i = cmalloc(pending_reply_t);
if (!i) {
abort();
}
i->sequence = sequence;
i->next = 0;
*ps->ignore_tail = i;
ps->ignore_tail = &i->next;
i->action = action;
*ps->pending_reply_tail = i;
ps->pending_reply_tail = &i->next;
}
/**
* Ignore X errors caused by given X request.
*/
static inline void set_ignore_cookie(session_t *ps, xcb_void_cookie_t cookie) {
set_ignore(ps, cookie.sequence);
if (ps->o.show_all_xerrors) {
return;
}
set_reply_action(ps, cookie.sequence, PENDING_REPLY_ACTION_IGNORE);
}
static inline void set_cant_fail_cookie(session_t *ps, xcb_void_cookie_t cookie) {
set_reply_action(ps, cookie.sequence, PENDING_REPLY_ACTION_ABORT);
}
/**

View File

@@ -1435,7 +1435,10 @@ static bool cdbus_process_windows_root_introspect(session_t *ps, DBusMessage *ms
continue;
}
char *tmp = NULL;
asprintf(&tmp, "<node name='%#010x'/>\n", w->id);
if (asprintf(&tmp, "<node name='%#010x'/>\n", w->id) < 0) {
log_fatal("Failed to allocate memory.");
abort();
}
mstrextend(&ret, tmp);
free(tmp);
}

View File

@@ -121,11 +121,13 @@ static inline const char *ev_name(session_t *ps, xcb_generic_event_t *ev) {
CASESTRRET(ClientMessage);
}
if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type)
if (ps->damage_event + XCB_DAMAGE_NOTIFY == ev->response_type) {
return "Damage";
}
if (ps->shape_exists && ev->response_type == ps->shape_event)
if (ps->shape_exists && ev->response_type == ps->shape_event) {
return "ShapeNotify";
}
if (ps->xsync_exists) {
int o = ev->response_type - ps->xsync_event;
@@ -672,7 +674,7 @@ ev_selection_clear(session_t *ps, xcb_selection_clear_event_t attr_unused *ev) {
void ev_handle(session_t *ps, xcb_generic_event_t *ev) {
if ((ev->response_type & 0x7f) != KeymapNotify) {
discard_ignore(ps, ev->full_sequence);
discard_pending(ps, ev->full_sequence);
}
xcb_window_t wid = ev_window(ps, ev);
@@ -704,8 +706,11 @@ void ev_handle(session_t *ps, xcb_generic_event_t *ev) {
// missing sequence numbers.
//
// We only need the low 16 bits
uint16_t seq = ev->sequence;
ev->sequence = (uint16_t)(LastKnownRequestProcessed(ps->dpy) & 0xffff);
proc(ps->dpy, &dummy, (xEvent *)ev);
// Restore the sequence number
ev->sequence = seq;
}
// XXX redraw needs to be more fine grained

View File

@@ -256,7 +256,7 @@ static void file_logger_write(struct log_target *tgt, const char *str, size_t le
static void file_logger_writev(struct log_target *tgt, const struct iovec *vec, int vcnt) {
auto f = (struct file_logger *)tgt;
fflush(f->f);
writev(fileno(f->f), vec, vcnt);
ssize_t _ attr_unused = writev(fileno(f->f), vec, vcnt);
}
static void file_logger_destroy(struct log_target *tgt) {

View File

@@ -282,14 +282,14 @@ static bool run_fade(session_t *ps, struct managed_win **_w, long long steps) {
// === Error handling ===
void discard_ignore(session_t *ps, unsigned long sequence) {
while (ps->ignore_head) {
if (sequence > ps->ignore_head->sequence) {
ignore_t *next = ps->ignore_head->next;
free(ps->ignore_head);
ps->ignore_head = next;
if (!ps->ignore_head) {
ps->ignore_tail = &ps->ignore_head;
void discard_pending(session_t *ps, uint32_t sequence) {
while (ps->pending_reply_head) {
if (sequence > ps->pending_reply_head->sequence) {
auto next = ps->pending_reply_head->next;
free(ps->pending_reply_head);
ps->pending_reply_head = next;
if (!ps->pending_reply_head) {
ps->pending_reply_tail = &ps->pending_reply_head;
}
} else {
break;
@@ -297,13 +297,28 @@ void discard_ignore(session_t *ps, unsigned long sequence) {
}
}
static int should_ignore(session_t *ps, unsigned long sequence) {
static void handle_error(session_t *ps, xcb_generic_error_t *ev) {
if (ps == NULL) {
// Do not ignore errors until the session has been initialized
return false;
return;
}
discard_ignore(ps, sequence);
return ps->ignore_head && ps->ignore_head->sequence == sequence;
discard_pending(ps, ev->full_sequence);
if (ps->pending_reply_head && ps->pending_reply_head->sequence == ev->full_sequence) {
if (ps->pending_reply_head->action != PENDING_REPLY_ACTION_IGNORE) {
x_log_error(LOG_LEVEL_ERROR, ev->full_sequence, ev->major_code,
ev->minor_code, ev->error_code);
}
switch (ps->pending_reply_head->action) {
case PENDING_REPLY_ACTION_ABORT:
log_fatal("An unrecoverable X error occurred, aborting...");
abort();
case PENDING_REPLY_ACTION_DEBUG_ABORT: assert(false); break;
case PENDING_REPLY_ACTION_IGNORE: break;
}
return;
}
x_log_error(LOG_LEVEL_WARN, ev->full_sequence, ev->major_code, ev->minor_code,
ev->error_code);
}
// === Windows ===
@@ -1136,9 +1151,13 @@ void root_damaged(session_t *ps) {
* Xlib error handler function.
*/
static int xerror(Display attr_unused *dpy, XErrorEvent *ev) {
if (!should_ignore(ps_g, ev->serial)) {
x_print_error(ev->serial, ev->request_code, ev->minor_code, ev->error_code);
}
// Fake a xcb error, fill in just enough information
xcb_generic_error_t xcb_err;
xcb_err.full_sequence = (uint32_t)ev->serial;
xcb_err.major_code = ev->request_code;
xcb_err.minor_code = ev->minor_code;
xcb_err.error_code = ev->error_code;
handle_error(ps_g, &xcb_err);
return 0;
}
@@ -1146,9 +1165,7 @@ static int xerror(Display attr_unused *dpy, XErrorEvent *ev) {
* XCB error handler function.
*/
void ev_xcb_error(session_t *ps, xcb_generic_error_t *err) {
if (!should_ignore(ps, err->sequence)) {
x_print_error(err->sequence, err->major_code, err->minor_code, err->error_code);
}
handle_error(ps, err);
}
/**
@@ -1864,8 +1881,8 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
.alpha_picts = NULL,
.fade_time = 0L,
.animation_time = 0L,
.ignore_head = NULL,
.ignore_tail = NULL,
.pending_reply_head = NULL,
.pending_reply_tail = NULL,
.quit = false,
.expose_rects = NULL,
@@ -1927,7 +1944,7 @@ static session_t *session_init(int argc, char **argv, Display *dpy,
ps->loop = EV_DEFAULT;
pixman_region32_init(&ps->screen_reg);
ps->ignore_tail = &ps->ignore_head;
ps->pending_reply_tail = &ps->pending_reply_head;
ps->o.show_all_xerrors = all_xerrors;
@@ -2528,26 +2545,28 @@ static void session_destroy(session_t *ps) {
// Free ignore linked list
{
ignore_t *next = NULL;
for (ignore_t *ign = ps->ignore_head; ign; ign = next) {
pending_reply_t *next = NULL;
for (auto ign = ps->pending_reply_head; ign; ign = next) {
next = ign->next;
free(ign);
}
// Reset head and tail
ps->ignore_head = NULL;
ps->ignore_tail = &ps->ignore_head;
ps->pending_reply_head = NULL;
ps->pending_reply_tail = &ps->pending_reply_head;
}
// Free tgt_{buffer,picture} and root_picture
if (ps->tgt_buffer.pict == ps->tgt_picture)
if (ps->tgt_buffer.pict == ps->tgt_picture) {
ps->tgt_buffer.pict = XCB_NONE;
}
if (ps->tgt_picture == ps->root_picture)
if (ps->tgt_picture == ps->root_picture) {
ps->tgt_picture = XCB_NONE;
else
} else {
free_picture(ps->c, &ps->tgt_picture);
}
free_picture(ps->c, &ps->root_picture);
free_paint(ps, &ps->tgt_buffer);
@@ -2759,7 +2778,11 @@ int main(int argc, char **argv) {
// Notify the parent that we are done. This might cause the parent
// to quit, so only do this after setsid()
int tmp = 1;
write(pfds[1], &tmp, sizeof tmp);
if (write(pfds[1], &tmp, sizeof tmp) != sizeof tmp) {
log_fatal("Failed to notify parent process");
ret_code = 1;
break;
}
close(pfds[1]);
// We only do this once
need_fork = false;

View File

@@ -46,7 +46,7 @@ void cxinerama_upd_scrs(session_t *ps);
void queue_redraw(session_t *ps);
void discard_ignore(session_t *ps, unsigned long sequence);
void discard_pending(session_t *ps, uint32_t sequence);
void set_root_flags(session_t *ps, uint64_t flags);

View File

@@ -4,6 +4,7 @@
#include "compiler.h"
#include "string_utils.h"
#include "test.h"
#include "utils.h"
/// Report allocation failure without allocating memory
@@ -26,7 +27,7 @@ void report_allocation_failure(const char *func, const char *file, unsigned int
{.iov_base = (void *)msg2, .iov_len = sizeof(msg2) - 1},
};
writev(STDERR_FILENO, v, ARR_SIZE(v));
ssize_t _ attr_unused = writev(STDERR_FILENO, v, ARR_SIZE(v));
abort();
unreachable;
@@ -36,8 +37,7 @@ void report_allocation_failure(const char *func, const char *file, unsigned int
/// Calculates next closest power of two of 32bit integer n
/// ref: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
///
int next_power_of_two(int n)
{
int next_power_of_two(int n) {
n--;
n |= n >> 1;
n |= n >> 2;
@@ -48,4 +48,199 @@ int next_power_of_two(int n)
return n;
}
/// Track the rolling maximum of a stream of integers.
struct rolling_max {
/// A priority queue holding the indices of the maximum element candidates.
/// The head of the queue is the index of the maximum element.
/// The indices in the queue are the "original" indices.
///
/// There are only `capacity` elements in `elem`, all previous elements are
/// discarded. But the discarded elements' indices are not forgotten, that's why
/// it's called the "original" indices.
int *p;
int p_head, np;
/// The elemets
int *elem;
int elem_head, nelem;
int window_size;
};
void rolling_max_destroy(struct rolling_max *rm) {
free(rm->elem);
free(rm->p);
free(rm);
}
struct rolling_max *rolling_max_new(int size) {
auto rm = ccalloc(1, struct rolling_max);
if (!rm) {
return NULL;
}
rm->p = ccalloc(size, int);
rm->elem = ccalloc(size, int);
rm->window_size = size;
if (!rm->p || !rm->elem) {
goto err;
}
return rm;
err:
rolling_max_destroy(rm);
return NULL;
}
void rolling_max_reset(struct rolling_max *rm) {
rm->p_head = 0;
rm->np = 0;
rm->nelem = 0;
rm->elem_head = 0;
}
void rolling_max_push(struct rolling_max *rm, int val) {
#define IDX(n) ((n) % rm->window_size)
if (rm->nelem == rm->window_size) {
auto old_head = rm->elem_head;
// Discard the oldest element.
// rm->elem.pop_front();
rm->nelem--;
rm->elem_head = IDX(rm->elem_head + 1);
// Remove discarded element from the priority queue too.
assert(rm->np);
if (rm->p[rm->p_head] == old_head) {
// rm->p.pop_front()
rm->p_head = IDX(rm->p_head + 1);
rm->np--;
}
}
// Add the new element to the queue.
// rm->elem.push_back(val)
rm->elem[IDX(rm->elem_head + rm->nelem)] = val;
rm->nelem++;
// Update the prority queue.
// Remove all elements smaller than the new element from the queue. Because
// the new element will become the maximum element before them, and since they
// come b1efore the new element, they will have been popped before the new
// element, so they will never become the maximum element.
while (rm->np) {
int p_tail = IDX(rm->p_head + rm->np - 1);
if (rm->elem[rm->p[p_tail]] > val) {
break;
}
// rm->p.pop_back()
rm->np--;
}
// Add the new element to the end of the queue.
// rm->p.push_back(rm->start_index + rm->nelem - 1)
rm->p[IDX(rm->p_head + rm->np)] = IDX(rm->elem_head + rm->nelem - 1);
rm->np++;
#undef IDX
}
int rolling_max_get_max(struct rolling_max *rm) {
if (rm->np == 0) {
return INT_MIN;
}
return rm->elem[rm->p[rm->p_head]];
}
TEST_CASE(rolling_max_test) {
#define NELEM 15
auto rm = rolling_max_new(3);
const int data[NELEM] = {1, 2, 3, 1, 4, 5, 2, 3, 6, 5, 4, 3, 2, 0, 0};
const int expected_max[NELEM] = {1, 2, 3, 3, 4, 5, 5, 5, 6, 6, 6, 5, 4, 3, 2};
int max[NELEM] = {0};
for (int i = 0; i < NELEM; i++) {
rolling_max_push(rm, data[i]);
max[i] = rolling_max_get_max(rm);
}
TEST_TRUE(memcmp(max, expected_max, sizeof(max)) == 0);
#undef NELEM
}
/// A rolling average of a stream of integers.
struct rolling_avg {
/// The sum of the elements in the window.
int64_t sum;
/// The elements in the window.
int *elem;
int head, nelem;
int window_size;
};
struct rolling_avg *rolling_avg_new(int size) {
auto rm = ccalloc(1, struct rolling_avg);
if (!rm) {
return NULL;
}
rm->elem = ccalloc(size, int);
rm->window_size = size;
if (!rm->elem) {
free(rm);
return NULL;
}
return rm;
}
void rolling_avg_destroy(struct rolling_avg *rm) {
free(rm->elem);
free(rm);
}
void rolling_avg_reset(struct rolling_avg *ra) {
ra->sum = 0;
ra->nelem = 0;
ra->head = 0;
}
void rolling_avg_push(struct rolling_avg *ra, int val) {
if (ra->nelem == ra->window_size) {
// Discard the oldest element.
// rm->elem.pop_front();
ra->sum -= ra->elem[ra->head % ra->window_size];
ra->nelem--;
ra->head = (ra->head + 1) % ra->window_size;
}
// Add the new element to the queue.
// rm->elem.push_back(val)
ra->elem[(ra->head + ra->nelem) % ra->window_size] = val;
ra->sum += val;
ra->nelem++;
}
double rolling_avg_get_avg(struct rolling_avg *ra) {
if (ra->nelem == 0) {
return 0;
}
return (double)ra->sum / (double)ra->nelem;
}
TEST_CASE(rolling_avg_test) {
#define NELEM 15
auto rm = rolling_avg_new(3);
const int data[NELEM] = {1, 2, 3, 1, 4, 5, 2, 3, 6, 5, 4, 3, 2, 0, 0};
const double expected_avg[NELEM] = {
1, 1.5, 2, 2, 8.0 / 3.0, 10.0 / 3.0, 11.0 / 3.0, 10.0 / 3.0,
11.0 / 3.0, 14.0 / 3.0, 5, 4, 3, 5.0 / 3.0, 2.0 / 3.0};
double avg[NELEM] = {0};
for (int i = 0; i < NELEM; i++) {
rolling_avg_push(rm, data[i]);
avg[i] = rolling_avg_get_avg(rm);
}
for (int i = 0; i < NELEM; i++) {
TEST_EQUAL(avg[i], expected_avg[i]);
}
}
// vim: set noet sw=8 ts=8 :

View File

@@ -303,6 +303,21 @@ static inline void free_charpp(char **str) {
///
int next_power_of_two(int n);
struct rolling_max;
struct rolling_max *rolling_max_new(int window_size);
void rolling_max_free(struct rolling_max *rm);
void rolling_max_reset(struct rolling_max *rm);
void rolling_max_push(struct rolling_max *rm, int val);
int rolling_max_get_max(struct rolling_max *rm);
struct rolling_avg;
struct rolling_avg *rolling_avg_new(int window_size);
void rolling_avg_free(struct rolling_avg *ra);
void rolling_avg_reset(struct rolling_avg *ra);
void rolling_avg_push(struct rolling_avg *ra, int val);
double rolling_avg_get_avg(struct rolling_avg *ra);
// Some versions of the Android libc do not have timespec_get(), use
// clock_gettime() instead.
#ifdef __ANDROID__

48
src/x.c
View File

@@ -388,6 +388,41 @@ bool x_fetch_region(xcb_connection_t *c, xcb_xfixes_region_t r, pixman_region32_
return ret;
}
uint32_t x_create_region(xcb_connection_t *c, const region_t *reg) {
if (!reg) {
return XCB_NONE;
}
int nrects;
// In older pixman versions, pixman_region32_rectangles doesn't take const
// region_t, instead of dealing with this version difference, just suppress the
// warning.
const pixman_box32_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_xfixes_region_t ret = x_new_id(c);
bool success =
XCB_AWAIT_VOID(xcb_xfixes_create_region, c, ret, to_u32_checked(nrects), xrects);
free(xrects);
if (!success) {
return XCB_NONE;
}
return ret;
}
void x_destroy_region(xcb_connection_t *c, xcb_xfixes_region_t r) {
if (r != XCB_NONE) {
xcb_xfixes_destroy_region(c, r);
}
}
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) {
@@ -414,9 +449,10 @@ void x_set_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict,
}
void x_clear_picture_clip_region(xcb_connection_t *c, xcb_render_picture_t pict) {
assert(pict != XCB_NONE);
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));
c, xcb_render_change_picture_checked(c, pict, XCB_RENDER_CP_CLIP_MASK, &v));
if (e) {
log_error_x_error(e, "failed to clear clip region");
free(e);
@@ -527,8 +563,16 @@ _x_strerror(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_c
/**
* Log a X11 error
*/
void x_log_error(enum log_level level, unsigned long serial, uint8_t major,
uint16_t minor, uint8_t error_code) {
if (unlikely(level >= log_get_level_tls())) {
log_printf(tls_logger, level, __func__, "%s",
_x_strerror(serial, major, minor, error_code));
}
}
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));
x_log_error(LOG_LEVEL_DEBUG, serial, major, minor, error_code);
}
/*

View File

@@ -216,6 +216,12 @@ x_create_picture_with_visual(xcb_connection_t *, xcb_drawable_t, int w, int h,
/// Fetch a X region and store it in a pixman region
bool x_fetch_region(xcb_connection_t *, xcb_xfixes_region_t r, region_t *res);
/// Create a X region from a pixman region
uint32_t x_create_region(xcb_connection_t *c, const region_t *reg);
/// Destroy a X region
void x_destroy_region(xcb_connection_t *c, uint32_t region);
void x_set_picture_clip_region(xcb_connection_t *, xcb_render_picture_t, int16_t clip_x_origin,
int16_t clip_y_origin, const region_t *);
@@ -225,6 +231,8 @@ void x_clear_picture_clip_region(xcb_connection_t *, xcb_render_picture_t pict);
* Log a X11 error
*/
void x_print_error(unsigned long serial, uint8_t major, uint16_t minor, uint8_t error_code);
void x_log_error(enum log_level level, unsigned long serial, uint8_t major,
uint16_t minor, uint8_t error_code);
/*
* Convert a xcb_generic_error_t to a string that describes the error