1cc5ac34b5
A wayland compositor doesn't provide a mechanism for buffer sharing between clients. Under X, one client can render to a Pixmap and another can use it as a source in a subsequent drawing operations. Wayland doesn't have a mechanims to share Pixmaps or textures between clients like that, but it's possible for one client to act as a nested compositor to another client. This less work than it sounds, since the nested compositor won't have to provide input devices or even any kind of shell extension. The nested compositor and its client can be very tightly coupled and have very specific expectations of what the other process should provide. In this example, nested.c is a toytoolkit application that uses cairo-gl for rendering and forks and execs nested-client.c. As it execs the client, it passes it one end of a socketpair that will be the clients connection to the nested compositor. The nested compositor doesn't even create a listening socket. The client is a minimal GLES2 application, which just renders a spinning triangle in its frame callback.
609 lines
15 KiB
C
609 lines
15 KiB
C
/*
|
|
* Copyright © 2013 Intel Corporation
|
|
*
|
|
* Permission to use, copy, modify, distribute, and sell this software and its
|
|
* documentation for any purpose is hereby granted without fee, provided that
|
|
* the above copyright notice appear in all copies and that both that copyright
|
|
* notice and this permission notice appear in supporting documentation, and
|
|
* that the name of the copyright holders not be used in advertising or
|
|
* publicity pertaining to distribution of the software without specific,
|
|
* written prior permission. The copyright holders make no representations
|
|
* about the suitability of this software for any purpose. It is provided "as
|
|
* is" without express or implied warranty.
|
|
*
|
|
* THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
|
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
|
|
* EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
|
|
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
|
|
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
|
* OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <cairo.h>
|
|
#include <math.h>
|
|
#include <assert.h>
|
|
#include <pixman.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
|
|
#include <EGL/egl.h>
|
|
#include <EGL/eglext.h>
|
|
#include <GLES2/gl2.h>
|
|
#include <GLES2/gl2ext.h>
|
|
|
|
#include <cairo-gl.h>
|
|
|
|
#include <wayland-client.h>
|
|
#include <wayland-server.h>
|
|
|
|
#include "window.h"
|
|
|
|
struct nested {
|
|
struct display *display;
|
|
struct window *window;
|
|
struct widget *widget;
|
|
struct wl_display *child_display;
|
|
struct task child_task;
|
|
|
|
EGLDisplay egl_display;
|
|
struct program *texture_program;
|
|
|
|
struct wl_list surface_list;
|
|
struct wl_list frame_callback_list;
|
|
};
|
|
|
|
struct nested_region {
|
|
struct wl_resource resource;
|
|
pixman_region32_t region;
|
|
};
|
|
|
|
struct nested_surface {
|
|
struct wl_resource resource;
|
|
struct wl_resource *buffer_resource;
|
|
struct nested *nested;
|
|
EGLImageKHR *image;
|
|
GLuint texture;
|
|
struct wl_list link;
|
|
cairo_surface_t *cairo_surface;
|
|
};
|
|
|
|
struct nested_frame_callback {
|
|
struct wl_resource resource;
|
|
struct wl_list link;
|
|
};
|
|
|
|
static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC image_target_texture_2d;
|
|
static PFNEGLCREATEIMAGEKHRPROC create_image;
|
|
static PFNEGLDESTROYIMAGEKHRPROC destroy_image;
|
|
static PFNEGLBINDWAYLANDDISPLAYWL bind_display;
|
|
static PFNEGLUNBINDWAYLANDDISPLAYWL unbind_display;
|
|
static PFNEGLQUERYWAYLANDBUFFERWL query_buffer;
|
|
|
|
static void
|
|
frame_callback(void *data, struct wl_callback *callback, uint32_t time)
|
|
{
|
|
struct nested *nested = data;
|
|
struct nested_frame_callback *nc, *next;
|
|
|
|
if (callback)
|
|
wl_callback_destroy(callback);
|
|
|
|
wl_list_for_each_safe(nc, next, &nested->frame_callback_list, link) {
|
|
wl_callback_send_done(&nc->resource, time);
|
|
wl_resource_destroy(&nc->resource);
|
|
}
|
|
wl_list_init(&nested->frame_callback_list);
|
|
|
|
/* FIXME: toytoolkit need a pre-block handler where we can
|
|
* call this. */
|
|
wl_display_flush_clients(nested->child_display);
|
|
}
|
|
|
|
static const struct wl_callback_listener frame_listener = {
|
|
frame_callback
|
|
};
|
|
|
|
static void
|
|
redraw_handler(struct widget *widget, void *data)
|
|
{
|
|
struct nested *nested = data;
|
|
cairo_surface_t *surface;
|
|
cairo_t *cr;
|
|
struct rectangle allocation;
|
|
struct wl_callback *callback;
|
|
struct nested_surface *s;
|
|
|
|
widget_get_allocation(nested->widget, &allocation);
|
|
|
|
surface = window_get_surface(nested->window);
|
|
|
|
cr = cairo_create(surface);
|
|
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
|
|
cairo_rectangle(cr,
|
|
allocation.x,
|
|
allocation.y,
|
|
allocation.width,
|
|
allocation.height);
|
|
cairo_set_source_rgba(cr, 0, 0, 0, 0.8);
|
|
cairo_fill(cr);
|
|
|
|
wl_list_for_each(s, &nested->surface_list, link) {
|
|
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
|
|
cairo_set_source_surface(cr, s->cairo_surface,
|
|
allocation.x + 10,
|
|
allocation.y + 10);
|
|
cairo_rectangle(cr, allocation.x + 10,
|
|
allocation.y + 10,
|
|
allocation.width - 10,
|
|
allocation.height - 10);
|
|
|
|
cairo_fill(cr);
|
|
}
|
|
|
|
cairo_destroy(cr);
|
|
|
|
cairo_surface_destroy(surface);
|
|
|
|
callback = wl_surface_frame(window_get_wl_surface(nested->window));
|
|
wl_callback_add_listener(callback, &frame_listener, nested);
|
|
}
|
|
|
|
static void
|
|
keyboard_focus_handler(struct window *window,
|
|
struct input *device, void *data)
|
|
{
|
|
struct nested *nested = data;
|
|
|
|
window_schedule_redraw(nested->window);
|
|
}
|
|
|
|
static void
|
|
handle_child_data(struct task *task, uint32_t events)
|
|
{
|
|
struct nested *nested = container_of(task, struct nested, child_task);
|
|
struct wl_event_loop *loop;
|
|
|
|
loop = wl_display_get_event_loop(nested->child_display);
|
|
|
|
wl_event_loop_dispatch(loop, -1);
|
|
wl_display_flush_clients(nested->child_display);
|
|
}
|
|
|
|
struct nested_client {
|
|
struct wl_client *client;
|
|
pid_t pid;
|
|
};
|
|
|
|
static struct nested_client *
|
|
launch_client(struct nested *nested, const char *path)
|
|
{
|
|
int sv[2];
|
|
pid_t pid;
|
|
struct nested_client *client;
|
|
|
|
client = malloc(sizeof *client);
|
|
if (client == NULL)
|
|
return NULL;
|
|
|
|
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) < 0) {
|
|
fprintf(stderr, "launch_client: "
|
|
"socketpair failed while launching '%s': %m\n",
|
|
path);
|
|
return NULL;
|
|
}
|
|
|
|
pid = fork();
|
|
if (pid == -1) {
|
|
close(sv[0]);
|
|
close(sv[1]);
|
|
fprintf(stderr, "launch_client: "
|
|
"fork failed while launching '%s': %m\n", path);
|
|
return NULL;
|
|
}
|
|
|
|
if (pid == 0) {
|
|
int clientfd;
|
|
char s[32];
|
|
|
|
/* SOCK_CLOEXEC closes both ends, so we dup the fd to
|
|
* get a non-CLOEXEC fd to pass through exec. */
|
|
clientfd = dup(sv[1]);
|
|
if (clientfd == -1) {
|
|
fprintf(stderr, "compositor: dup failed: %m\n");
|
|
exit(-1);
|
|
}
|
|
|
|
snprintf(s, sizeof s, "%d", clientfd);
|
|
setenv("WAYLAND_SOCKET", s, 1);
|
|
|
|
execl(path, path, NULL);
|
|
|
|
fprintf(stderr, "compositor: executing '%s' failed: %m\n",
|
|
path);
|
|
exit(-1);
|
|
}
|
|
|
|
close(sv[1]);
|
|
|
|
client->client = wl_client_create(nested->child_display, sv[0]);
|
|
if (!client->client) {
|
|
close(sv[0]);
|
|
fprintf(stderr, "launch_client: "
|
|
"wl_client_create failed while launching '%s'.\n",
|
|
path);
|
|
return NULL;
|
|
}
|
|
|
|
client->pid = pid;
|
|
|
|
return client;
|
|
}
|
|
|
|
static void
|
|
destroy_surface(struct wl_resource *resource)
|
|
{
|
|
struct nested_surface *surface =
|
|
container_of(resource, struct nested_surface, resource);
|
|
|
|
free(surface);
|
|
}
|
|
|
|
static void
|
|
surface_destroy(struct wl_client *client, struct wl_resource *resource)
|
|
{
|
|
wl_resource_destroy(resource);
|
|
}
|
|
|
|
static void
|
|
surface_attach(struct wl_client *client,
|
|
struct wl_resource *resource,
|
|
struct wl_resource *buffer_resource, int32_t sx, int32_t sy)
|
|
{
|
|
struct nested_surface *surface = resource->data;
|
|
struct nested *nested = surface->nested;
|
|
struct wl_buffer *buffer = buffer_resource->data;
|
|
EGLint format, width, height;
|
|
cairo_device_t *device;
|
|
|
|
if (surface->buffer_resource)
|
|
wl_buffer_send_release(surface->buffer_resource);
|
|
|
|
surface->buffer_resource = buffer_resource;
|
|
if (!query_buffer(nested->egl_display, buffer,
|
|
EGL_TEXTURE_FORMAT, &format)) {
|
|
fprintf(stderr, "attaching non-egl wl_buffer\n");
|
|
return;
|
|
}
|
|
|
|
if (surface->image != EGL_NO_IMAGE_KHR)
|
|
destroy_image(nested->egl_display, surface->image);
|
|
if (surface->cairo_surface)
|
|
cairo_surface_destroy(surface->cairo_surface);
|
|
|
|
switch (format) {
|
|
case EGL_TEXTURE_RGB:
|
|
case EGL_TEXTURE_RGBA:
|
|
break;
|
|
default:
|
|
fprintf(stderr, "unhandled format: %x\n", format);
|
|
return;
|
|
}
|
|
|
|
surface->image = create_image(nested->egl_display, NULL,
|
|
EGL_WAYLAND_BUFFER_WL, buffer, NULL);
|
|
if (surface->image == EGL_NO_IMAGE_KHR) {
|
|
fprintf(stderr, "failed to create img\n");
|
|
return;
|
|
}
|
|
|
|
query_buffer(nested->egl_display, buffer, EGL_WIDTH, &width);
|
|
query_buffer(nested->egl_display, buffer, EGL_HEIGHT, &height);
|
|
|
|
device = display_get_cairo_device(nested->display);
|
|
surface->cairo_surface =
|
|
cairo_gl_surface_create_for_texture(device,
|
|
CAIRO_CONTENT_COLOR_ALPHA,
|
|
surface->texture,
|
|
width, height);
|
|
|
|
glBindTexture(GL_TEXTURE_2D, surface->texture);
|
|
image_target_texture_2d(GL_TEXTURE_2D, surface->image);
|
|
|
|
window_schedule_redraw(nested->window);
|
|
}
|
|
|
|
static void
|
|
surface_damage(struct wl_client *client,
|
|
struct wl_resource *resource,
|
|
int32_t x, int32_t y, int32_t width, int32_t height)
|
|
{
|
|
}
|
|
|
|
static void
|
|
surface_frame(struct wl_client *client,
|
|
struct wl_resource *resource, uint32_t id)
|
|
{
|
|
struct nested_frame_callback *callback;
|
|
struct nested_surface *surface = resource->data;
|
|
struct nested *nested = surface->nested;
|
|
|
|
callback = malloc(sizeof *callback);
|
|
if (callback == NULL) {
|
|
wl_resource_post_no_memory(resource);
|
|
return;
|
|
}
|
|
|
|
wl_resource_init(&callback->resource, &wl_callback_interface,
|
|
NULL, id, callback);
|
|
|
|
wl_client_add_resource(client, &callback->resource);
|
|
wl_list_insert(nested->frame_callback_list.prev, &callback->link);
|
|
}
|
|
|
|
static void
|
|
surface_set_opaque_region(struct wl_client *client,
|
|
struct wl_resource *resource,
|
|
struct wl_resource *region_resource)
|
|
{
|
|
fprintf(stderr, "surface_set_opaque_region\n");
|
|
}
|
|
|
|
static void
|
|
surface_set_input_region(struct wl_client *client,
|
|
struct wl_resource *resource,
|
|
struct wl_resource *region_resource)
|
|
{
|
|
fprintf(stderr, "surface_set_input_region\n");
|
|
}
|
|
|
|
static void
|
|
surface_commit(struct wl_client *client, struct wl_resource *resource)
|
|
{
|
|
}
|
|
|
|
static void
|
|
surface_set_buffer_transform(struct wl_client *client,
|
|
struct wl_resource *resource, int transform)
|
|
{
|
|
fprintf(stderr, "surface_set_buffer_transform\n");
|
|
}
|
|
|
|
static const struct wl_surface_interface surface_interface = {
|
|
surface_destroy,
|
|
surface_attach,
|
|
surface_damage,
|
|
surface_frame,
|
|
surface_set_opaque_region,
|
|
surface_set_input_region,
|
|
surface_commit,
|
|
surface_set_buffer_transform
|
|
};
|
|
|
|
static void
|
|
compositor_create_surface(struct wl_client *client,
|
|
struct wl_resource *resource, uint32_t id)
|
|
{
|
|
struct nested *nested = resource->data;
|
|
struct nested_surface *surface;
|
|
|
|
surface = malloc(sizeof *surface);
|
|
if (surface == NULL) {
|
|
wl_resource_post_no_memory(resource);
|
|
return;
|
|
}
|
|
|
|
memset(surface, 0, sizeof *surface);
|
|
surface->nested = nested;
|
|
|
|
display_acquire_window_surface(nested->display,
|
|
nested->window, NULL);
|
|
|
|
glGenTextures(1, &surface->texture);
|
|
glBindTexture(GL_TEXTURE_2D, surface->texture);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
|
|
display_release_window_surface(nested->display, nested->window);
|
|
|
|
wl_resource_init(&surface->resource, &wl_surface_interface,
|
|
&surface_interface, id, surface);
|
|
surface->resource.destroy = destroy_surface;
|
|
|
|
wl_client_add_resource(client, &surface->resource);
|
|
|
|
wl_list_insert(nested->surface_list.prev, &surface->link);
|
|
}
|
|
|
|
static void
|
|
destroy_region(struct wl_resource *resource)
|
|
{
|
|
struct nested_region *region =
|
|
container_of(resource, struct nested_region, resource);
|
|
|
|
pixman_region32_fini(®ion->region);
|
|
free(region);
|
|
}
|
|
|
|
static void
|
|
region_destroy(struct wl_client *client, struct wl_resource *resource)
|
|
{
|
|
wl_resource_destroy(resource);
|
|
}
|
|
|
|
static void
|
|
region_add(struct wl_client *client, struct wl_resource *resource,
|
|
int32_t x, int32_t y, int32_t width, int32_t height)
|
|
{
|
|
struct nested_region *region = resource->data;
|
|
|
|
pixman_region32_union_rect(®ion->region, ®ion->region,
|
|
x, y, width, height);
|
|
}
|
|
|
|
static void
|
|
region_subtract(struct wl_client *client, struct wl_resource *resource,
|
|
int32_t x, int32_t y, int32_t width, int32_t height)
|
|
{
|
|
struct nested_region *region = resource->data;
|
|
pixman_region32_t rect;
|
|
|
|
pixman_region32_init_rect(&rect, x, y, width, height);
|
|
pixman_region32_subtract(®ion->region, ®ion->region, &rect);
|
|
pixman_region32_fini(&rect);
|
|
}
|
|
|
|
static const struct wl_region_interface region_interface = {
|
|
region_destroy,
|
|
region_add,
|
|
region_subtract
|
|
};
|
|
|
|
static void
|
|
compositor_create_region(struct wl_client *client,
|
|
struct wl_resource *resource, uint32_t id)
|
|
{
|
|
struct nested_region *region;
|
|
|
|
region = malloc(sizeof *region);
|
|
if (region == NULL) {
|
|
wl_resource_post_no_memory(resource);
|
|
return;
|
|
}
|
|
|
|
wl_resource_init(®ion->resource, &wl_region_interface,
|
|
®ion_interface, id, region);
|
|
region->resource.destroy = destroy_region;
|
|
|
|
pixman_region32_init(®ion->region);
|
|
|
|
wl_client_add_resource(client, ®ion->resource);
|
|
}
|
|
|
|
static const struct wl_compositor_interface compositor_interface = {
|
|
compositor_create_surface,
|
|
compositor_create_region
|
|
};
|
|
static void
|
|
compositor_bind(struct wl_client *client,
|
|
void *data, uint32_t version, uint32_t id)
|
|
{
|
|
struct nested *nested = data;
|
|
|
|
wl_client_add_object(client, &wl_compositor_interface,
|
|
&compositor_interface, id, nested);
|
|
}
|
|
|
|
static int
|
|
nested_init_compositor(struct nested *nested)
|
|
{
|
|
const char *extensions;
|
|
struct wl_event_loop *loop;
|
|
int fd, ret;
|
|
|
|
wl_list_init(&nested->surface_list);
|
|
wl_list_init(&nested->frame_callback_list);
|
|
nested->child_display = wl_display_create();
|
|
loop = wl_display_get_event_loop(nested->child_display);
|
|
fd = wl_event_loop_get_fd(loop);
|
|
nested->child_task.run = handle_child_data;
|
|
display_watch_fd(nested->display, fd,
|
|
EPOLLIN, &nested->child_task);
|
|
|
|
if (!wl_display_add_global(nested->child_display,
|
|
&wl_compositor_interface,
|
|
nested, compositor_bind))
|
|
return -1;
|
|
|
|
wl_display_init_shm(nested->child_display);
|
|
|
|
nested->egl_display = display_get_egl_display(nested->display);
|
|
extensions = eglQueryString(nested->egl_display, EGL_EXTENSIONS);
|
|
if (strstr(extensions, "EGL_WL_bind_wayland_display") == NULL) {
|
|
fprintf(stderr, "no EGL_WL_bind_wayland_display extension\n");
|
|
return -1;
|
|
}
|
|
|
|
bind_display = (void *) eglGetProcAddress("eglBindWaylandDisplayWL");
|
|
unbind_display = (void *) eglGetProcAddress("eglUnbindWaylandDisplayWL");
|
|
create_image = (void *) eglGetProcAddress("eglCreateImageKHR");
|
|
destroy_image = (void *) eglGetProcAddress("eglDestroyImageKHR");
|
|
query_buffer = (void *) eglGetProcAddress("eglQueryWaylandBufferWL");
|
|
image_target_texture_2d =
|
|
(void *) eglGetProcAddress("glEGLImageTargetTexture2DOES");
|
|
|
|
ret = bind_display(nested->egl_display, nested->child_display);
|
|
if (!ret) {
|
|
fprintf(stderr, "failed to bind wl_display\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct nested *
|
|
nested_create(struct display *display)
|
|
{
|
|
struct nested *nested;
|
|
|
|
nested = malloc(sizeof *nested);
|
|
if (nested == NULL)
|
|
return nested;
|
|
memset(nested, 0, sizeof *nested);
|
|
|
|
nested->window = window_create(display);
|
|
nested->widget = frame_create(nested->window, nested);
|
|
window_set_title(nested->window, "Wayland Nested");
|
|
nested->display = display;
|
|
|
|
window_set_user_data(nested->window, nested);
|
|
widget_set_redraw_handler(nested->widget, redraw_handler);
|
|
window_set_keyboard_focus_handler(nested->window,
|
|
keyboard_focus_handler);
|
|
|
|
nested_init_compositor(nested);
|
|
|
|
widget_schedule_resize(nested->widget, 400, 400);
|
|
|
|
return nested;
|
|
}
|
|
|
|
static void
|
|
nested_destroy(struct nested *nested)
|
|
{
|
|
widget_destroy(nested->widget);
|
|
window_destroy(nested->window);
|
|
free(nested);
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
struct display *display;
|
|
struct nested *nested;
|
|
|
|
display = display_create(&argc, argv);
|
|
if (display == NULL) {
|
|
fprintf(stderr, "failed to create display: %m\n");
|
|
return -1;
|
|
}
|
|
|
|
nested = nested_create(display);
|
|
|
|
launch_client(nested, "nested-client");
|
|
|
|
display_run(display);
|
|
|
|
nested_destroy(nested);
|
|
display_destroy(display);
|
|
|
|
return 0;
|
|
}
|