| /* |
| * Copyright © 2012 Intel Corporation |
| * |
| * 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 <stdlib.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <sys/mman.h> |
| #include <cairo.h> |
| |
| #include "shared/os-compatibility.h" |
| #include "shared/xalloc.h" |
| #include "shared/zalloc.h" |
| #include "weston-test-client-helper.h" |
| |
| #define max(a, b) (((a) > (b)) ? (a) : (b)) |
| #define min(a, b) (((a) > (b)) ? (b) : (a)) |
| #define clip(x, a, b) min(max(x, a), b) |
| |
| int |
| surface_contains(struct surface *surface, int x, int y) |
| { |
| /* test whether a global x,y point is contained in the surface */ |
| int sx = surface->x; |
| int sy = surface->y; |
| int sw = surface->width; |
| int sh = surface->height; |
| return x >= sx && y >= sy && x < sx + sw && y < sy + sh; |
| } |
| |
| static void |
| frame_callback_handler(void *data, struct wl_callback *callback, uint32_t time) |
| { |
| int *done = data; |
| |
| *done = 1; |
| |
| wl_callback_destroy(callback); |
| } |
| |
| static const struct wl_callback_listener frame_listener = { |
| frame_callback_handler |
| }; |
| |
| struct wl_callback * |
| frame_callback_set(struct wl_surface *surface, int *done) |
| { |
| struct wl_callback *callback; |
| |
| *done = 0; |
| callback = wl_surface_frame(surface); |
| wl_callback_add_listener(callback, &frame_listener, done); |
| |
| return callback; |
| } |
| |
| int |
| frame_callback_wait_nofail(struct client *client, int *done) |
| { |
| while (!*done) { |
| if (wl_display_dispatch(client->wl_display) < 0) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| void |
| move_client(struct client *client, int x, int y) |
| { |
| struct surface *surface = client->surface; |
| int done; |
| |
| client->surface->x = x; |
| client->surface->y = y; |
| weston_test_move_surface(client->test->weston_test, surface->wl_surface, |
| surface->x, surface->y); |
| /* The attach here is necessary because commit() will call configure |
| * only on surfaces newly attached, and the one that sets the surface |
| * position is the configure. */ |
| wl_surface_attach(surface->wl_surface, surface->buffer->proxy, 0, 0); |
| wl_surface_damage(surface->wl_surface, 0, 0, surface->width, |
| surface->height); |
| |
| frame_callback_set(surface->wl_surface, &done); |
| |
| wl_surface_commit(surface->wl_surface); |
| |
| frame_callback_wait(client, &done); |
| } |
| |
| 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 pointer *pointer = data; |
| |
| if (wl_surface) |
| pointer->focus = wl_surface_get_user_data(wl_surface); |
| else |
| pointer->focus = NULL; |
| |
| pointer->x = wl_fixed_to_int(x); |
| pointer->y = wl_fixed_to_int(y); |
| |
| fprintf(stderr, "test-client: got pointer enter %d %d, surface %p\n", |
| pointer->x, pointer->y, pointer->focus); |
| } |
| |
| static void |
| pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, |
| uint32_t serial, struct wl_surface *wl_surface) |
| { |
| struct pointer *pointer = data; |
| |
| pointer->focus = NULL; |
| |
| fprintf(stderr, "test-client: got pointer leave, surface %p\n", |
| wl_surface ? wl_surface_get_user_data(wl_surface) : NULL); |
| } |
| |
| static void |
| pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, |
| uint32_t time_msec, wl_fixed_t x, wl_fixed_t y) |
| { |
| struct pointer *pointer = data; |
| |
| pointer->x = wl_fixed_to_int(x); |
| pointer->y = wl_fixed_to_int(y); |
| pointer->motion_time_msec = time_msec; |
| pointer->motion_time_timespec = pointer->input_timestamp; |
| pointer->input_timestamp = (struct timespec) { 0 }; |
| |
| fprintf(stderr, "test-client: got pointer motion %d %d\n", |
| pointer->x, pointer->y); |
| } |
| |
| static void |
| pointer_handle_button(void *data, struct wl_pointer *wl_pointer, |
| uint32_t serial, uint32_t time_msec, uint32_t button, |
| uint32_t state) |
| { |
| struct pointer *pointer = data; |
| |
| pointer->button = button; |
| pointer->state = state; |
| pointer->button_time_msec = time_msec; |
| pointer->button_time_timespec = pointer->input_timestamp; |
| pointer->input_timestamp = (struct timespec) { 0 }; |
| |
| fprintf(stderr, "test-client: got pointer button %u %u\n", |
| button, state); |
| } |
| |
| static void |
| pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, |
| uint32_t time_msec, uint32_t axis, wl_fixed_t value) |
| { |
| struct pointer *pointer = data; |
| |
| pointer->axis = axis; |
| pointer->axis_value = wl_fixed_to_double(value); |
| pointer->axis_time_msec = time_msec; |
| pointer->axis_time_timespec = pointer->input_timestamp; |
| pointer->input_timestamp = (struct timespec) { 0 }; |
| |
| fprintf(stderr, "test-client: got pointer axis %u %f\n", |
| axis, wl_fixed_to_double(value)); |
| } |
| |
| static void |
| pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) |
| { |
| fprintf(stderr, "test-client: got pointer frame\n"); |
| } |
| |
| static void |
| pointer_handle_axis_source(void *data, struct wl_pointer *wl_pointer, |
| uint32_t source) |
| { |
| fprintf(stderr, "test-client: got pointer axis source %u\n", source); |
| } |
| |
| static void |
| pointer_handle_axis_stop(void *data, struct wl_pointer *wl_pointer, |
| uint32_t time_msec, uint32_t axis) |
| { |
| struct pointer *pointer = data; |
| |
| pointer->axis = axis; |
| pointer->axis_stop_time_msec = time_msec; |
| pointer->axis_stop_time_timespec = pointer->input_timestamp; |
| pointer->input_timestamp = (struct timespec) { 0 }; |
| |
| fprintf(stderr, "test-client: got pointer axis stop %u\n", axis); |
| } |
| |
| static void |
| pointer_handle_axis_discrete(void *data, struct wl_pointer *wl_pointer, |
| uint32_t axis, int32_t value) |
| { |
| fprintf(stderr, "test-client: got pointer axis discrete %u %d\n", |
| axis, value); |
| } |
| |
| static const struct wl_pointer_listener pointer_listener = { |
| pointer_handle_enter, |
| pointer_handle_leave, |
| pointer_handle_motion, |
| pointer_handle_button, |
| pointer_handle_axis, |
| pointer_handle_frame, |
| pointer_handle_axis_source, |
| pointer_handle_axis_stop, |
| pointer_handle_axis_discrete, |
| }; |
| |
| 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 keyboard *keyboard = data; |
| |
| if (wl_surface) |
| keyboard->focus = wl_surface_get_user_data(wl_surface); |
| else |
| keyboard->focus = NULL; |
| |
| 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 keyboard *keyboard = data; |
| |
| keyboard->focus = NULL; |
| |
| fprintf(stderr, "test-client: got keyboard leave, surface %p\n", |
| wl_surface ? wl_surface_get_user_data(wl_surface) : NULL); |
| } |
| |
| static void |
| keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, |
| uint32_t serial, uint32_t time_msec, uint32_t key, |
| uint32_t state) |
| { |
| struct keyboard *keyboard = data; |
| |
| keyboard->key = key; |
| keyboard->state = state; |
| keyboard->key_time_msec = time_msec; |
| keyboard->key_time_timespec = keyboard->input_timestamp; |
| keyboard->input_timestamp = (struct timespec) { 0 }; |
| |
| 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 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 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 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 void |
| touch_handle_down(void *data, struct wl_touch *wl_touch, |
| uint32_t serial, uint32_t time_msec, |
| struct wl_surface *surface, int32_t id, |
| wl_fixed_t x_w, wl_fixed_t y_w) |
| { |
| struct touch *touch = data; |
| |
| touch->down_x = wl_fixed_to_int(x_w); |
| touch->down_y = wl_fixed_to_int(y_w); |
| touch->id = id; |
| touch->down_time_msec = time_msec; |
| touch->down_time_timespec = touch->input_timestamp; |
| touch->input_timestamp = (struct timespec) { 0 }; |
| |
| 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_msec, int32_t id) |
| { |
| struct touch *touch = data; |
| touch->up_id = id; |
| touch->up_time_msec = time_msec; |
| touch->up_time_timespec = touch->input_timestamp; |
| touch->input_timestamp = (struct timespec) { 0 }; |
| |
| 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_msec, int32_t id, |
| wl_fixed_t x_w, wl_fixed_t y_w) |
| { |
| struct touch *touch = data; |
| touch->x = wl_fixed_to_int(x_w); |
| touch->y = wl_fixed_to_int(y_w); |
| touch->motion_time_msec = time_msec; |
| touch->motion_time_timespec = touch->input_timestamp; |
| touch->input_timestamp = (struct timespec) { 0 }; |
| |
| 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 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 touch *touch = data; |
| |
| ++touch->cancel_no; |
| |
| fprintf(stderr, "test-client: got touch cancel (%d)\n", touch->cancel_no); |
| } |
| |
| static const struct wl_touch_listener touch_listener = { |
| touch_handle_down, |
| touch_handle_up, |
| touch_handle_motion, |
| touch_handle_frame, |
| touch_handle_cancel, |
| }; |
| |
| static void |
| surface_enter(void *data, |
| struct wl_surface *wl_surface, struct wl_output *output) |
| { |
| struct 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_leave(void *data, |
| struct wl_surface *wl_surface, struct wl_output *output) |
| { |
| struct surface *surface = data; |
| |
| surface->output = NULL; |
| |
| fprintf(stderr, "test-client: got surface leave output %p\n", |
| wl_output_get_user_data(output)); |
| } |
| |
| static const struct wl_surface_listener surface_listener = { |
| surface_enter, |
| surface_leave |
| }; |
| |
| static struct buffer * |
| create_shm_buffer(struct client *client, int width, int height, |
| pixman_format_code_t format, uint32_t wlfmt) |
| { |
| struct wl_shm *shm = client->wl_shm; |
| struct buffer *buf; |
| size_t stride_bytes; |
| struct wl_shm_pool *pool; |
| int fd; |
| void *data; |
| size_t bytes_pp; |
| |
| assert(width > 0); |
| assert(height > 0); |
| |
| buf = xzalloc(sizeof *buf); |
| |
| bytes_pp = PIXMAN_FORMAT_BPP(format) / 8; |
| stride_bytes = width * bytes_pp; |
| /* round up to multiple of 4 bytes for Pixman */ |
| stride_bytes = (stride_bytes + 3) & ~3u; |
| assert(stride_bytes / bytes_pp >= (unsigned)width); |
| |
| buf->len = stride_bytes * height; |
| assert(buf->len / stride_bytes == (unsigned)height); |
| |
| fd = os_create_anonymous_file(buf->len); |
| assert(fd >= 0); |
| |
| data = mmap(NULL, buf->len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); |
| if (data == MAP_FAILED) { |
| close(fd); |
| assert(data != MAP_FAILED); |
| } |
| |
| pool = wl_shm_create_pool(shm, fd, buf->len); |
| buf->proxy = wl_shm_pool_create_buffer(pool, 0, width, height, |
| stride_bytes, wlfmt); |
| wl_shm_pool_destroy(pool); |
| close(fd); |
| |
| buf->image = pixman_image_create_bits(format, width, height, |
| data, stride_bytes); |
| |
| assert(buf->proxy); |
| assert(buf->image); |
| |
| return buf; |
| } |
| |
| struct buffer * |
| create_shm_buffer_a8r8g8b8(struct client *client, int width, int height) |
| { |
| assert(client->has_argb); |
| |
| return create_shm_buffer(client, width, height, |
| PIXMAN_a8r8g8b8, WL_SHM_FORMAT_ARGB8888); |
| } |
| |
| void |
| buffer_destroy(struct buffer *buf) |
| { |
| void *pixels; |
| |
| pixels = pixman_image_get_data(buf->image); |
| |
| if (buf->proxy) { |
| wl_buffer_destroy(buf->proxy); |
| assert(munmap(pixels, buf->len) == 0); |
| } |
| |
| assert(pixman_image_unref(buf->image)); |
| |
| free(buf); |
| } |
| |
| static void |
| shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) |
| { |
| struct client *client = data; |
| |
| if (format == WL_SHM_FORMAT_ARGB8888) |
| client->has_argb = 1; |
| } |
| |
| struct wl_shm_listener shm_listener = { |
| shm_format |
| }; |
| |
| static void |
| test_handle_pointer_position(void *data, struct weston_test *weston_test, |
| wl_fixed_t x, wl_fixed_t y) |
| { |
| struct test *test = data; |
| test->pointer_x = wl_fixed_to_int(x); |
| test->pointer_y = wl_fixed_to_int(y); |
| |
| fprintf(stderr, "test-client: got global pointer %d %d\n", |
| test->pointer_x, test->pointer_y); |
| } |
| |
| static void |
| test_handle_capture_screenshot_done(void *data, struct weston_test *weston_test) |
| { |
| struct test *test = data; |
| |
| printf("Screenshot has been captured\n"); |
| test->buffer_copy_done = 1; |
| } |
| |
| static const struct weston_test_listener test_listener = { |
| test_handle_pointer_position, |
| test_handle_capture_screenshot_done, |
| }; |
| |
| static void |
| input_destroy(struct input *inp) |
| { |
| if (inp->pointer) { |
| wl_pointer_release(inp->pointer->wl_pointer); |
| free(inp->pointer); |
| } |
| if (inp->keyboard) { |
| wl_keyboard_release(inp->keyboard->wl_keyboard); |
| free(inp->keyboard); |
| } |
| if (inp->touch) { |
| wl_touch_release(inp->touch->wl_touch); |
| free(inp->touch); |
| } |
| wl_list_remove(&inp->link); |
| wl_seat_release(inp->wl_seat); |
| free(inp->seat_name); |
| free(inp); |
| } |
| |
| static void |
| input_update_devices(struct input *input) |
| { |
| struct pointer *pointer; |
| struct keyboard *keyboard; |
| struct touch *touch; |
| |
| struct wl_seat *seat = input->wl_seat; |
| enum wl_seat_capability caps = input->caps; |
| |
| if ((caps & WL_SEAT_CAPABILITY_POINTER) && !input->pointer) { |
| pointer = xzalloc(sizeof *pointer); |
| pointer->wl_pointer = wl_seat_get_pointer(seat); |
| wl_pointer_set_user_data(pointer->wl_pointer, pointer); |
| 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) { |
| keyboard = xzalloc(sizeof *keyboard); |
| keyboard->wl_keyboard = wl_seat_get_keyboard(seat); |
| wl_keyboard_set_user_data(keyboard->wl_keyboard, keyboard); |
| 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) { |
| touch = xzalloc(sizeof *touch); |
| touch->wl_touch = wl_seat_get_touch(seat); |
| wl_touch_set_user_data(touch->wl_touch, touch); |
| 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; |
| } |
| } |
| |
| static void |
| seat_handle_capabilities(void *data, struct wl_seat *seat, |
| enum wl_seat_capability caps) |
| { |
| struct 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 input *input = data; |
| |
| input->seat_name = strdup(name); |
| assert(input->seat_name && "No memory"); |
| |
| /* We only update the devices and set client input for the test seat */ |
| if (strcmp(name, "test-seat") == 0) { |
| assert(!input->client->input && |
| "Multiple test seats detected!"); |
| |
| input_update_devices(input); |
| input->client->input = input; |
| } |
| |
| fprintf(stderr, "test-client: got seat %p name: \'%s\'\n", |
| input, name); |
| } |
| |
| static const struct wl_seat_listener seat_listener = { |
| seat_handle_capabilities, |
| seat_handle_name, |
| }; |
| |
| 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 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 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 output *output = data; |
| |
| output->scale = scale; |
| } |
| |
| static void |
| output_handle_done(void *data, |
| struct wl_output *wl_output) |
| { |
| struct output *output = data; |
| |
| output->initialized = 1; |
| } |
| |
| static const struct wl_output_listener output_listener = { |
| output_handle_geometry, |
| output_handle_mode, |
| output_handle_done, |
| output_handle_scale, |
| }; |
| |
| static void |
| handle_global(void *data, struct wl_registry *registry, |
| uint32_t id, const char *interface, uint32_t version) |
| { |
| struct client *client = data; |
| struct output *output; |
| struct test *test; |
| struct global *global; |
| struct input *input; |
| |
| global = xzalloc(sizeof *global); |
| global->name = id; |
| global->interface = strdup(interface); |
| assert(interface); |
| global->version = version; |
| wl_list_insert(client->global_list.prev, &global->link); |
| |
| if (strcmp(interface, "wl_compositor") == 0) { |
| client->wl_compositor = |
| wl_registry_bind(registry, id, |
| &wl_compositor_interface, version); |
| } else if (strcmp(interface, "wl_seat") == 0) { |
| input = xzalloc(sizeof *input); |
| input->client = client; |
| input->global_name = global->name; |
| input->wl_seat = |
| wl_registry_bind(registry, id, |
| &wl_seat_interface, version); |
| wl_seat_add_listener(input->wl_seat, &seat_listener, input); |
| wl_list_insert(&client->inputs, &input->link); |
| } else if (strcmp(interface, "wl_shm") == 0) { |
| client->wl_shm = |
| wl_registry_bind(registry, id, |
| &wl_shm_interface, version); |
| wl_shm_add_listener(client->wl_shm, &shm_listener, client); |
| } else if (strcmp(interface, "wl_output") == 0) { |
| output = xzalloc(sizeof *output); |
| output->wl_output = |
| wl_registry_bind(registry, id, |
| &wl_output_interface, version); |
| wl_output_add_listener(output->wl_output, |
| &output_listener, output); |
| client->output = output; |
| } else if (strcmp(interface, "weston_test") == 0) { |
| test = xzalloc(sizeof *test); |
| test->weston_test = |
| wl_registry_bind(registry, id, |
| &weston_test_interface, version); |
| weston_test_add_listener(test->weston_test, &test_listener, test); |
| client->test = test; |
| } else if (strcmp(interface, "wl_drm") == 0) { |
| client->has_wl_drm = true; |
| } |
| } |
| |
| static struct global * |
| client_find_global_with_name(struct client *client, uint32_t name) |
| { |
| struct global *global; |
| |
| wl_list_for_each(global, &client->global_list, link) { |
| if (global->name == name) |
| return global; |
| } |
| |
| return NULL; |
| } |
| |
| static struct input * |
| client_find_input_with_name(struct client *client, uint32_t name) |
| { |
| struct input *input; |
| |
| wl_list_for_each(input, &client->inputs, link) { |
| if (input->global_name == name) |
| return input; |
| } |
| |
| return NULL; |
| } |
| |
| static void |
| handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) |
| { |
| struct client *client = data; |
| struct global *global; |
| struct input *input; |
| |
| global = client_find_global_with_name(client, name); |
| assert(global && "Request to remove unknown global"); |
| |
| if (strcmp(global->interface, "wl_seat") == 0) { |
| input = client_find_input_with_name(client, name); |
| if (input) { |
| if (client->input == input) |
| client->input = NULL; |
| input_destroy(input); |
| } |
| } |
| |
| wl_list_remove(&global->link); |
| free(global->interface); |
| free(global); |
| } |
| |
| static const struct wl_registry_listener registry_listener = { |
| handle_global, |
| handle_global_remove, |
| }; |
| |
| void |
| skip(const char *fmt, ...) |
| { |
| va_list argp; |
| |
| va_start(argp, fmt); |
| vfprintf(stderr, fmt, argp); |
| va_end(argp); |
| |
| /* automake tests uses exit code 77. weston-test-runner will see |
| * this and use it, and then weston-test's sigchld handler (in the |
| * weston process) will use that as an exit status, which is what |
| * automake will see in the end. */ |
| exit(77); |
| } |
| |
| void |
| expect_protocol_error(struct client *client, |
| const struct wl_interface *intf, |
| uint32_t code) |
| { |
| int err; |
| uint32_t errcode, failed = 0; |
| const struct wl_interface *interface; |
| unsigned int id; |
| |
| /* if the error has not come yet, make it happen */ |
| wl_display_roundtrip(client->wl_display); |
| |
| err = wl_display_get_error(client->wl_display); |
| |
| assert(err && "Expected protocol error but nothing came"); |
| assert(err == EPROTO && "Expected protocol error but got local error"); |
| |
| errcode = wl_display_get_protocol_error(client->wl_display, |
| &interface, &id); |
| |
| /* check error */ |
| if (errcode != code) { |
| fprintf(stderr, "Should get error code %d but got %d\n", |
| code, errcode); |
| failed = 1; |
| } |
| |
| /* this should be definitely set */ |
| assert(interface); |
| |
| if (strcmp(intf->name, interface->name) != 0) { |
| fprintf(stderr, "Should get interface '%s' but got '%s'\n", |
| intf->name, interface->name); |
| failed = 1; |
| } |
| |
| if (failed) { |
| fprintf(stderr, "Expected other protocol error\n"); |
| abort(); |
| } |
| |
| /* all OK */ |
| fprintf(stderr, "Got expected protocol error on '%s' (object id: %d) " |
| "with code %d\n", interface->name, id, errcode); |
| } |
| |
| static void |
| log_handler(const char *fmt, va_list args) |
| { |
| fprintf(stderr, "libwayland: "); |
| vfprintf(stderr, fmt, args); |
| } |
| |
| struct client * |
| create_client(void) |
| { |
| struct client *client; |
| |
| wl_log_set_handler_client(log_handler); |
| |
| /* connect to display */ |
| client = xzalloc(sizeof *client); |
| client->wl_display = wl_display_connect(NULL); |
| assert(client->wl_display); |
| wl_list_init(&client->global_list); |
| wl_list_init(&client->inputs); |
| |
| /* setup registry so we can bind to interfaces */ |
| client->wl_registry = wl_display_get_registry(client->wl_display); |
| wl_registry_add_listener(client->wl_registry, ®istry_listener, |
| client); |
| |
| /* this roundtrip makes sure we have all globals and we bound to them */ |
| client_roundtrip(client); |
| /* this roundtrip makes sure we got all wl_shm.format and wl_seat.* |
| * events */ |
| client_roundtrip(client); |
| |
| /* must have WL_SHM_FORMAT_ARGB32 */ |
| assert(client->has_argb); |
| |
| /* must have weston_test interface */ |
| assert(client->test); |
| |
| /* must have an output */ |
| assert(client->output); |
| |
| /* the output must be initialized */ |
| assert(client->output->initialized == 1); |
| |
| /* must have seat set */ |
| assert(client->input); |
| |
| return client; |
| } |
| |
| struct surface * |
| create_test_surface(struct client *client) |
| { |
| struct surface *surface; |
| |
| surface = xzalloc(sizeof *surface); |
| |
| surface->wl_surface = |
| wl_compositor_create_surface(client->wl_compositor); |
| assert(surface->wl_surface); |
| |
| wl_surface_add_listener(surface->wl_surface, &surface_listener, |
| surface); |
| |
| wl_surface_set_user_data(surface->wl_surface, surface); |
| |
| return surface; |
| } |
| |
| struct client * |
| create_client_and_test_surface(int x, int y, int width, int height) |
| { |
| struct client *client; |
| struct surface *surface; |
| pixman_color_t color = { 16384, 16384, 16384, 16384 }; /* uint16_t */ |
| pixman_image_t *solid; |
| |
| client = create_client(); |
| |
| /* initialize the client surface */ |
| surface = create_test_surface(client); |
| client->surface = surface; |
| |
| surface->width = width; |
| surface->height = height; |
| surface->buffer = create_shm_buffer_a8r8g8b8(client, width, height); |
| |
| solid = pixman_image_create_solid_fill(&color); |
| pixman_image_composite32(PIXMAN_OP_SRC, |
| solid, /* src */ |
| NULL, /* mask */ |
| surface->buffer->image, /* dst */ |
| 0, 0, /* src x,y */ |
| 0, 0, /* mask x,y */ |
| 0, 0, /* dst x,y */ |
| width, height); |
| pixman_image_unref(solid); |
| |
| move_client(client, x, y); |
| |
| return client; |
| } |
| |
| static const char* |
| output_path(void) |
| { |
| char *path = getenv("WESTON_TEST_OUTPUT_PATH"); |
| |
| if (!path) |
| return "./logs"; |
| |
| return path; |
| } |
| |
| char* |
| screenshot_output_filename(const char *basename, uint32_t seq) |
| { |
| char *filename; |
| |
| if (asprintf(&filename, "%s/%s-%02d.png", |
| output_path(), basename, seq) < 0) |
| return NULL; |
| return filename; |
| } |
| |
| static const char* |
| reference_path(void) |
| { |
| char *path = getenv("WESTON_TEST_REFERENCE_PATH"); |
| |
| if (!path) |
| return "./tests/reference"; |
| return path; |
| } |
| |
| char* |
| screenshot_reference_filename(const char *basename, uint32_t seq) |
| { |
| char *filename; |
| |
| if (asprintf(&filename, "%s/%s-%02d.png", |
| reference_path(), basename, seq) < 0) |
| return NULL; |
| return filename; |
| } |
| |
| struct format_map_entry { |
| cairo_format_t cairo; |
| pixman_format_code_t pixman; |
| }; |
| |
| static const struct format_map_entry format_map[] = { |
| { CAIRO_FORMAT_ARGB32, PIXMAN_a8r8g8b8 }, |
| { CAIRO_FORMAT_RGB24, PIXMAN_x8r8g8b8 }, |
| { CAIRO_FORMAT_A8, PIXMAN_a8 }, |
| { CAIRO_FORMAT_RGB16_565, PIXMAN_r5g6b5 }, |
| }; |
| |
| static pixman_format_code_t |
| format_cairo2pixman(cairo_format_t fmt) |
| { |
| unsigned i; |
| |
| for (i = 0; i < ARRAY_LENGTH(format_map); i++) |
| if (format_map[i].cairo == fmt) |
| return format_map[i].pixman; |
| |
| assert(0 && "unknown Cairo pixel format"); |
| } |
| |
| static cairo_format_t |
| format_pixman2cairo(pixman_format_code_t fmt) |
| { |
| unsigned i; |
| |
| for (i = 0; i < ARRAY_LENGTH(format_map); i++) |
| if (format_map[i].pixman == fmt) |
| return format_map[i].cairo; |
| |
| assert(0 && "unknown Pixman pixel format"); |
| } |
| |
| /** |
| * Compute the ROI for image comparisons |
| * |
| * \param img_a An image. |
| * \param img_b Another image. |
| * \param clip_rect Explicit ROI, or NULL for using the whole |
| * image area. |
| * |
| * \return The region of interest (ROI) that is guaranteed to be inside both |
| * images. |
| * |
| * If clip_rect is given, it must fall inside of both images. |
| * If clip_rect is NULL, the images must be of the same size. |
| * If any precondition is violated, this function aborts with an error. |
| * |
| * The ROI is given as pixman_box32_t, where x2,y2 are non-inclusive. |
| */ |
| static pixman_box32_t |
| image_check_get_roi(pixman_image_t *img_a, pixman_image_t *img_b, |
| const struct rectangle *clip_rect) |
| { |
| int width_a; |
| int width_b; |
| int height_a; |
| int height_b; |
| pixman_box32_t box; |
| |
| width_a = pixman_image_get_width(img_a); |
| height_a = pixman_image_get_height(img_a); |
| |
| width_b = pixman_image_get_width(img_b); |
| height_b = pixman_image_get_height(img_b); |
| |
| if (clip_rect) { |
| box.x1 = clip_rect->x; |
| box.y1 = clip_rect->y; |
| box.x2 = clip_rect->x + clip_rect->width; |
| box.y2 = clip_rect->y + clip_rect->height; |
| } else { |
| box.x1 = 0; |
| box.y1 = 0; |
| box.x2 = max(width_a, width_b); |
| box.y2 = max(height_a, height_b); |
| } |
| |
| assert(box.x1 >= 0); |
| assert(box.y1 >= 0); |
| assert(box.x2 > box.x1); |
| assert(box.y2 > box.y1); |
| assert(box.x2 <= width_a); |
| assert(box.x2 <= width_b); |
| assert(box.y2 <= height_a); |
| assert(box.y2 <= height_b); |
| |
| return box; |
| } |
| |
| struct image_iterator { |
| char *data; |
| int stride; /* bytes */ |
| }; |
| |
| static void |
| image_iter_init(struct image_iterator *it, pixman_image_t *image) |
| { |
| pixman_format_code_t fmt; |
| |
| it->stride = pixman_image_get_stride(image); |
| it->data = (void *)pixman_image_get_data(image); |
| |
| fmt = pixman_image_get_format(image); |
| assert(PIXMAN_FORMAT_BPP(fmt) == 32); |
| } |
| |
| static uint32_t * |
| image_iter_get_row(struct image_iterator *it, int y) |
| { |
| return (uint32_t *)(it->data + y * it->stride); |
| } |
| |
| /** |
| * Test if a given region within two images are pixel-identical. |
| * |
| * Returns true if the two images pixel-wise identical, and false otherwise. |
| * |
| * \param img_a First image. |
| * \param img_b Second image. |
| * \param clip_rect The region of interest, or NULL for comparing the whole |
| * images. |
| * |
| * This function hard-fails if clip_rect is not inside both images. If clip_rect |
| * is given, the images do not have to match in size, otherwise size mismatch |
| * will be a hard failure. |
| */ |
| bool |
| check_images_match(pixman_image_t *img_a, pixman_image_t *img_b, |
| const struct rectangle *clip_rect) |
| { |
| struct image_iterator it_a; |
| struct image_iterator it_b; |
| pixman_box32_t box; |
| int x, y; |
| uint32_t *pix_a; |
| uint32_t *pix_b; |
| |
| box = image_check_get_roi(img_a, img_b, clip_rect); |
| |
| image_iter_init(&it_a, img_a); |
| image_iter_init(&it_b, img_b); |
| |
| for (y = box.y1; y < box.y2; y++) { |
| pix_a = image_iter_get_row(&it_a, y) + box.x1; |
| pix_b = image_iter_get_row(&it_b, y) + box.x1; |
| |
| for (x = box.x1; x < box.x2; x++) { |
| if (*pix_a != *pix_b) |
| return false; |
| |
| pix_a++; |
| pix_b++; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Tint a color |
| * |
| * \param src Source pixel as x8r8g8b8. |
| * \param add The tint as x8r8g8b8, x8 must be zero; r8, g8 and b8 must be |
| * no greater than 0xc0 to avoid overflow to another channel. |
| * \return The tinted pixel color as x8r8g8b8, x8 guaranteed to be 0xff. |
| * |
| * The source pixel RGB values are divided by 4, and then the tint is added. |
| * To achieve colors outside of the range of src, a tint color channel must be |
| * at least 0x40. (0xff / 4 = 0x3f, 0xff - 0x3f = 0xc0) |
| */ |
| static uint32_t |
| tint(uint32_t src, uint32_t add) |
| { |
| uint32_t v; |
| |
| v = ((src & 0xfcfcfcfc) >> 2) | 0xff000000; |
| |
| return v + add; |
| } |
| |
| /** |
| * Create a visualization of image differences. |
| * |
| * \param img_a First image, which is used as the basis for the output. |
| * \param img_b Second image. |
| * \param clip_rect The region of interest, or NULL for comparing the whole |
| * images. |
| * \return A new image with the differences highlighted. |
| * |
| * Regions outside of the region of interest are shaded with black, matching |
| * pixels are shaded with green, and differing pixels are shaded with |
| * bright red. |
| * |
| * This function hard-fails if clip_rect is not inside both images. If clip_rect |
| * is given, the images do not have to match in size, otherwise size mismatch |
| * will be a hard failure. |
| */ |
| pixman_image_t * |
| visualize_image_difference(pixman_image_t *img_a, pixman_image_t *img_b, |
| const struct rectangle *clip_rect) |
| { |
| pixman_image_t *diffimg; |
| pixman_image_t *shade; |
| struct image_iterator it_a; |
| struct image_iterator it_b; |
| struct image_iterator it_d; |
| int width; |
| int height; |
| pixman_box32_t box; |
| int x, y; |
| uint32_t *pix_a; |
| uint32_t *pix_b; |
| uint32_t *pix_d; |
| pixman_color_t shade_color = { 0, 0, 0, 32768 }; |
| |
| width = pixman_image_get_width(img_a); |
| height = pixman_image_get_height(img_a); |
| box = image_check_get_roi(img_a, img_b, clip_rect); |
| |
| diffimg = pixman_image_create_bits_no_clear(PIXMAN_x8r8g8b8, |
| width, height, NULL, 0); |
| |
| /* Fill diffimg with a black-shaded copy of img_a, and then fill |
| * the clip_rect area with original img_a. |
| */ |
| shade = pixman_image_create_solid_fill(&shade_color); |
| pixman_image_composite32(PIXMAN_OP_SRC, img_a, shade, diffimg, |
| 0, 0, 0, 0, 0, 0, width, height); |
| pixman_image_unref(shade); |
| pixman_image_composite32(PIXMAN_OP_SRC, img_a, NULL, diffimg, |
| box.x1, box.y1, 0, 0, box.x1, box.y1, |
| box.x2 - box.x1, box.y2 - box.y1); |
| |
| image_iter_init(&it_a, img_a); |
| image_iter_init(&it_b, img_b); |
| image_iter_init(&it_d, diffimg); |
| |
| for (y = box.y1; y < box.y2; y++) { |
| pix_a = image_iter_get_row(&it_a, y) + box.x1; |
| pix_b = image_iter_get_row(&it_b, y) + box.x1; |
| pix_d = image_iter_get_row(&it_d, y) + box.x1; |
| |
| for (x = box.x1; x < box.x2; x++) { |
| if (*pix_a == *pix_b) |
| *pix_d = tint(*pix_d, 0x00008000); /* green */ |
| else |
| *pix_d = tint(*pix_d, 0x00c00000); /* red */ |
| |
| pix_a++; |
| pix_b++; |
| pix_d++; |
| } |
| } |
| |
| return diffimg; |
| } |
| |
| /** |
| * Write an image into a PNG file. |
| * |
| * \param image The image. |
| * \param fname The name and path for the file. |
| * |
| * \returns true if successfully saved file; false otherwise. |
| * |
| * \note Only image formats directly supported by Cairo are accepted, not all |
| * Pixman formats. |
| */ |
| bool |
| write_image_as_png(pixman_image_t *image, const char *fname) |
| { |
| cairo_surface_t *cairo_surface; |
| cairo_status_t status; |
| cairo_format_t fmt; |
| |
| fmt = format_pixman2cairo(pixman_image_get_format(image)); |
| |
| cairo_surface = cairo_image_surface_create_for_data( |
| (void *)pixman_image_get_data(image), |
| fmt, |
| pixman_image_get_width(image), |
| pixman_image_get_height(image), |
| pixman_image_get_stride(image)); |
| |
| status = cairo_surface_write_to_png(cairo_surface, fname); |
| if (status != CAIRO_STATUS_SUCCESS) { |
| fprintf(stderr, "Failed to save image '%s': %s\n", fname, |
| cairo_status_to_string(status)); |
| |
| return false; |
| } |
| |
| cairo_surface_destroy(cairo_surface); |
| |
| return true; |
| } |
| |
| static pixman_image_t * |
| image_convert_to_a8r8g8b8(pixman_image_t *image) |
| { |
| pixman_image_t *ret; |
| int width; |
| int height; |
| |
| if (pixman_image_get_format(image) == PIXMAN_a8r8g8b8) |
| return pixman_image_ref(image); |
| |
| width = pixman_image_get_width(image); |
| height = pixman_image_get_height(image); |
| |
| ret = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8, width, height, |
| NULL, 0); |
| assert(ret); |
| |
| pixman_image_composite32(PIXMAN_OP_SRC, image, NULL, ret, |
| 0, 0, 0, 0, 0, 0, width, height); |
| |
| return ret; |
| } |
| |
| static void |
| destroy_cairo_surface(pixman_image_t *image, void *data) |
| { |
| cairo_surface_t *surface = data; |
| |
| cairo_surface_destroy(surface); |
| } |
| |
| /** |
| * Load an image from a PNG file |
| * |
| * Reads a PNG image from disk using the given filename (and path) |
| * and returns as a Pixman image. Use pixman_image_unref() to free it. |
| * |
| * The returned image is always in PIXMAN_a8r8g8b8 format. |
| * |
| * @returns Pixman image, or NULL in case of error. |
| */ |
| pixman_image_t * |
| load_image_from_png(const char *fname) |
| { |
| pixman_image_t *image; |
| pixman_image_t *converted; |
| cairo_format_t cairo_fmt; |
| pixman_format_code_t pixman_fmt; |
| cairo_surface_t *reference_cairo_surface; |
| cairo_status_t status; |
| int width; |
| int height; |
| int stride; |
| void *data; |
| |
| reference_cairo_surface = cairo_image_surface_create_from_png(fname); |
| cairo_surface_flush(reference_cairo_surface); |
| status = cairo_surface_status(reference_cairo_surface); |
| if (status != CAIRO_STATUS_SUCCESS) { |
| printf("Could not open %s: %s\n", fname, cairo_status_to_string(status)); |
| cairo_surface_destroy(reference_cairo_surface); |
| return NULL; |
| } |
| |
| cairo_fmt = cairo_image_surface_get_format(reference_cairo_surface); |
| pixman_fmt = format_cairo2pixman(cairo_fmt); |
| |
| width = cairo_image_surface_get_width(reference_cairo_surface); |
| height = cairo_image_surface_get_height(reference_cairo_surface); |
| stride = cairo_image_surface_get_stride(reference_cairo_surface); |
| data = cairo_image_surface_get_data(reference_cairo_surface); |
| |
| /* The Cairo surface will own the data, so we keep it around. */ |
| image = pixman_image_create_bits_no_clear(pixman_fmt, |
| width, height, data, stride); |
| assert(image); |
| |
| pixman_image_set_destroy_function(image, destroy_cairo_surface, |
| reference_cairo_surface); |
| |
| converted = image_convert_to_a8r8g8b8(image); |
| pixman_image_unref(image); |
| |
| return converted; |
| } |
| |
| /** |
| * Take screenshot of a single output |
| * |
| * Requests a screenshot from the server of the output that the |
| * client appears on. This implies that the compositor goes through an output |
| * repaint to provide the screenshot before this function returns. This |
| * function is therefore both a server roundtrip and a wait for a repaint. |
| * |
| * @returns A new buffer object, that should be freed with buffer_destroy(). |
| */ |
| struct buffer * |
| capture_screenshot_of_output(struct client *client) |
| { |
| struct buffer *buffer; |
| |
| buffer = create_shm_buffer_a8r8g8b8(client, |
| client->output->width, |
| client->output->height); |
| |
| client->test->buffer_copy_done = 0; |
| weston_test_capture_screenshot(client->test->weston_test, |
| client->output->wl_output, |
| buffer->proxy); |
| while (client->test->buffer_copy_done == 0) |
| if (wl_display_dispatch(client->wl_display) < 0) |
| break; |
| |
| /* FIXME: Document somewhere the orientation the screenshot is taken |
| * and how the clip coords are interpreted, in case of scaling/transform. |
| * If we're using read_pixels() just make sure it is documented somewhere. |
| * Protocol docs in the XML, comparison function docs in Doxygen style. |
| */ |
| |
| return buffer; |
| } |