/* * 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 #include #include #include #include #include #include "compositor.h" #include "compositor/weston.h" #include "weston-test-server-protocol.h" #ifdef ENABLE_EGL #include #include #include "weston-egl-ext.h" #endif /* ENABLE_EGL */ #include "shared/helpers.h" #include "shared/timespec-util.h" struct weston_test { struct weston_compositor *compositor; struct weston_layer layer; struct weston_process process; struct weston_seat seat; bool is_seat_initialized; }; struct weston_test_surface { struct weston_surface *surface; struct weston_view *view; int32_t x, y; struct weston_test *test; }; static void test_client_sigchld(struct weston_process *process, int status) { struct weston_test *test = container_of(process, struct weston_test, process); /* Chain up from weston-test-runner's exit code so that automake * knows the exit status and can report e.g. skipped tests. */ if (WIFEXITED(status) && WEXITSTATUS(status) != 0) exit(WEXITSTATUS(status)); /* In case the child aborted or segfaulted... */ assert(status == 0); wl_display_terminate(test->compositor->wl_display); } static int test_seat_init(struct weston_test *test) { assert(!test->is_seat_initialized && "Trying to add already added test seat"); /* create our own seat */ weston_seat_init(&test->seat, test->compositor, "test-seat"); test->is_seat_initialized = true; /* add devices */ weston_seat_init_pointer(&test->seat); if (weston_seat_init_keyboard(&test->seat, NULL) < 0) return -1; weston_seat_init_touch(&test->seat); return 0; } static void test_seat_release(struct weston_test *test) { assert(test->is_seat_initialized && "Trying to release already released test seat"); test->is_seat_initialized = false; weston_seat_release(&test->seat); memset(&test->seat, 0, sizeof test->seat); } static struct weston_seat * get_seat(struct weston_test *test) { return &test->seat; } static void notify_pointer_position(struct weston_test *test, struct wl_resource *resource) { struct weston_seat *seat = get_seat(test); struct weston_pointer *pointer = weston_seat_get_pointer(seat); weston_test_send_pointer_position(resource, pointer->x, pointer->y); } static void test_surface_committed(struct weston_surface *surface, int32_t sx, int32_t sy) { struct weston_test_surface *test_surface = surface->committed_private; struct weston_test *test = test_surface->test; if (wl_list_empty(&test_surface->view->layer_link.link)) weston_layer_entry_insert(&test->layer.view_list, &test_surface->view->layer_link); weston_view_set_position(test_surface->view, test_surface->x, test_surface->y); weston_view_update_transform(test_surface->view); test_surface->surface->is_mapped = true; test_surface->view->is_mapped = true; } static void move_surface(struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface_resource, int32_t x, int32_t y) { struct weston_surface *surface = wl_resource_get_user_data(surface_resource); struct weston_test_surface *test_surface; test_surface = surface->committed_private; if (!test_surface) { test_surface = malloc(sizeof *test_surface); if (!test_surface) { wl_resource_post_no_memory(resource); return; } test_surface->view = weston_view_create(surface); if (!test_surface->view) { wl_resource_post_no_memory(resource); free(test_surface); return; } surface->committed_private = test_surface; surface->committed = test_surface_committed; } test_surface->surface = surface; test_surface->test = wl_resource_get_user_data(resource); test_surface->x = x; test_surface->y = y; } static void move_pointer(struct wl_client *client, struct wl_resource *resource, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, int32_t x, int32_t y) { struct weston_test *test = wl_resource_get_user_data(resource); struct weston_seat *seat = get_seat(test); struct weston_pointer *pointer = weston_seat_get_pointer(seat); struct weston_pointer_motion_event event = { 0 }; struct timespec time; event = (struct weston_pointer_motion_event) { .mask = WESTON_POINTER_MOTION_REL, .dx = wl_fixed_to_double(wl_fixed_from_int(x) - pointer->x), .dy = wl_fixed_to_double(wl_fixed_from_int(y) - pointer->y), }; timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); notify_motion(seat, &time, &event); notify_pointer_position(test, resource); } static void send_button(struct wl_client *client, struct wl_resource *resource, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, int32_t button, uint32_t state) { struct timespec time; struct weston_test *test = wl_resource_get_user_data(resource); struct weston_seat *seat = get_seat(test); timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); notify_button(seat, &time, button, state); } static void send_axis(struct wl_client *client, struct wl_resource *resource, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, uint32_t axis, wl_fixed_t value) { struct weston_test *test = wl_resource_get_user_data(resource); struct weston_seat *seat = get_seat(test); struct timespec time; struct weston_pointer_axis_event axis_event; timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); axis_event.axis = axis; axis_event.value = wl_fixed_to_double(value); axis_event.has_discrete = false; axis_event.discrete = 0; notify_axis(seat, &time, &axis_event); } static void activate_surface(struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface_resource) { struct weston_surface *surface = surface_resource ? wl_resource_get_user_data(surface_resource) : NULL; struct weston_test *test = wl_resource_get_user_data(resource); struct weston_seat *seat; struct weston_keyboard *keyboard; seat = get_seat(test); keyboard = weston_seat_get_keyboard(seat); if (surface) { weston_seat_set_keyboard_focus(seat, surface); notify_keyboard_focus_in(seat, &keyboard->keys, STATE_UPDATE_AUTOMATIC); } else { notify_keyboard_focus_out(seat); weston_seat_set_keyboard_focus(seat, surface); } } static void send_key(struct wl_client *client, struct wl_resource *resource, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, uint32_t key, enum wl_keyboard_key_state state) { struct weston_test *test = wl_resource_get_user_data(resource); struct weston_seat *seat = get_seat(test); struct timespec time; timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); notify_key(seat, &time, key, state, STATE_UPDATE_AUTOMATIC); } static void device_release(struct wl_client *client, struct wl_resource *resource, const char *device) { struct weston_test *test = wl_resource_get_user_data(resource); struct weston_seat *seat = get_seat(test); if (strcmp(device, "pointer") == 0) { weston_seat_release_pointer(seat); } else if (strcmp(device, "keyboard") == 0) { weston_seat_release_keyboard(seat); } else if (strcmp(device, "touch") == 0) { weston_seat_release_touch(seat); } else if (strcmp(device, "seat") == 0) { test_seat_release(test); } else { assert(0 && "Unsupported device"); } } static void device_add(struct wl_client *client, struct wl_resource *resource, const char *device) { struct weston_test *test = wl_resource_get_user_data(resource); struct weston_seat *seat = get_seat(test); if (strcmp(device, "pointer") == 0) { weston_seat_init_pointer(seat); } else if (strcmp(device, "keyboard") == 0) { weston_seat_init_keyboard(seat, NULL); } else if (strcmp(device, "touch") == 0) { weston_seat_init_touch(seat); } else if (strcmp(device, "seat") == 0) { test_seat_init(test); } else { assert(0 && "Unsupported device"); } } enum weston_test_screenshot_outcome { WESTON_TEST_SCREENSHOT_SUCCESS, WESTON_TEST_SCREENSHOT_NO_MEMORY, WESTON_TEST_SCREENSHOT_BAD_BUFFER }; typedef void (*weston_test_screenshot_done_func_t)(void *data, enum weston_test_screenshot_outcome outcome); struct test_screenshot { struct weston_compositor *compositor; struct wl_global *global; struct wl_client *client; struct weston_process process; struct wl_listener destroy_listener; }; struct test_screenshot_frame_listener { struct wl_listener listener; struct weston_buffer *buffer; weston_test_screenshot_done_func_t done; void *data; }; static void copy_bgra_yflip(uint8_t *dst, uint8_t *src, int height, int stride) { uint8_t *end; end = dst + height * stride; while (dst < end) { memcpy(dst, src, stride); dst += stride; src -= stride; } } static void copy_bgra(uint8_t *dst, uint8_t *src, int height, int stride) { /* TODO: optimize this out */ memcpy(dst, src, height * stride); } static void copy_row_swap_RB(void *vdst, void *vsrc, int bytes) { uint32_t *dst = vdst; uint32_t *src = vsrc; uint32_t *end = dst + bytes / 4; while (dst < end) { uint32_t v = *src++; /* A R G B */ uint32_t tmp = v & 0xff00ff00; tmp |= (v >> 16) & 0x000000ff; tmp |= (v << 16) & 0x00ff0000; *dst++ = tmp; } } static void copy_rgba_yflip(uint8_t *dst, uint8_t *src, int height, int stride) { uint8_t *end; end = dst + height * stride; while (dst < end) { copy_row_swap_RB(dst, src, stride); dst += stride; src -= stride; } } static void copy_rgba(uint8_t *dst, uint8_t *src, int height, int stride) { uint8_t *end; end = dst + height * stride; while (dst < end) { copy_row_swap_RB(dst, src, stride); dst += stride; src += stride; } } static void test_screenshot_frame_notify(struct wl_listener *listener, void *data) { struct test_screenshot_frame_listener *l = container_of(listener, struct test_screenshot_frame_listener, listener); struct weston_output *output = data; struct weston_compositor *compositor = output->compositor; int32_t stride; uint8_t *pixels, *d, *s; output->disable_planes--; wl_list_remove(&listener->link); stride = l->buffer->width * (PIXMAN_FORMAT_BPP(compositor->read_format) / 8); pixels = malloc(stride * l->buffer->height); if (pixels == NULL) { l->done(l->data, WESTON_TEST_SCREENSHOT_NO_MEMORY); free(l); return; } /* FIXME: Needs to handle output transformations */ compositor->renderer->read_pixels(output, compositor->read_format, pixels, 0, 0, output->current_mode->width, output->current_mode->height); stride = wl_shm_buffer_get_stride(l->buffer->shm_buffer); d = wl_shm_buffer_get_data(l->buffer->shm_buffer); s = pixels + stride * (l->buffer->height - 1); wl_shm_buffer_begin_access(l->buffer->shm_buffer); /* XXX: It would be nice if we used Pixman to do all this rather * than our own implementation */ switch (compositor->read_format) { case PIXMAN_a8r8g8b8: case PIXMAN_x8r8g8b8: if (compositor->capabilities & WESTON_CAP_CAPTURE_YFLIP) copy_bgra_yflip(d, s, output->current_mode->height, stride); else copy_bgra(d, pixels, output->current_mode->height, stride); break; case PIXMAN_x8b8g8r8: case PIXMAN_a8b8g8r8: if (compositor->capabilities & WESTON_CAP_CAPTURE_YFLIP) copy_rgba_yflip(d, s, output->current_mode->height, stride); else copy_rgba(d, pixels, output->current_mode->height, stride); break; default: break; } wl_shm_buffer_end_access(l->buffer->shm_buffer); l->done(l->data, WESTON_TEST_SCREENSHOT_SUCCESS); free(pixels); free(l); } static bool weston_test_screenshot_shoot(struct weston_output *output, struct weston_buffer *buffer, weston_test_screenshot_done_func_t done, void *data) { struct test_screenshot_frame_listener *l; /* Get the shm buffer resource the client created */ if (!wl_shm_buffer_get(buffer->resource)) { done(data, WESTON_TEST_SCREENSHOT_BAD_BUFFER); return false; } buffer->shm_buffer = wl_shm_buffer_get(buffer->resource); buffer->width = wl_shm_buffer_get_width(buffer->shm_buffer); buffer->height = wl_shm_buffer_get_height(buffer->shm_buffer); /* Verify buffer is big enough */ if (buffer->width < output->current_mode->width || buffer->height < output->current_mode->height) { done(data, WESTON_TEST_SCREENSHOT_BAD_BUFFER); return false; } /* allocate the frame listener */ l = malloc(sizeof *l); if (l == NULL) { done(data, WESTON_TEST_SCREENSHOT_NO_MEMORY); return false; } /* Set up the listener */ l->buffer = buffer; l->done = done; l->data = data; l->listener.notify = test_screenshot_frame_notify; wl_signal_add(&output->frame_signal, &l->listener); /* Fire off a repaint */ output->disable_planes++; weston_output_schedule_repaint(output); return true; } static void capture_screenshot_done(void *data, enum weston_test_screenshot_outcome outcome) { struct wl_resource *resource = data; switch (outcome) { case WESTON_TEST_SCREENSHOT_SUCCESS: weston_test_send_capture_screenshot_done(resource); break; case WESTON_TEST_SCREENSHOT_NO_MEMORY: wl_resource_post_no_memory(resource); break; default: break; } } /** * Grabs a snapshot of the screen. */ static void capture_screenshot(struct wl_client *client, struct wl_resource *resource, struct wl_resource *output_resource, struct wl_resource *buffer_resource) { struct weston_output *output = weston_head_from_resource(output_resource)->output; struct weston_buffer *buffer = weston_buffer_from_resource(buffer_resource); if (buffer == NULL) { wl_resource_post_no_memory(resource); return; } weston_test_screenshot_shoot(output, buffer, capture_screenshot_done, resource); } static void send_touch(struct wl_client *client, struct wl_resource *resource, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, int32_t touch_id, wl_fixed_t x, wl_fixed_t y, uint32_t touch_type) { struct weston_test *test = wl_resource_get_user_data(resource); struct weston_seat *seat = get_seat(test); struct timespec time; timespec_from_proto(&time, tv_sec_hi, tv_sec_lo, tv_nsec); notify_touch(seat, &time, touch_id, wl_fixed_to_double(x), wl_fixed_to_double(y), touch_type); } static const struct weston_test_interface test_implementation = { move_surface, move_pointer, send_button, send_axis, activate_surface, send_key, device_release, device_add, capture_screenshot, send_touch, }; static void bind_test(struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct weston_test *test = data; struct wl_resource *resource; resource = wl_resource_create(client, &weston_test_interface, 1, id); if (!resource) { wl_client_post_no_memory(client); return; } wl_resource_set_implementation(resource, &test_implementation, test, NULL); notify_pointer_position(test, resource); } static void idle_launch_client(void *data) { struct weston_test *test = data; pid_t pid; sigset_t allsigs; char *path; path = getenv("WESTON_TEST_CLIENT_PATH"); if (path == NULL) return; pid = fork(); if (pid == -1) exit(EXIT_FAILURE); if (pid == 0) { sigfillset(&allsigs); sigprocmask(SIG_UNBLOCK, &allsigs, NULL); execl(path, path, NULL); weston_log("compositor: executing '%s' failed: %m\n", path); exit(EXIT_FAILURE); } test->process.pid = pid; test->process.cleanup = test_client_sigchld; weston_watch_process(&test->process); } WL_EXPORT int wet_module_init(struct weston_compositor *ec, int *argc, char *argv[]) { struct weston_test *test; struct wl_event_loop *loop; test = zalloc(sizeof *test); if (test == NULL) return -1; test->compositor = ec; weston_layer_init(&test->layer, ec); weston_layer_set_position(&test->layer, WESTON_LAYER_POSITION_CURSOR - 1); if (wl_global_create(ec->wl_display, &weston_test_interface, 1, test, bind_test) == NULL) return -1; if (test_seat_init(test) == -1) return -1; loop = wl_display_get_event_loop(ec->wl_display); wl_event_loop_add_idle(loop, idle_launch_client, test); return 0; }