615 lines
15 KiB
C
615 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 = wl_resource_get_user_data(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
|
|
destroy_frame_callback(struct wl_resource *resource)
|
|
{
|
|
struct nested_frame_callback *callback = wl_resource_get_user_data(resource);
|
|
|
|
wl_list_remove(&callback->link);
|
|
free(callback);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
callback->resource =
|
|
wl_client_add_object(client, &wl_callback_interface,
|
|
NULL, id, callback);
|
|
wl_resource_set_destructor(callback->resource, destroy_frame_callback);
|
|
|
|
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);
|
|
|
|
surface->resource =
|
|
wl_client_add_object(client, &wl_surface_interface,
|
|
&surface_interface, id, surface);
|
|
wl_resource_set_destructor(surface->resource, destroy_surface);
|
|
|
|
wl_list_insert(nested->surface_list.prev, &surface->link);
|
|
}
|
|
|
|
static void
|
|
destroy_region(struct wl_resource *resource)
|
|
{
|
|
struct nested_region *region = wl_resource_get_user_data(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;
|
|
}
|
|
|
|
pixman_region32_init(®ion->region);
|
|
|
|
region->resource =
|
|
wl_client_add_object(client, &wl_region_interface,
|
|
®ion_interface, id, region);
|
|
wl_resource_set_destructor(region->resource, destroy_region);
|
|
}
|
|
|
|
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;
|
|
}
|