I am trying to do some post-processing (colors manipulation, sharpening, blurring etc) of an arbitrary application window. I am using the Composite extension to get the content as an off-screen pixmap, and then I apply effects to it. To avoid flickering I am trying to use MANUAL update to later display the resulting pixels in a single frame. According to the documentation, that should be possible by design:
This automatic update mechanism may be disabled so that the parent window contents can be completely determined by an external application.
However it does not say anything about how to actually do the update. I guess I should not simply copy the off-screen pixmap back to the window, as the source and destination are essentially the same?
UPDATE: I looked up the extension source code and found where it performs the copying (compWindowUpdateAutomatic()
), but using xcb_render_composite()
gives me the same result.
This is my current attempt but copying back obviously is not working:
#include <cairo/cairo-xcb.h>
#include <stdio.h>
#include <stdlib.h>
#include <xcb/composite.h>
#include <xcb/damage.h>
#include <xcb/xcb_aux.h>
#include <xcb/xcb_event.h>
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "Usage: %s <window_id>\n", argv[0]);
return -1;
}
xcb_window_t wid = (xcb_window_t)strtoul(argv[1], NULL, 0);
xcb_connection_t *conn = xcb_connect(NULL, NULL);
xcb_screen_t *screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
xcb_visualtype_t *visual = xcb_aux_find_visual_by_attrs(screen, XCB_VISUAL_CLASS_TRUE_COLOR, 24);
xcb_get_geometry_reply_t *geom = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, wid), NULL);
xcb_damage_query_version(conn, XCB_DAMAGE_MAJOR_VERSION, XCB_DAMAGE_MINOR_VERSION);
uint8_t DAMAGE_EVENT = xcb_get_extension_data(conn, &xcb_damage_id)->first_event;
{
//track damage in subwindows:
xcb_query_tree_reply_t *r = xcb_query_tree_reply(conn, xcb_query_tree_unchecked(conn, wid), NULL);
xcb_window_t *w = xcb_query_tree_children(r);
for (int i = 0; i < r->children_len; ++i) {
xcb_damage_damage_t damage = xcb_generate_id(conn);
xcb_damage_create(conn, damage, w[i], XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY);
xcb_flush(conn);
}
{
xcb_damage_damage_t damage = xcb_generate_id(conn);
xcb_damage_create(conn, damage, wid, XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY);
xcb_flush(conn);
}
free(r);
}
xcb_composite_redirect_window(conn, wid, XCB_COMPOSITE_REDIRECT_MANUAL);
xcb_pixmap_t pixmap = xcb_generate_id(conn);
xcb_composite_name_window_pixmap(conn, wid, pixmap);
xcb_gcontext_t gc = xcb_generate_id(conn);
xcb_create_gc(conn, gc, wid, 0, NULL);
xcb_generic_event_t *event;
while ((event = xcb_wait_for_event(conn))) {
if (XCB_EVENT_RESPONSE_TYPE(event) == DAMAGE_EVENT + XCB_DAMAGE_NOTIFY) {
xcb_damage_notify_event_t *de = (xcb_damage_notify_event_t *)event;
{ // draw a translucent layer:
cairo_surface_t *surface = cairo_xcb_surface_create(conn, pixmap, visual, geom->width, geom->height);
cairo_t *cr = cairo_create(surface);
cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.5);
cairo_paint(cr);
cairo_destroy(cr);
cairo_surface_destroy(surface);
}
{ // copy back:
xcb_copy_area(conn, pixmap, wid, gc, 0, 0, 0, 0, geom->width, geom->height);
}
xcb_damage_subtract(conn, de->damage, XCB_NONE, XCB_NONE);
xcb_flush(conn);
}
free(event);
}
return -1;
}
cc main.c -o main -lxcb-composite -lxcb-damage -lcairo -lxcb-render -lxcb-util -lxcb
After closer inspection of compWindowUpdateAutomatic() and reading the Composite extension documentation multiple times I found what I was doing wrong. The destination for copying should be not the redirected window but its parent.
- xcb_create_gc(conn, gc, wid, 0, NULL);
+ xcb_window_t parent;
+ {
+ xcb_query_tree_reply_t *r = xcb_query_tree_reply(conn, xcb_query_tree(conn, wid), NULL);
+ parent = r->parent;
+ free(r);
+ }
+ xcb_create_gc(conn, gc, parent, XCB_GC_SUBWINDOW_MODE, (const uint32_t[]){XCB_SUBWINDOW_MODE_INCLUDE_INFERIORS});
...
- xcb_copy_area(conn, pixmap, wid, gc, 0, 0, 0, 0, geom->width, geom->height);
+ xcb_copy_area(conn, pixmap, parent, gc, 0, 0, geom->x, geom->y, geom->width, geom->height);