From a78e051421cdcf94de5e5c9191a8af0a51bfa25d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Lopat=C3=A1=C5=99?= Date: Tue, 31 Mar 2026 09:45:08 +0200 Subject: [PATCH] backend-x11: Support pixman rendering without MIT-SHM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement fallback to standard XPutImage when the MIT-SHM extension is unavailable or disabled. This allows the X11 backend with the pixman renderer to run on remote X servers (e.g., via SSH forwarding) or nested X11 environments that lack shared memory support. To support this, testing fixtures were added to x11-backend-test to cover both SHM and non-SHM scenarios using Xvfb. Signed-off-by: Martin Lopatář --- doc/sphinx/toc/test-suite.rst | 2 +- libweston/backend-x11/x11.c | 394 +++++++++++++++++++++++----------- tests/meson.build | 10 + tests/x11-backend-test.c | 254 ++++++++++++++++++++++ 4 files changed, 528 insertions(+), 132 deletions(-) create mode 100644 tests/x11-backend-test.c diff --git a/doc/sphinx/toc/test-suite.rst b/doc/sphinx/toc/test-suite.rst index c5845e136..6218e8e73 100644 --- a/doc/sphinx/toc/test-suite.rst +++ b/doc/sphinx/toc/test-suite.rst @@ -148,7 +148,7 @@ This is an example of a plugin test that just logs a line: Client tests ^^^^^^^^^^^^ -Plugin tests must have a fixture setup function that calls +Client tests must have a fixture setup function that calls :func:`weston_test_harness_execute_as_client`. All test cases must be defined with :c:func:`TEST` or :c:func:`TEST_P`, and each such function must return a value from :type:`test_result_code`. diff --git a/libweston/backend-x11/x11.c b/libweston/backend-x11/x11.c index 182baf6aa..68a3d9253 100644 --- a/libweston/backend-x11/x11.c +++ b/libweston/backend-x11/x11.c @@ -95,6 +95,7 @@ struct x11_backend { uint8_t xkb_event_base; int fullscreen; int no_input; + bool has_shm; int has_net_wm_state_fullscreen; @@ -139,7 +140,10 @@ struct x11_output { struct weston_mode native; struct wl_event_source *finish_frame_timer; + const struct pixel_format_info *pfmt; + xcb_gc_t gc; + bool use_shm; xcb_shm_seg_t segment; weston_renderbuffer_t renderbuffer; int shm_id; @@ -148,6 +152,10 @@ struct x11_output { int32_t scale; bool resize_pending; bool window_resized; + + /* Scratch buffer for non-SHM Pixman rendering */ + uint8_t *image_scratch_buf; + uint32_t image_scratch_size; }; struct window_delete_data { @@ -156,7 +164,7 @@ struct window_delete_data { }; static void -x11_destroy(struct weston_backend *backend); +x11_destroy(struct weston_backend *base); static inline struct x11_head * to_x11_head(struct weston_head *base) @@ -170,8 +178,7 @@ static void x11_output_destroy(struct weston_output *base); static int -x11_output_init_shm(struct x11_backend *b, struct x11_output *output, - const struct pixel_format_info *pfmt); +x11_output_init_pixman(struct x11_output *output); static inline struct x11_output * to_x11_output(struct weston_output *base) @@ -479,72 +486,92 @@ x11_output_repaint_vulkan(struct weston_output *output_base) return 0; } -static void -set_clip_for_output(struct weston_output *output_base, pixman_region32_t *region) +static int +x11_output_repaint_pixman_shm(struct x11_output *output, xcb_rectangle_t *output_rects, int nrects) { - struct x11_output *output = to_x11_output(output_base); - struct x11_backend *b; - pixman_region32_t transformed_region; - pixman_box32_t *rects; - xcb_rectangle_t *output_rects; + xcb_connection_t *conn = output->backend->conn; xcb_void_cookie_t cookie; - int nrects, i; xcb_generic_error_t *err; - if (!output) - return; - - b = output->backend; - - pixman_region32_init(&transformed_region); - weston_region_global_to_output(&transformed_region, - output_base, - region); - - rects = pixman_region32_rectangles(&transformed_region, &nrects); - output_rects = calloc(nrects, sizeof(xcb_rectangle_t)); - - if (output_rects == NULL) { - pixman_region32_fini(&transformed_region); - return; - } - - for (i = 0; i < nrects; i++) { - output_rects[i].x = rects[i].x1; - output_rects[i].y = rects[i].y1; - output_rects[i].width = rects[i].x2 - rects[i].x1; - output_rects[i].height = rects[i].y2 - rects[i].y1; - } - - pixman_region32_fini(&transformed_region); - - cookie = xcb_set_clip_rectangles_checked(b->conn, XCB_CLIP_ORDERING_UNSORTED, - output->gc, - 0, 0, nrects, - output_rects); - err = xcb_request_check(b->conn, cookie); + cookie = xcb_set_clip_rectangles_checked( + conn, XCB_CLIP_ORDERING_UNSORTED, output->gc, + 0, 0, nrects, output_rects + ); + err = xcb_request_check(conn, cookie); if (err != NULL) { weston_log("Failed to set clip rects, err: %d\n", err->error_code); free(err); + return -1; } - free(output_rects); + + cookie = xcb_shm_put_image_checked( + conn, output->window, output->gc, + output->base.current_mode->width, output->base.current_mode->height, 0, 0, + output->base.current_mode->width, output->base.current_mode->height, 0, 0, + output->depth, XCB_IMAGE_FORMAT_Z_PIXMAP, 0, output->segment, 0 + ); + err = xcb_request_check(conn, cookie); + if (err != NULL) { + weston_log("Failed to put shm image, err: %d\n", err->error_code); + free(err); + return -1; + } + + return 0; } +static int +x11_output_repaint_pixman_putimage(struct x11_output *output, xcb_rectangle_t *output_rects, int nrects) +{ + int i, dst_stride, lines_per_chunk, y, chunk_y, chunk_h, rect_w, rect_h; + int bytes_pp = output->pfmt->bpp / 8; + int32_t src_stride = output->base.current_mode->width * bytes_pp; + uint8_t *src, *dst; + + for (i = 0; i < nrects; i++) { + rect_w = output_rects[i].width; + rect_h = output_rects[i].height; + /* X11 ZPixmap scanlines must be padded to a multiple of 4 bytes */ + dst_stride = (rect_w * bytes_pp + 3) & ~3; + + if (output->image_scratch_size == 0 || dst_stride == 0) + continue; + + lines_per_chunk = output->image_scratch_size / dst_stride; + if (lines_per_chunk == 0) + lines_per_chunk = 1; + + for (chunk_y = 0; chunk_y < rect_h; chunk_y += lines_per_chunk) { + chunk_h = MIN(rect_h - chunk_y, lines_per_chunk); + + for (y = 0; y < chunk_h; y++) { + src = output->buf + (output_rects[i].y + chunk_y + y) * src_stride + (output_rects[i].x * bytes_pp); + dst = output->image_scratch_buf + y * dst_stride; + memcpy(dst, src, rect_w * bytes_pp); + } + + xcb_put_image(output->backend->conn, XCB_IMAGE_FORMAT_Z_PIXMAP, + output->window, output->gc, + rect_w, chunk_h, + output_rects[i].x, output_rects[i].y + chunk_y, + 0, output->depth, dst_stride * chunk_h, output->image_scratch_buf + ); + } + } + + return 0; +} static int -x11_output_repaint_shm(struct weston_output *output_base) +x11_output_repaint_pixman(struct weston_output *output_base) { struct x11_output *output = to_x11_output(output_base); - struct weston_compositor *ec; - struct x11_backend *b; - xcb_void_cookie_t cookie; - xcb_generic_error_t *err; - pixman_region32_t damage; - - assert(output); - - ec = output->base.compositor; - b = output->backend; + struct weston_compositor *ec = output->base.compositor; + pixman_region32_t damage, transformed_region; + int nrects, i; + pixman_box32_t *rects; + xcb_rectangle_t *output_rects; + int result; pixman_region32_init(&damage); @@ -552,26 +579,32 @@ x11_output_repaint_shm(struct weston_output *output_base) ec->renderer->repaint_output(output_base, &damage, output->renderbuffer); - set_clip_for_output(output_base, &damage); + pixman_region32_init(&transformed_region); + weston_region_global_to_output(&transformed_region, output_base, &damage); + + rects = pixman_region32_rectangles(&transformed_region, &nrects); + + output_rects = xcalloc(nrects, sizeof(xcb_rectangle_t)); + for (i = 0; i < nrects; i++) { + output_rects[i].x = (int16_t) rects[i].x1; + output_rects[i].y = (int16_t) rects[i].y1; + output_rects[i].width = rects[i].x2 - rects[i].x1; + output_rects[i].height = rects[i].y2 - rects[i].y1; + } + + pixman_region32_fini(&transformed_region); + + result = (output->use_shm) + ? x11_output_repaint_pixman_shm(output, output_rects, nrects) + : x11_output_repaint_pixman_putimage(output, output_rects, nrects); + + free(output_rects); pixman_region32_fini(&damage); - cookie = xcb_shm_put_image_checked(b->conn, output->window, output->gc, - output_base->current_mode->width, - output_base->current_mode->height, - 0, 0, - output_base->current_mode->width, - output_base->current_mode->height, - 0, 0, output->depth, XCB_IMAGE_FORMAT_Z_PIXMAP, - 0, output->segment, 0); - err = xcb_request_check(b->conn, cookie); - if (err != NULL) { - weston_log("Failed to put shm image, err: %d\n", err->error_code); - free(err); - } - weston_output_arm_frame_timer(output_base, output->finish_frame_timer); - return 0; + + return result; } static int @@ -585,7 +618,7 @@ finish_frame_handler(void *data) } static void -x11_output_deinit_shm(struct x11_backend *b, struct x11_output *output) +x11_output_deinit_pixman(struct x11_backend *b, struct x11_output *output) { xcb_void_cookie_t cookie; xcb_generic_error_t *err; @@ -593,13 +626,21 @@ x11_output_deinit_shm(struct x11_backend *b, struct x11_output *output) b->compositor->renderer->destroy_renderbuffer(output->renderbuffer); output->renderbuffer = NULL; - cookie = xcb_shm_detach_checked(b->conn, output->segment); - err = xcb_request_check(b->conn, cookie); - if (err) { - weston_log("xcb_shm_detach failed, error %d\n", err->error_code); - free(err); + + if (output->use_shm) { + cookie = xcb_shm_detach_checked(b->conn, output->segment); + err = xcb_request_check(b->conn, cookie); + if (err) { + weston_log("xcb_shm_detach failed, error %d\n", err->error_code); + free(err); + } + shmdt(output->buf); + } else { + free(output->buf); + free(output->image_scratch_buf); + output->image_scratch_size = 0; } - shmdt(output->buf); + output->buf = NULL; } static void @@ -753,24 +794,14 @@ get_depth_of_visual(xcb_screen_t *screen, } static const struct pixel_format_info * -x11_output_get_shm_pixel_format(struct x11_output *output) +x11_output_get_pixman_pixel_format(struct x11_output *output) { struct x11_backend *b = output->backend; xcb_visualtype_t *visual_type; xcb_screen_t *screen; xcb_format_iterator_t fmt; - const xcb_query_extension_reply_t *ext; int bitsperpixel = 0; - /* Check if SHM is available */ - ext = xcb_get_extension_data(b->conn, &xcb_shm_id); - if (ext == NULL || !ext->present) { - /* SHM is missing */ - weston_log("SHM extension is not available\n"); - errno = ENOENT; - return NULL; - } - screen = x11_compositor_get_default_screen(b); visual_type = find_visual_by_id(screen, screen->root_visual); if (!visual_type) { @@ -801,16 +832,16 @@ x11_output_get_shm_pixel_format(struct x11_output *output) visual_type->red_mask == 0xff0000 && visual_type->green_mask == 0x00ff00 && visual_type->blue_mask == 0x0000ff) { - weston_log("Will use x8r8g8b8 format for SHM surfaces\n"); + weston_log("Will use x8r8g8b8 format for pixman surfaces\n"); return pixel_format_get_info_by_pixman(PIXMAN_x8r8g8b8); } else if (bitsperpixel == 16 && visual_type->red_mask == 0x00f800 && visual_type->green_mask == 0x0007e0 && visual_type->blue_mask == 0x00001f) { - weston_log("Will use r5g6b5 format for SHM surfaces\n"); + weston_log("Will use r5g6b5 format for pixman surfaces\n"); return pixel_format_get_info_by_pixman(PIXMAN_r5g6b5); } else { - weston_log("Can't find appropriate format for SHM pixmap\n"); + weston_log("Can't find appropriate format for pixman pixmap\n"); errno = ENOTSUP; return NULL; } @@ -820,15 +851,11 @@ static bool x11_rb_discarded_cb(weston_renderbuffer_t rb, void *data) { struct x11_output *output = (struct x11_output *) data; - const struct pixel_format_info *pfmt; if (output->base.compositor->renderer->type == WESTON_RENDERER_PIXMAN) { - x11_output_deinit_shm(output->backend, output); - pfmt = x11_output_get_shm_pixel_format(output); - if (!pfmt) - return false; - if (x11_output_init_shm(output->backend, output, pfmt) < 0) { - weston_log("Failed to initialize SHM for the X11 output\n"); + x11_output_deinit_pixman(output->backend, output); + if (x11_output_init_pixman(output) < 0) { + weston_log("Failed to initialize pixman renderer for the X11 output\n"); return false; } } @@ -836,49 +863,135 @@ x11_rb_discarded_cb(weston_renderbuffer_t rb, void *data) return true; } -static int -x11_output_init_shm(struct x11_backend *b, struct x11_output *output, - const struct pixel_format_info *pfmt) +static void +x11_output_init_pixman_shm(struct x11_output *output, size_t size) { - struct weston_renderer *renderer = output->base.compositor->renderer; - int bitsperpixel = pfmt->bpp; - size_t size = output->base.current_mode->width * - output->base.current_mode->height * (bitsperpixel / 8); - int stride = output->base.current_mode->width * (bitsperpixel / 8); + xcb_connection_t *conn = output->backend->conn; xcb_void_cookie_t cookie; xcb_generic_error_t *err; /* Create SHM segment and attach it */ output->shm_id = shmget(IPC_PRIVATE, size, IPC_CREAT | S_IRWXU); if (output->shm_id == -1) { - weston_log("x11shm: failed to allocate SHM segment\n"); - return -1; + weston_log("x11shm: failed to allocate SHM segment of size %zu: %s\n", + size, strerror(errno)); + return; } - output->buf = shmat(output->shm_id, NULL, 0 /* read/write */); - if (-1 == (long)output->buf) { + + output->buf = shmat(output->shm_id, NULL, 0); + if (output->buf == (void *)-1) { weston_log("x11shm: failed to attach SHM segment\n"); - return -1; + goto err_free; } - output->segment = xcb_generate_id(b->conn); - cookie = xcb_shm_attach_checked(b->conn, output->segment, output->shm_id, 1); - err = xcb_request_check(b->conn, cookie); + + output->segment = xcb_generate_id(conn); + cookie = xcb_shm_attach_checked(conn, output->segment, output->shm_id, 1); + err = xcb_request_check(conn, cookie); if (err) { weston_log("x11shm: xcb_shm_attach error %d, op code %d, resource id %d\n", err->error_code, err->major_code, err->minor_code); free(err); + shmdt(output->buf); + goto err_free; + } + + output->use_shm = true; + goto release_segment; + +err_free: + output->buf = NULL; + +release_segment: + shmctl(output->shm_id, IPC_RMID, NULL); +} + +static void +x11_output_init_pixman_putimage(struct x11_output *output, size_t size) +{ + uint32_t max_req_len, max_payload, min_stride; + + output->buf = malloc(size); + + if (output->buf == NULL) { + weston_log("x11: failed to allocate buffer for XPutImage of size %zu\n", size); + return; + } + + max_req_len = xcb_get_maximum_request_length(output->backend->conn) * 4; + max_payload = max_req_len > 32 ? max_req_len - 32 : 0; + min_stride = (output->base.current_mode->width * (output->pfmt->bpp / 8) + 3) & ~3; + + if (max_payload < min_stride) { + weston_log("x11: X11 server max request length is too small to transfer a single line\n"); + goto error_free; + } + + output->image_scratch_size = size; + if (max_payload > 0 && max_payload < output->image_scratch_size) { + output->image_scratch_size = max_payload; + } + + output->image_scratch_buf = malloc(output->image_scratch_size); + if (output->image_scratch_buf == NULL) { + weston_log("x11: failed to allocate buffer for XPutImage of size %u\n", + output->image_scratch_size); + goto error_free; + } + + return; + +error_free: + free(output->buf); + output->buf = NULL; +} + +static int +x11_output_init_pixman(struct x11_output *output) +{ + struct weston_renderer *renderer = output->base.compositor->renderer; + int stride = output->base.current_mode->width * (output->pfmt->bpp / 8); + size_t size = stride * output->base.current_mode->height; + const char *env_val; + bool force_shm, force_putimage; + + env_val = getenv("WESTON_X11_ONLY_SHM"); + force_shm = env_val && strcmp(env_val, "1") == 0; + + env_val = getenv("WESTON_X11_NO_SHM"); + force_putimage = env_val && strcmp(env_val, "1") == 0; + + if (output->backend->has_shm && !force_putimage) { + x11_output_init_pixman_shm(output, size); + + if (output->buf != NULL) + weston_log("x11: using MIT-SHM\n"); + else if (!force_shm) + weston_log("x11: MIT-SHM not available, falling back to XPutImage\n"); + } + + if (output->buf == NULL && force_shm) { + weston_log("x11: MIT-SHM not available, not falling back to XPutImage as requested\n"); + return -1; } - shmctl(output->shm_id, IPC_RMID, NULL); + if (output->buf == NULL) { + x11_output_init_pixman_putimage(output, size); + + if (output->buf == NULL) + return -1; + + weston_log("x11: using XPutImage\n"); + } /* Now create pixman image */ output->renderbuffer = - renderer->create_renderbuffer(&output->base, pfmt, output->buf, + renderer->create_renderbuffer(&output->base, output->pfmt, output->buf, stride, x11_rb_discarded_cb, output); - output->gc = xcb_generate_id(b->conn); - xcb_create_gc(b->conn, output->gc, output->window, 0, NULL); + output->gc = xcb_generate_id(output->backend->conn); + xcb_create_gc(output->backend->conn, output->gc, output->window, 0, NULL); return 0; } @@ -960,7 +1073,7 @@ x11_output_disable(struct weston_output *base) switch (renderer->type) { case WESTON_RENDERER_PIXMAN: - x11_output_deinit_shm(backend, output); + x11_output_deinit_pixman(backend, output); renderer->pixman->output_destroy(&output->base); break; case WESTON_RENDERER_GL: @@ -998,11 +1111,10 @@ x11_output_enable(struct weston_output *base) const struct weston_renderer *renderer = base->compositor->renderer; struct x11_output *output = to_x11_output(base); const struct weston_mode *mode = output->base.current_mode; - struct x11_backend *b; + struct pixman_renderer_output_options options; - assert(output); - - b = output->backend; + assert(output); + struct x11_backend *b = output->backend; static const char name[] = "Weston Compositor"; static const char class[] = "weston-1\0Weston Compositor"; @@ -1021,7 +1133,7 @@ x11_output_enable(struct weston_output *base) 0 }; - if (!b->no_input) + if (!b->no_input) values[0] |= XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | @@ -1103,27 +1215,28 @@ x11_output_enable(struct weston_output *base) switch (renderer->type) { case WESTON_RENDERER_PIXMAN: { - const struct pixman_renderer_output_options options = { + options = (struct pixman_renderer_output_options) { .use_shadow = true, .fb_size = { .width = mode->width, .height = mode->height }, - .format = x11_output_get_shm_pixel_format(output) + .format = x11_output_get_pixman_pixel_format(output) }; - if (!options.format) + output->pfmt = options.format; + if (!output->pfmt) goto err; if (renderer->pixman->output_create(&output->base, &options) < 0) { weston_log("Failed to create pixman renderer for output\n"); goto err; } - if (x11_output_init_shm(b, output, options.format) < 0) { - weston_log("Failed to initialize SHM for the X11 output\n"); + if (x11_output_init_pixman(output) < 0) { + weston_log("Failed to initialize pixman renderer for the X11 output\n"); renderer->pixman->output_destroy(&output->base); goto err; } - output->base.repaint = x11_output_repaint_shm; + output->base.repaint = x11_output_repaint_pixman; break; } case WESTON_RENDERER_GL: { @@ -1959,6 +2072,9 @@ x11_backend_create(struct weston_compositor *compositor, { struct x11_backend *b; struct wl_event_loop *loop; + const xcb_query_extension_reply_t *ext; + xcb_shm_query_version_cookie_t query_ext_cookie; + xcb_shm_query_version_reply_t *reply; int ret; b = zalloc(sizeof *b); @@ -1981,6 +2097,22 @@ x11_backend_create(struct weston_compositor *compositor, b->conn = XGetXCBConnection(b->dpy); XSetEventQueueOwner(b->dpy, XCBOwnsEventQueue); + b->has_shm = false; + ext = xcb_get_extension_data(b->conn, &xcb_shm_id); + if (ext != NULL && ext->present) { + query_ext_cookie = xcb_shm_query_version(b->conn); + reply = xcb_shm_query_version_reply(b->conn, query_ext_cookie, NULL); + if (reply != NULL) { + weston_log("SHM extension has version %d.%d\n", reply->major_version, reply->minor_version); + b->has_shm = true; + free(reply); + } else { + weston_log("SHM extension has unknown version\n"); + } + } else { + weston_log("SHM extension is not available\n"); + } + if (xcb_connection_has_error(b->conn)) goto err_xdisplay; diff --git a/tests/meson.build b/tests/meson.build index 7b04125d5..b768692bc 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -292,6 +292,16 @@ if get_option('shell-lua') ] endif +xvfb = find_program('Xvfb', required: false) +if get_option('backend-x11') and xvfb.found() + tests += [ + { + 'name': 'x11-backend', + 'dep_objs': [ dep_x11_xcb ], + }, + ] +endif + test_config_h = configuration_data() test_config_h.set_quoted('WESTON_TEST_REFERENCE_PATH', meson.current_source_dir() + '/reference') test_config_h.set_quoted('WESTON_MODULE_MAP', env_modmap) diff --git a/tests/x11-backend-test.c b/tests/x11-backend-test.c new file mode 100644 index 000000000..55bba905e --- /dev/null +++ b/tests/x11-backend-test.c @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shared/helpers.h" + +#include "weston-test-runner.h" +#include "weston-test-client-helper.h" +#include "weston-test-assert.h" + +#define XVFB_DISPLAY "99" + +struct setup_args { + struct fixture_metadata meta; + + bool xvfb_allow_shm; + + bool weston_x11_only_shm; + bool weston_x11_no_shm; + + bool expect_fail; +}; + +static const struct setup_args my_setup_args[] = { + { + /* server DOESN'T support MIT-SHM + * => automatic fallback to XPutImage */ + .meta.name = "xvfb_shm_off__env_shm_default", + .xvfb_allow_shm = false, + .weston_x11_only_shm = false, + .weston_x11_no_shm = false, + }, + { + /* server DOESN'T support SHM, but forced by WESTON_X11_ONLY_SHM=1 + * => expected FAIL */ + .meta.name = "xvfb_shm_off__env_shm_only", + .xvfb_allow_shm = false, + .weston_x11_only_shm = true, + .weston_x11_no_shm = false, + .expect_fail = true, + }, + { + /* server DOESN'T support MIT-SHM (also forbidden by WESTON_X11_NO_SHM=1) + * => automatic fallback to XPutImage */ + .meta.name = "xvfb_shm_off__env_shm_no", + .xvfb_allow_shm = false, + .weston_x11_only_shm = false, + .weston_x11_no_shm = true, + }, + { + /* server supports MIT-SHM + * => use MIT-SHM */ + .meta.name = "xvfb_shm_on__env_shm_default", + .xvfb_allow_shm = true, + .weston_x11_only_shm = false, + .weston_x11_no_shm = false, + }, + { + /* server supports MIT-SHM (also forced by WESTON_X11_ONLY_SHM=1) + * => use MIT-SHM */ + .meta.name = "xvfb_shm_on__env_shm_only", + .xvfb_allow_shm = true, + .weston_x11_only_shm = true, + .weston_x11_no_shm = false, + }, + { + /* server supports SHM, but forbidden by WESTON_X11_NO_SHM=1 + * => use XPutImage */ + .meta.name = "xvfb_shm_on__env_shm_no", + .xvfb_allow_shm = true, + .weston_x11_only_shm = false, + .weston_x11_no_shm = true, + }, +}; + + +static enum test_result_code +fixture_setup(struct weston_test_harness *harness, const struct setup_args *arg) +{ + struct compositor_setup setup; + pid_t pid_xvfb; + char *mitshm_ext_arg, *weston_x11_only_shm, *weston_x11_no_shm; + int retries, status; + enum test_result_code result, ret; + + weston_x11_only_shm = arg->weston_x11_only_shm ? "1" : "0"; + weston_x11_no_shm = arg->weston_x11_no_shm ? "1" : "0"; + + testlog("Test fixture: %s (Xvfb MIT-SHM extension %s, WESTON_X11_NO_SHM=%s, WESTON_X11_ONLY_SHM=%s)\n", + arg->meta.name, arg->xvfb_allow_shm ? "ON" : "OFF", + weston_x11_no_shm, weston_x11_only_shm); + + pid_xvfb = fork(); + assert(pid_xvfb >= 0); + + if (pid_xvfb == 0) { + prctl(PR_SET_PDEATHSIG, SIGTERM); + + mitshm_ext_arg = arg->xvfb_allow_shm ? "+extension" : "-extension"; + execlp("Xvfb", "Xvfb", ":" XVFB_DISPLAY, + "-screen", "0", "640x480x24", + mitshm_ext_arg, "MIT-SHM", + "-terminate", NULL); + + fprintf(stderr, "Error: Xvfb cannot be started: %s\n", + strerror(errno)); + return RESULT_HARD_ERROR; + } + + retries = 0; + while (access("/tmp/.X11-unix/X" XVFB_DISPLAY, F_OK) != 0) { + if (retries == 50) { + fprintf(stderr, "Error: Timeout waiting for Xvfb socket\n"); + return RESULT_HARD_ERROR; + } + if (waitpid(pid_xvfb, &status, WNOHANG) > 0) { + fprintf(stderr, "Error: Xvfb process died unexpectedly during startup\n"); + return RESULT_HARD_ERROR; + } + usleep(100000); // 100 ms + retries++; + } + + setenv("DISPLAY", ":" XVFB_DISPLAY, 1); + setenv("WESTON_X11_ONLY_SHM", weston_x11_only_shm, 1); + setenv("WESTON_X11_NO_SHM", weston_x11_no_shm, 1); + + compositor_setup_defaults(&setup); + setup.backend = WESTON_BACKEND_X11; + setup.renderer = WESTON_RENDERER_PIXMAN; + + result = weston_test_harness_execute_as_client(harness, &setup); + testlog("Test %s result: %d\n", arg->meta.name, result); + + if (arg->expect_fail) { + ret = (result != RESULT_OK) ? RESULT_SKIP : RESULT_FAIL; + } else { + ret = result; + } + + kill(pid_xvfb, SIGTERM); + waitpid(pid_xvfb, NULL, 0); + + return ret; +} +DECLARE_FIXTURE_SETUP_WITH_ARG(fixture_setup, my_setup_args, meta) + + +static void +get_x11_screen_pixels(int16_t x, int16_t y, uint16_t width, uint16_t height, uint32_t *out_pixels) +{ + xcb_connection_t *conn; + const xcb_setup_t *xsetup; + xcb_screen_t *screen; + xcb_query_tree_cookie_t qt_cookie; + xcb_query_tree_reply_t *qt_reply; + xcb_window_t weston_win; + xcb_get_image_cookie_t img_cookie; + xcb_get_image_reply_t *img_reply; + + conn = xcb_connect(":" XVFB_DISPLAY, NULL); + assert(conn && !xcb_connection_has_error(conn)); + xsetup = xcb_get_setup(conn); + screen = xcb_setup_roots_iterator(xsetup).data; + + qt_cookie = xcb_query_tree(conn, screen->root); + qt_reply = xcb_query_tree_reply(conn, qt_cookie, NULL); + assert(qt_reply != NULL && qt_reply->children_len > 0); + weston_win = xcb_query_tree_children(qt_reply)[qt_reply->children_len - 1]; + + img_cookie = xcb_get_image(conn, XCB_IMAGE_FORMAT_Z_PIXMAP, weston_win, + x, y, width, height, ~0); + img_reply = xcb_get_image_reply(conn, img_cookie, NULL); + assert(img_reply != NULL); + + memcpy(out_pixels, xcb_get_image_data(img_reply), width * height * 4); + + free(img_reply); + free(qt_reply); + xcb_disconnect(conn); +} + +static void +draw_buffer_to_wayland(struct client *client, struct buffer *buf) +{ + int frame_done = 0; + + client->surface = create_test_surface(client); + + client->surface->buffer = buf; + client->surface->width = buf->buf->width; + client->surface->height = buf->buf->height; + + move_client(client, 0, 0); + + wl_surface_set_buffer_scale(client->surface->wl_surface, 1); + wl_surface_set_buffer_transform(client->surface->wl_surface, WL_OUTPUT_TRANSFORM_NORMAL); + wl_surface_attach(client->surface->wl_surface, client->surface->buffer->proxy, 0, 0); + wl_surface_damage(client->surface->wl_surface, 0, 0, client->surface->width, client->surface->height); + + frame_callback_set(client->surface->wl_surface, &frame_done); + wl_surface_commit(client->surface->wl_surface); + + frame_callback_wait(client, &frame_done); +} + +TEST(x11_backend) +{ + const struct setup_args *args = &my_setup_args[get_test_fixture_index()]; + struct client *client; + struct buffer *img_buffer; + pixman_image_t *x11_img; + uint32_t *x11_pixels; + struct rectangle clip; + struct range fuzz = { .a = -5, .b = 4 }; + int width, height; + bool match; + + testlog("Test: %s (Xvfb MIT-SHM extension %s, WESTON_X11_NO_SHM=%s, WESTON_X11_ONLY_SHM=%s)\n", + args->meta.name, args->xvfb_allow_shm ? "ON" : "OFF", + getenv("WESTON_X11_NO_SHM"), getenv("WESTON_X11_ONLY_SHM")); + + client = create_client(); + width = client->output->width; + height = client->output->height; + + img_buffer = client_buffer_from_image_file(client, "chocolate-cake", 1); + draw_buffer_to_wayland(client, img_buffer); + + x11_pixels = malloc(width * height * 4); + get_x11_screen_pixels(0, 0, width, height, x11_pixels); + x11_img = pixman_image_create_bits(PIXMAN_x8r8g8b8, width, height, + x11_pixels, width * 4); + + clip.x = 0; + clip.y = 0; + clip.width = MIN(img_buffer->buf->width, width); + clip.height = MIN(img_buffer->buf->height, height); + + match = check_images_match(img_buffer->image, x11_img, &clip, &fuzz); + + pixman_image_unref(x11_img); + free(x11_pixels); + + client_destroy(client); + + return match ? RESULT_OK : RESULT_FAIL; +}