Adds an initial implementation of a testing tool that uses the unit test framework to run checks against an arbitrary Wayland compositor. Note that this is not intended for Weston-specific testing, but for generic Wayland testing.
Signed-off-by: Jon A. Cruz <j...@osg.samsung.com> --- Changes from v1: - Added test fixture to launch compositor used for tests. - Hooked waycheck into "make check" - Corrected problem in version binding that was inherited from legacy test code. - Incorporated misc fixes from code review feedback. - Removed files duplicated in rebasing. --- .gitignore | 1 + Makefile.am | 52 +- doc/doxygen/devtools.dox | 10 +- doc/doxygen/tooldev.doxygen.in | 3 +- doc/doxygen/tools.dox | 1 + doc/doxygen/tools.doxygen.in | 6 +- tools/waycheck/rough_draw.c | 285 ++++++ tools/waycheck/rough_draw.h | 49 + tools/waycheck/waycheck.c | 457 +++++++++ tools/waycheck/waycheck.dox | 29 + tools/wayland_fixtures/inc/wtst_compfixture.h | 76 ++ tools/wayland_fixtures/inc/wtst_fixtures.h | 268 ++++++ tools/wayland_fixtures/src/wtst_compfixture.c | 411 ++++++++ tools/wayland_fixtures/src/wtst_fixtures.c | 1238 +++++++++++++++++++++++++ 14 files changed, 2875 insertions(+), 11 deletions(-) create mode 100644 tools/waycheck/rough_draw.c create mode 100644 tools/waycheck/rough_draw.h create mode 100644 tools/waycheck/waycheck.c create mode 100644 tools/waycheck/waycheck.dox create mode 100644 tools/wayland_fixtures/inc/wtst_compfixture.h create mode 100644 tools/wayland_fixtures/inc/wtst_fixtures.h create mode 100644 tools/wayland_fixtures/src/wtst_compfixture.c create mode 100644 tools/wayland_fixtures/src/wtst_fixtures.c diff --git a/.gitignore b/.gitignore index 11d23da..7278bcf 100644 --- a/.gitignore +++ b/.gitignore @@ -106,4 +106,5 @@ weston.ini.5 /tests/weston-ivi.ini internal-screenshot-00.png +/waycheck /zuctest diff --git a/Makefile.am b/Makefile.am index cbb3b57..72f131e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -971,7 +971,7 @@ endif # noinst_LTLIBRARIES += libshared.la libshared-cairo.la \ - libzunitc.la libzunitcmain.la + libzunitc.la libzunitcmain.la libwayland-fixtures.la libshared_la_CFLAGS = $(AM_CFLAGS) $(COMPOSITOR_CFLAGS) @@ -1010,6 +1010,24 @@ libshared_cairo_la_SOURCES = \ shared/frame.c \ shared/cairo-util.h +libwayland_fixtures_la_SOURCES = \ + tools/wayland_fixtures/inc/wtst_compfixture.h \ + tools/wayland_fixtures/src/wtst_compfixture.c \ + tools/wayland_fixtures/inc/wtst_fixtures.h \ + tools/wayland_fixtures/src/wtst_fixtures.c \ + shared/helpers.h + +libwayland_fixtures_la_CFLAGS = \ + -I$(top_srcdir)/tools/wayland_fixtures/inc \ + -I$(top_srcdir)/tools/zunitc/inc \ + $(TEST_CLIENT_CFLAGS) \ + $(AM_CFLAGS) + +libwayland_fixtures_la_LIBADD = \ + $(TEST_CLIENT_LIBS) \ + libzunitc.la \ + libshared.la + libzunitc_la_SOURCES = \ tools/zunitc/inc/zunitc/zunitc.h \ tools/zunitc/inc/zunitc/zunitc_impl.h \ @@ -1044,12 +1062,12 @@ libzunitcmain_la_SOURCES = \ tools/zunitc/src/main.c libzunitcmain_la_CFLAGS = \ - $(AM_CFLAGS) \ - -I$(top_srcdir)/tools/zunitc/inc + -I$(top_srcdir)/tools/zunitc/inc \ + $(AM_CFLAGS) libzunitcmain_la_LIBADD = \ - libzunitc.la \ - libshared.la + libshared.la \ + libzunitc.la # # tests subdirectory @@ -1063,6 +1081,7 @@ internal_tests = \ shared_tests = \ config-parser.test \ vertex-clip.test \ + waycheck \ zuctest module_tests = \ @@ -1304,7 +1323,7 @@ setbacklight_CFLAGS = $(AM_CFLAGS) $(SETBACKLIGHT_CFLAGS) setbacklight_LDADD = $(SETBACKLIGHT_LIBS) endif -all-local: zuctest$(EXEEXT) +all-local: waycheck$(EXEEXT) zuctest$(EXEEXT) noinst_PROGRAMS += zuctest$(EXEEXT) @@ -1320,6 +1339,27 @@ zuctest_SOURCES = \ tools/zunitc/test/fixtures_test.c \ tools/zunitc/test/zunitc_test.c +noinst_PROGRAMS += waycheck$(EXEEXT) + +waycheck_LDADD = \ + $(TEST_CLIENT_LIBS) \ + libwayland-fixtures.la \ + libshared.la + +waycheck_CFLAGS = \ + -I$(top_srcdir)/tools/zunitc/inc \ + -I$(top_srcdir)/tools/wayland_fixtures/inc \ + -I$(top_builddir)/clients \ + -I$(top_srcdir)/shared \ + $(TEST_CLIENT_CFLAGS) \ + $(AM_CFLAGS) + +waycheck_SOURCES = \ + tools/waycheck/waycheck.c \ + tools/waycheck/rough_draw.c \ + tools/waycheck/rough_draw.h \ + shared/helpers.h + EXTRA_DIST += \ tests/weston-tests-env \ tests/internal-screenshot.ini \ diff --git a/doc/doxygen/devtools.dox b/doc/doxygen/devtools.dox index 2d6672f..08d1572 100644 --- a/doc/doxygen/devtools.dox +++ b/doc/doxygen/devtools.dox @@ -28,10 +28,13 @@ - @ref zunitc - Simple test framework +- @ref waycheck - Simple integration/acceptance test tool + @section tools_overview Overview -The tools area currently consists of one sub-project (@ref zunitc) that is -refined from the prior single weston/tests source folder. +The tools area currently consists of two sub-projects (@ref zunitc and +@ref waycheck) that are refined from the prior single weston/tests source +folder. @subsection tools_overview_old Old Code Organization @@ -44,7 +47,8 @@ stage where splitting apart into discrete layers was warranted. @subsection tools_overview_new New Code Organization The test code that is not weston-specific gets split out to a separate -folder and/or folders. +folder and/or folders. Then an additional testing tool 'waycheck' that is +compositor-agnostic is added. @dotfile tools_arch_new.gv "Refactored test code organization" diff --git a/doc/doxygen/tooldev.doxygen.in b/doc/doxygen/tooldev.doxygen.in index b3d86f5..1915975 100644 --- a/doc/doxygen/tooldev.doxygen.in +++ b/doc/doxygen/tooldev.doxygen.in @@ -5,7 +5,8 @@ OPTIMIZE_OUTPUT_FOR_C = YES EXTRACT_ALL = YES INPUT = \ @top_srcdir@/doc/doxygen/devtools.dox \ - @top_srcdir@/tools/zunitc + @top_srcdir@/tools/zunitc \ + @top_srcdir@/tools/waycheck RECURSIVE = YES GENERATE_LATEX = NO DOTFILE_DIRS = @top_srcdir@/doc/doxygen diff --git a/doc/doxygen/tools.dox b/doc/doxygen/tools.dox index 9bbc11d..0be1372 100644 --- a/doc/doxygen/tools.dox +++ b/doc/doxygen/tools.dox @@ -28,4 +28,5 @@ - @ref zunitc - Simple test framework +- @ref waycheck - Simple integration/acceptance test tool */ diff --git a/doc/doxygen/tools.doxygen.in b/doc/doxygen/tools.doxygen.in index 613edd4..88c2f65 100644 --- a/doc/doxygen/tools.doxygen.in +++ b/doc/doxygen/tools.doxygen.in @@ -5,7 +5,11 @@ OPTIMIZE_OUTPUT_FOR_C = YES INPUT = \ @top_srcdir@/doc/doxygen/tools.dox \ @top_srcdir@/tools/zunitc/doc/zunitc.dox \ - @top_srcdir@/tools/zunitc/inc/zunitc/zunitc.h + @top_srcdir@/tools/zunitc/inc/zunitc/zunitc.h \ + @top_srcdir@/tools/waycheck/waycheck.dox \ + @top_srcdir@/tools/wayland_fixtures/inc/wtst_fixtures.h \ + @top_srcdir@/tools/wayland_fixtures/src/wtst_fixtures.c \ + @top_srcdir@/tools/waycheck/rough_draw.h GENERATE_LATEX = NO DOTFILE_DIRS = @top_srcdir@/doc/doxygen STRIP_FROM_PATH = @top_srcdir@ diff --git a/tools/waycheck/rough_draw.c b/tools/waycheck/rough_draw.c new file mode 100644 index 0000000..ae2dd33 --- /dev/null +++ b/tools/waycheck/rough_draw.c @@ -0,0 +1,285 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * @file simple drawing for test. + * + * @todo refactor to use pixman instead of direct manipulation. + */ + +#include "config.h" + +#include <stddef.h> +#include <string.h> + +#include "rough_draw.h" +#include "shared/helpers.h" + +/** + * Very simple way to track colors. + */ +struct colorval { + float r; + float g; + float b; + float a; + enum wl_shm_format form; + int used; + uint8_t raw[4]; +}; + +static void +populate_color(struct colorval *color, enum wl_shm_format form) +{ + memset(color->raw, 0, sizeof(color->raw)); + color->form = form; + switch (form) { + case WL_SHM_FORMAT_ARGB8888: + color->used = 4; + color->raw[3] = (0xff * color->a) + 0.5; + color->raw[2] = (0xff * color->r) + 0.5; + color->raw[1] = (0xff * color->g) + 0.5; + color->raw[0] = (0xff * color->b) + 0.5; + break; + case WL_SHM_FORMAT_RGB565: + color->used = 2; + color->raw[1] = (uint8_t)((0x1f * color->r) + 0.5) << 3; + color->raw[0] = (0x3f * color->g) + 0.5; + color->raw[1] |= 0x07 & (color->raw[0] >> 3); + color->raw[0] <<= 5; + color->raw[0] |= (uint8_t)((0x1f * color->b) + 0.5); + break; + default: + color->used = 0; + } +} + +static void +fill_rect(uint8_t *raw, struct colorval *color, + int width, int height, int stride) +{ + int y = 0; + for (y = 0; y < height; ++y) { + int x = 0; + uint8_t *ptr = raw + (y * stride); + for (x = 0; x < width; ++x) { + memcpy(ptr, &color->raw[0], color->used); + ptr += color->used; + } + } +} + +static void +blend(struct colorval *dest, + struct colorval const *val1, struct colorval const *val2, + double factor) +{ + dest->r = (val1->r * (1.0 - factor)) + (val2->r * factor); + dest->g = (val1->g * (1.0 - factor)) + (val2->g * factor); + dest->b = (val1->b * (1.0 - factor)) + (val2->b * factor); + dest->a = (val1->a * (1.0 - factor)) + (val2->a * factor); + populate_color(dest, val1->form); +} + +static void +hgrad_rect(uint8_t *buf, + struct colorval const *val1, struct colorval const *val2, + int width, int height, int stride) +{ + int x = 0; + int y = 0; + int pw = val1->used; + struct colorval color = {.a=1.0}; + double dwidth = (double)width; + for (x = 0; x < width; ++x) { + uint8_t *ptr = buf + x * pw; + blend(&color, val1, val2, x / dwidth); + for (y = 0; y < height; ++y) { + memcpy(ptr, &color.raw[0], color.used); + ptr += stride; + } + } +} + +/* + * Approximates common color bars with black at 16 and white at 235. + */ +static const double lvblk = (16 + 0.00 * (235 - 16)) / 255.0; +static const double lvwht = (16 + 1.00 * (235 - 16)) / 255.0; +static const double lvupr = (16 + 0.75 * (235 - 16)) / 255.0; +static const double lvmid = (16 + 0.40 * (235 - 16)) / 255.0; +static const double lvb15 = (16 + 0.15 * (235 - 16)) / 255.0; +static const double lvb_2 = (16 - 0.02 * (235 - 16)) / 255.0; +static const double lvb02 = (16 + 0.02 * (235 - 16)) / 255.0; +static const double lvb04 = (16 + 0.04 * (235 - 16)) / 255.0; + +/** + * Draws a simple color-bars test pattern to the specified buffer. + */ +void +rd_draw_bars(uint8_t *buf, enum wl_shm_format form, int stride, + int width, int height) +{ + size_t i; + int y0, y1, y2; + int h1, h2; + int w1, w2, w3, w4, hw; + int xr; + int pw; + int xoff; + double bar_span; + size_t bar_count; + struct colorval bars[] = { + {.r=lvmid, .g=lvmid, .b=lvmid, .a=1.0}, + {.r=lvupr, .g=lvupr, .b=lvupr, .a=1.0}, + {.r=lvupr, .g=lvupr, .b=lvblk, .a=1.0}, + {.r=lvblk, .g=lvupr, .b=lvupr, .a=1.0}, + {.r=lvblk, .g=lvupr, .b=lvblk, .a=1.0}, + {.r=lvupr, .g=lvblk, .b=lvupr, .a=1.0}, + {.r=lvupr, .g=lvblk, .b=lvblk, .a=1.0}, + {.r=lvblk, .g=lvblk, .b=lvupr, .a=1.0}, + {.r=lvmid, .g=lvmid, .b=lvmid, .a=1.0}, + {.r=lvb15, .g=lvb15, .b=lvb15, .a=1.0}, + {.r=lvwht, .g=lvwht, .b=lvwht, .a=1.0}, + {.r=lvblk, .g=lvblk, .b=lvblk, .a=1.0}, + {.r=lvb_2, .g=lvb_2, .b=lvb_2, .a=1.0}, + {.r=lvb02, .g=lvb02, .b=lvb02, .a=1.0}, + {.r=lvb04, .g=lvb04, .b=lvb04, .a=1.0}, + }; + + if (!buf) + return; + + for (i = 0; i < ARRAY_LENGTH(bars); ++i) + populate_color(&bars[i], form); + + pw = bars[0].used; + y0 = ((height * 1.75) / 3); + y1 = ((height * 2) / 3); + y2 = ((height * 3) / 4); + h1 = y1 - y0; + h2 = height - y2; + w1 = width / 8; + w2 = (width * 0.75) / 7; + xr = pw * ((width * 7) / 8); + + /* left gray: */ + fill_rect(buf, &bars[0], w1, y0, stride); + fill_rect(buf + (y0 * stride), &bars[3], w1, h1, stride); + fill_rect(buf + (y1 * stride), &bars[2], w1, h1, stride); + fill_rect(buf + (y2 * stride), &bars[9], w1, h2, stride); + + /* right gray */ + fill_rect(buf + xr, &bars[0], w1, y0, stride); + fill_rect(buf + xr + (y0 * stride), &bars[7], w1, h1, stride); + fill_rect(buf + xr + (y1 * stride), &bars[6], w1, h1, stride); + fill_rect(buf + xr + (y2 * stride), &bars[9], w1, h2, stride); + + xoff = w1 * pw; + fill_rect(buf + xoff + (y0 * stride), &bars[10], w2, h1, stride); + fill_rect(buf + xoff + (y1 * stride), &bars[11], w2, h1, stride); + + bar_span = (width * 0.75); + bar_count = 7; + for (i = 0; i < bar_count; ++i) + { + int ww = (bar_span * (i + 1)) / bar_count; + xoff = (bar_span * i) / bar_count; + ww -= xoff; + xoff += w1; + fill_rect(buf + xoff * pw, &bars[i + 1], ww, y0, stride); + } + + + hw = width - (w1 + w1 + w2); + /* Gray horiz: */ + xoff = w1 + w2; + fill_rect(buf + xoff * pw + (y0 * stride), &bars[1], hw, h1, stride); + hgrad_rect(buf + xoff * pw + (y1 * stride), &bars[11], &bars[10], + hw, h1, stride); + + /* note - horizontal positions of lower bars are not simple values */ + + xoff = (width * 0.85625) / 3; + w3 = (width * 0.64375) / 3; + fill_rect(buf + w1 * pw + (y2 * stride), &bars[11], width - (w1 * 2), + h2, stride); + fill_rect(buf + xoff * pw + (y2 * stride), &bars[10], w3, + h2, stride); + + w4 = (width * 0.10625) / 3; + xoff = (width * 1.765625) / 3; + fill_rect(buf + xoff * pw + (y2 * stride), &bars[12], w4, h2, stride); + + xoff = (width * 1.98125) / 3; + fill_rect(buf + xoff * pw + (y2 * stride), &bars[13], w4, h2, stride); + + xoff = (width * 2.196875) / 3; + fill_rect(buf + xoff * pw + (y2 * stride), &bars[14], w4, h2, stride); +} + +/** + * Draws a simple cross-hairs image suitable for a cursor to the specified + * buffer. + */ +void +rd_draw_crosshairs(uint8_t *buf, enum wl_shm_format form, int stride, + int width, int height) +{ + int pw; + int x = (width + 1) / 2; + int y = (height + 1) / 2; + struct colorval fg = {.r=0, .g=0, .b=0, .a=0.5}; + struct colorval bg = {.r=0.5, .g=0.5, .b=0.5, .a=0.5}; + + if (!buf) + return; + + populate_color(&fg, form); + populate_color(&bg, form); + pw = fg.used; + + fill_rect(buf + ((y - 1) * stride), &bg, width, 3, stride); + fill_rect(buf + (y * stride), &fg, width, 1, stride); + + fill_rect(buf + ((y - 2) * stride), &bg, width / 3, 5, stride); + fill_rect(buf + ((y - 1) * stride), &fg, width / 3, 3, stride); + fill_rect(buf + ((y - 2) * stride + (width * 2 / 3) * pw), &bg, + width / 3, 5, stride); + fill_rect(buf + ((y - 1) * stride + (width * 2 / 3) * pw), &fg, + width / 3, 3, stride); + + + fill_rect(buf + (x - 1) * pw, &bg, 3, height, stride); + fill_rect(buf + x * pw, &fg, 1, height, stride); + + fill_rect(buf + (x - 2) * pw, &bg, 5, height / 3, stride); + fill_rect(buf + (x - 1) * pw, &fg, 3, height / 3, stride); + + fill_rect(buf + (x - 2) * pw + ((height * 2 / 3) * stride), &bg, + 5, height / 3, stride); + fill_rect(buf + (x - 1) * pw + ((height * 2 / 3) * stride), &fg, + 3, height / 3, stride); +} diff --git a/tools/waycheck/rough_draw.h b/tools/waycheck/rough_draw.h new file mode 100644 index 0000000..18e05b3 --- /dev/null +++ b/tools/waycheck/rough_draw.h @@ -0,0 +1,49 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef ROUGH_DRAW_H +#define ROUGH_DRAW_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> + +#include <wayland-client-protocol.h> + +void +rd_draw_bars(uint8_t *buf, enum wl_shm_format form, int stride, + int width, int height); + +void +rd_draw_crosshairs(uint8_t *buf, enum wl_shm_format form, int stride, + int width, int height); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* ROUGH_DRAW_H */ diff --git a/tools/waycheck/waycheck.c b/tools/waycheck/waycheck.c new file mode 100644 index 0000000..b0420c0 --- /dev/null +++ b/tools/waycheck/waycheck.c @@ -0,0 +1,457 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdbool.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <sys/mman.h> + +#include <linux/input.h> +#include <wayland-client.h> + + +#include "os-compatibility.h" +#include "rough_draw.h" +#include "shared/config-parser.h" +#include "shared/helpers.h" +#include "shared/zalloc.h" +#include "wtst_compfixture.h" +#include "wtst_fixtures.h" +#include "zunitc/zunitc.h" + +#define ARGB32_SIZE 4 + +static const uint32_t PIXEL_FORMAT = WL_SHM_FORMAT_ARGB8888; + +static struct wtst_comp_fxt *g_fxt = NULL; + +static void * +setup_suite_config(const void *data) +{ + struct wtst_comp_fxt *fxt = wtst_comp_fxt_create(); + + ZUC_ASSERTG_NOT_NULL(fxt, out); + +out: + g_fxt = fxt; + return fxt; +} + +static void +cleanup_suite_config(void *data) +{ + struct wtst_comp_fxt *fxt = data; + + g_fxt = NULL; + ZUC_ASSERT_NOT_NULL(fxt); + wtst_comp_fxt_destroy(fxt); +} + +static void * +setup_test_config(void *data) +{ + struct wtst_ctx *ctx = wtst_ctx_create(g_fxt ? g_fxt->sockname : NULL); + ZUC_ASSERTG_NOT_NULL(ctx, out); + +out: + return ctx; +} + +static void +cleanup_test_config(void *data) +{ + struct wtst_ctx *ctx = data; + ZUC_ASSERT_NOT_NULL(ctx); + wtst_ctx_destroy(ctx); +} + +static struct zuc_fixture base_test_f = { + .set_up_test_case = setup_suite_config, + .tear_down_test_case = cleanup_suite_config, + .set_up = setup_test_config, + .tear_down = cleanup_test_config +}; + +/* A very simple case that only exercises the test fixture. */ +ZUC_TEST_F(base_test_f, hello_wayland, data) +{ + ZUC_ASSERT_NOT_NULL(data); +} + +ZUC_TEST_F(base_test_f, advertises_required, data) +{ + int num_required = 0; + int i = 0; + char const *required[] = { + "wl_compositor", + "wl_data_device_manager", + "wl_output", + "wl_seat", + "wl_shell", + "wl_shm", + "wl_subcompositor", + }; + struct wtst_ctx *ctx = data; + + num_required = ARRAY_LENGTH(required); + for (i = 0; i < num_required; i++) { + ZUC_TRACEPOINT("Check %s\n", required[i]); + ZUC_ASSERT_TRUE(wtst_is_global_advertised(ctx, required[i])); + } +} + +typedef void (*button_callback_func_t)(uint32_t serial, uint32_t time, + uint32_t button, uint32_t state); + +struct surface_extras { + button_callback_func_t button_cb; + uint8_t *membuf; + struct wl_buffer *buffer; + struct wl_surface *cursor; + int32_t hit_x; + int32_t hit_y; +}; + +static void +setup_surface_extras(struct wtst_surface *wsurf) +{ + ZUC_ASSERT_NOT_NULL(wsurf); + ZUC_ASSERT_NULL(wsurf->data); + struct surface_extras *extras = zalloc(sizeof(*extras)); + ZUC_ASSERT_NOT_NULL(extras); + wsurf->data = extras; +} + +/** + * @todo refactor to be more generically helpful then move this along with + * the corresponding helper functions to fixtures lib + */ +struct wtst_shm_pool { + struct wl_shm_pool *pool; + + int fd; + uint8_t *membuf; + size_t flen; + size_t used; +}; + +/* Simple testing helpers: */ +static uint8_t * +wtst_shm_pool_get_membuf(struct wtst_shm_pool *pool) +{ + ZUC_ASSERTG_NOT_NULL(pool, err); + return pool->membuf; +err: + return NULL; +} + +static struct wl_shm_pool * +wtst_shm_pool_get_pool(struct wtst_shm_pool *pool) +{ + ZUC_ASSERTG_NOT_NULL(pool, err); + return pool->pool; +err: + return NULL; +} + +static void +wtst_shm_pool_consume(struct wtst_shm_pool *pool, size_t mem_used) +{ + ZUC_ASSERT_NOT_NULL(pool); + pool->used += mem_used; +} + +static struct wtst_shm_pool * +wtst_create_mempool(struct wl_shm *shm, int fd, size_t flen) +{ + struct wtst_shm_pool *wtst_pool = zalloc(sizeof(struct wtst_shm_pool)); + ZUC_ASSERTG_NOT_NULL(wtst_pool, err); + + wtst_pool->fd = fd; + wtst_pool->flen = flen; + wtst_pool->membuf = mmap(NULL, flen, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + ZUC_ASSERTG_TRUE(wtst_pool->membuf != MAP_FAILED, err_free); + + wtst_pool->pool = wl_shm_create_pool(shm, fd, flen); + ZUC_ASSERTG_NOT_NULL(wtst_pool->pool, err_unmap); + + return wtst_pool; +err_unmap: + munmap(wtst_pool->membuf, flen); +err_free: + free(wtst_pool); +err: + return NULL; +} + +static void +setup_cursor(struct wtst_shm_pool *pool, + unsigned int width, unsigned int height, + int32_t hit_x, int32_t hit_y, + struct wl_compositor *compositor, + struct surface_extras *extras) +{ + int32_t stride = width * ARGB32_SIZE; + ZUC_ASSERT_NOT_NULL(extras); + extras->hit_x = hit_x; + extras->hit_y = hit_y; + + extras->cursor = wl_compositor_create_surface(compositor); + ZUC_ASSERT_NOT_NULL(extras->cursor); + + extras->membuf = pool->membuf; + extras->membuf += pool->used / sizeof(*extras->membuf); + rd_draw_crosshairs(extras->membuf, WL_SHM_FORMAT_ARGB8888, + width * ARGB32_SIZE, + width, height); + + extras->buffer = wl_shm_pool_create_buffer(pool->pool, + pool->used, + width, + height, + stride, + PIXEL_FORMAT); + pool->used += width * height * ARGB32_SIZE; + ZUC_ASSERT_NOT_NULL(extras->buffer); +} + +static void +set_button_cb(struct wtst_shell_surface *shell_surface, + button_callback_func_t callback) +{ + struct wtst_surface *wsurf = NULL; + ZUC_ASSERT_NOT_NULL(shell_surface); + ZUC_ASSERT_NOT_NULL(shell_surface->surface); + ZUC_ASSERT_NOT_NULL(shell_surface->surface->wl_surface); + + wsurf = shell_surface->surface; + ZUC_ASSERT_NOT_NULL(wsurf); + ZUC_ASSERT_NOT_NULL(wsurf->data); + + ((struct surface_extras *)wsurf->data)->button_cb = callback; +} + +static bool keep_alive = true; + +static void +handle_button(uint32_t serial, uint32_t time, + uint32_t button, uint32_t state) +{ + keep_alive = false; +} + +static void +example_test_enter(void *data, + struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *surface, + wl_fixed_t surface_x, wl_fixed_t surface_y) +{ + struct wtst_pointer *pointer = data; + if (pointer && pointer->focus && pointer->focus->data) { + struct surface_extras *extras = pointer->focus->data; + wl_surface_attach(extras->cursor, extras->buffer, 0, 0); + wl_surface_commit(extras->cursor); + wl_pointer_set_cursor(pointer->wl_pointer, serial, + extras->cursor, + extras->hit_x, extras->hit_y); + } +} + +static void +example_test_leave(void *data, + struct wl_pointer *wl_pointer, uint32_t serial, + struct wl_surface *wl_surface) +{ +} + +static void +example_test_motion(void *data, + struct wl_pointer *wl_pointer, uint32_t time, + wl_fixed_t surface_x, wl_fixed_t surface_y) +{ +} + +static void +example_test_button(void *data, + struct wl_pointer *wl_pointer, uint32_t serial, + uint32_t time, uint32_t button, uint32_t state) +{ + struct wtst_pointer *pointer = data; + if (pointer && pointer->focus && pointer->focus->data) { + struct surface_extras *extras = pointer->focus->data; + if (extras->button_cb) + extras->button_cb(serial, time, button, state); + } +} + +static void +example_test_axis(void *data, + struct wl_pointer *wl_pointer, uint32_t time, + uint32_t axis, wl_fixed_t value) +{ +} + +static const struct wl_pointer_listener pointer_listener = { + .enter = example_test_enter, + .leave = example_test_leave, + .motion = example_test_motion, + .button = example_test_button, + .axis = example_test_axis +}; + +ZUC_TEST_F(base_test_f, simple_setup, data) +{ + struct wtst_ctx *ctx = data; + size_t needed = 0; + int anon = 0; + const int surf_width = 355; + const int surf_height = 200; + const int cursor_size = 45; + const int mid_hit = cursor_size / 2; + struct wtst_shm_pool *pool = NULL; + struct wl_buffer *buffer = NULL; + struct wtst_shell_surface *sh_surface = NULL; + + keep_alive = false; /* Don't run the main loop for normal testing */ + + wtst_set_dump_pointer_events(WTST_DBG_POINTER_ALL + & ~WTST_DBG_POINTER_MOTION); + + needed = ARGB32_SIZE * surf_width * surf_height; + needed += ARGB32_SIZE * cursor_size * cursor_size; + + anon = os_create_anonymous_file(needed); + buffer = NULL; + pool = wtst_create_mempool(ctx->shm, anon, needed); + if (!pool) { + close(anon); + ZUC_ASSERT_NOT_NULL(pool); + } else { + struct wl_shm_pool *shpool = NULL; + uint8_t *mem = wtst_shm_pool_get_membuf(pool); + rd_draw_bars(mem, WL_SHM_FORMAT_ARGB8888, + surf_width * ARGB32_SIZE, + surf_width, surf_height); + + shpool = wtst_shm_pool_get_pool(pool); + buffer = wl_shm_pool_create_buffer(shpool, + 0, + surf_width, + surf_height, + surf_width * ARGB32_SIZE, + PIXEL_FORMAT); + wtst_shm_pool_consume(pool, + surf_width * surf_height * ARGB32_SIZE); + ZUC_ASSERT_NOT_NULL(buffer); + } + + sh_surface = wtst_create_shell_surface(ctx->compositor, ctx->shell); + setup_surface_extras(sh_surface->surface); + + wl_surface_attach(sh_surface->surface->wl_surface, buffer, 0, 0); + wl_surface_commit(sh_surface->surface->wl_surface); + + setup_cursor(pool, cursor_size, cursor_size, mid_hit, mid_hit, + ctx->compositor, sh_surface->surface->data); + + set_button_cb(sh_surface, handle_button); + wtst_pointer_add_listener(ctx->input->pointer, + &pointer_listener, ctx->input->pointer); + + while (keep_alive) { + if (wl_display_dispatch(ctx->display) < 0) + keep_alive = false; + } + + wtst_destroy_shell_surface(sh_surface); + wl_buffer_destroy(buffer); +} + +/** + * Set up environment and fixture data for launching weston. + */ +static void +prep_for_weston(int argc, char* argv[]) +{ + char *path = NULL; + char *params[] = { + "--backend=headless-backend.so", + NULL + }; + + wtst_comp_fxt_set_environment("weston", + argc > 0 ? argv[0] : NULL, + getenv("PATH")); + + /* Certain Weston internal code uses this variable */ + path = wtst_comp_fxt_get_absbase(); + setenv("WESTON_BUILD_DIR", path, 1); + free(path); + + wtst_comp_fxt_set_params("waycheck", "--log", "--socket", params); +} + +/* + * Since we need to hook argv[0] to check on runtime path, we can not use + * the stock zunitc main() lib. + * This is copied from that with the needed addition of grabbing the name of + * the executable. + */ +int +main(int argc, char* argv[]) +{ + bool helped = false; + int rc = zuc_initialize(&argc, argv, &helped); + + /* TODO add command-line param to allow overriding the name. */ + /* This next line is the only addition to the stock zunitc main. */ + prep_for_weston(argc, argv); + + if ((rc == EXIT_SUCCESS) && !helped) { + /* Stop if any unrecognized parameters were encountered. */ + if (argc > 1) { + printf("%s: unrecognized option '%s'\n", + argv[0], argv[1]); + printf("Try '%s --help' for more information.\n", + argv[0]); + rc = EXIT_FAILURE; + } else { + rc = ZUC_RUN_TESTS(); + } + } + + zuc_cleanup(); + return rc; +} diff --git a/tools/waycheck/waycheck.dox b/tools/waycheck/waycheck.dox new file mode 100644 index 0000000..66ae65b --- /dev/null +++ b/tools/waycheck/waycheck.dox @@ -0,0 +1,29 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** +@page waycheck + +A simple integration/acceptance tool to exercise Wayland compositors. + +Tests use @ref zunitc for their infrastructure, and most include use of a common wtst_ctx test fixture. +*/ \ No newline at end of file diff --git a/tools/wayland_fixtures/inc/wtst_compfixture.h b/tools/wayland_fixtures/inc/wtst_compfixture.h new file mode 100644 index 0000000..f8c9868 --- /dev/null +++ b/tools/wayland_fixtures/inc/wtst_compfixture.h @@ -0,0 +1,76 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WTST_COMP_FIXTURE_H +#define WTST_COMP_FIXTURE_H + +/** + * @file + * Simple fixture to ensure a proper compositor is running. + */ + + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Simple fixture to launch the Wayland compositor to test, defaulting to + * weston. + */ +struct wtst_comp_fxt { + /** + * Name of wayland socket used for this instance. + * Note that this may be NULL if the defaults are to be used. + */ + const char *sockname; + + /** Context for internal implementation details. */ + struct wtst_comp_fxt_private *private; +}; + +struct wtst_comp_fxt * +wtst_comp_fxt_create(void); + +void +wtst_comp_fxt_destroy(struct wtst_comp_fxt *fxt); + +void +wtst_comp_fxt_set_environment(const char *compname, const char *argv0, + const char *path); + +char * +wtst_comp_fxt_get_absbase(void); + +void +wtst_comp_fxt_set_params(const char *log_name, const char *log_param, + const char *sock_param, char **params); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* WTST_COMP_FIXTURE_H */ diff --git a/tools/wayland_fixtures/inc/wtst_fixtures.h b/tools/wayland_fixtures/inc/wtst_fixtures.h new file mode 100644 index 0000000..64f94c1 --- /dev/null +++ b/tools/wayland_fixtures/inc/wtst_fixtures.h @@ -0,0 +1,268 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WTST_FIXTURES_H +#define WTST_FIXTURES_H + +/** + * @file + * Common helpers for Wayland tests. + */ + +#include <stdint.h> +#include <stdbool.h> + +#include <wayland-client.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Test fixture that holds the basics for simple Wayland client testing. + * + * @see wtst_ctx_create() + * @see wtst_ctx_destroy() + */ +struct wtst_ctx +{ + /** The display the client is connected to. */ + struct wl_display *display; + + /** The registry the client is connected to. */ + struct wl_registry *registry; + + /** Main compositor as advertised and updated by the registry. */ + struct wl_compositor *compositor; + + /** + * Main shared memory object as advertised and updated by the + * registry. + */ + struct wl_shm *shm; + + /** The seat that is actually used for input events. */ + struct wtst_input *input; + + /** + * Server can have more wl_seats. We need keep them all until we + * find the one that we need. After that, the others + * will be destroyed, so this list will have the length of 1. + * If some day in the future we will need the other seats, + * we can just keep them here. + */ + struct wl_list inputs; + + /** + * The output used during testing. + */ + struct wtst_output *output; + + /** + * The surface used during testing. + */ + struct wtst_surface *surface; + + /** + * Flag indicating the presence of DRM. + */ + bool has_wl_drm; + + /** Main shell as advertised and updated by the registry. */ + struct wl_shell *shell; + + /** Listener registered with the registry. */ + struct wl_registry_listener *reg_listener; + + /** Context for internal implementation details. */ + struct wtst_ctx_private *private; +}; + +/** + * Structure for tracking Wayland registry globals. + */ +struct wtst_global { + uint32_t name; /**< name of the global object. */ + char *interface; /**< interface the global object implements. */ + uint32_t version; /**< given version of the interface + implemented by the the global object. */ + struct wl_list link; /**< list implementation hook. */ +}; + +/** + * Structure for tracking wl_seat instances for input. + */ +struct wtst_input { + struct wl_seat *wl_seat; /**< The target Wayland seat. */ + struct wtst_pointer *pointer; /**< Pointer for the seat. */ + struct wtst_keyboard *keyboard; /**< Keyboard for the seat. */ + struct wtst_touch *touch; /**< Touch device for the seat. */ + uint32_t seat_version; /**< The version of the seat. */ + char *seat_name; /**< Name of the seat. */ + enum wl_seat_capability caps; /**< Base seat capabilities. */ + struct wl_list link; /**< list implementation hook. */ +}; + +/** + * Structure for tracking Wayland pointers. + */ +struct wtst_pointer { + struct wl_pointer *wl_pointer; /**< The target Wayland pointer. */ + struct wtst_surface *focus; /**< Tracked focused surface. */ + int x; /**< Tracked x coord on the surface + 'focus'. */ + int y; /**< Tracked y coord on the surface + 'focus'. */ + uint32_t button; + uint32_t state; + struct wtst_pointer_private *private; /**< private instance data. */ +}; + +/** + * Structure for tracking Wayland keyboards. + */ +struct wtst_keyboard { + struct wl_keyboard *wl_keyboard; + struct wtst_surface *focus; + uint32_t key; + uint32_t state; + uint32_t mods_depressed; + uint32_t mods_latched; + uint32_t mods_locked; + uint32_t group; + struct { + int rate; + int delay; + } repeat_info; +}; + +/** + * Structure for tracking Wayland touch instances. + */ +struct wtst_touch { + struct wl_touch *wl_touch; /**< The target Wayland touch. */ + int down_x; /**< Tracked down x position. */ + int down_y; /**< Tracked down y position. */ + int x; /**< Tracked x position. */ + int y; /**< Tracked y potision. */ + int id; /**< Tracked ID. */ + int up_id; /**< Id of last wl_touch.up event */ + int frame_no; /**< Count of frame events. */ + int cancel_no; /**< Count of cancel events. */ +}; + +/** + * Structure for tracking output instances. + */ +struct wtst_output { + struct wl_output *wl_output; /**< The target Wayland output. */ + int x; /**< Tracked x. */ + int y; /**< Tracked y. */ + int width; /**< Tracked width. */ + int height; /**< Tracked height. */ + int scale; /**< Tracked scale. */ + bool initialized; /**< Tracked initialized flag. */ +}; + +/** + * Strcuture for tracking Wayland surfaces. + */ +struct wtst_surface { + struct wl_surface *wl_surface; /**< The target Wayland surface. */ + struct wl_buffer *wl_buffer; /**< Buffer created for working with + this surface. */ + struct wtst_output *output; /**< Tracked output for this surface. */ + int x; /**< Tracked x. */ + int y; /**< Tracked y. */ + int width; /**< Tracked width. */ + int height; /**< Tracked height. */ + void *data; /**< private instance data. */ +}; + +/** + * Structure for tracking Wayland shell surfaces. + */ +struct wtst_shell_surface { + struct wl_shell_surface *wl_shell_surface; /**< The target Wayland + shell surface. */ + struct wtst_surface *surface; /**< surface that this is wrapping. */ +}; + +/** + * Enum to control what debugging information is dumped during tests. + */ +enum wtst_dbgevents_pointer { + WTST_DBG_POINTER_NONE = 0x00, + WTST_DBG_POINTER_ENTER = 0x01, + WTST_DBG_POINTER_LEAVE = 0x02, + WTST_DBG_POINTER_MOTION = 0x04, + WTST_DBG_POINTER_BUTTON = 0x08, + WTST_DBG_POINTER_AXIS = 0x10, + WTST_DBG_POINTER_ALL = 0x1f +}; + +struct wtst_ctx * +wtst_ctx_create(const char *display); + +struct wtst_ctx * +wtst_ctx_create_with_surface(const char *display, int width, int height); + +struct wtst_ctx * +wtst_ctx_create_chained(const char *display, + struct wl_registry_listener *reg_listener, + void *reg_data); + +struct wtst_ctx * +wtst_ctx_create_with_surface_chained(const char *display, + int width, int height, + struct wl_registry_listener *reg_listener, + void *reg_data); + +void +wtst_ctx_destroy(struct wtst_ctx *ctx); + +int +wtst_is_global_advertised(struct wtst_ctx *ctx, char const *interface); + +int +wtst_pointer_add_listener(struct wtst_pointer *pointer, + const struct wl_pointer_listener *listener, + void *data); + +void +wtst_set_dump_pointer_events(enum wtst_dbgevents_pointer mask); + +struct wtst_shell_surface * +wtst_create_shell_surface(struct wl_compositor *compositor, + struct wl_shell *shell); + +void +wtst_destroy_shell_surface(struct wtst_shell_surface *wss); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* WTST_FIXTURES_H */ diff --git a/tools/wayland_fixtures/src/wtst_compfixture.c b/tools/wayland_fixtures/src/wtst_compfixture.c new file mode 100644 index 0000000..02f80b0 --- /dev/null +++ b/tools/wayland_fixtures/src/wtst_compfixture.c @@ -0,0 +1,411 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include "wtst_compfixture.h" + +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> +#include <time.h> +#include <unistd.h> + +#include "shared/zalloc.h" +#include "zunitc/zunitc.h" + +#define MAX_ARGS 256 + +#define MAX_SOCK_PARAM_LEN 36 + +#define NS_PER_MS 1000000 +#define WAIT_DURATION_MS 2000 + +static char g_compname[NAME_MAX] = {0}; +static char g_basepath[PATH_MAX] = {0}; +static char *g_abs_basepath = NULL; +static char *g_pathenv = NULL; +static char *g_sock_param = NULL; +static char *g_log_param = NULL; +static char *g_log_name = NULL; +static char **g_extra_params = NULL; + +struct wtst_comp_fxt_private { + pid_t pid; + char sockname[MAX_SOCK_PARAM_LEN]; +}; + +static struct wtst_comp_fxt_private * +wtst_comp_fxt_private_create(void) +{ + struct wtst_comp_fxt_private *private = zalloc(sizeof(*private)); + + ZUC_ASSERTG_NOT_NULL(private, out); + private->pid = -1; + +out: + return private; +} + +static void +wtst_comp_fxt_private_destroy(struct wtst_comp_fxt_private *private) +{ + if (!private) + return; + + if (private->pid != -1) + kill(private->pid, SIGTERM); + + free(private); +} + +/** + * Helper function to create a common name based on the passed in pid. + * + * @param str the output buffer to write the string to. + * @param strlen the size of the output buffer. + * @param id the pid to base the name on. + */ +static void +populate_sockname(char *str, size_t strlen, pid_t id) +{ + snprintf(str, strlen, "WAYLANDTEST_%u", (unsigned int)id); +} + +static void +create_logdir(void) +{ + char dir[PATH_MAX] = {0}; + snprintf(dir, sizeof(dir), "%s/logs", g_abs_basepath); + mkdir(dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); +} + +/** + * Helper to wait for a local socket with a given name to become available. + * + * @param sockname the socket name to connect to. + */ +static void +wait_for_local_socket(const char *sockname) +{ + socklen_t size = 0; + int name_size = 0; + int fd = -1; + int rc = 0; + int code = 0; + struct timespec ts = {.tv_nsec = 1 * NS_PER_MS}; + struct sockaddr_un addr = {.sun_family = AF_LOCAL}; + const char *runtime_dir = getenv("XDG_RUNTIME_DIR"); + + if (!runtime_dir) { + return; + } + name_size = snprintf(addr.sun_path, sizeof(addr.sun_path), + "%s/%s", runtime_dir, sockname) + 1; + if (name_size > (int)sizeof(addr.sun_path)) + return; + size = offsetof(struct sockaddr_un, sun_path) + name_size; + + fd = socket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + int flags = 0; + if (errno != EINVAL) + return; + fd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (fd < 0) + return; + flags = fcntl(fd, F_GETFD); + if (flags == -1) + goto exit; + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + goto exit; + } + + rc = 0; + do { + if (rc == -1) { + nanosleep(&ts, NULL); + ts.tv_nsec *= 2; + } + errno = 0; + rc = connect(fd, (struct sockaddr *)&addr, size); + code = errno; + } while ((rc == -1) + && (ts.tv_nsec < WAIT_DURATION_MS * NS_PER_MS) + && ((code == ENOENT) || (code == EAGAIN) || (code == EINTR))); + + exit: + close(fd); +} + +/** + * Creates an instance of the compositor-launch test fixture. + * + * @return a pointer to a test fixture upon success, NULL otherwise. + * @see wtst_comp_fxt_destroy() + */ +struct wtst_comp_fxt * +wtst_comp_fxt_create(void) +{ + pid_t pid = -1; + + struct wtst_comp_fxt *fxt = zalloc(sizeof(*fxt)); + ZUC_ASSERTG_NOT_NULL(fxt, err); + + fxt->private = wtst_comp_fxt_private_create(); + ZUC_ASSERTG_NOT_NULL(fxt->private, err_free); + fxt->sockname = NULL; + + create_logdir(); + + fflush(NULL); /* important. avoid duplication of output */ + pid = fork(); + switch (pid) { + case -1: /* Error forking */ + printf("%s:%d: error: Problem with fork: %d\n", + __FILE__, __LINE__, errno); + ZUC_MARK_FATAL("Error forking"); + ZUC_ASSERTG_NE(-1, pid, err_free); + break; + case 0: { /* child */ + int rc = EXIT_SUCCESS; + char **curr = NULL; + int argc = 0; + char *argv[MAX_ARGS] = {0}; + pid_t id = getpid(); + size_t offset = 0; + int logfd = -1; + char progname[PATH_MAX] = {0}; + char logname[PATH_MAX] = {0}; + char sockparam[MAX_SOCK_PARAM_LEN] = {0}; + char logparam[PATH_MAX] = {0}; + sigset_t allsigs; + + strcpy(sockparam, g_sock_param ? g_sock_param : "--socket"); + strcat(sockparam, "="); + offset = strlen(sockparam); + populate_sockname(sockparam + offset, + sizeof(sockparam) - offset, + id); + + if (g_basepath[0]) + snprintf(progname, sizeof(progname), "%s/%s", + g_basepath, g_compname); + else + snprintf(progname, sizeof(progname), "%s", g_compname); + + /* + * Note: the following snprintf's can have (int)id added + * with %d to differentiate runs from multiple simultaneous + * instances. + */ + + snprintf(logname, sizeof(logname), "%s/logs/%s-serverlog.txt", + g_abs_basepath, + g_log_name ? g_log_name : "test"); + unlink(logname); + + snprintf(logparam, sizeof(logparam), + "%s=%s/logs/%s-serverlog.txt", + g_log_param ? g_log_param : "--log", g_abs_basepath, + g_log_name ? g_log_name : "test"); + + /* Redirect stdout and stderr to a file */ + snprintf(logname, sizeof(logname), "%s/logs/%s-log.txt", + g_abs_basepath, + g_log_name ? g_log_name : "test"); + logfd = open(logname, O_RDWR | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); + dup2(logfd, fileno(stdout)); + dup2(logfd, fileno(stderr)); + fflush(stdout); + fflush(stderr); + close(logfd); + logfd = -1; + + argv[argc++] = progname; + argv[argc++] = sockparam; + argv[argc++] = logparam; + if (g_extra_params) + for (curr = g_extra_params; + *curr && (argc < MAX_ARGS); ++curr) + argv[argc++] = *curr; + argv[argc] = NULL; + + sigfillset(&allsigs); + sigprocmask(SIG_UNBLOCK, &allsigs, NULL); + + rc = execv(progname, argv); + + zuc_cleanup(); + exit(rc); + break; + } + default: { /* parent */ + fxt->private->pid = pid; + populate_sockname(fxt->private->sockname, + sizeof(fxt->private->sockname), pid); + fxt->sockname = fxt->private->sockname; + wait_for_local_socket(fxt->sockname); + } + } + + return fxt; + +err_free: + wtst_comp_fxt_destroy(fxt); +err: + return NULL; +} + +/** + * Destroys a test fixture. + * + * @param fxt pointer to a test fixture to destroy. + * It should have previously been created via wtst_comp_fxt_create(). + * @see wtst_comp_fxt_create() + */ +void +wtst_comp_fxt_destroy(struct wtst_comp_fxt *fxt) +{ + if (!fxt) + return; + + wtst_comp_fxt_private_destroy(fxt->private); + free(fxt); +} + +/** + * Set the environment to be used for determining behavior. + * This includes storing values in global variables for later use by the + * constructor. + * + * @param compname the name of the compositor to launch. + * @param argv0 the first arg passsed to the program, normally containing + * the name used to execute the program, or NULL if no args are passed to + * main(). + * @param path the PATH environment in use at the time the program is + * launched. + */ +void +wtst_comp_fxt_set_environment(const char *compname, + const char *argv0, + const char *path) +{ + char tmp[PATH_MAX] = {0}; + struct stat st = {.st_size = 0}; + + strncpy(g_compname, compname ? compname : "weston", sizeof(g_compname)); + g_basepath[0] = 0; + free(g_pathenv); + g_pathenv = NULL; + if (path) + g_pathenv = strdup(path); + + if (argv0) { + char *ptr = strrchr(argv0, '/'); + + if (ptr) { + size_t len = ptr - argv0; + + strncpy(tmp, argv0, len); + strcat(tmp, "/"); + strcat(tmp, g_compname); + if (!stat(tmp, &st)) + strncpy(g_basepath, argv0, len); + } + } + + if (g_pathenv && !g_basepath[0]) { + char *save = NULL; + char *ptr = strdup(g_pathenv); + char *val = strtok_r(ptr, ":", &save); + while (val && !g_basepath[0]) { + snprintf(tmp, sizeof(tmp), "%s/%s", val, g_compname); + if (!stat(tmp, &st)) + strncpy(g_basepath, val, sizeof(g_basepath)); + else + val = strtok_r(NULL, ":", &save); + } + free(ptr); + } + + g_abs_basepath = realpath(g_basepath[0] ? g_basepath : ".", NULL); +} + +/** + * Returns a copy of the current base path in absolute form. + */ +char * +wtst_comp_fxt_get_absbase(void) +{ + return g_abs_basepath ? strdup(g_abs_basepath) : realpath(".", NULL); +} + +/** + * Sets parameters to use for launching the compositor. + * @param sock_param parameter string to use for setting the socket. + * @param log_param parameter string to use for setting the log. + * @param params additional parameters to include, or NULL for none. + */ +void +wtst_comp_fxt_set_params(const char *log_name, const char *log_param, + const char *sock_param, char **params) +{ + size_t i = 0; + size_t count = 0; + char **curr = NULL; + + free(g_sock_param); + free(g_log_param); + free(g_log_name); + g_sock_param = sock_param ? strdup(sock_param) : NULL; + g_log_param = log_param ? strdup(log_param) : NULL; + g_log_name = log_name ? strdup(log_name) : NULL; + + + if (g_extra_params) { + for (curr = g_extra_params; *curr; ++curr) { + free(*curr); + } + free(g_extra_params); + g_extra_params = NULL; + } + if (params) { + for (curr = params; *curr; ++curr) + count++; + g_extra_params = calloc(count + 1, sizeof(char*)); + for (i = 0; i < count; ++i) + g_extra_params[i] = strdup(params[i]); + } +} diff --git a/tools/wayland_fixtures/src/wtst_fixtures.c b/tools/wayland_fixtures/src/wtst_fixtures.c new file mode 100644 index 0000000..91225d5 --- /dev/null +++ b/tools/wayland_fixtures/src/wtst_fixtures.c @@ -0,0 +1,1238 @@ +/* + * Copyright © 2015 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include "wtst_fixtures.h" + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <unistd.h> + +#include "shared/helpers.h" +#include "shared/os-compatibility.h" +#include "shared/zalloc.h" +#include "zunitc/zunitc.h" + +/** + * Private data for implementation of test fixture context. + */ +struct wtst_ctx_private { + + /** List of all known global advertised by the registry. */ + struct wl_list global_list; + + /** List of formats supported by the wl_shm. */ + struct wl_list format_list; + + /** Chained registry listener to invoke. */ + struct wl_registry_listener *chained; + + /** Data to pass back to chained registry listener. */ + void *chained_data; + + /** Current main compositor name advertised by the registry. */ + uint32_t wl_compositor_name; + + /** Current main compositor version advertised by the registry. */ + uint32_t wl_compositor_version; + + /** Current shared memory object name advertised by the registry. */ + uint32_t wl_shm_name; + + /** Current shared memory object version advertised by the registry. */ + uint32_t wl_shm_version; + + /** Current wl_shell/xdg_shell name advertised by the registry. */ + uint32_t wl_shell_name; + + /** Current wl_shell/xdg_shell version advertised by the registry. */ + uint32_t wl_shell_version; +}; + +static void +input_update_devices(struct wtst_input *input); + +static enum wtst_dbgevents_pointer g_pointer_dbg_mask = WTST_DBG_POINTER_ALL; + +/** + * Sets what events will be dumped during testing. + * + * @param mask a mask of which events to dump out. + */ +void +wtst_set_dump_pointer_events(enum wtst_dbgevents_pointer mask) +{ + g_pointer_dbg_mask = mask; +} + +static void +input_destroy(struct wtst_input *inp) +{ + wl_list_remove(&inp->link); + if (inp->seat_version >= WL_SEAT_RELEASE_SINCE_VERSION) + wl_seat_release(inp->wl_seat); + else + wl_seat_destroy(inp->wl_seat); + free(inp); +} + +/** + * Private data for implementation of pointer tracking. + */ +struct +wtst_pointer_private +{ + /** opaque data. */ + void *data; + + /** connected listener. */ + const struct wl_pointer_listener *listener; + + /** list implementation hook. */ + struct wl_list link; +}; + +static void +output_handle_geometry(void *data, + struct wl_output *wl_output, + int x, int y, + int physical_width, int physical_height, + int subpixel, + const char *make, const char *model, + int32_t transform) +{ + struct wtst_output *output = data; + + output->x = x; + output->y = y; +} + +static void +output_handle_mode(void *data, + struct wl_output *wl_output, + uint32_t flags, + int width, int height, + int refresh) +{ + struct wtst_output *output = data; + + if (flags & WL_OUTPUT_MODE_CURRENT) { + output->width = width; + output->height = height; + } +} + +static void +output_handle_scale(void *data, + struct wl_output *wl_output, + int scale) +{ + struct wtst_output *output = data; + + output->scale = scale; +} + +static void +output_handle_done(void *data, + struct wl_output *wl_output) +{ + struct wtst_output *output = data; + + output->initialized = true; +} + +static void +seat_handle_capabilities(void *data, struct wl_seat *seat, + enum wl_seat_capability caps) +{ + struct wtst_input *input = data; + + input->caps = caps; + + /* we will create/update the devices only with the right (test) seat. + * If we haven't discovered which seat is the test seat, just + * store capabilities and bail out */ + if (input->seat_name && strcmp(input->seat_name, "test-seat") == 0) + input_update_devices(input); + + fprintf(stderr, "test-client: got seat %p capabilities: %x\n", + input, caps); +} + +static void +seat_handle_name(void *data, struct wl_seat *seat, const char *name) +{ + struct wtst_input *input = data; + + input->seat_name = strdup(name); + ZUC_ASSERT_NOT_NULL(input->seat_name); + + fprintf(stderr, "test-client: got seat %p name: \'%s\'\n", + input, name); +} + +static void +shell_surface_ping(void *data, + struct wl_shell_surface *shell_surface, + uint32_t serial) +{ + wl_shell_surface_pong(shell_surface, serial); +} + +static void +shell_surface_configure(void *data, + struct wl_shell_surface *shell_surface, + uint32_t edges, + int32_t width, int32_t height) +{ +} + +static void +shell_surface_popup_done(void *data, struct wl_shell_surface *shell_surface) +{ +} + + +static void +pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *wl_surface, + wl_fixed_t x, wl_fixed_t y) +{ + struct wtst_pointer *pointer = data; + struct wtst_pointer_private *entry = NULL; + struct wtst_pointer_private *tmp = NULL; + + pointer->focus = wl_surface_get_user_data(wl_surface); + pointer->x = wl_fixed_to_int(x); + pointer->y = wl_fixed_to_int(y); + + if (g_pointer_dbg_mask & WTST_DBG_POINTER_ENTER) + fprintf(stderr, "test-client: " + "got pointer enter %d %d, surface %p\n", + pointer->x, pointer->y, pointer->focus); + + wl_list_for_each_safe(entry, tmp, &pointer->private->link, link) + if (entry->listener && entry->listener->enter) + entry->listener->enter(entry->data, wl_pointer, + serial, wl_surface, x, y); +} + +static void +pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, struct wl_surface *wl_surface) +{ + struct wtst_pointer *pointer = data; + struct wtst_pointer_private *entry = NULL; + struct wtst_pointer_private *tmp = NULL; + + pointer->focus = NULL; + + if (g_pointer_dbg_mask & WTST_DBG_POINTER_LEAVE) + fprintf(stderr, "test-client: " + "got pointer leave, surface %p\n", + wl_surface_get_user_data(wl_surface)); + + wl_list_for_each_safe(entry, tmp, &pointer->private->link, link) + if (entry->listener && entry->listener->leave) + entry->listener->leave(entry->data, wl_pointer, + serial, wl_surface); +} + +static void +pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, + uint32_t time, wl_fixed_t x, wl_fixed_t y) +{ + struct wtst_pointer *pointer = data; + struct wtst_pointer_private *entry = NULL; + struct wtst_pointer_private *tmp = NULL; + + pointer->x = wl_fixed_to_int(x); + pointer->y = wl_fixed_to_int(y); + + if (g_pointer_dbg_mask & WTST_DBG_POINTER_MOTION) + fprintf(stderr, "test-client: " + "got pointer motion %d %d\n", + pointer->x, pointer->y); + + wl_list_for_each_safe(entry, tmp, &pointer->private->link, link) + if (entry->listener && entry->listener->motion) + entry->listener->motion(entry->data, wl_pointer, + time, x, y); +} + +static void +pointer_handle_button(void *data, struct wl_pointer *wl_pointer, + uint32_t serial, uint32_t time, + uint32_t button, uint32_t state) +{ + struct wtst_pointer *pointer = data; + struct wtst_pointer_private *entry = NULL; + struct wtst_pointer_private *tmp = NULL; + + pointer->button = button; + pointer->state = state; + + if (g_pointer_dbg_mask & WTST_DBG_POINTER_BUTTON) + fprintf(stderr, "test-client: " + "got pointer button %u %u\n", + button, state); + + wl_list_for_each_safe(entry, tmp, &pointer->private->link, link) + if (entry->listener && entry->listener->button) + entry->listener->button(entry->data, wl_pointer, + serial, time, button, state); +} + +static void +pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, + uint32_t time, uint32_t axis, wl_fixed_t value) +{ + struct wtst_pointer *pointer = data; + struct wtst_pointer_private *entry = NULL; + struct wtst_pointer_private *tmp = NULL; + + fprintf(stderr, "test-client: got pointer axis %u %f\n", + axis, wl_fixed_to_double(value)); + + wl_list_for_each_safe(entry, tmp, &pointer->private->link, link) + if (entry->listener && entry->listener->axis) + entry->listener->axis(entry->data, wl_pointer, + time, axis, value); +} + +static void +keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int fd, uint32_t size) +{ + close(fd); + + fprintf(stderr, "test-client: got keyboard keymap\n"); +} + +static void +keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, + struct wl_surface *wl_surface, + struct wl_array *keys) +{ + struct wtst_keyboard *keyboard = data; + + keyboard->focus = wl_surface_get_user_data(wl_surface); + + fprintf(stderr, "test-client: got keyboard enter, surface %p\n", + keyboard->focus); +} + +static void +keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, + struct wl_surface *wl_surface) +{ + struct wtst_keyboard *keyboard = data; + + keyboard->focus = NULL; + + fprintf(stderr, "test-client: got keyboard leave, surface %p\n", + wl_surface_get_user_data(wl_surface)); +} + +static void +keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t time, uint32_t key, + uint32_t state) +{ + struct wtst_keyboard *keyboard = data; + + keyboard->key = key; + keyboard->state = state; + + fprintf(stderr, "test-client: got keyboard key %u %u\n", key, state); +} + +static void +keyboard_handle_modifiers(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, + uint32_t group) +{ + struct wtst_keyboard *keyboard = data; + + keyboard->mods_depressed = mods_depressed; + keyboard->mods_latched = mods_latched; + keyboard->mods_locked = mods_locked; + keyboard->group = group; + + fprintf(stderr, "test-client: got keyboard modifiers %u %u %u %u\n", + mods_depressed, mods_latched, mods_locked, group); +} + +static void +keyboard_handle_repeat_info(void *data, + struct wl_keyboard *wl_keyboard, + int32_t rate, int32_t delay) +{ + struct wtst_keyboard *keyboard = data; + + keyboard->repeat_info.rate = rate; + keyboard->repeat_info.delay = delay; + + fprintf(stderr, "test-client: got keyboard repeat_info %d %d\n", + rate, delay); +} + +static void +touch_handle_down(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, + struct wl_surface *surface, + int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) +{ + struct wtst_touch *touch = data; + + touch->down_x = wl_fixed_to_int(x_w); + touch->down_y = wl_fixed_to_int(y_w); + touch->id = id; + + fprintf(stderr, "test-client: got touch down %d %d, surf: %p, id: %d\n", + touch->down_x, touch->down_y, surface, id); +} + +static void +touch_handle_up(void *data, struct wl_touch *wl_touch, + uint32_t serial, uint32_t time, int32_t id) +{ + struct wtst_touch *touch = data; + touch->up_id = id; + + fprintf(stderr, "test-client: got touch up, id: %d\n", id); +} + +static void +touch_handle_motion(void *data, struct wl_touch *wl_touch, + uint32_t time, int32_t id, + wl_fixed_t x_w, wl_fixed_t y_w) +{ + struct wtst_touch *touch = data; + touch->x = wl_fixed_to_int(x_w); + touch->y = wl_fixed_to_int(y_w); + + fprintf(stderr, "test-client: got touch motion, %d %d, id: %d\n", + touch->x, touch->y, id); +} + +static void +touch_handle_frame(void *data, struct wl_touch *wl_touch) +{ + struct wtst_touch *touch = data; + + ++touch->frame_no; + + fprintf(stderr, "test-client: got touch frame (%d)\n", touch->frame_no); +} + +static void +touch_handle_cancel(void *data, struct wl_touch *wl_touch) +{ + struct wtst_touch *touch = data; + + ++touch->cancel_no; + + fprintf(stderr, "test-client: got touch cancel (%d)\n", + touch->cancel_no); +} + +static void +surface_handle_enter(void *data, struct wl_surface *wl_surface, + struct wl_output *output) +{ + struct wtst_surface *surface = data; + + surface->output = wl_output_get_user_data(output); + + fprintf(stderr, "test-client: got surface enter output %p\n", + surface->output); +} + +static void +surface_handle_leave(void *data, struct wl_surface *wl_surface, + struct wl_output *output) +{ + struct wtst_surface *surface = data; + + surface->output = NULL; + + fprintf(stderr, "test-client: got surface leave output %p\n", + wl_output_get_user_data(output)); +} + +/** + * Item for listing supported memory formats. + */ +struct shm_format { + uint32_t format; /**< Format type. */ + + struct wl_list link; /**< list implementation hook. */ +}; + +static bool +has_shm_format(struct wtst_ctx *ctx, uint32_t format) +{ + bool status = false; + struct shm_format *format_info = NULL; + + wl_list_for_each(format_info, &ctx->private->format_list, link) + status |= (format == format_info->format); + + return status; +} + +static void +shm_handle_format(void *data, struct wl_shm *wl_shm, uint32_t format) +{ + struct wtst_ctx *ctx = data; + struct shm_format *shm_format = zalloc(sizeof(*shm_format)); + + ZUC_ASSERT_NOT_NULL(shm_format); + shm_format->format = format; + wl_list_insert(&ctx->private->format_list, &shm_format->link); +} + +struct wl_shm_listener shm_listener = { + shm_handle_format +}; + +static const struct wl_shell_surface_listener +shell_surface_listener = { + .ping = shell_surface_ping, + .configure = shell_surface_configure, + .popup_done = shell_surface_popup_done +}; + +static const struct wl_output_listener output_listener = { + output_handle_geometry, + output_handle_mode, + output_handle_done, + output_handle_scale, +}; + +static const struct wl_seat_listener seat_listener = { + seat_handle_capabilities, + seat_handle_name, +}; + +static const struct wl_pointer_listener pointer_listener = { + pointer_handle_enter, + pointer_handle_leave, + pointer_handle_motion, + pointer_handle_button, + pointer_handle_axis, +}; + +static const struct wl_keyboard_listener keyboard_listener = { + keyboard_handle_keymap, + keyboard_handle_enter, + keyboard_handle_leave, + keyboard_handle_key, + keyboard_handle_modifiers, + keyboard_handle_repeat_info +}; + +static const struct wl_touch_listener touch_listener = { + touch_handle_down, + touch_handle_up, + touch_handle_motion, + touch_handle_frame, + touch_handle_cancel, +}; + +static const struct wl_surface_listener surface_listener = { + surface_handle_enter, + surface_handle_leave +}; + +static bool is_singleton_interface(const char *interface) +{ + size_t i = 0; + const char *known_singletons[] = { + "wl_compositor", + "wl_display", + "wl_compositor", + "wl_shm", + "wl_data_device_manager", + }; + + for (i = 0; i < ARRAY_LENGTH(known_singletons); ++i) + if (strcmp(known_singletons[i], interface) == 0) + return true; + + return false; +} + +static void +add_global_advert(struct wtst_ctx *ctx, + uint32_t name, + const char *interface, + uint32_t version) +{ + struct wtst_global *curr = NULL; + struct wtst_global *tmp = NULL; + + if (!ctx || !ctx->private) + return; + + /* See if we've got this interface already */ + wl_list_for_each(tmp, &ctx->private->global_list, link) + if (strcmp(tmp->interface, interface) == 0) { + curr = tmp; + break; + } + + if (curr) { + /* These are not supposed to change. */ + if (is_singleton_interface(interface)) { + ZUC_FATAL("Singleton advertised more than once."); + + ZUC_ASSERT_EQ(curr->name, name); + ZUC_ASSERT_EQ(curr->version, version); + } + } else { + struct wtst_global *glbl = zalloc(sizeof(*glbl)); + ZUC_ASSERT_NOT_NULL(glbl); + glbl->name = name; + glbl->interface = strdup(interface); + glbl->version = version; + wl_list_insert(&ctx->private->global_list, &glbl->link); + } +} + +static void +warn_if_newer(const char *name, uint32_t version, uint32_t used_ver) +{ + if (version > used_ver) + fprintf(stderr, "test-client: warning: %s advertised " + "version %d but code only supports %d\n", + name, version, used_ver); +} + +static void +reg_global(void *data, + struct wl_registry *registry, + uint32_t name, + const char *interface, + uint32_t version) +{ + struct wtst_ctx *ctx = (struct wtst_ctx *)data; + struct wtst_input *input = NULL; + + ZUC_ASSERT_STREQ("wl_compositor", wl_compositor_interface.name); + + add_global_advert(ctx, name, interface, version); + + if (strcmp(interface, wl_compositor_interface.name) == 0) { + int used_ver = MIN(version, 4); + + warn_if_newer("wl_compositor", version, used_ver); + ctx->compositor = wl_registry_bind(registry, name, + &wl_compositor_interface, + used_ver); + ctx->private->wl_compositor_name = name; + ctx->private->wl_compositor_version = used_ver; + } else if (strcmp(interface, wl_seat_interface.name) == 0) { + int used_ver = MIN(version, 5); + + warn_if_newer("wl_seat", version, used_ver); + input = zalloc(sizeof(*input)); + ZUC_ASSERT_NOT_NULL(input); + input->wl_seat = wl_registry_bind(registry, name, + &wl_seat_interface, used_ver); + wl_seat_add_listener(input->wl_seat, &seat_listener, input); + input->seat_version = used_ver; + wl_list_insert(&ctx->inputs, &input->link); + } else if (strcmp(interface, wl_shm_interface.name) == 0) { + int used_ver = MIN(version, 1); + + warn_if_newer("wl_shm", version, used_ver); + ZUC_ASSERT_NULL(ctx->shm); + ctx->shm = wl_registry_bind(registry, name, + &wl_shm_interface, used_ver); + ctx->private->wl_shm_name = name; + ctx->private->wl_shm_version = used_ver; + wl_shm_add_listener(ctx->shm, &shm_listener, ctx); + } else if (strcmp(interface, wl_output_interface.name) == 0) { + int used_ver = MIN(version, 2); + struct wtst_output *output = zalloc(sizeof(*output)); + + warn_if_newer("wl_output", version, used_ver); + ZUC_ASSERT_NOT_NULL(output); + output->wl_output = wl_registry_bind(registry, name, + &wl_output_interface, + used_ver); + wl_output_add_listener(output->wl_output, + &output_listener, output); + ctx->output = output; + } else if (strcmp(interface, wl_shell_interface.name) == 0) { + int used_ver = MIN(version, 1); + + warn_if_newer("wl_shell", version, used_ver); + ZUC_ASSERT_NULL(ctx->shell); + ctx->shell = wl_registry_bind(registry, name, + &wl_shell_interface, used_ver); + ctx->private->wl_shell_name = name; + ctx->private->wl_shell_version = used_ver; + } else if (strcmp(interface, "wl_drm") == 0) { + ctx->has_wl_drm = true; + } + + if (ctx->private->chained && ctx->private->chained->global) { + ctx->private->chained->global(ctx->private->chained_data, + registry, name, + interface, version); + } +} + +static void +reg_global_remove(void *data, + struct wl_registry *registry, + uint32_t name) +{ + struct wtst_ctx *ctx = (struct wtst_ctx *)data; + + /* + * Note that removal of wl_compositor, wl_shm and wl_shell are not + * likely to occur. However, handling the cases will make crashes + * easier to debug if some implementation has an odd behavior. + */ + if (name == ctx->private->wl_compositor_name) { + if (ctx->compositor) + wl_compositor_destroy(ctx->compositor); + ctx->compositor = NULL; + ctx->private->wl_compositor_name = 0; + ctx->private->wl_compositor_version = 0; + } else if (name == ctx->private->wl_shm_name) { + if (ctx->shm) + wl_shm_destroy(ctx->shm); + ctx->shm = NULL; + ctx->private->wl_shm_name = 0; + ctx->private->wl_shm_version = 0; + } else if (name == ctx->private->wl_shell_name) { + if (ctx->shell) + wl_shell_destroy(ctx->shell); + ctx->shell = NULL; + ctx->private->wl_shell_name = 0; + ctx->private->wl_shell_version = 0; + } + + if (ctx->private->chained && ctx->private->chained->global_remove) { + ctx->private->chained->global_remove(ctx->private->chained_data, + registry, name); + } +} + +static struct wtst_surface * +wrap_wl_surface(struct wl_surface *wl_surface) +{ + struct wtst_surface *wsurf = NULL; + ZUC_ASSERTG_NOT_NULL(wl_surface, out); + wsurf = zalloc(sizeof(*wsurf)); + ZUC_ASSERTG_NOT_NULL(wsurf, out); + + wsurf->wl_surface = wl_surface; + wl_surface_add_listener(wl_surface, &surface_listener, wsurf); + +out: + return wsurf; +} + +/* + * TODO follow-up by moving shm buffer code to a common API. + */ +static struct wl_buffer * +create_shm_buffer(struct wl_shm *shm, int width, int height, void **pixels) +{ + int stride = width * 4; + int size = stride * height; + struct wl_shm_pool *pool = NULL; + struct wl_buffer *buffer = NULL; + int fd = -1; + void *data = NULL; + + if (pixels) + *pixels = NULL; + + fd = os_create_anonymous_file(size); + ZUC_ASSERTG_TRUE(fd >= 0, out); + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + ZUC_ASSERTG_NE(MAP_FAILED, data, out_close); + + pool = wl_shm_create_pool(shm, fd, size); + ZUC_ASSERTG_NOT_NULL(pool, out_close); + buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, + WL_SHM_FORMAT_ARGB8888); + ZUC_ASSERTG_NOT_NULL(buffer, out_free); + + if (pixels) + *pixels = data; + +out_free: + wl_shm_pool_destroy(pool); +out_close: + close(fd); +out: + return buffer; +} + +static void +wtst_surface_destroy(struct wtst_surface *surface) +{ + ZUC_ASSERT_NOT_NULL(surface); + wl_surface_destroy(surface->wl_surface); + free(surface); +} + +static struct wtst_ctx_private * +wtst_ctx_private_create(void) +{ + struct wtst_ctx_private *private = zalloc(sizeof(*private)); + + ZUC_ASSERTG_NOT_NULL(private, out); + + wl_list_init(&private->global_list); + wl_list_init(&private->format_list); + +out: + return private; +} + +static void +wtst_ctx_private_destroy(struct wtst_ctx_private *private) +{ + if (!private) + return; + + if (!wl_list_empty(&private->global_list)) { + struct wtst_global *entry = NULL; + struct wtst_global *tmp = NULL; + wl_list_for_each_reverse_safe(entry, tmp, + &private->global_list, link) { + wl_list_remove(&entry->link); + free(entry); + } + } + + if (!wl_list_empty(&private->format_list)) { + struct shm_format *entry = NULL; + struct shm_format *tmp = NULL; + wl_list_for_each_reverse_safe(entry, tmp, + &private->format_list, link) { + wl_list_remove(&entry->link); + free(entry); + } + } + + free(private); +} + +/** + * Find the test-seat and set it in client while destroying other inputs. + * @todo probably need to defer input selection to higher layers and not + * hardcode to "test-seat" here. + */ +static void +wtst_ctx_set_input(struct wtst_ctx *ctx) +{ + /* For now prune all but first. Later support looking for named seats */ + struct wtst_input *inp, *inptmp; + wl_list_for_each_safe(inp, inptmp, &ctx->inputs, link) { + ZUC_ASSERT_TRUE(inp->seat_name && "BUG: input with no name"); + if (!ctx->input) { + ctx->input = inp; + input_update_devices(inp); + } else { + input_destroy(inp); + } + } + + /* we keep only one input */ + ZUC_ASSERT_EQ(1, wl_list_length(&ctx->inputs)); +} + +/** + * Creates an instance of the test fixture. + * + * @param display name of the wayland display to connect to or NULL for + * the default. + * @return a pointer to a test fixture upon success, NULL otherwise. + * @see wtst_ctx_destroy() + */ +struct wtst_ctx * +wtst_ctx_create(const char *display) +{ + return wtst_ctx_create_chained(display, NULL, NULL); +} + +/** + * Creates an instance of the test fixture with a corresponding surface. + * + * @param display name of the wayland display to connect to or NULL for + * the default. + * @param width the width for the surface. + * @param height the height for the surface. + * @return a pointer to a test fixture upon success, NULL otherwise. + * @see wtst_ctx_destroy() + */ +struct wtst_ctx * +wtst_ctx_create_with_surface(const char *display, int width, int height) +{ + struct wtst_ctx *ctx = + wtst_ctx_create_with_surface_chained(display, width, height, + NULL, NULL); + return ctx; +} + +/** + * Creates an instance of the test fixture that is chained and with a + * corresponding surface. + * Additional registry advertisements will be passed on. + * + * @param display name for the Wayland display to connect to. + * @param width the width for the surface. + * @param height the height for the surface. + * @param reg_listener the registry listener to chain in. + * @param reg_data pointer to pass back to registry listener. + * @return a pointer to a test fixture upon success, NULL otherwise. + * @see wtst_ctx_destroy() + */ +struct wtst_ctx * +wtst_ctx_create_with_surface_chained(const char *display, + int width, int height, + struct wl_registry_listener *reg_listener, + void *reg_data) +{ + struct wl_surface *wl_surface = NULL; + struct wtst_surface *surface = NULL; + struct wtst_ctx *ctx = wtst_ctx_create_chained(display, + reg_listener, reg_data); + ZUC_ASSERTG_NOT_NULL(ctx, err); + + wl_surface = wl_compositor_create_surface(ctx->compositor); + ZUC_ASSERTG_NOT_NULL(wl_surface, err_free); + + surface = wrap_wl_surface(wl_surface); + ZUC_ASSERTG_NOT_NULL(surface, err_destroy); + + ctx->surface = surface; + + surface->width = width; + surface->height = height; + + surface->wl_buffer = create_shm_buffer(ctx->shm, width, height, + &surface->data); + + memset(surface->data, 64, width * height * 4); + + return ctx; +err_destroy: + wl_surface_destroy(wl_surface); +err_free: + wtst_ctx_destroy(ctx); +err: + return NULL; +} + +/** + * Creates an instance of the test fixture that is chained. + * Additional registry advertisements will be passed on. + * + * @param display name of the wayland display to connect to or NULL for + * the default. + * @param reg_listener the registry listener to chain in. + * @param reg_data pointer to pass back to registry listener. + * @return a pointer to a test fixture upon success, NULL otherwise. + * @see wtst_ctx_destroy() + */ +struct wtst_ctx * +wtst_ctx_create_chained(const char *display, + struct wl_registry_listener *reg_listener, + void *reg_data) +{ + struct wtst_ctx *ctx = zalloc(sizeof(*ctx)); + ZUC_ASSERTG_NOT_NULL(ctx, err); + + wl_list_init(&ctx->inputs); + ctx->reg_listener = zalloc(sizeof(*(ctx->reg_listener))); + ZUC_ASSERTG_NOT_NULL(ctx->reg_listener, err_free); + + /* connect to display. */ + ctx->display = wl_display_connect(display); + ZUC_ASSERTG_NOT_NULL(ctx->display, err_free); + + ctx->private = wtst_ctx_private_create(); + ZUC_ASSERTG_NOT_NULL(ctx->private, err_free); + + ctx->private->chained = reg_listener; + ctx->private->chained_data = reg_data; + + /* setup registry so we can bind to interfaces. */ + ctx->registry = wl_display_get_registry(ctx->display); + ctx->reg_listener->global = reg_global; + ctx->reg_listener->global_remove = reg_global_remove; + wl_registry_add_listener(ctx->registry, + ctx->reg_listener, + ctx); + + /* this roundtrip makes sure we have all globals + * and we have bound to them: */ + wl_display_roundtrip(ctx->display); + + /* this roundtrip makes sure we got all wl_shm + * format and wl_seat.* and wl_output events: */ + wl_display_roundtrip(ctx->display); + + /* find the right input for us */ + wtst_ctx_set_input(ctx); + + /* must have WL_SHM_FORMAT_ARGB32 */ + ZUC_ASSERTG_TRUE(has_shm_format(ctx, WL_SHM_FORMAT_ARGB8888), err_free); + + /* must have an output */ + ZUC_ASSERTG_NOT_NULL(ctx->output, err_free); + + /* the output must be initialized */ + ZUC_ASSERTG_TRUE(ctx->output->initialized, err_free); + + /* must have seat set */ + ZUC_ASSERTG_NOT_NULL(ctx->input, err_free); + + return ctx; + +err_free: + wtst_ctx_destroy(ctx); +err: + return NULL; +} + +/** + * Destroys a test fixture. + * + * @param ctx pointer to a test fixture to destroy. + * It should have previously been created via wtst_ctx_create(). + * @see wtst_ctx_create() + */ +void +wtst_ctx_destroy(struct wtst_ctx *ctx) +{ + if (!ctx) + return; + + if (ctx->surface) + wtst_surface_destroy(ctx->surface); + + if (ctx->registry) + wl_registry_destroy(ctx->registry); + + if (ctx->reg_listener) + free(ctx->reg_listener); + + if (ctx->shell) + wl_shell_destroy(ctx->shell); + + if (ctx->shm) + wl_shm_destroy(ctx->shm); + + if (ctx->compositor) + wl_compositor_destroy(ctx->compositor); + + if (ctx->display) + wl_display_disconnect(ctx->display); + + wtst_ctx_private_destroy(ctx->private); + free(ctx); +} + +static struct wtst_pointer * +wtst_pointer_create(void) +{ + struct wtst_pointer *pointer = zalloc(sizeof(*pointer)); + ZUC_ASSERTG_NOT_NULL(pointer, err); + + pointer->private = zalloc(sizeof(*pointer->private)); + ZUC_ASSERTG_NOT_NULL(pointer->private, err_free); + wl_list_init(&pointer->private->link); + + return pointer; +err_free: + free(pointer); +err: + return NULL; +} + +/** + * Adds a listener to a specified pointer. + * + * @param pointer the pointer instance to add a listener to. + * @param listener the listener to add. + * @param data callback data. + */ +int +wtst_pointer_add_listener(struct wtst_pointer *pointer, + const struct wl_pointer_listener *listener, + void *data) +{ + struct wtst_pointer_private *priv = zalloc(sizeof(*priv)); + ZUC_ASSERTG_NOT_NULL(priv, err); + priv->data = data; + priv->listener = listener; + wl_list_insert(&pointer->private->link, &priv->link); + return 0; +err: + return -1; +} + +void +input_update_devices(struct wtst_input *input) +{ + struct wl_seat *seat = input->wl_seat; + enum wl_seat_capability caps = input->caps; + + if ((caps & WL_SEAT_CAPABILITY_POINTER) && !input->pointer) { + struct wtst_pointer *pointer = wtst_pointer_create(); + pointer->wl_pointer = wl_seat_get_pointer(seat); + wl_pointer_add_listener(pointer->wl_pointer, &pointer_listener, + pointer); + input->pointer = pointer; + } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && input->pointer) { + wl_pointer_destroy(input->pointer->wl_pointer); + free(input->pointer); + input->pointer = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !input->keyboard) { + struct wtst_keyboard *keyboard = zalloc(sizeof(*keyboard)); + ZUC_ASSERT_NOT_NULL(keyboard); + keyboard->wl_keyboard = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(keyboard->wl_keyboard, + &keyboard_listener, keyboard); + input->keyboard = keyboard; + } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && input->keyboard) { + wl_keyboard_destroy(input->keyboard->wl_keyboard); + free(input->keyboard); + input->keyboard = NULL; + } + + if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !input->touch) { + struct wtst_touch *touch = zalloc(sizeof(*touch)); + ZUC_ASSERT_NOT_NULL(touch); + touch->wl_touch = wl_seat_get_touch(seat); + wl_touch_add_listener(touch->wl_touch, &touch_listener, + touch); + input->touch = touch; + } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && input->touch) { + wl_touch_destroy(input->touch->wl_touch); + free(input->touch); + input->touch = NULL; + } +} + +/** + * Determines of an interface has been advertized by the Wayland registry + * as being present. + * + * @param ctx the test fixture in use. + * @param interface the name of the interface to query. + * @return true if the interface is supported, false otherwise. + */ +int +wtst_is_global_advertised(struct wtst_ctx *ctx, char const *interface) +{ + int found = 0; + if (ctx && ctx->private && interface) { + struct wtst_global *tmp = NULL; + wl_list_for_each(tmp, &ctx->private->global_list, link) + if (strcmp(interface, tmp->interface) == 0) { + found = true; + break; + } + } + return found; +} + +/** + * Creates a wl_shell_surface and packages it in a wtst_shell_surface wrapper. + */ +struct wtst_shell_surface * +wtst_create_shell_surface(struct wl_compositor *compositor, + struct wl_shell *shell) +{ + struct wl_surface *wl_surface = NULL; + struct wtst_shell_surface *wss = zalloc(sizeof(*wss)); + ZUC_ASSERTG_NOT_NULL(wss, err); + + wl_surface = wl_compositor_create_surface(compositor); + ZUC_ASSERTG_NOT_NULL(wl_surface, err_free); + + wss->surface = wrap_wl_surface(wl_surface); + ZUC_ASSERTG_NOT_NULL(wss->surface, err_destroy); + wl_surface = NULL; /* ownership was transferred. */ + + wss->wl_shell_surface = + wl_shell_get_shell_surface(shell, wss->surface->wl_surface); + ZUC_ASSERTG_NOT_NULL(wss->wl_shell_surface, err_destroy); + + wl_shell_surface_add_listener(wss->wl_shell_surface, + &shell_surface_listener, + wss); + wl_shell_surface_set_toplevel(wss->wl_shell_surface); + + return wss; + +err_destroy: + if (wl_surface) + wl_surface_destroy(wl_surface); + + if (wss->surface) { + if (wss->surface->wl_surface) + wl_surface_destroy(wss->surface->wl_surface); + free(wss->surface); + } +err_free: + free(wss); +err: + return NULL; +} + +void +wtst_destroy_shell_surface(struct wtst_shell_surface *wss) +{ + ZUC_ASSERT_NOT_NULL(wss); + wl_shell_surface_destroy(wss->wl_shell_surface); + wtst_surface_destroy(wss->surface); + free(wss); +} -- 2.5.0 _______________________________________________ wayland-devel mailing list wayland-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/wayland-devel