82dd6ce830
Instead of relying on Meson setting up environment so that Weston and tests find all their files, build those values into the tests. This way one can execute a test program successfully wihtout Meson, simply by running it. The old environment variables are still honoured if set. This might change in the future. Baking the source or build directory paths into the tests should not regress reproducible builds, because the binaries where test-config.h values are used will not be installed. Signed-off-by: Pekka Paalanen <pekka.paalanen@collabora.com>
1467 lines
37 KiB
C
1467 lines
37 KiB
C
/*
|
|
* 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 "test-config.h"
|
|
#include "shared/os-compatibility.h"
|
|
#include "shared/xalloc.h"
|
|
#include <libweston/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);
|
|
|
|
testlog("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;
|
|
|
|
testlog("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 };
|
|
|
|
testlog("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 };
|
|
|
|
testlog("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 };
|
|
|
|
testlog("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)
|
|
{
|
|
testlog("test-client: got pointer frame\n");
|
|
}
|
|
|
|
static void
|
|
pointer_handle_axis_source(void *data, struct wl_pointer *wl_pointer,
|
|
uint32_t source)
|
|
{
|
|
testlog("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 };
|
|
|
|
testlog("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)
|
|
{
|
|
testlog("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);
|
|
|
|
testlog("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;
|
|
|
|
testlog("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;
|
|
|
|
testlog("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 };
|
|
|
|
testlog("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;
|
|
|
|
testlog("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;
|
|
|
|
testlog("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 };
|
|
|
|
testlog("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 };
|
|
|
|
testlog("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 };
|
|
|
|
testlog("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;
|
|
|
|
testlog("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;
|
|
|
|
testlog("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);
|
|
|
|
testlog("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;
|
|
|
|
testlog("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);
|
|
|
|
testlog("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;
|
|
|
|
testlog("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);
|
|
|
|
testlog("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;
|
|
}
|
|
|
|
testlog("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
|
|
* ninja 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) {
|
|
testlog("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) {
|
|
testlog("Should get interface '%s' but got '%s'\n",
|
|
intf->name, interface->name);
|
|
failed = 1;
|
|
}
|
|
|
|
if (failed) {
|
|
testlog("Expected other protocol error\n");
|
|
abort();
|
|
}
|
|
|
|
/* all OK */
|
|
testlog("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 ".";
|
|
|
|
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 WESTON_TEST_REFERENCE_PATH;
|
|
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) {
|
|
testlog("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) {
|
|
testlog("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;
|
|
}
|