Initial libds 12/278012/1
authorSeunghun Lee <shiin.lee@samsung.com>
Mon, 21 Feb 2022 04:58:09 +0000 (13:58 +0900)
committerSooChan Lim <sc1.lim@samsung.com>
Mon, 18 Jul 2022 05:07:41 +0000 (14:07 +0900)
Change-Id: I60d7c170e7f0b918c17f2bf52c01018167dafe91

70 files changed:
LICENSE [new file with mode: 0644]
README.md
TODO.md [new file with mode: 0644]
examples/meson.build [new file with mode: 0644]
examples/tinyds.c [new file with mode: 0644]
examples/wl-backend.c [new file with mode: 0644]
include/libds/allocator.h [new file with mode: 0644]
include/libds/allocator/shm.h [new file with mode: 0644]
include/libds/backend.h [new file with mode: 0644]
include/libds/backend/wayland.h [new file with mode: 0644]
include/libds/buffer.h [new file with mode: 0644]
include/libds/compositor.h [new file with mode: 0644]
include/libds/interfaces/allocator.h [new file with mode: 0644]
include/libds/interfaces/backend.h [new file with mode: 0644]
include/libds/interfaces/buffer.h [new file with mode: 0644]
include/libds/interfaces/output.h [new file with mode: 0644]
include/libds/log.h [new file with mode: 0644]
include/libds/output.h [new file with mode: 0644]
include/libds/presentation.h [new file with mode: 0644]
include/libds/subsurface.h [new file with mode: 0644]
include/libds/surface.h [new file with mode: 0644]
include/libds/swapchain.h [new file with mode: 0644]
include/libds/xdg_shell.h [new file with mode: 0644]
include/meson.build [new file with mode: 0644]
meson.build [new file with mode: 0644]
packaging/libds.manifest [new file with mode: 0644]
packaging/libds.spec [new file with mode: 0644]
src/libds/addon.c [new file with mode: 0644]
src/libds/addon.h [new file with mode: 0644]
src/libds/allocator/allocator.c [new file with mode: 0644]
src/libds/allocator/shm.c [new file with mode: 0644]
src/libds/backend/backend.c [new file with mode: 0644]
src/libds/backend/meson.build [new file with mode: 0644]
src/libds/backend/wayland/backend.c [new file with mode: 0644]
src/libds/backend/wayland/backend.h [new file with mode: 0644]
src/libds/backend/wayland/meson.build [new file with mode: 0644]
src/libds/backend/wayland/output.c [new file with mode: 0644]
src/libds/buffer.c [new file with mode: 0644]
src/libds/buffer.h [new file with mode: 0644]
src/libds/client_buffer.h [new file with mode: 0644]
src/libds/client_buffer/shm_client_buffer.c [new file with mode: 0644]
src/libds/compositor.c [new file with mode: 0644]
src/libds/log.c [new file with mode: 0644]
src/libds/meson.build [new file with mode: 0644]
src/libds/output.c [new file with mode: 0644]
src/libds/pixel_format.c [new file with mode: 0644]
src/libds/pixel_format.h [new file with mode: 0644]
src/libds/presentation.c [new file with mode: 0644]
src/libds/region.c [new file with mode: 0644]
src/libds/region.h [new file with mode: 0644]
src/libds/subcompositor.c [new file with mode: 0644]
src/libds/subcompositor.h [new file with mode: 0644]
src/libds/surface.h [new file with mode: 0644]
src/libds/surface/subsurface.c [new file with mode: 0644]
src/libds/surface/surface-private.h [new file with mode: 0644]
src/libds/surface/surface.c [new file with mode: 0644]
src/libds/swapchain.c [new file with mode: 0644]
src/libds/util.h [new file with mode: 0644]
src/libds/util/shm.c [new file with mode: 0644]
src/libds/util/time.c [new file with mode: 0644]
src/libds/xdg_shell/xdg_shell.c [new file with mode: 0644]
src/libds/xdg_shell/xdg_shell.h [new file with mode: 0644]
src/libds/xdg_shell/xdg_surface.c [new file with mode: 0644]
src/libds/xdg_shell/xdg_toplevel.c [new file with mode: 0644]
src/meson.build [new file with mode: 0644]
src/tests/meson.build [new file with mode: 0644]
src/tests/test-backend.c [new file with mode: 0644]
src/tests/test-compositor.c [new file with mode: 0644]
src/tests/test-subsurface.c [new file with mode: 0644]
src/tests/test-surface.c [new file with mode: 0644]

diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..54e4ef7
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2022 Samsung Electronics co., Ltd. All Rights Reserved.
+Copyright (c) 2017, 2018 Drew DeVault
+Copyright (c) 2014 Jari Vetoniemi
+
+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 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.
index 4c1f3c7..c8dc588 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,2 +1,14 @@
-# libds-c
-libds with C
+libds
+=====
+
+libds is a library that provides a building block which can be used for
+building wayland compositors. It is heavily inspired, conceptually as well as
+code-wise, by wlroots.
+
+libds initially aims to provide common set of functionality that users such as
+Enlightenment compositor and Dali's compositor library expect. Such compositors
+have its own renderer, so libds currently doesn't provide renderers.
+However libds can provide renderers later on for general purposes, and this
+renderers should stay decoupled with other objects of libds.
+
+WIP
diff --git a/TODO.md b/TODO.md
new file mode 100644 (file)
index 0000000..f9681ed
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,8 @@
+[] Implement a xdg shell
+[] Implement a KMS/DRM backend
+[] Implement a libinput backend
+[] Implement an input
+[] Implement a viewporter
+[] Make up a ds output interface
+[] Make up tests
+[] Document APIs
diff --git a/examples/meson.build b/examples/meson.build
new file mode 100644 (file)
index 0000000..a107a77
--- /dev/null
@@ -0,0 +1,22 @@
+project('libds-samples', 'c',
+  version : '0.1',
+  default_options : ['warning_level=3'])
+
+common_deps = [
+  dependency('wayland-server', required: true),
+  dependency('libds', required: true),
+]
+
+executable('wl-backend',
+           'wl-backend.c',
+           dependencies: common_deps,
+           install : true)
+
+executable('tinyds',
+           'tinyds.c',
+           dependencies: [ 
+             common_deps,
+             dependency('pixman-1', required: true),
+             dependency('libdrm', required: true),
+           ],
+           install : true)
diff --git a/examples/tinyds.c b/examples/tinyds.c
new file mode 100644 (file)
index 0000000..3925014
--- /dev/null
@@ -0,0 +1,522 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <time.h>
+
+#include <drm_fourcc.h>
+#include <pixman.h>
+#include <wayland-server.h>
+#include <libds/log.h>
+#include <libds/backend.h>
+#include <libds/allocator/shm.h>
+#include <libds/backend/wayland.h>
+#include <libds/swapchain.h>
+#include <libds/compositor.h>
+#include <libds/xdg_shell.h>
+
+#define TINYDS_UNUSED   __attribute__((unused))
+
+#define OUTPUT_WIDTH   1280
+#define OUTPUT_HEIGHT  720
+
+struct tinyds_output
+{
+    struct tinyds_server *server;
+    struct ds_output *ds_output;
+    struct ds_allocator *allocator;
+    struct ds_swapchain *swapchain;
+    struct ds_buffer *front_buffer;
+
+    struct wl_listener output_destroy;
+    struct wl_listener output_frame;
+    struct wl_list link; // tinyds_server::outputs
+
+    int width, height;
+
+    bool drawable;
+    bool damaged;
+};
+
+struct tinyds_server
+{
+    struct wl_display *display;
+
+    struct ds_backend *backend;
+    struct ds_compositor *compositor;
+    struct ds_xdg_shell *xdg_shell;
+
+    struct tinyds_output primary_output;
+
+    struct wl_list views;
+    struct wl_list outputs;
+
+    struct wl_listener new_xdg_surface;
+};
+
+struct tinyds_view
+{
+    struct tinyds_server *server;
+
+    struct ds_xdg_surface *xdg_surface;
+
+    struct wl_listener xdg_surface_map;
+    struct wl_listener xdg_surface_unmap;
+    struct wl_listener xdg_surface_destroy;
+    struct wl_listener surface_commit;
+    struct wl_list link; // tinyds_server::views
+
+    int x, y;
+    bool mapped;
+};
+
+struct tinyds_server _tinyds;
+
+static bool init_server(struct tinyds_server *server, struct wl_display *display);
+static void fini_server(struct tinyds_server *server);
+static bool init_output(struct tinyds_output *output, struct tinyds_server *server,
+        int width, int height);
+static void fini_output(struct tinyds_output *output);
+static void output_handle_destroy(struct wl_listener *listener, void *data);
+static void output_handle_frame(struct wl_listener *listener, void *data);
+static void draw_server(struct tinyds_server *server);
+static void draw_server_with_damage(struct tinyds_server *server);
+static void draw_output(struct tinyds_output *output);
+static void draw_view(struct tinyds_view *view, pixman_image_t *dst_image);
+
+int
+main(void)
+{
+    struct tinyds_server *server = &_tinyds;
+    struct wl_display *display;
+    const char *socket;
+
+    ds_log_init(DS_DBG, NULL);
+
+    display = wl_display_create();
+    assert(display);
+
+    assert(init_server(server, display) == true);
+
+    assert(init_output(&server->primary_output, server,
+                OUTPUT_WIDTH, OUTPUT_HEIGHT) == true);
+
+    socket = wl_display_add_socket_auto(display);
+    assert(socket);
+
+    ds_backend_start(server->backend);
+
+    draw_server(server);
+
+    setenv("WAYLAND_DISPLAY", socket, true);
+
+    ds_inf("Running Wayland compositor on WAYLAND_DISPLAY=%s", socket);
+
+    wl_display_run(server->display);
+
+    fini_output(&server->primary_output);
+    fini_server(server);
+    wl_display_destroy(display);
+
+    return 0;
+}
+
+static struct ds_backend *
+tinyds_create_backend_auto(struct wl_display *display)
+{
+    struct ds_backend *backend = NULL;
+    char name[512];
+    int i;
+
+    for (i = 0; i < 5; i++) {
+        snprintf(name, sizeof name, "wayland-%d", i);
+        backend = ds_wl_backend_create(display, name);
+        if (backend)
+            break;
+    }
+
+    return backend;
+}
+
+static void
+view_handle_xdg_surface_map(struct wl_listener *listener,
+        void *data TINYDS_UNUSED)
+{
+    struct tinyds_view *view;
+
+    view = wl_container_of(listener, view, xdg_surface_map);
+    view->mapped = true;
+}
+
+static void
+view_handle_xdg_surface_unmap(struct wl_listener *listener,
+        void *data TINYDS_UNUSED)
+{
+    struct tinyds_view *view;
+
+    view = wl_container_of(listener, view, xdg_surface_unmap);
+    view->mapped = false;
+}
+
+static void
+view_handle_xdg_surface_destroy(struct wl_listener *listener,
+        void *data TINYDS_UNUSED) 
+{
+    struct tinyds_view *view;
+
+    view = wl_container_of(listener, view, xdg_surface_destroy);
+
+    draw_server_with_damage(view->server);
+
+    wl_list_remove(&view->xdg_surface_destroy.link);
+    wl_list_remove(&view->xdg_surface_map.link);
+    wl_list_remove(&view->xdg_surface_unmap.link);
+    wl_list_remove(&view->surface_commit.link);
+    wl_list_remove(&view->link);
+    free(view);
+}
+
+static void
+view_handle_surface_commit(struct wl_listener *listener,
+        void *data TINYDS_UNUSED)
+{
+    struct tinyds_view *view;
+
+    view = wl_container_of(listener, view, surface_commit);
+    draw_server_with_damage(view->server);
+}
+
+static void
+server_new_xdg_surface(struct wl_listener *listener, void *data)
+{
+    struct tinyds_server *server;
+    struct tinyds_view *view;
+    struct ds_xdg_surface *xdg_surface;
+
+    server = wl_container_of(listener, server, new_xdg_surface);
+    xdg_surface = data;
+
+    ds_inf("New xdg_surface(%p)", (void *)xdg_surface);
+
+    view = calloc(1, sizeof *view);
+    view->server = server;
+    view->xdg_surface = xdg_surface;
+
+    view->xdg_surface_map.notify = view_handle_xdg_surface_map;
+    ds_xdg_surface_add_map_listener(xdg_surface,
+            &view->xdg_surface_map);
+
+    view->xdg_surface_unmap.notify = view_handle_xdg_surface_unmap;
+    ds_xdg_surface_add_unmap_listener(xdg_surface,
+            &view->xdg_surface_unmap);
+
+    view->xdg_surface_destroy.notify = view_handle_xdg_surface_destroy;
+    ds_xdg_surface_add_destroy_listener(xdg_surface,
+            &view->xdg_surface_destroy);
+
+    view->surface_commit.notify = view_handle_surface_commit;
+    ds_surface_add_commit_listener(
+            ds_xdg_surface_get_surface(xdg_surface),
+            &view->surface_commit);
+
+    wl_list_insert(server->views.prev, &view->link);
+}
+
+static bool
+init_server(struct tinyds_server *server, struct wl_display *display)
+{
+    server->display = display;
+
+    wl_list_init(&server->outputs);
+    wl_list_init(&server->views);
+
+    if (wl_display_init_shm(display) != 0)
+        return false;
+
+    server->backend = tinyds_create_backend_auto(display);
+    if (!server->backend)
+        return false;
+
+    server->compositor = ds_compositor_create(display);
+    if (!server->compositor) {
+        ds_backend_destroy(server->backend);
+        return false;
+    }
+
+    server->xdg_shell = ds_xdg_shell_create(display);
+    if (!server->xdg_shell) {
+        ds_backend_destroy(server->backend);
+        return false;
+    }
+
+    server->new_xdg_surface.notify = server_new_xdg_surface;
+    ds_xdg_shell_add_new_surface_listener(server->xdg_shell,
+            &server->new_xdg_surface);
+
+    return true;
+}
+
+static void
+fini_server(struct tinyds_server *server)
+{
+    wl_list_remove(&server->new_xdg_surface.link);
+}
+
+static void
+output_handle_destroy(struct wl_listener *listener, void *data TINYDS_UNUSED)
+{
+    struct tinyds_output *output =
+        wl_container_of(listener, output, output_destroy);
+
+    wl_list_remove(&output->output_destroy.link);
+    wl_list_remove(&output->output_frame.link);
+    output->ds_output = NULL;
+
+    wl_display_terminate(output->server->display);
+}
+
+static void
+output_handle_frame(struct wl_listener *listener, void *data TINYDS_UNUSED)
+{
+    struct tinyds_output *output =
+        wl_container_of(listener, output, output_frame);
+
+    output->drawable = true;
+    draw_output(output);
+}
+
+static bool
+init_output(struct tinyds_output *output, struct tinyds_server *server,
+        int width, int height)
+{
+    output->server = server;
+    output->width = width;
+    output->height = height;
+    output->front_buffer = NULL;
+    output->drawable = true;
+    output->damaged = true;
+
+    output->allocator = ds_shm_allocator_create();
+    if (!output->allocator)
+        return false;
+
+    output->swapchain = ds_swapchain_create(output->allocator,
+            width, height, DRM_FORMAT_XRGB8888);
+    if (!output->swapchain)
+        goto err_swapchain;
+
+    output->ds_output =
+        ds_wl_backend_create_output(server->backend);
+    if (!output->ds_output)
+        goto err_output;
+
+    output->output_destroy.notify = output_handle_destroy;
+    ds_output_add_destroy_listener(output->ds_output, &output->output_destroy);
+
+    output->output_frame.notify = output_handle_frame;
+    ds_output_add_frame_listener(output->ds_output, &output->output_frame);
+
+    wl_list_insert(&server->outputs, &output->link);
+
+    return true;
+
+err_output:
+    ds_swapchain_destroy(output->swapchain);
+err_swapchain:
+    ds_allocator_destroy(output->allocator);
+
+    return false;
+}
+
+static void
+fini_output(struct tinyds_output *output)
+{
+    wl_list_remove(&output->link);
+    if (output->front_buffer)
+        ds_buffer_unlock(output->front_buffer);
+    if (output->ds_output)
+        ds_output_destroy(output->ds_output);
+    ds_swapchain_destroy(output->swapchain);
+    ds_allocator_destroy(output->allocator);
+}
+
+static void
+draw_server(struct tinyds_server *server)
+{
+    struct tinyds_output *output;
+
+    wl_list_for_each(output, &server->outputs, link)
+        draw_output(output);
+}
+
+static void
+draw_server_with_damage(struct tinyds_server *server)
+{
+    struct tinyds_output *output;
+
+    wl_list_for_each(output, &server->outputs, link) {
+        output->damaged = true;
+        draw_output(output);
+    }
+}
+
+static void image_fill_color(pixman_image_t *image,
+        uint8_t r, uint8_t g, uint8_t b);
+static pixman_image_t *image_from_buffer(struct ds_buffer *buffer,
+        enum ds_buffer_data_ptr_access_flag access_flag);
+static void view_send_frame_done(struct tinyds_view *view);
+
+static void
+draw_output(struct tinyds_output *output)
+{
+    struct ds_buffer *output_buffer;
+    pixman_image_t *output_image;
+    struct tinyds_view *view;
+
+    if (!output->drawable || !output->damaged)
+        return;
+
+    output_buffer = ds_swapchain_acquire(output->swapchain, NULL);
+    if (!output_buffer)
+        return;
+
+    output_image = image_from_buffer(output_buffer,
+            DS_BUFFER_DATA_PTR_ACCESS_WRITE);
+    if (!output_image) {
+        ds_buffer_unlock(output_buffer);
+        return;
+    }
+
+    image_fill_color(output_image, 80, 80, 80);
+
+    wl_list_for_each(view, &output->server->views, link) {
+        if (!view->mapped)
+            continue;
+        draw_view(view, output_image);
+    }
+    pixman_image_unref(output_image);
+
+    ds_output_attach_buffer(output->ds_output, output_buffer);
+    ds_output_commit(output->ds_output);
+
+    if (output->front_buffer)
+        ds_buffer_unlock(output->front_buffer);
+    output->front_buffer = output_buffer;
+
+    output->drawable = false;
+    output->damaged = false;
+}
+
+static void
+draw_view(struct tinyds_view *view, pixman_image_t *dst_image)
+{
+    struct ds_buffer *buffer;
+    pixman_image_t *src_image;
+
+    buffer = ds_surface_get_buffer(
+            ds_xdg_surface_get_surface(view->xdg_surface));
+    if (!buffer)
+        return;
+
+    src_image = image_from_buffer(buffer,
+            DS_BUFFER_DATA_PTR_ACCESS_READ);
+    pixman_image_composite32(PIXMAN_OP_OVER,
+            src_image,
+            NULL,
+            dst_image,
+            0, 0, 0, 0, 0, 0,
+            pixman_image_get_width(src_image),
+            pixman_image_get_height(src_image));
+    pixman_image_unref(src_image);
+
+    view_send_frame_done(view);
+}
+
+static pixman_color_t *
+color_rgb888(pixman_color_t *tmp, uint8_t r, uint8_t g, uint8_t b)
+{
+    tmp->alpha = 65535;
+    tmp->red = (r << 8) + r;
+    tmp->green = (g << 8) + g;
+    tmp->blue = (b << 8) +b;
+
+    return tmp;
+}
+
+static void
+image_fill_color(pixman_image_t *image, uint8_t r, uint8_t g, uint8_t b)
+{
+    pixman_image_t *color_image;
+    pixman_color_t color;
+
+    color_rgb888(&color, r, g, b);
+    color_image = pixman_image_create_solid_fill(&color);
+    pixman_image_composite32(PIXMAN_OP_SRC,
+            color_image,
+            NULL,
+            image,
+            0, 0, 0, 0, 0, 0,
+            pixman_image_get_width(image),
+            pixman_image_get_height(image));
+    pixman_image_unref(color_image);
+}
+
+static void                                             
+destroy_pixman_image(pixman_image_t *image TINYDS_UNUSED, void *data)
+{
+    struct ds_buffer *buffer = data;
+    ds_buffer_end_data_ptr_access(buffer);
+    ds_buffer_unlock(buffer);
+}
+
+static uint32_t
+convert_drm_format_to_pixman(uint32_t fmt)
+{
+    switch (fmt) {
+        case DRM_FORMAT_XRGB8888:
+            return PIXMAN_x8r8g8b8;
+        case DRM_FORMAT_ARGB8888:
+            return PIXMAN_a8r8g8b8;
+        default:
+            assert(0 && "not reached");
+    }
+}
+
+static pixman_image_t *
+image_from_buffer(struct ds_buffer *buffer,
+        enum ds_buffer_data_ptr_access_flag access_flag)
+{
+    pixman_image_t *image;
+    void *data;
+    uint32_t format;
+    size_t stride;
+    int width, height;
+
+    ds_buffer_get_size(buffer, &width, &height);
+
+    if (!ds_buffer_begin_data_ptr_access(buffer,
+                access_flag, &data, &format, &stride))
+        return NULL;
+
+    format = convert_drm_format_to_pixman(format);
+    image = pixman_image_create_bits(format, width, height, data, stride);
+    if (!image) {
+        ds_buffer_end_data_ptr_access(buffer);
+        return NULL;
+    }
+
+    pixman_image_set_destroy_function(image,
+            destroy_pixman_image, ds_buffer_lock(buffer));
+
+    return image;
+}
+
+static void
+view_send_frame_done(struct tinyds_view *view)
+{
+    struct timespec now;
+    clock_gettime(CLOCK_MONOTONIC, &now);
+    ds_surface_send_frame_done(ds_xdg_surface_get_surface(view->xdg_surface),
+            &now);
+}
diff --git a/examples/wl-backend.c b/examples/wl-backend.c
new file mode 100644 (file)
index 0000000..38dea1b
--- /dev/null
@@ -0,0 +1,226 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+
+#include <wayland-server.h>
+#include <libds/log.h>
+#include <libds/backend.h>
+#include <libds/allocator/shm.h>
+#include <libds/backend/wayland.h>
+#include <libds/swapchain.h>
+#include <libds/compositor.h>
+
+#define WIDTH   700
+#define HEIGHT  400
+
+struct server
+{
+    struct ds_backend *backend;
+    struct ds_output *output;
+    struct ds_allocator *allocator;
+    struct ds_swapchain *swapchain;
+    struct ds_buffer *front_buffer;
+
+    struct wl_display *display;
+
+    struct {
+        struct wl_listener output_destroy;
+        struct wl_listener output_frame;
+    } listener;
+
+    int width, height;
+};
+
+struct server _server;
+
+static void init_server(struct server *server, struct wl_display *display);
+static void fini_server(struct server *server);
+static void output_handle_destroy(struct wl_listener *listener, void *data);
+static void output_handle_frame(struct wl_listener *listener, void *data);
+static void draw_output(struct server *server);
+
+int
+main(void)
+{
+    struct server *server = &_server;
+    struct wl_display *display;
+
+    ds_log_init(DS_DBG, NULL);
+
+    display = wl_display_create();
+    assert(display);
+
+    server->width = WIDTH;
+    server->height = HEIGHT;
+
+    init_server(server, display);
+
+    server->listener.output_destroy.notify = output_handle_destroy;
+    ds_output_add_destroy_listener(server->output,
+            &server->listener.output_destroy);
+
+    server->listener.output_frame.notify = output_handle_frame;
+    ds_output_add_frame_listener(server->output,
+            &server->listener.output_frame);
+
+    ds_backend_start(server->backend);
+
+    draw_output(server);
+
+    wl_display_run(server->display);
+
+    fini_server(server);
+    wl_display_destroy(display);
+    return 0;
+}
+
+static struct ds_backend *
+create_backend_auto(struct wl_display *display)
+{
+    struct ds_backend *backend = NULL;
+    char name[512];
+    int i;
+
+    for (i = 0; i < 5; i++) {
+        snprintf(name, sizeof name, "wayland-%d", i);
+        backend = ds_wl_backend_create(display, name);
+        if (backend)
+            break;
+    }
+
+    return backend;
+}
+
+static void
+init_server(struct server *server, struct wl_display *display)
+{
+    server->display = display;
+
+    server->backend = create_backend_auto(display);
+    assert(server->backend);
+
+    server->allocator = ds_shm_allocator_create();
+    assert(server->allocator);
+
+    server->swapchain = ds_swapchain_create(server->allocator,
+            server->width, server->height, WL_SHM_FORMAT_XRGB8888);
+    assert(server->swapchain);
+
+    server->output =
+        ds_wl_backend_create_output(server->backend);
+    assert(server->output);
+}
+
+static void
+fini_server(struct server *server)
+{
+    ds_buffer_unlock(server->front_buffer);
+    ds_swapchain_destroy(server->swapchain);
+    ds_allocator_destroy(server->allocator);
+}
+
+static void
+paint_pixels(void *image, int padding, int width, int height, uint32_t time)
+{
+       const int halfh = padding + (height - padding * 2) / 2;
+       const int halfw = padding + (width - padding * 2) / 2;
+       int ir, or;
+       uint32_t *pixel = image;
+       int y;
+
+       /* squared radii thresholds */
+       or = (halfw < halfh ? halfw : halfh) - 8;
+       ir = or - 32;
+       or *= or;
+       ir *= ir;
+
+       pixel += padding * width;
+       for (y = padding; y < height - padding; y++) {
+               int x;
+               int y2 = (y - halfh) * (y - halfh);
+
+               pixel += padding;
+               for (x = padding; x < width - padding; x++) {
+                       uint32_t v;
+
+                       /* squared distance from center */
+                       int r2 = (x - halfw) * (x - halfw) + y2;
+
+                       if (r2 < ir)
+                               v = (r2 / 32 + time / 64) * 0x0080401;
+                       else if (r2 < or)
+                               v = (y + time / 32) * 0x0080401;
+                       else
+                               v = (x + time / 16) * 0x0080401;
+                       v &= 0x00ffffff;
+
+                       /* cross if compositor uses X from XRGB as alpha */
+                       if (abs(x - y) > 6 && abs(x + y - height) > 6)
+                               v |= 0xff000000;
+
+                       *pixel++ = v;
+               }
+
+               pixel += padding;
+       }
+}
+
+static inline int64_t                                       
+timespec_to_msec(const struct timespec *a)                  
+{                                                           
+    return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000;
+}                                                           
+
+static void
+output_handle_destroy(struct wl_listener *listener,
+        void *data __attribute__((unused)))
+{
+    struct server *server =
+        wl_container_of(listener, server, listener.output_destroy);
+    wl_display_terminate(server->display);
+}
+
+static void
+output_handle_frame(struct wl_listener *listener,
+        void *data __attribute__((unused)))
+{
+    struct server *server =
+        wl_container_of(listener, server, listener.output_frame);
+    draw_output(server);
+}
+
+static void
+draw_output(struct server *server)
+{
+    struct ds_buffer *buffer;
+    void *data;
+    uint32_t format;
+    size_t stride;
+    struct timespec now;                 
+    uint32_t frame_time_msec;
+
+    ds_dbg("Redraw output");
+
+    clock_gettime(CLOCK_MONOTONIC, &now);
+    frame_time_msec = timespec_to_msec(&now);
+
+    buffer = ds_swapchain_acquire(server->swapchain, NULL);
+    assert(buffer);
+
+    assert(ds_buffer_begin_data_ptr_access(buffer,
+                0, &data, &format, &stride) == true);
+
+    paint_pixels(data, 20, server->width, server->height, frame_time_msec);
+
+    ds_buffer_end_data_ptr_access(buffer);
+
+    ds_output_attach_buffer(server->output, buffer);
+    ds_output_commit(server->output);
+
+    if (server->front_buffer)
+        ds_buffer_unlock(server->front_buffer);
+
+    server->front_buffer = buffer;
+}
diff --git a/include/libds/allocator.h b/include/libds/allocator.h
new file mode 100644 (file)
index 0000000..d4a6a80
--- /dev/null
@@ -0,0 +1,19 @@
+#ifndef LIBDS_ALLOCATOR_H
+#define LIBDS_ALLOCATOR_H
+
+#include <libds/buffer.h>
+
+struct ds_allocator;
+
+void
+ds_allocator_destroy(struct ds_allocator *alloc);
+
+struct ds_buffer *
+ds_allocator_create_buffer(struct ds_allocator *alloc, int width, int height,
+        uint32_t format);
+
+void
+ds_allocator_add_destroy_listener(struct ds_allocator *alloc,
+        struct wl_listener *listener);
+
+#endif
diff --git a/include/libds/allocator/shm.h b/include/libds/allocator/shm.h
new file mode 100644 (file)
index 0000000..1a02338
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef LIBDS_ALLOCATOR_SHM_H
+#define LIBDS_ALLOCATOR_SHM_H
+
+#include <libds/allocator.h>
+
+struct ds_allocator *
+ds_shm_allocator_create(void);
+
+#endif
diff --git a/include/libds/backend.h b/include/libds/backend.h
new file mode 100644 (file)
index 0000000..71d9e63
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef LIBDS_BACKEND_H
+#define LIBDS_BACKEND_H
+
+#include <stdbool.h>
+#include <wayland-server.h>
+#include <libds/buffer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ds_backend;
+
+bool
+ds_backend_start(struct ds_backend *backend);
+
+void
+ds_backend_destroy(struct ds_backend *backend);
+
+int
+ds_backend_get_drm_fd(struct ds_backend *backend);
+
+ds_buffer_caps_t
+ds_backend_get_buffer_caps(struct ds_backend *backend);
+
+void
+ds_backend_add_destroy_listener(struct ds_backend *backend,
+        struct wl_listener *listener);
+
+void
+ds_backend_add_new_output_listener(struct ds_backend *backend,
+        struct wl_listener *listener);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/libds/backend/wayland.h b/include/libds/backend/wayland.h
new file mode 100644 (file)
index 0000000..e7f233a
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef LIBDS_BACKEND_WAYLAND_H
+#define LIBDS_BACKEND_WAYLAND_H
+
+#include <libds/backend.h>
+#include <libds/output.h>
+
+struct ds_backend *
+ds_wl_backend_create(struct wl_display *display, const char *server_name);
+
+struct ds_output *
+ds_wl_backend_create_output(struct ds_backend *backend);
+
+#endif
diff --git a/include/libds/buffer.h b/include/libds/buffer.h
new file mode 100644 (file)
index 0000000..eac5146
--- /dev/null
@@ -0,0 +1,74 @@
+#ifndef LIBDS_BUFFER_H
+#define LIBDS_BUFFER_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <wayland-server.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ds_buffer;
+
+typedef enum
+{
+    DS_BUFFER_CAP_DATA_PTR = 1 << 0,
+    DS_BUFFER_CAP_DMABUF = 1 << 1,
+    DS_BUFFER_CAP_SHM = 1 << 2,
+} ds_buffer_caps_t;
+
+enum ds_buffer_data_ptr_access_flag
+{
+    DS_BUFFER_DATA_PTR_ACCESS_READ = 1 << 0,
+    DS_BUFFER_DATA_PTR_ACCESS_WRITE = 1 << 1,
+};
+
+struct ds_shm_attributes
+{
+    int fd;
+    uint32_t format;
+    int width, height, stride;
+    off_t offset;
+};
+
+struct ds_buffer *
+ds_buffer_from_resource(struct wl_resource *resource);
+
+void
+ds_buffer_drop(struct ds_buffer *buffer);
+
+struct ds_buffer *
+ds_buffer_lock(struct ds_buffer *buffer);
+
+void
+ds_buffer_unlock(struct ds_buffer *buffer);
+
+bool
+ds_buffer_get_shm(struct ds_buffer *buffer, struct ds_shm_attributes *attribs);
+
+void
+ds_buffer_get_size(struct ds_buffer *buffer, int *out_width, int *out_height);
+
+void
+ds_buffer_add_destroy_listener(struct ds_buffer *buffer,
+        struct wl_listener *listener);
+
+void
+ds_buffer_add_release_listener(struct ds_buffer *buffer,
+        struct wl_listener *listener);
+
+bool
+ds_buffer_begin_data_ptr_access(struct ds_buffer *buffer,
+        enum ds_buffer_data_ptr_access_flag flags, void **data,
+        uint32_t *format, size_t *stride);
+
+void
+ds_buffer_end_data_ptr_access(struct ds_buffer *buffer);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/libds/compositor.h b/include/libds/compositor.h
new file mode 100644 (file)
index 0000000..343fa0c
--- /dev/null
@@ -0,0 +1,27 @@
+#ifndef LIBDS_COMPOSITOR_H
+#define LIBDS_COMPOSITOR_H
+
+#include <wayland-server.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ds_compositor;
+
+struct ds_compositor *
+ds_compositor_create(struct wl_display *display);
+
+void
+ds_compositor_add_new_surface_listener(struct ds_compositor *compositor,
+        struct wl_listener *listener);
+
+void
+ds_compositor_add_destroy_listener(struct ds_compositor *compositor,
+        struct wl_listener *listener);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/libds/interfaces/allocator.h b/include/libds/interfaces/allocator.h
new file mode 100644 (file)
index 0000000..1e60668
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef LIBDS_INTERFACES_ALLOCATOR_H
+#define LIBDS_INTERFACES_ALLOCATOR_H
+
+#include <wayland-server.h>
+#include <libds/buffer.h>
+
+struct ds_allocator;
+
+struct ds_allocator_interface {
+    struct ds_buffer *(*create_buffer)(struct ds_allocator *alloc,
+            int width, int height, uint32_t format);
+    void (*destroy)(struct ds_allocator *alloc);
+};
+
+struct ds_allocator
+{
+    const struct ds_allocator_interface *iface;
+
+    uint32_t buffer_caps;
+
+    struct {
+        struct wl_signal destroy;
+    } events;
+};
+
+void
+ds_allocator_init(struct ds_allocator *alloc,
+        const struct ds_allocator_interface *iface, uint32_t buffer_caps);
+
+void
+ds_allocator_destroy(struct ds_allocator *alloc);
+
+#endif
diff --git a/include/libds/interfaces/backend.h b/include/libds/interfaces/backend.h
new file mode 100644 (file)
index 0000000..e960ac4
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef LIBDS_INTERFACES_BACKEND_H
+#define LIBDS_INTERFACES_BACKEND_H
+
+#include <wayland-server.h>
+
+struct ds_backend;
+
+struct ds_backend_interface
+{
+    bool (*start)(struct ds_backend *backend);
+    void (*destroy)(struct ds_backend *backend);
+    int (*get_drm_fd)(struct ds_backend *backend);
+};
+
+struct ds_backend
+{
+    const struct ds_backend_interface *iface;
+
+    struct wl_display *display;
+
+    struct
+    {
+        struct wl_signal destroy;
+        struct wl_signal new_output;
+    } events;
+
+    bool started;
+};
+
+void
+ds_backend_init(struct ds_backend *backend, const struct ds_backend_interface *iface);
+
+void
+ds_backend_finish(struct ds_backend *backend);
+
+#endif
diff --git a/include/libds/interfaces/buffer.h b/include/libds/interfaces/buffer.h
new file mode 100644 (file)
index 0000000..3e288aa
--- /dev/null
@@ -0,0 +1,48 @@
+#ifndef LIBDS_INTERFACES_BUFFER_H
+#define LIBDS_INTERFACES_BUFFER_H
+
+#include <wayland-server.h>
+#include <libds/buffer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ds_buffer;
+
+struct ds_buffer_interface
+{
+    void (*destroy)(struct ds_buffer *buffer);
+    bool (*get_shm)(struct ds_buffer *buffer,
+            struct ds_shm_attributes *attribs);
+    bool (*begin_data_ptr_access)(struct ds_buffer *buffer, uint32_t flags,
+            void **data, uint32_t *format, size_t *stride);
+    void (*end_data_ptr_access)(struct ds_buffer *buffer);
+};
+
+struct ds_buffer
+{
+    const struct ds_buffer_interface *iface;
+    void *iface_data;
+
+    int width, height;
+    size_t n_locks;
+
+    bool dropped;
+    bool accessing_data_ptr;
+
+    struct {
+        struct wl_signal destroy;
+        struct wl_signal release;
+    } events;
+};
+
+void
+ds_buffer_init(struct ds_buffer *buffer,
+        const struct ds_buffer_interface *iface, int width, int height);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/libds/interfaces/output.h b/include/libds/interfaces/output.h
new file mode 100644 (file)
index 0000000..cacb927
--- /dev/null
@@ -0,0 +1,76 @@
+#ifndef LIBDS_INTERFACES_OUTPUT_H
+#define LIBDS_INTERFACES_OUTPUT_H
+
+#include <stdint.h>
+#include <wayland-server.h>
+#include <libds/backend.h>
+#include <libds/buffer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ds_output;
+
+enum ds_output_state_field
+{
+    DS_OUTPUT_STATE_BUFFER = 1 << 0,
+    DS_OUTPUT_STATE_DAMAGE = 1 << 1,
+    DS_OUTPUT_STATE_MODE = 1 << 2,
+    DS_OUTPUT_STATE_ENABLED = 1 << 3,
+    DS_OUTPUT_STATE_SCALE = 1 << 4,
+    DS_OUTPUT_STATE_TRANSFORM = 1 << 5,
+};
+
+struct ds_output_interface
+{
+    void (*destroy)(struct ds_output *output);
+    bool (*commit)(struct ds_output *output);
+};
+
+struct ds_output_mode {
+    struct wl_list link;
+    int32_t width, height;
+    int32_t refresh;
+    bool preferred;
+};
+
+struct ds_output_state
+{
+    enum ds_output_state_field committed;
+    struct ds_buffer *buffer;
+    struct ds_output_mode *mode;
+
+    bool enabled;
+};
+
+struct ds_output
+{
+    const struct ds_output_interface *iface;
+
+    struct ds_backend *backend;
+
+    struct wl_display *display;
+    struct wl_global *global;
+
+    struct ds_buffer *back_buffer, *front_buffer;
+    struct ds_output_state pending;
+
+    struct wl_listener display_destroy;
+
+    struct {
+        struct wl_signal destroy;
+        struct wl_signal frame;
+        struct wl_signal commit;
+    } events;
+};
+
+void
+ds_output_init(struct ds_output *output, struct ds_backend *backend,
+        const struct ds_output_interface *iface, struct wl_display *display);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/libds/log.h b/include/libds/log.h
new file mode 100644 (file)
index 0000000..5296d89
--- /dev/null
@@ -0,0 +1,59 @@
+#ifndef LIBDS_LOG_H
+#define LIBDS_LOG_H
+
+#include <stdbool.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum ds_log_level {
+    DS_SILENT = 0,
+    DS_ERR = 1,
+    DS_INF = 2,
+    DS_DBG = 3,
+    DS_LOG_LEVEL_LAST,
+};
+
+typedef void (*ds_log_func_t)(enum ds_log_level level,
+        const char *fmt, va_list args);
+
+void ds_log_init(enum ds_log_level level, ds_log_func_t callback);
+
+enum ds_log_level ds_log_get_level(void);
+
+#ifdef __GNUC__
+#define _DS_ATTRIB_PRINTF(start, end) __attribute__((format(printf, start, end)))
+#else
+#define _DS_ATTRIB_PRINTF(start, end)
+#endif
+
+void _ds_log(enum ds_log_level level, const char *format, ...) _DS_ATTRIB_PRINTF(2, 3);
+void _ds_vlog(enum ds_log_level level, const char *format, va_list args) _DS_ATTRIB_PRINTF(2, 0);
+
+#define ds_log(level, fmt, ...) \
+    _ds_log(level, "[%s:%d] " fmt, __FILE__, __LINE__, ##__VA_ARGS__)
+
+#define ds_vlog(level, fmt, args) \
+    _ds_vlog(level, "[%s:%d] " fmt, __FILE__, __LINE__, args)
+
+#define ds_log_errno(level, fmt, ...) \
+    ds_log(level, fmt ": %s", ##__VA_ARGS__, strerror(errno))
+
+#define ds_err(fmt, ...) \
+    ds_log(DS_ERR, fmt, ##__VA_ARGS__)
+
+#define ds_inf(fmt, ...) \
+    ds_log(DS_INF, fmt, ##__VA_ARGS__)
+
+#define ds_dbg(fmt, ...) \
+    ds_log(DS_DBG, fmt, ##__VA_ARGS__)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/libds/output.h b/include/libds/output.h
new file mode 100644 (file)
index 0000000..ea8e276
--- /dev/null
@@ -0,0 +1,30 @@
+#ifndef LIBDS_OUTPUT_H
+#define LIBDS_OUTPUT_H
+
+#include <time.h>
+#include <libds/buffer.h>
+
+struct ds_output;
+
+void
+ds_output_destroy(struct ds_output *output);
+
+bool
+ds_output_commit(struct ds_output *output);
+
+void
+ds_output_attach_buffer(struct ds_output *output, struct ds_buffer *buffer);
+
+void
+ds_output_add_destroy_listener(struct ds_output *output,
+        struct wl_listener *listener);
+
+void
+ds_output_add_frame_listener(struct ds_output *output,
+        struct wl_listener *listener);
+
+void
+ds_output_add_commit_listener(struct ds_output *output,
+        struct wl_listener *listener);
+
+#endif
diff --git a/include/libds/presentation.h b/include/libds/presentation.h
new file mode 100644 (file)
index 0000000..2b496b4
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef LIBstruct ds_presentationIME_H
+#define LIBstruct ds_presentationIME_H
+
+#include <time.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ds_presentation;
+
+struct ds_presentation *
+ds_presentation_create(struct wl_display *display,
+        clockid_t clk_id);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/libds/subsurface.h b/include/libds/subsurface.h
new file mode 100644 (file)
index 0000000..72ed088
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef LIBDS_SUBSURFACE_H
+#define LIBDS_SUBSURFACE_H
+
+#include <libds/surface.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ds_subsurface;
+
+bool ds_surface_is_subsurface(struct ds_surface *surface);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/libds/surface.h b/include/libds/surface.h
new file mode 100644 (file)
index 0000000..9c26cea
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef LIBDS_SURFACE_H
+#define LIBDS_SURFACE_H
+
+#include <time.h>
+#include <wayland-server.h>
+
+#include <libds/buffer.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ds_surface;
+
+typedef bool (*ds_surface_for_each_func_t)(struct ds_surface *surface,
+        int sx, int sy, void *data);
+
+void
+ds_surface_add_destroy_listener(struct ds_surface *surface,
+        struct wl_listener *listener);
+
+void
+ds_surface_add_commit_listener(struct ds_surface *surface,
+        struct wl_listener *listener);
+
+void
+ds_surface_add_new_subsurface_listener(struct ds_surface *surface,
+        struct wl_listener *listener);
+
+struct ds_buffer *
+ds_surface_get_buffer(struct ds_surface *surface);
+
+void 
+ds_surface_for_each(struct ds_surface *surface,
+        ds_surface_for_each_func_t iterator, void *data);
+
+void
+ds_surface_send_frame_done(struct ds_surface *surface,
+        const struct timespec *when);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/libds/swapchain.h b/include/libds/swapchain.h
new file mode 100644 (file)
index 0000000..d78d428
--- /dev/null
@@ -0,0 +1,22 @@
+#ifndef LIBDS_SWAPCHAIN_H
+#define LIBDS_SWAPCHAIN_H
+
+#include <libds/allocator.h>
+
+struct ds_swapchain;
+
+struct ds_swapchain *
+ds_swapchain_create(struct ds_allocator *alloc, int width, int height,
+        uint32_t format);
+
+void
+ds_swapchain_destroy(struct ds_swapchain *swapchain);
+
+struct ds_buffer *
+ds_swapchain_acquire(struct ds_swapchain *swapchain, int *age);
+
+void
+ds_swapchain_set_buffer_submitted(struct ds_swapchain *swapchain,
+        struct ds_buffer *buffer);
+
+#endif
diff --git a/include/libds/xdg_shell.h b/include/libds/xdg_shell.h
new file mode 100644 (file)
index 0000000..e3b386f
--- /dev/null
@@ -0,0 +1,50 @@
+#ifndef LIBDS_XDG_SHELL_H
+#define LIBDS_XDG_SHELL_H
+
+#include <stdint.h>
+#include <wayland-server.h>
+
+#include "surface.h"
+
+struct ds_xdg_shell;
+
+struct ds_xdg_surface;
+
+struct ds_xdg_shell *
+ds_xdg_shell_create(struct wl_display *display);
+
+void
+ds_xdg_shell_add_destroy_listener(struct ds_xdg_shell *shell,
+        struct wl_listener *listener);
+
+void
+ds_xdg_shell_add_new_surface_listener(struct ds_xdg_shell *shell,
+        struct wl_listener *listener);
+
+void
+ds_xdg_surface_add_destroy_listener(struct ds_xdg_surface *surface,
+        struct wl_listener *listener);
+
+void
+ds_xdg_surface_add_map_listener(struct ds_xdg_surface *surface,
+        struct wl_listener *listener);
+
+void
+ds_xdg_surface_add_unmap_listener(struct ds_xdg_surface *surface,
+        struct wl_listener *listener);
+
+void
+ds_xdg_surface_ping(struct ds_xdg_surface *surface);
+
+struct ds_surface *
+ds_xdg_surface_get_surface(struct ds_xdg_surface *surface);
+
+uint32_t
+ds_xdg_toplevel_set_size(struct ds_xdg_surface *surface,
+        uint32_t width, uint32_t height);
+
+uint32_t
+ds_xdg_toplevel_set_activated(struct ds_xdg_surface *surface,
+        bool activated);
+
+#endif
diff --git a/include/meson.build b/include/meson.build
new file mode 100644 (file)
index 0000000..731f7d3
--- /dev/null
@@ -0,0 +1,3 @@
+install_subdir('libds',
+  install_dir: get_option('includedir')
+)
diff --git a/meson.build b/meson.build
new file mode 100644 (file)
index 0000000..24506b6
--- /dev/null
@@ -0,0 +1,38 @@
+project('libds', 'c',
+  license: 'MIT',
+  version: '0.1.0',
+  default_options: [
+    'warning_level=1',
+    'c_std=gnu99',
+    'buildtype=debug'
+  ]
+)
+
+libds_version = meson.project_version()
+version_arr = libds_version.split('.')
+libds_version_major = version_arr[0]
+libds_version_minor = version_arr[1]
+libds_version_patch = version_arr[2]
+
+dir_prefix = get_option('prefix')
+
+libds_inc = include_directories('include')
+common_inc = [ include_directories('.'), libds_inc ]
+
+cdata = configuration_data()
+cdata.set('LIBDS_VERSION_MAJOR', libds_version_major)
+cdata.set('LIBDS_VERSION_MINOR', libds_version_minor)
+cdata.set('LIBDS_VERSION_PATCH', libds_version_patch)
+
+subdir('include')
+subdir('src')
+
+configure_file(output: 'config.h', install: false, configuration: cdata)
+
+pkgconfig = import('pkgconfig')
+pkgconfig.generate(lib_libds,
+  version: meson.project_version(),
+  filebase: meson.project_name(),
+  name: meson.project_name(),
+  description: 'Wayland compositor library',
+)
diff --git a/packaging/libds.manifest b/packaging/libds.manifest
new file mode 100644 (file)
index 0000000..75b0fa5
--- /dev/null
@@ -0,0 +1,5 @@
+<manifest>
+    <request>
+        <domain name="_"/>
+    </request>
+</manifest>
diff --git a/packaging/libds.spec b/packaging/libds.spec
new file mode 100644 (file)
index 0000000..8b7c5a7
--- /dev/null
@@ -0,0 +1,56 @@
+Name:           libds
+Version:        0.0.1
+Release:        0
+Summary:        Wayland Compositor Library
+License:        MIT
+URL:            http://www.tizen.org/
+Source:         %name-%version.tar.xz
+Source1001:     %name.manifest
+
+BuildRequires:  meson
+BuildRequires:  pkgconfig(wayland-server)
+BuildRequires:  pkgconfig(wayland-client)
+BuildRequires:  pkgconfig(wayland-protocols)
+BuildRequires:  pkgconfig(pixman-1)
+BuildRequires:  pkgconfig(libdrm)
+
+%description
+Wayland Compositor Library
+
+%package devel
+Summary:    Wayland Compositor Library
+Group:      Development/Libraries
+Requires:   %{name} = %{version}-%{release}
+
+%description devel
+Development package of Wayland Compositor Library
+
+%prep
+%setup -q
+cp %{SOURCE1001} .
+
+%build
+meson setup \
+    --prefix /usr \
+    --libdir %{_libdir} \
+    --bindir %{_bindir} \
+    builddir
+ninja -C builddir all
+
+%install
+export DESTDIR=%{buildroot}
+ninja -C builddir install
+
+%files
+%manifest %{name}.manifest
+%defattr(-,root,root,-)
+%license LICENSE
+%{_libdir}/*.so.*
+
+%files devel
+%manifest %{name}.manifest
+%defattr(-,root,root,-)
+%license LICENSE
+%{_includedir}/*
+%{_libdir}/pkgconfig/libds.pc
+%{_libdir}/libds.so
diff --git a/src/libds/addon.c b/src/libds/addon.c
new file mode 100644 (file)
index 0000000..9b34b11
--- /dev/null
@@ -0,0 +1,60 @@
+#include <assert.h>
+
+#include "addon.h"
+
+void
+ds_addon_set_init(struct ds_addon_set *set)
+{
+    wl_list_init(&set->addons);
+}
+
+void
+ds_addon_set_finish(ds_addon_set_t *set)
+{
+    ds_addon_t *addon, *tmp;
+
+    wl_list_for_each_safe(addon, tmp, &set->addons, link) {
+        ds_addon_finish(addon);
+        addon->impl->destroy(addon);
+    }
+}
+
+void
+ds_addon_init(ds_addon_t *addon, ds_addon_set_t *set,
+        const void *owner, const ds_addon_interface_t *impl)
+{
+    ds_addon_t *iter;
+
+    assert(owner && impl);
+
+    wl_list_for_each(iter, &set->addons, link) {
+        if (iter->owner == addon->owner && iter->impl == addon->impl)
+            assert(0 && "Can't have two addons of the same type with the same owner");
+    }
+
+    wl_list_insert(&set->addons, &addon->link);
+
+    addon->owner = owner;
+    addon->impl = impl;
+}
+
+void
+ds_addon_finish(ds_addon_t *addon)
+{
+    wl_list_remove(&addon->link);
+    wl_list_init(&addon->link);
+}
+
+ds_addon_t *
+ds_addon_find(ds_addon_set_t *set, const void *owner,
+        const ds_addon_interface_t *impl)
+{
+    struct ds_addon *addon;
+
+    wl_list_for_each(addon, &set->addons, link) {
+        if (addon->owner == owner && addon->impl == impl)
+            return addon;
+    }
+
+    return NULL;
+}
diff --git a/src/libds/addon.h b/src/libds/addon.h
new file mode 100644 (file)
index 0000000..bb04e7b
--- /dev/null
@@ -0,0 +1,42 @@
+#ifndef DS_ADDON_H
+#define DS_ADDON_H
+
+#include <wayland-util.h>
+
+typedef struct ds_addon_set ds_addon_set_t;
+typedef struct ds_addon_interface ds_addon_interface_t;
+typedef struct ds_addon ds_addon_t;
+
+struct ds_addon_set {
+    struct wl_list addons;
+};
+
+struct ds_addon_interface {
+    const char *name;
+    void (*destroy)(struct ds_addon *addon);
+};
+
+struct ds_addon {
+    const ds_addon_interface_t *impl;
+    const void *owner;
+    struct wl_list link;
+};
+
+void
+ds_addon_set_init(ds_addon_set_t *set);
+
+void
+ds_addon_set_finish(ds_addon_set_t *set);
+
+void
+ds_addon_init(ds_addon_t *addon, ds_addon_set_t *set,
+        const void *owner, const ds_addon_interface_t *impl);
+
+void
+ds_addon_finish(ds_addon_t *addon);
+
+ds_addon_t *
+ds_addon_find(ds_addon_set_t *set, const void *owner,
+        const ds_addon_interface_t *impl);
+
+#endif
diff --git a/src/libds/allocator/allocator.c b/src/libds/allocator/allocator.c
new file mode 100644 (file)
index 0000000..34c0aa7
--- /dev/null
@@ -0,0 +1,37 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+
+#include "libds/log.h"
+#include "libds/interfaces/allocator.h"
+
+WL_EXPORT void
+ds_allocator_init(struct ds_allocator *alloc,
+        const struct ds_allocator_interface *iface, uint32_t buffer_caps)
+{
+    alloc->iface = iface;
+    alloc->buffer_caps = buffer_caps;
+
+    wl_signal_init(&alloc->events.destroy);
+}
+
+WL_EXPORT void
+ds_allocator_destroy(struct ds_allocator *alloc)
+{
+    wl_signal_emit(&alloc->events.destroy, NULL);
+    alloc->iface->destroy(alloc);
+}
+
+WL_EXPORT struct ds_buffer *
+ds_allocator_create_buffer(struct ds_allocator *alloc, int width, int height,
+        uint32_t format)
+{
+    return alloc->iface->create_buffer(alloc, width, height, format);
+}
+
+WL_EXPORT void
+ds_allocator_add_destroy_listener(struct ds_allocator *alloc,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&alloc->events.destroy, listener);
+}
diff --git a/src/libds/allocator/shm.c b/src/libds/allocator/shm.c
new file mode 100644 (file)
index 0000000..3b9e219
--- /dev/null
@@ -0,0 +1,174 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <drm_fourcc.h>
+#include <wayland-server.h>
+
+#include "libds/allocator/shm.h"
+#include "libds/interfaces/allocator.h"
+#include "libds/interfaces/buffer.h"
+#include "libds/log.h"
+#include "util.h"
+
+typedef struct ds_shm_allocator ds_shm_allocator_t;
+
+typedef struct ds_shm_buffer ds_shm_buffer_t;
+
+struct ds_shm_allocator
+{
+    struct ds_allocator base;
+};
+
+struct ds_shm_buffer
+{
+    struct ds_buffer base;
+    struct ds_shm_attributes shm;
+    void *data;
+    size_t size;
+};
+
+static const struct ds_allocator_interface shm_allocator_iface;
+
+WL_EXPORT struct ds_allocator *
+ds_shm_allocator_create(void)
+{
+    ds_shm_allocator_t *alloc;
+
+    alloc = calloc(1, sizeof *alloc);
+    if (!alloc)
+        return NULL;
+
+    ds_allocator_init(&alloc->base, &shm_allocator_iface,
+            DS_BUFFER_CAP_DATA_PTR | DS_BUFFER_CAP_SHM);
+
+    ds_dbg("Shm allocator(%p) created", alloc);
+
+    return &alloc->base;
+}
+
+static ds_shm_allocator_t *
+shm_allocator_from_allocator(struct ds_allocator *ds_allocator)
+{
+    assert(ds_allocator->iface == &shm_allocator_iface);
+    return (ds_shm_allocator_t *)ds_allocator;
+}
+
+static void
+shm_allocator_destroy(struct ds_allocator *ds_allocator)
+{
+    ds_shm_allocator_t *alloc;
+
+    alloc = shm_allocator_from_allocator(ds_allocator);
+    ds_dbg("Destroy Shm allocator(%p)", alloc);
+    free(alloc);
+}
+
+static const struct ds_buffer_interface shm_buffer_interface;
+
+static ds_shm_buffer_t *
+shm_buffer_from_buffer(struct ds_buffer *buffer)
+{
+    assert(buffer->iface == &shm_buffer_interface);
+    return (ds_shm_buffer_t *)buffer;
+}
+
+static void
+shm_buffer_destroy(struct ds_buffer *ds_buffer)
+{
+    ds_shm_buffer_t *buffer;
+
+    buffer = shm_buffer_from_buffer(ds_buffer);
+
+    ds_dbg("Destroy shm buffer(%p)", buffer);
+
+    munmap(buffer->data, buffer->size);
+    close(buffer->shm.fd);
+    free(buffer);
+}
+
+static bool
+shm_buffer_get_shm(struct ds_buffer *ds_buffer, struct ds_shm_attributes *shm)
+{
+    ds_shm_buffer_t *buffer;
+
+    buffer = shm_buffer_from_buffer(ds_buffer);
+    memcpy(shm, &buffer->shm, sizeof *shm);
+    return true;
+}
+
+static bool
+shm_buffer_begin_data_ptr_access(struct ds_buffer *ds_buffer, uint32_t flags,
+        void **data, uint32_t *format, size_t *stride)
+{
+    ds_shm_buffer_t *buffer;
+
+    buffer = shm_buffer_from_buffer(ds_buffer);
+    *data = buffer->data;
+    *format = buffer->shm.format;
+    *stride = buffer->shm.stride;
+    return true;
+}
+
+static void
+shm_buffer_end_data_ptr_access(struct ds_buffer *buffer)
+{
+    (void) buffer;
+
+    // This space is intentionally left blank
+}
+
+static const struct ds_buffer_interface shm_buffer_interface =
+{
+    .destroy = shm_buffer_destroy,
+    .get_shm = shm_buffer_get_shm,
+    .begin_data_ptr_access = shm_buffer_begin_data_ptr_access,
+    .end_data_ptr_access = shm_buffer_end_data_ptr_access,
+};
+
+static struct ds_buffer *
+shm_allocator_create_buffer(struct ds_allocator *ds_allocator,
+        int width, int height, uint32_t format)
+{
+    ds_shm_buffer_t *buffer;
+
+    buffer = calloc(1, sizeof *buffer);
+    if (!buffer)
+        return NULL;
+
+    ds_buffer_init(&buffer->base, &shm_buffer_interface, width, height);
+
+    // FIXME
+    int bytes_per_pixel = 4;
+    int stride = width * bytes_per_pixel;
+    buffer->size = stride * height;
+    buffer->shm.fd = allocate_shm_file(buffer->size);
+    if (buffer->shm.fd < 0) {
+        free(buffer);
+        return NULL;
+    }
+
+    buffer->shm.format = format;
+    buffer->shm.width = width;
+    buffer->shm.height = height;
+    buffer->shm.stride = stride;
+    buffer->shm.offset = 0;
+
+    buffer->data = mmap(NULL, buffer->size, PROT_READ | PROT_WRITE, MAP_SHARED,
+            buffer->shm.fd, 0);
+    if (buffer->data == MAP_FAILED) {
+        ds_log_errno(DS_ERR, "mmap failed");
+        close(buffer->shm.fd);
+        free(buffer);
+        return NULL;
+    }
+
+    ds_dbg("Shm buffer(%p) created: size(%dx%d)", buffer, width, height);
+
+    return &buffer->base;
+}
+
+static const struct ds_allocator_interface shm_allocator_iface = {
+    .destroy = shm_allocator_destroy,
+    .create_buffer = shm_allocator_create_buffer,
+};
diff --git a/src/libds/backend/backend.c b/src/libds/backend/backend.c
new file mode 100644 (file)
index 0000000..d52a8d2
--- /dev/null
@@ -0,0 +1,55 @@
+#include <stdlib.h>
+#include <wayland-server.h>
+
+#include "libds/interfaces/backend.h"
+
+WL_EXPORT bool
+ds_backend_start(struct ds_backend *backend)
+{
+    if (backend->iface->start)
+        return backend->iface->start(backend);
+
+    backend->started = true;
+
+    return true;
+}
+
+WL_EXPORT void
+ds_backend_destroy(struct ds_backend *backend)
+{
+    if (!backend)
+        return;
+
+    if (backend->iface && backend->iface->destroy)
+        backend->iface->destroy(backend);
+    else
+        free(backend);
+}
+
+WL_EXPORT void
+ds_backend_add_destroy_listener(struct ds_backend *backend,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&backend->events.destroy, listener);
+}
+
+WL_EXPORT void
+ds_backend_add_new_output_listener(struct ds_backend *backend,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&backend->events.new_output, listener);
+}
+
+void
+ds_backend_init(struct ds_backend *backend, const struct ds_backend_interface *iface)
+{
+    backend->iface = iface;
+    wl_signal_init(&backend->events.destroy);
+    wl_signal_init(&backend->events.new_output);
+}
+
+void
+ds_backend_finish(struct ds_backend *backend)
+{
+    wl_signal_emit(&backend->events.destroy, backend);
+}
diff --git a/src/libds/backend/meson.build b/src/libds/backend/meson.build
new file mode 100644 (file)
index 0000000..b135534
--- /dev/null
@@ -0,0 +1,5 @@
+libds_files += files(
+  'backend.c',
+)
+
+subdir('wayland')
diff --git a/src/libds/backend/wayland/backend.c b/src/libds/backend/wayland/backend.c
new file mode 100644 (file)
index 0000000..faa2c64
--- /dev/null
@@ -0,0 +1,281 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <wayland-server.h>
+#include <wayland-client.h>
+
+#include "backend.h"
+#include "libds/log.h"
+#include "libds/output.h"
+
+#include "xdg-shell-client-protocol.h"
+
+static const struct ds_backend_interface wl_backend_interface;
+static void wl_backend_handle_display_destroy(struct wl_listener *listener,
+        void *data);
+static bool wl_backend_server_init(ds_wl_backend_server_t *server,
+        const char *name);
+static void wl_backend_server_finish(ds_wl_backend_server_t *server);
+static int wl_backend_handle_dispatch_events(int fd, uint32_t mask,
+        void *data);
+
+WL_EXPORT struct ds_backend *
+ds_wl_backend_create(struct wl_display *display, const char *server_name)
+{
+    ds_wl_backend_t *wl_backend;
+    struct wl_event_loop *loop;
+    int fd;
+
+    wl_backend = calloc(1, sizeof *wl_backend);
+    if (!wl_backend) {
+        ds_log_errno(DS_ERR, "Could not allocate memory");
+        return NULL;
+    }
+
+    ds_backend_init(&wl_backend->base, &wl_backend_interface);
+
+    wl_backend->display = display;
+    wl_list_init(&wl_backend->buffers);
+    wl_list_init(&wl_backend->outputs);
+
+    if (!wl_backend_server_init(&wl_backend->server, server_name)) {
+        ds_err("Failed to initialize Wayland Server");
+        goto err_server;
+    }
+
+    loop = wl_display_get_event_loop(wl_backend->display);
+    fd = wl_display_get_fd(wl_backend->server.display);
+
+    wl_backend->server_event_source =
+        wl_event_loop_add_fd(loop, fd, WL_EVENT_READABLE,
+                wl_backend_handle_dispatch_events, wl_backend);
+    if (!wl_backend->server_event_source) {
+        ds_err("Failed to create event source");
+        goto err_src;
+    }
+
+    wl_backend->display_destroy.notify = wl_backend_handle_display_destroy;
+    wl_display_add_destroy_listener(display, &wl_backend->display_destroy);
+
+    ds_inf("Wayland backend(%p) created: server name \"%s\"",
+            wl_backend, server_name);
+
+    return &wl_backend->base;
+
+err_src:
+    wl_backend_server_finish(&wl_backend->server);
+err_server:
+    ds_backend_finish(&wl_backend->base);
+    free(wl_backend);
+
+    return NULL;
+}
+
+ds_wl_backend_t *
+wl_backend_from_backend(struct ds_backend *backend)
+{
+    assert(backend->iface == &wl_backend_interface);
+    return (ds_wl_backend_t *)backend;
+}
+
+static void
+wl_backend_destroy(ds_wl_backend_t *backend)
+{
+    ds_wl_output_t *output, *tmp_output;
+    ds_wl_buffer_t *buffer, *tmp_buffer;
+
+    ds_dbg("Destroy wayland backend(%p)", backend);
+
+    wl_list_for_each_safe(output, tmp_output, &backend->outputs, link) {
+        ds_output_destroy(&output->base);
+    }
+
+    wl_list_for_each_safe(buffer, tmp_buffer, &backend->buffers, link) {
+        destroy_wl_buffer(buffer);
+    }
+
+    ds_backend_finish(&backend->base);
+
+    wl_list_remove(&backend->display_destroy.link);
+
+    wl_event_source_remove(backend->server_event_source);
+
+    wl_backend_server_finish(&backend->server);
+
+    free(backend);
+}
+
+static void
+wl_backend_iface_destroy(struct ds_backend *backend)
+{
+    ds_wl_backend_t *wl_backend;
+
+    if (!backend)
+        return;
+
+    wl_backend = wl_backend_from_backend(backend);
+
+    wl_backend_destroy(wl_backend);
+}
+
+static const struct ds_backend_interface wl_backend_interface = {
+    .start = NULL,
+    .destroy = wl_backend_iface_destroy,
+    .get_drm_fd = NULL,
+};
+
+static void
+wl_backend_handle_display_destroy(struct wl_listener *listener, void *data)
+{
+    ds_wl_backend_t *wl_backend;
+
+    wl_backend = wl_container_of(listener, wl_backend, display_destroy);
+    wl_backend_destroy(wl_backend);
+}
+
+static void
+shm_handle_format(void *data, struct wl_shm *shm, uint32_t shm_format)
+{
+}
+
+static const struct wl_shm_listener shm_listener =
+{
+    .format = shm_handle_format,
+};
+
+static void
+xdg_wm_base_handle_ping(void *data, struct xdg_wm_base *base, uint32_t serial)
+{
+    xdg_wm_base_pong(base, serial);
+}
+
+static const struct xdg_wm_base_listener xdg_wm_base_listener =
+{
+    .ping = xdg_wm_base_handle_ping,
+};
+
+static void
+registry_handle_global(void *data, struct wl_registry *registry,
+        uint32_t name, const char *iface, uint32_t version)
+{
+    ds_wl_backend_server_t *server = data;
+
+    ds_log(DS_DBG, "Wayland global: %s v%d", iface, version);
+
+    if (strcmp(iface, wl_compositor_interface.name) == 0) {
+        server->compositor = wl_registry_bind(registry, name,
+                &wl_compositor_interface, 4);
+    }
+    else if (strcmp(iface, xdg_wm_base_interface.name) == 0) {
+        server->xdg_wm_base = wl_registry_bind(registry, name,
+                &xdg_wm_base_interface, 1);
+        xdg_wm_base_add_listener(server->xdg_wm_base,
+                &xdg_wm_base_listener, NULL);
+    }
+    else if (strcmp(iface, wl_shm_interface.name) == 0) {
+        server->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
+        wl_shm_add_listener(server->shm, &shm_listener, server);
+    }
+}
+
+static void
+registry_handle_global_remove(void *data, struct wl_registry *registry,
+        uint32_t name)
+{
+    // TODO
+}
+
+static const struct wl_registry_listener registry_listener = {
+    .global = registry_handle_global,
+    .global_remove = registry_handle_global_remove,
+};
+
+static bool
+wl_backend_server_init(ds_wl_backend_server_t *server, const char *name)
+{
+    server->display = wl_display_connect(name);
+    if (!server->display) {
+        ds_log_errno(DS_ERR, "Could not connect to display: name \"%s\"", name);
+        return false;
+    }
+
+    server->registry = wl_display_get_registry(server->display);
+    if (!server->registry) {
+        ds_log_errno(DS_ERR, "Could not get wl_registry");
+        goto err_reg;
+    }
+
+    wl_registry_add_listener(server->registry, &registry_listener, server);
+
+    wl_display_roundtrip(server->display);
+
+    if (!server->compositor) {
+        ds_err("Wayland Server does not support wl_compositor");
+        goto err_bind;
+    }
+
+    if (!server->xdg_wm_base) {
+        ds_err("Wayland Server does not support xdg_wm_base");
+        goto err_bind;
+    }
+
+    return true;
+
+err_bind:
+    if (server->compositor)
+        wl_compositor_destroy(server->compositor);
+
+    if (server->xdg_wm_base)
+        xdg_wm_base_destroy(server->xdg_wm_base);
+
+    wl_registry_destroy(server->registry);
+err_reg:
+    wl_display_disconnect(server->display);
+
+    return false;
+}
+
+static void
+wl_backend_server_finish(ds_wl_backend_server_t *server)
+{
+    xdg_wm_base_destroy(server->xdg_wm_base);
+    wl_compositor_destroy(server->compositor);
+    wl_registry_destroy(server->registry);
+    wl_display_disconnect(server->display);
+}
+
+static int
+wl_backend_handle_dispatch_events(int fd, uint32_t mask, void *data)
+{
+    ds_wl_backend_t *wl_backend = data;
+    ds_wl_backend_server_t *server = &wl_backend->server;
+    int count = 0;
+
+    if ((mask & WL_EVENT_HANGUP) || (mask & WL_EVENT_ERROR)) {
+        if (mask & WL_EVENT_ERROR) {
+            ds_err("Failed to read from Wayland Server");
+        }
+        wl_display_terminate(wl_backend->display);
+        return 0;
+    }
+
+    count = 0;
+    if (mask & WL_EVENT_READABLE)
+        count = wl_display_dispatch(server->display);
+
+    if (mask & WL_EVENT_WRITABLE)
+        wl_display_flush(server->display);
+
+    if (mask == 0) {
+        count = wl_display_dispatch_pending(server->display);
+        wl_display_flush(server->display);
+    }
+
+    if (count < 0) {
+        ds_err("Failed to dispatch Wayland Server");
+        wl_display_terminate(wl_backend->display);
+        return 0;
+    }
+
+    return count;
+}
diff --git a/src/libds/backend/wayland/backend.h b/src/libds/backend/wayland/backend.h
new file mode 100644 (file)
index 0000000..1c3cd5b
--- /dev/null
@@ -0,0 +1,69 @@
+#ifndef DS_BACKEND_WAYLAND_H
+#define DS_BACKEND_WAYLAND_H
+
+#include "libds/interfaces/backend.h"
+#include "libds/interfaces/output.h"
+
+typedef struct ds_wl_backend_server ds_wl_backend_server_t;
+
+typedef struct ds_wl_backend ds_wl_backend_t;
+
+typedef struct ds_wl_buffer ds_wl_buffer_t;
+
+typedef struct ds_wl_output ds_wl_output_t;
+
+struct ds_wl_backend_server
+{
+    struct wl_display *display;
+    struct wl_registry *registry;
+    struct wl_compositor *compositor;
+    struct xdg_wm_base *xdg_wm_base;
+    struct wl_event_source *event_source;
+    struct wl_shm *shm;
+};
+
+struct ds_wl_backend
+{
+    struct ds_backend base;
+
+    struct wl_display *display;
+    struct wl_listener display_destroy;
+
+    struct wl_list outputs; // ds_wl_output.link
+    struct wl_list buffers; // ds_wl_buffer.link
+
+    struct wl_event_source *server_event_source;
+    ds_wl_backend_server_t server;
+};
+
+struct ds_wl_buffer
+{
+    struct ds_buffer *buffer;
+    struct wl_buffer *wl_buffer;
+    struct wl_list link; // ds_wl_backend.buffers
+    struct wl_listener buffer_destroy;
+
+    bool released;
+};
+
+struct ds_wl_output
+{
+    struct ds_output base;
+
+    ds_wl_backend_t *backend;
+
+    struct wl_surface *surface;
+    struct xdg_surface *xdg_surface;
+    struct xdg_toplevel *xdg_toplevel;
+    struct wl_callback *frame_callback;
+
+    struct wl_list link;
+};
+
+ds_wl_backend_t *
+wl_backend_from_backend(struct ds_backend *backend);
+
+void
+destroy_wl_buffer(ds_wl_buffer_t *buffer);
+
+#endif
diff --git a/src/libds/backend/wayland/meson.build b/src/libds/backend/wayland/meson.build
new file mode 100644 (file)
index 0000000..db68fe9
--- /dev/null
@@ -0,0 +1,26 @@
+libds_files += files(
+  'backend.c',
+  'output.c',
+)
+
+protocols = {
+  'xdg-shell': wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml',
+}
+
+protocols_code = {}
+protocols_client_header = {}
+
+foreach name, path : protocols
+  client_header = custom_target(
+    name.underscorify() + '_client_h',
+    input: path,
+    output: '@BASENAME@-client-protocol.h',
+    command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'],
+    build_by_default: false,
+  )
+  libds_files += client_header
+endforeach
+
+libds_deps += [
+  dependency('wayland-client', required: true),
+]
diff --git a/src/libds/backend/wayland/output.c b/src/libds/backend/wayland/output.c
new file mode 100644 (file)
index 0000000..a995dbf
--- /dev/null
@@ -0,0 +1,309 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <wayland-client.h>
+
+#include "backend.h"
+#include "libds/log.h"
+#include "libds/output.h"
+
+#include "xdg-shell-client-protocol.h"
+
+const struct ds_output_interface wl_output_iface;
+static const struct xdg_surface_listener wl_output_xdg_surface_listener;
+static const struct xdg_toplevel_listener wl_output_xdg_toplevel_listener;
+
+struct ds_output *
+ds_wl_backend_create_output(struct ds_backend *ds_backend)
+{
+    ds_wl_backend_t *backend;
+    ds_wl_output_t *output;
+
+    backend = wl_backend_from_backend(ds_backend);
+
+    output = calloc(1, sizeof *output);
+    if (!output) {
+        ds_log_errno(DS_ERR, "Could not allocate ds_wl_output");
+        return NULL;
+    }
+
+    ds_output_init(&output->base, &backend->base, &wl_output_iface,
+            backend->display);
+
+    output->backend = backend;
+
+    output->surface = wl_compositor_create_surface(backend->server.compositor);
+    if (!output->surface) {
+        ds_log_errno(DS_ERR, "Could not create output surface");
+        goto err;
+    }
+    wl_surface_set_user_data(output->surface, output);
+
+    output->xdg_surface =
+        xdg_wm_base_get_xdg_surface(backend->server.xdg_wm_base,
+                output->surface);
+    if (!output->xdg_surface) {
+        ds_log_errno(DS_ERR, "Could not get xdg_surface");
+        goto err;
+    }
+    xdg_surface_add_listener(output->xdg_surface,
+            &wl_output_xdg_surface_listener, output);
+
+    output->xdg_toplevel =
+        xdg_surface_get_toplevel(output->xdg_surface);
+    if (!output->xdg_toplevel) {
+        ds_log_errno(DS_ERR, "Could not get xdg_toplevel");
+        goto err;
+    }
+    xdg_toplevel_add_listener(output->xdg_toplevel,
+            &wl_output_xdg_toplevel_listener, output);
+
+    xdg_toplevel_set_app_id(output->xdg_toplevel, "libds");
+
+    wl_surface_commit(output->surface);
+
+    wl_display_roundtrip(backend->server.display);
+
+    wl_list_insert(&backend->outputs, &output->link);
+
+    wl_signal_emit(&backend->base.events.new_output, &output->base);
+
+    ds_dbg("Wayland output(%p) created", output);
+
+    return &output->base;
+
+err:
+    ds_output_destroy(&output->base);
+
+    return NULL;
+}
+
+void
+destroy_wl_buffer(ds_wl_buffer_t *buffer)
+{
+    if (buffer == NULL)
+        return;
+
+    wl_list_remove(&buffer->buffer_destroy.link);
+    wl_list_remove(&buffer->link);
+    wl_buffer_destroy(buffer->wl_buffer);
+    free(buffer);
+}
+
+static ds_wl_output_t *
+wl_output_from_output(struct ds_output *ds_output)
+{
+    assert(ds_output->iface == &wl_output_iface);
+    return (ds_wl_output_t *)ds_output;
+}
+
+static void
+wl_output_iface_destroy(struct ds_output *ds_output)
+{
+    ds_wl_output_t *output;
+
+    output = wl_output_from_output(ds_output);
+
+    ds_dbg("Destroy wayland output(%p)", output);
+
+    wl_list_remove(&output->link);
+
+    if (output->frame_callback)
+        wl_callback_destroy(output->frame_callback);
+
+    if (output->xdg_toplevel)
+        xdg_toplevel_destroy(output->xdg_toplevel);
+
+    if (output->xdg_surface)
+        xdg_surface_destroy(output->xdg_surface);
+
+    if (output->surface)
+        wl_surface_destroy(output->surface);
+
+    wl_display_flush(output->backend->server.display);
+
+    free(output);
+}
+
+static struct wl_buffer *
+import_shm(ds_wl_backend_t *backend, struct ds_shm_attributes *shm)
+{
+    enum wl_shm_format wl_shm_format = WL_SHM_FORMAT_XRGB8888;
+    struct wl_shm_pool *pool;
+    struct wl_buffer *wl_buffer;
+    uint32_t size;
+
+    size = shm->stride * shm->height;
+    pool = wl_shm_create_pool(backend->server.shm, shm->fd, size);
+    if (!pool)
+        return NULL;
+
+    wl_buffer = wl_shm_pool_create_buffer(pool, shm->offset,
+            shm->width, shm->height, shm->stride, wl_shm_format);
+    wl_shm_pool_destroy(pool);
+
+    return wl_buffer;
+}
+
+static void
+buffer_handle_release(void *data, struct wl_buffer *wl_buffer)
+{
+    ds_wl_buffer_t *buffer = data;
+
+    ds_dbg("Wayland output: Buffer(%p) released.", buffer->buffer);
+    buffer->released = true;
+    ds_buffer_unlock(buffer->buffer);
+}
+
+static const struct wl_buffer_listener buffer_listener = {
+    .release = buffer_handle_release,
+};
+
+static void
+buffer_handle_buffer_destroy(struct wl_listener *listener, void *data)
+{
+    ds_wl_buffer_t *buffer;
+
+    buffer = wl_container_of(listener, buffer, buffer_destroy);
+    destroy_wl_buffer(buffer);
+}
+
+static ds_wl_buffer_t *
+create_wl_buffer(ds_wl_backend_t *backend, struct ds_buffer *ds_buffer)
+{
+    struct ds_shm_attributes shm;
+    ds_wl_buffer_t *buffer;
+    struct wl_buffer *wl_buffer;
+
+    if (ds_buffer_get_shm(ds_buffer, &shm)) {
+        wl_buffer = import_shm(backend, &shm);
+    }
+
+    buffer = calloc(1, sizeof *buffer);
+    if (!buffer) {
+        wl_buffer_destroy(wl_buffer);
+        return NULL;
+    }
+
+    buffer->wl_buffer = wl_buffer;
+    buffer->buffer = ds_buffer_lock(ds_buffer);
+    wl_list_insert(&backend->buffers, &buffer->link);
+
+    wl_buffer_add_listener(wl_buffer, &buffer_listener, buffer);
+
+    buffer->buffer_destroy.notify = buffer_handle_buffer_destroy;
+    ds_buffer_add_destroy_listener(ds_buffer, &buffer->buffer_destroy);
+
+    return buffer;
+}
+
+static ds_wl_buffer_t *
+get_or_create_wl_buffer(ds_wl_backend_t *backend, struct ds_buffer *ds_buffer)
+{
+    ds_wl_buffer_t *buffer;
+
+    wl_list_for_each(buffer, &backend->buffers, link) {
+        if (buffer->buffer == ds_buffer && buffer->released) {
+            buffer->released = false;
+            ds_buffer_lock(buffer->buffer);
+            return buffer;
+        }
+    }
+
+    return create_wl_buffer(backend, ds_buffer);
+}
+
+static void
+surface_frame_callback(void *data, struct wl_callback *cb, uint32_t time)
+{
+    ds_wl_output_t *output = data;
+
+    wl_callback_destroy(cb);
+    output->frame_callback = NULL;
+
+    wl_signal_emit(&output->base.events.frame, &output->base);
+}
+
+static const struct wl_callback_listener frame_listener = {
+    .done = surface_frame_callback
+};
+
+static bool
+wl_output_iface_commit(struct ds_output *ds_output)
+{
+    ds_wl_output_t *output;
+    ds_wl_buffer_t *buffer;
+    struct ds_buffer *ds_buffer;
+
+    output = wl_output_from_output(ds_output);
+
+    ds_buffer = ds_output->pending.buffer;
+    buffer = get_or_create_wl_buffer(output->backend, ds_buffer);
+    if (!buffer)
+        return NULL;
+
+    if (ds_output->pending.committed & DS_OUTPUT_STATE_BUFFER) {
+
+        if (output->frame_callback != NULL) {
+            ds_err("Skipping buffer swap");
+            return false;
+        }
+
+        output->frame_callback = wl_surface_frame(output->surface);
+        wl_callback_add_listener(output->frame_callback, &frame_listener,
+                output);
+        wl_surface_attach(output->surface, buffer->wl_buffer, 0, 0);
+        wl_surface_damage_buffer(output->surface, 0, 0, INT32_MAX, INT32_MAX);
+        wl_surface_commit(output->surface);
+
+        ds_dbg("Swap Buffer!!!!!");
+    }
+
+    wl_display_flush(output->backend->server.display);
+
+    return true;
+}
+
+const struct ds_output_interface wl_output_iface =
+{
+    .destroy = wl_output_iface_destroy,
+    .commit = wl_output_iface_commit,
+};
+
+static void
+wl_output_xdg_surface_handle_configure(void *data,
+        struct xdg_surface *xdg_surface, uint32_t serial)
+{
+    xdg_surface_ack_configure(xdg_surface, serial);
+}
+
+static const struct xdg_surface_listener wl_output_xdg_surface_listener =
+{
+    .configure = wl_output_xdg_surface_handle_configure,
+};
+
+static void
+wl_output_xdg_toplevel_handle_configure(void *data,
+        struct xdg_toplevel *xdg_toplevel,
+        int32_t width, int32_t height, struct wl_array *states)
+{
+    // TODO
+    // ds_wl_output_t *output = data;
+
+    if (width == 0 || height == 0)
+        return;
+}
+
+static void
+wl_output_xdg_toplevel_handle_close(void *data,
+        struct xdg_toplevel *xdg_toplevel)
+{
+    ds_wl_output_t *output = data;
+
+    ds_output_destroy(&output->base);
+}
+
+static const struct xdg_toplevel_listener wl_output_xdg_toplevel_listener =
+{
+    .configure = wl_output_xdg_toplevel_handle_configure,
+    .close = wl_output_xdg_toplevel_handle_close,
+};
diff --git a/src/libds/buffer.c b/src/libds/buffer.c
new file mode 100644 (file)
index 0000000..96edec5
--- /dev/null
@@ -0,0 +1,149 @@
+#include <assert.h>
+#include <stdlib.h>
+
+#include "buffer.h"
+#include "client_buffer.h"
+#include "libds/log.h"
+#include "libds/interfaces/buffer.h"
+
+static void buffer_consider_destroy(struct ds_buffer *buffer);
+static bool ds_resource_is_buffer(struct wl_resource *resource);
+
+WL_EXPORT void
+ds_buffer_init(struct ds_buffer *buffer, const struct ds_buffer_interface *iface,
+        int width, int height)
+{
+    buffer->iface = iface;
+    buffer->width = width;
+    buffer->height = height;
+
+    wl_signal_init(&buffer->events.destroy);
+    wl_signal_init(&buffer->events.release);
+}
+
+WL_EXPORT struct ds_buffer *
+ds_buffer_from_resource(struct wl_resource *resource)
+{
+    struct ds_buffer *buffer;
+
+    assert(resource && ds_resource_is_buffer(resource));
+
+    if (wl_shm_buffer_get(resource) != NULL) {
+        ds_shm_client_buffer_t *shm_client_buffer =
+            ds_shm_client_buffer_get_or_create(resource);
+        if (!shm_client_buffer) {
+            ds_err("Failed to create shm client buffer");
+            return NULL;
+        }
+
+        buffer = ds_buffer_lock(&shm_client_buffer->base);
+    }
+    else {
+        // TODO;
+        buffer = NULL;
+    }
+
+    return buffer;
+}
+
+WL_EXPORT void
+ds_buffer_drop(struct ds_buffer *buffer)
+{
+    assert(!buffer->dropped);
+    buffer->dropped = true;
+    ds_dbg("Buffer(%p) dropped: n_locks(%zu)", buffer, buffer->n_locks);
+    buffer_consider_destroy(buffer);
+}
+
+WL_EXPORT struct ds_buffer *
+ds_buffer_lock(struct ds_buffer *buffer)
+{
+    buffer->n_locks++;
+    ds_dbg("Buffer(%p) n_locks(%zu)", buffer, buffer->n_locks);
+    return buffer;
+}
+
+WL_EXPORT void
+ds_buffer_unlock(struct ds_buffer *buffer)
+{
+    assert(buffer->n_locks > 0);
+    buffer->n_locks--;
+    ds_dbg("Buffer(%p) n_locks(%zu)", buffer, buffer->n_locks);
+
+    if (buffer->n_locks == 0)
+        wl_signal_emit(&buffer->events.release, NULL);
+
+    buffer_consider_destroy(buffer);
+}
+
+WL_EXPORT bool
+ds_buffer_begin_data_ptr_access(struct ds_buffer *buffer, uint32_t flags,
+        void **data, uint32_t *format, size_t *stride)
+{
+    assert(!buffer->accessing_data_ptr);
+    if (!buffer->iface->begin_data_ptr_access)
+        return false;
+    if (!buffer->iface->begin_data_ptr_access(buffer, flags, data, format, stride))
+        return false;
+    buffer->accessing_data_ptr = true;
+    return true;
+}
+
+WL_EXPORT void
+ds_buffer_end_data_ptr_access(struct ds_buffer *buffer)
+{
+    assert(buffer->accessing_data_ptr);
+    buffer->iface->end_data_ptr_access(buffer);
+    buffer->accessing_data_ptr = false;
+}
+
+WL_EXPORT void
+ds_buffer_add_destroy_listener(struct ds_buffer *buffer,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&buffer->events.destroy, listener);
+}
+
+void
+ds_buffer_add_release_listener(struct ds_buffer *buffer,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&buffer->events.release, listener);
+}
+
+WL_EXPORT bool
+ds_buffer_get_shm(struct ds_buffer *buffer, struct ds_shm_attributes *attribs)
+{
+    if (!buffer->iface->get_shm)
+        return false;
+
+    return buffer->iface->get_shm(buffer, attribs);
+}
+
+WL_EXPORT void
+ds_buffer_get_size(struct ds_buffer *buffer, int *out_width, int *out_height)
+{
+    if (out_width)
+        *out_width = buffer->width;
+    if (out_height)
+        *out_height = buffer->height;
+}
+
+static void
+buffer_consider_destroy(struct ds_buffer *buffer)
+{
+    if (!buffer->dropped || buffer->n_locks > 0)
+        return;
+
+    assert(!buffer->accessing_data_ptr);
+
+    wl_signal_emit(&buffer->events.destroy, NULL);
+    buffer->iface->destroy(buffer);
+}
+
+static bool
+ds_resource_is_buffer(struct wl_resource *resource)
+{
+    return strcmp(wl_resource_get_class(resource),
+            wl_buffer_interface.name) == 0;
+}
diff --git a/src/libds/buffer.h b/src/libds/buffer.h
new file mode 100644 (file)
index 0000000..08b4784
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef DS_BUFFER_H
+#define DS_BUFFER_H
+
+#include <wayland-server.h>
+
+#include "libds/buffer.h"
+
+#endif
diff --git a/src/libds/client_buffer.h b/src/libds/client_buffer.h
new file mode 100644 (file)
index 0000000..f21ea7c
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef DS_CLIENT_BUFFER_H
+#define DS_CLIENT_BUFFER_H
+
+#include <wayland-server.h>
+
+#include "util.h"
+#include "libds/buffer.h"
+#include "libds/interfaces/buffer.h"
+
+typedef struct ds_shm_client_buffer ds_shm_client_buffer_t;
+
+struct ds_shm_client_buffer
+{
+    struct ds_buffer base;
+
+    uint32_t format;
+    size_t stride;
+
+    struct wl_resource *resource;
+    struct wl_shm_buffer *shm_buffer;
+
+    struct {
+        struct wl_listener buffer_release;
+        struct wl_listener resource_destroy;
+    } listener;
+};
+
+ds_shm_client_buffer_t *
+ds_shm_client_buffer_get_or_create(struct wl_resource *resource);
+
+#endif
diff --git a/src/libds/client_buffer/shm_client_buffer.c b/src/libds/client_buffer/shm_client_buffer.c
new file mode 100644 (file)
index 0000000..e2c4308
--- /dev/null
@@ -0,0 +1,157 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <drm_fourcc.h>
+
+#include "pixel_format.h"
+#include "buffer.h"
+#include "libds/log.h"
+#include "client_buffer.h"
+
+static void
+shm_client_buffer_resource_handle_destroy(struct wl_listener *listener,
+        void *data);
+static ds_shm_client_buffer_t *
+shm_client_buffer_create(struct wl_resource *resource);
+
+ds_shm_client_buffer_t *
+ds_shm_client_buffer_get_or_create(struct wl_resource *resource)
+{
+    ds_shm_client_buffer_t *buffer;
+    struct wl_listener *resource_destroy_listener;
+
+    resource_destroy_listener = wl_resource_get_destroy_listener(resource,
+            shm_client_buffer_resource_handle_destroy);
+    if (resource_destroy_listener) {
+        buffer = wl_container_of(resource_destroy_listener,
+                buffer, listener.resource_destroy);
+        return buffer;
+    }
+
+    return shm_client_buffer_create(resource);
+}
+
+static void
+shm_client_buffer_resource_handle_destroy(struct wl_listener *listener,
+        void *data)
+{
+    ds_shm_client_buffer_t *buffer;
+
+    buffer = wl_container_of(listener, buffer, listener.resource_destroy);
+
+    buffer->resource = NULL;
+    buffer->shm_buffer = NULL;
+    wl_list_remove(&buffer->listener.resource_destroy.link);
+    wl_list_init(&buffer->listener.resource_destroy.link);
+
+    ds_buffer_drop(&buffer->base);
+}
+
+static const struct ds_buffer_interface shm_client_buffer_iface;
+
+static ds_shm_client_buffer_t *
+shm_client_buffer_from_buffer(struct ds_buffer *buffer)
+{
+    assert(buffer->iface == &shm_client_buffer_iface);
+    return (ds_shm_client_buffer_t *)buffer;
+}
+
+static void
+shm_client_buffer_destroy(struct ds_buffer *ds_buffer)
+{
+    ds_shm_client_buffer_t *buffer;
+
+    buffer = shm_client_buffer_from_buffer(ds_buffer);
+
+    ds_dbg("Destroy shm client buffer(%p)", buffer);
+
+    wl_list_remove(&buffer->listener.resource_destroy.link);
+    wl_list_remove(&buffer->listener.buffer_release.link);
+    free(buffer);
+}
+
+static bool
+shm_client_buffer_begin_data_ptr_access(struct ds_buffer *ds_buffer,
+        enum ds_buffer_data_ptr_access_flag flags, void **data,
+        uint32_t *format, size_t *stride)
+{
+    ds_shm_client_buffer_t *buffer;
+
+    buffer = shm_client_buffer_from_buffer(ds_buffer);
+    *format = buffer->format;
+    *stride = buffer->stride;
+    if (buffer->shm_buffer) {
+        *data = wl_shm_buffer_get_data(buffer->shm_buffer);
+        wl_shm_buffer_begin_access(buffer->shm_buffer);
+    }
+    else
+        return false;
+
+    return true;
+}
+
+static void
+shm_client_buffer_end_data_ptr_access(struct ds_buffer *ds_buffer)
+{
+    ds_shm_client_buffer_t *buffer;
+
+    buffer = shm_client_buffer_from_buffer(ds_buffer);
+    if (buffer->shm_buffer)
+        wl_shm_buffer_end_access(buffer->shm_buffer);
+}
+
+static const struct ds_buffer_interface shm_client_buffer_iface = {
+    .destroy = shm_client_buffer_destroy,
+    .begin_data_ptr_access = shm_client_buffer_begin_data_ptr_access,
+    .end_data_ptr_access = shm_client_buffer_end_data_ptr_access,
+};
+
+static void
+shm_client_buffer_handle_release(struct wl_listener *listener, void *data)
+{
+    ds_shm_client_buffer_t *buffer;
+
+    buffer = wl_container_of(listener, buffer, listener.buffer_release);
+    if (buffer->resource)
+        wl_buffer_send_release(buffer->resource);
+}
+
+static ds_shm_client_buffer_t *
+shm_client_buffer_create(struct wl_resource *resource)
+{
+    ds_shm_client_buffer_t *buffer;
+    struct wl_shm_buffer *shm_buffer;
+    enum wl_shm_format wl_shm_format;
+    int32_t width;
+    int32_t height;
+
+    shm_buffer = wl_shm_buffer_get(resource);
+    width = wl_shm_buffer_get_width(shm_buffer);
+    height = wl_shm_buffer_get_height(shm_buffer);
+
+    buffer = calloc(1, sizeof(*buffer));
+    if (!buffer)
+        return NULL;
+
+    ds_buffer_init(&buffer->base, &shm_client_buffer_iface, width, height);
+
+    buffer->resource = resource;
+    buffer->shm_buffer = shm_buffer;
+
+    wl_shm_format = wl_shm_buffer_get_format(shm_buffer);
+    buffer->format = convert_wl_shm_format_to_drm(wl_shm_format);
+    buffer->stride = wl_shm_buffer_get_stride(shm_buffer);
+
+    buffer->listener.buffer_release.notify =
+        shm_client_buffer_handle_release;
+    ds_buffer_add_release_listener(&buffer->base,
+            &buffer->listener.buffer_release);
+
+    buffer->listener.resource_destroy.notify =
+        shm_client_buffer_resource_handle_destroy;
+    wl_resource_add_destroy_listener(resource,
+            &buffer->listener.resource_destroy);
+
+    ds_dbg("Shm client buffer(%p) created", buffer);
+
+    return buffer;
+}
diff --git a/src/libds/compositor.c b/src/libds/compositor.c
new file mode 100644 (file)
index 0000000..a073405
--- /dev/null
@@ -0,0 +1,143 @@
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "libds/log.h"
+#include "subcompositor.h"
+#include "surface.h"
+#include "region.h"
+
+#define COMPOSITOR_VERSION 4
+
+struct ds_compositor
+{
+    struct wl_global *global;
+    ds_subcompositor_t subcompositor;
+
+    struct {
+        struct wl_signal new_surface;
+        struct wl_signal destroy;
+    } events;
+
+    struct wl_listener display_destroy;
+};
+
+static void compositor_bind(struct wl_client *client, void *data,
+        uint32_t version, uint32_t id);
+static void compositor_handle_display_destroy(struct wl_listener *listener, 
+        void *data);
+
+WL_EXPORT struct ds_compositor *
+ds_compositor_create(struct wl_display *display)
+{
+    struct ds_compositor *compositor;
+
+    compositor = calloc(1, sizeof *compositor);
+    if (!compositor) {
+        ds_log_errno(DS_ERR, "Could not allocate memory");
+        return NULL;
+    }
+
+    compositor->global = wl_global_create(display, &wl_compositor_interface,
+            COMPOSITOR_VERSION, compositor, compositor_bind);
+    if (!compositor->global) {
+        ds_log_errno(DS_ERR, "Could not create global");
+        goto err_global;
+    }
+
+    if (!ds_subcompositor_init(&compositor->subcompositor, display)) {
+        ds_err("Could not initialize subcompositor");
+        goto err_subcomp;
+    }
+
+    wl_signal_init(&compositor->events.new_surface);
+    wl_signal_init(&compositor->events.destroy);
+
+    compositor->display_destroy.notify = compositor_handle_display_destroy;
+    wl_display_add_destroy_listener(display, &compositor->display_destroy);
+
+    ds_inf("Compositor(%p) created", compositor);
+
+    return compositor;
+
+err_subcomp:
+    wl_global_destroy(compositor->global);
+err_global:
+    free(compositor);
+
+    return NULL;
+}
+
+WL_EXPORT void
+ds_compositor_add_new_surface_listener(struct ds_compositor *compositor,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&compositor->events.new_surface, listener);
+}
+
+WL_EXPORT void
+ds_compositor_add_destroy_listener(struct ds_compositor *compositor,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&compositor->events.destroy, listener);
+}
+
+static void
+compositor_handle_create_surface(struct wl_client *client,
+        struct wl_resource *resource, uint32_t id)
+{
+    struct ds_compositor *compositor = wl_resource_get_user_data(resource);
+    struct ds_surface *surface;
+
+    surface = ds_surface_create(client,
+            wl_resource_get_version(resource), id);
+    if (!surface) {
+        ds_err("Could not create ds_surface");
+        return;
+    }
+
+    wl_signal_emit(&compositor->events.new_surface, surface);
+}
+
+static void
+compositor_handle_create_region(struct wl_client *client,
+        struct wl_resource *resource, uint32_t id)
+{
+    ds_region_add(client, wl_resource_get_version(resource), id);
+}
+
+static const struct wl_compositor_interface compositor_impl = {
+    .create_surface = compositor_handle_create_surface,
+    .create_region = compositor_handle_create_region,
+};
+
+static void compositor_bind(struct wl_client *client, void *data,
+        uint32_t version, uint32_t id)
+{
+    struct ds_compositor *compositor = data;
+    struct wl_resource *resource;
+
+    resource = wl_resource_create(client, &wl_compositor_interface,
+            version, id);
+    if (resource == NULL) {
+        wl_client_post_no_memory(client);
+        return;
+    }
+
+    wl_resource_set_implementation(resource, &compositor_impl,
+            compositor, NULL);
+}
+
+static void
+compositor_handle_display_destroy(struct wl_listener *listener, void *data)
+{
+    struct ds_compositor *compositor =
+        wl_container_of(listener, compositor, display_destroy);
+
+    ds_dbg("Destroy compositor(%p)", compositor);
+
+    wl_signal_emit(&compositor->events.destroy, compositor);
+
+    wl_list_remove(&compositor->display_destroy.link);
+    ds_subcompositor_finish(&compositor->subcompositor);
+    free(compositor);
+}
diff --git a/src/libds/log.c b/src/libds/log.c
new file mode 100644 (file)
index 0000000..2777f9a
--- /dev/null
@@ -0,0 +1,96 @@
+#include <error.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wayland-server.h>
+
+#include "libds/log.h"
+
+static bool colored = true;
+static enum ds_log_level log_level = DS_ERR;
+
+static const char *level_colors[] = {
+    [DS_SILENT] = "",
+    [DS_ERR] = "\x1B[1;31m",
+    [DS_INF] = "\x1B[1;34m",
+    [DS_DBG] = "\x1B[1;90m",
+};
+
+static const char *level_headers[] = {
+    [DS_SILENT] = "",
+    [DS_ERR] = "[ERROR]",
+    [DS_INF] = "[INFO]",
+    [DS_DBG] = "[DEBUG]",
+};
+
+static void log_stderr(enum ds_log_level level, const char *fmt, va_list args);
+static void log_wl(const char *fmt, va_list args);
+
+static ds_log_func_t log_callback = log_stderr;
+
+WL_EXPORT void
+ds_log_init(enum ds_log_level level, ds_log_func_t callback)
+{
+    if (level < DS_LOG_LEVEL_LAST)
+        log_level = level;
+    if (callback)
+        log_callback = callback;
+
+    wl_log_set_handler_server(log_wl);
+}
+
+WL_EXPORT void
+_ds_vlog(enum ds_log_level level, const char *fmt, va_list args)
+{
+    log_callback(level, fmt, args);
+}
+
+WL_EXPORT void
+_ds_log(enum ds_log_level level, const char *fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    log_callback(level, fmt, args);
+    va_end(args);
+}
+
+enum ds_log_level
+ds_log_get_level(void)
+{
+    return log_level;
+}
+
+static void
+log_stderr(enum ds_log_level level, const char *fmt, va_list args)
+{
+    bool colored_tty = false;
+
+    if (level > log_level)
+        return;
+
+    unsigned c = (level < DS_LOG_LEVEL_LAST) ? level : DS_LOG_LEVEL_LAST - 1;
+
+    colored_tty = colored && isatty(STDERR_FILENO);
+    if (colored_tty)
+        fprintf(stderr, "%s", level_colors[c]);
+    else
+        fprintf(stderr, "%s ", level_headers[c]);
+
+    vfprintf(stderr, fmt, args);
+
+    if (colored_tty)
+        fprintf(stderr, "\x1B[0m");
+    fprintf(stderr, "\n");
+}
+
+static void
+log_wl(const char *fmt, va_list args)
+{
+    static char ds_fmt[1024];
+    int n = snprintf(ds_fmt, sizeof(ds_fmt), "[wayland] %s", fmt);
+    if (n > 0 && ds_fmt[n - 1] == '\n')
+        ds_fmt[n - 1] = '\0';
+    _ds_vlog(DS_INF, ds_fmt, args);
+}
diff --git a/src/libds/meson.build b/src/libds/meson.build
new file mode 100644 (file)
index 0000000..2d09a91
--- /dev/null
@@ -0,0 +1,74 @@
+libds_files = [
+  'log.c',
+  'addon.c',
+  'buffer.c',
+  'allocator/allocator.c',
+  'allocator/shm.c',
+  'swapchain.c',
+  'output.c',
+  'compositor.c',
+  'subcompositor.c',
+  'region.c',
+  'util/time.c',
+  'util/shm.c',
+  'surface/surface.c',
+  'surface/subsurface.c',
+  'client_buffer/shm_client_buffer.c',
+  'xdg_shell/xdg_shell.c',
+  'xdg_shell/xdg_surface.c',
+  'xdg_shell/xdg_toplevel.c',
+  'pixel_format.c',
+]
+
+protocols = {
+  'xdg-shell': wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml',
+}
+
+protocols_code = {}
+protocols_server_header = {}
+
+foreach name, path : protocols
+  code = custom_target(
+    name.underscorify() + '_c',
+    input: path,
+    output: '@BASENAME@-protocol.c',
+    command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'],
+  )
+  libds_files += code
+
+  server_header = custom_target(
+    name.underscorify() + '_server_h',
+    input: path,
+    output: '@BASENAME@-server-protocol.h',
+    command: [wayland_scanner, 'server-header', '@INPUT@', '@OUTPUT@'],
+    build_by_default: false,
+  )
+  libds_files += server_header
+endforeach
+
+math = meson.get_compiler('c').find_library('m')
+wayland_server = dependency('wayland-server', required: true)
+pixman = dependency('pixman-1', required: true)
+libdrm = dependency('libdrm', required: true)
+
+libds_deps = [
+  math,
+  wayland_server,
+  pixman,
+  libdrm,
+]
+
+subdir('backend')
+
+lib_libds = shared_library('ds', libds_files,
+  dependencies: libds_deps,
+  include_directories: [ common_inc, include_directories('.') ],
+  version: meson.project_version(),
+  install: true
+)
+
+dep_libds = declare_dependency(
+  link_with: lib_libds,
+  dependencies: libds_deps,
+  include_directories: [ common_inc, include_directories('.') ],
+)
diff --git a/src/libds/output.c b/src/libds/output.c
new file mode 100644 (file)
index 0000000..21d4ff8
--- /dev/null
@@ -0,0 +1,116 @@
+#include <assert.h>
+#include <stdlib.h>
+
+#include "libds/log.h"
+#include "libds/output.h"
+#include "libds/interfaces/output.h"
+
+static void output_handle_display_destroy(struct wl_listener *listener,
+        void *data);
+static void output_state_clear(struct ds_output_state *state);
+static void output_state_clear_buffer(struct ds_output_state *state);
+
+WL_EXPORT void
+ds_output_init(struct ds_output *output, struct ds_backend *backend,
+        const struct ds_output_interface *iface, struct wl_display *display)
+{
+    assert(iface->commit);
+
+    output->backend = backend;
+    output->iface = iface;
+    output->display = display;
+
+    wl_signal_init(&output->events.destroy);
+    wl_signal_init(&output->events.frame);
+    wl_signal_init(&output->events.commit);
+
+    output->display_destroy.notify = output_handle_display_destroy;
+    wl_display_add_destroy_listener(display, &output->display_destroy);
+}
+
+WL_EXPORT void
+ds_output_destroy(struct ds_output *output)
+{
+    wl_list_remove(&output->display_destroy.link);
+
+    wl_signal_emit(&output->events.destroy, output);
+
+    if (output->iface && output->iface->destroy)
+        output->iface->destroy(output);
+    else
+        free(output);
+}
+
+WL_EXPORT bool
+ds_output_commit(struct ds_output *output)
+{
+    // TODO signal precommit
+
+    if (!output->iface->commit(output)) {
+        return false;
+    }
+
+    output_state_clear(&output->pending);
+
+    // TODO signal commit
+
+    return true;
+}
+
+WL_EXPORT void
+ds_output_attach_buffer(struct ds_output *output, struct ds_buffer *buffer)
+{
+    output_state_clear_buffer(&output->pending);
+    output->pending.committed |= DS_OUTPUT_STATE_BUFFER;
+    output->pending.buffer = ds_buffer_lock(buffer);
+}
+
+WL_EXPORT void
+ds_output_add_destroy_listener(struct ds_output *output,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&output->events.destroy, listener);
+}
+
+WL_EXPORT void
+ds_output_add_frame_listener(struct ds_output *output,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&output->events.frame, listener);
+}
+
+WL_EXPORT void
+ds_output_add_commit_listener(struct ds_output *output,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&output->events.commit, listener);
+}
+
+static void
+output_handle_display_destroy(struct wl_listener *listener, void *data)
+{
+    struct ds_output *output;
+
+    output = wl_container_of(listener, output, display_destroy);
+    // TODO
+    // destroy wl_global
+}
+
+static void
+output_state_clear(struct ds_output_state *state)
+{
+    output_state_clear_buffer(state);
+    state->committed = 0;
+}
+
+static void
+output_state_clear_buffer(struct ds_output_state *state)
+{
+    if (!(state->committed & DS_OUTPUT_STATE_BUFFER))
+        return;
+
+    ds_buffer_unlock(state->buffer);
+    state->buffer = NULL;
+
+    state->committed &= ~DS_OUTPUT_STATE_BUFFER;
+}
diff --git a/src/libds/pixel_format.c b/src/libds/pixel_format.c
new file mode 100644 (file)
index 0000000..29da532
--- /dev/null
@@ -0,0 +1,15 @@
+#include <drm_fourcc.h>
+#include "pixel_format.h"
+
+uint32_t
+convert_wl_shm_format_to_drm(enum wl_shm_format fmt)
+{
+    switch (fmt) {
+        case WL_SHM_FORMAT_XRGB8888:
+            return DRM_FORMAT_XRGB8888;
+        case WL_SHM_FORMAT_ARGB8888:
+            return DRM_FORMAT_ARGB8888;
+        default:
+            return (uint32_t)fmt;
+    }
+}
diff --git a/src/libds/pixel_format.h b/src/libds/pixel_format.h
new file mode 100644 (file)
index 0000000..21eb0a6
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef DS_PIXEL_FORMAT_H
+#define DS_PIXEL_FORMAT_H
+
+#include <stdint.h>
+#include <wayland-server.h>
+
+uint32_t convert_wl_shm_format_to_drm(enum wl_shm_format fmt);
+
+#endif
diff --git a/src/libds/presentation.c b/src/libds/presentation.c
new file mode 100644 (file)
index 0000000..d94ad04
--- /dev/null
@@ -0,0 +1,114 @@
+#include "libds-private.h"
+#include "presentation-time-protocol.h"
+
+#define PRESENTATION_VERSION 1
+
+struct ds_presentation
+{
+    struct wl_global *global;
+
+    struct {
+        struct wl_signal destroy;
+    } events;
+};
+
+struct ds_presentation_feedback
+{
+    struct wl_list resources;
+
+}
+
+struct ds_presentation_surface_state
+{
+};
+
+struct ds_presentation_feedback
+{
+    struct wl_resource *resource;
+    struct wl_list link;
+    uint32_t psf_flags;
+};
+
+
+
+static void presentation_bind(struct wl_client *client, void *data,
+        uint32_t version, uint32_t id);
+static void handle_display_destroy(struct wl_listener *listener, void *data);
+
+struct ds_presentation *
+ds_presentation_create(struct wl_display *display, clockid_t clk_id)
+{
+    struct ds_presentation *presentation;
+
+    presentation = calloc(1, sizeof *presentation);
+    if (!presentation)
+        return NULL;
+
+    presentation->global = wl_global_create(display, &wp_presentation_interface,
+            PRESENTATION_VERSION, presentation, presentation_bind);
+    if (!presentation->global) {
+        free(presentation);
+        return NULL;
+    }
+
+    presentation->clock = clk_id;
+
+    wl_signal_init(&presentation->events.destroy);
+
+    presentation->display_destroy.notify = handle_display_destroy;
+    wl_display_add_destroy_listener(display, &presentation->display_destroy);
+}
+
+static void
+presentation_handle_destroy(struct wl_client *client, struct wl_resource *resource)
+{
+    wl_resource_destroy(resource);
+}
+
+static void
+presentation_handle_feedback(struct wl_client *client,
+        struct wl_resource *presentation_resource,
+        struct wl_resource *surface_resource, uint32_t id)
+{
+    struct ds_presentation *presentation;
+    struct ds_surface *surface;
+
+    presentation = wl_resource_get_user_data(presentation_resource);
+    surface = ds_surface_from_resource(surface_resource);
+}
+
+static const struct wp_presentation_interface presentation_impl = {
+    .destroy = presentation_handle_destroy,
+    .feedback = presentation_handle_feedback,
+};
+
+static void
+presentation_bind(struct wl_client *client, void *data,
+        uint32_t version, uint32_t id)
+{
+    struct ds_presentation *presentation = data;
+    struct wl_resource *resource;
+
+    resource = wl_resource_create(client, &wp_presentation_interface,
+            version, id);
+    if (!resource) {
+        wl_client_post_no_memory(client);
+        return;
+    }
+    wl_resource_set_implementation(resource, &presentation_impl,
+            presentation, NULL);
+
+    wp_presentation_send_clock_id(resource, (uint32_t)presentation->clock);
+}
+
+static void
+handle_display_destroy(struct wl_listener *listener, void *data)
+{
+    struct ds_presentation *presentation;
+
+    presentation = wl_container_of(listener, presentation, display_destroy);
+    wl_signal_emit(&presentation->events.destroy, presentation);
+    wl_list_remove(&presentation->display_destroy.link);
+    wl_global_destroy(presentation->global);
+    free(presentation);
+}
diff --git a/src/libds/region.c b/src/libds/region.c
new file mode 100644 (file)
index 0000000..27d30da
--- /dev/null
@@ -0,0 +1,196 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <math.h>
+#include <pixman.h>
+
+#include "region.h"
+#include "libds/log.h"
+
+static const struct wl_region_interface region_impl;
+
+static void region_handle_resource_destroy(struct wl_resource *resource);
+
+void
+ds_region_add(struct wl_client *client, uint32_t version, uint32_t id)
+{
+    struct wl_resource *resource;
+    pixman_region32_t *region;
+
+    region = calloc(1, sizeof *region);
+    if (!region) {
+        wl_client_post_no_memory(client);
+        return;
+    }
+
+    pixman_region32_init(region);
+
+    resource = wl_resource_create(client, &wl_region_interface, version, id);
+    if (!resource) {
+        free(region);
+        wl_client_post_no_memory(client);
+        return;
+    }
+
+    wl_resource_set_implementation(resource, &region_impl, region,
+            region_handle_resource_destroy);
+}
+
+pixman_region32_t *
+ds_region_from_resource(struct wl_resource *resource)
+{
+    assert(wl_resource_instance_of(resource, &wl_region_interface,
+                &region_impl));
+    return wl_resource_get_user_data(resource);
+}
+
+void
+ds_region_transform(pixman_region32_t *dst, pixman_region32_t *src,
+        enum wl_output_transform transform, int width, int height)
+{
+    pixman_box32_t *src_rects, *dst_rects;
+    int nrects, i;
+
+    if (transform == WL_OUTPUT_TRANSFORM_NORMAL) {
+        pixman_region32_copy(dst, src);
+        return;
+    }
+
+    src_rects = pixman_region32_rectangles(src, &nrects);
+    dst_rects = malloc(nrects * sizeof *dst_rects);
+    if (!dst_rects)
+        return;
+
+    for (i = 0; i < nrects; i++) {
+        switch (transform) {
+            default:
+                ds_err("Unkown transform value(%d)", transform);
+            case WL_OUTPUT_TRANSFORM_NORMAL:
+                dst_rects[i].x1 = src_rects[i].x1;
+                dst_rects[i].y1 = src_rects[i].y1;
+                dst_rects[i].x2 = src_rects[i].x2;
+                dst_rects[i].y2 = src_rects[i].y2;
+                break; 
+            case WL_OUTPUT_TRANSFORM_90:
+                dst_rects[i].x1 = height - src_rects[i].y2;
+                dst_rects[i].y1 = src_rects[i].x1;
+                dst_rects[i].x2 = height - src_rects[i].y1;
+                dst_rects[i].y2 = src_rects[i].x2;
+                break;
+            case WL_OUTPUT_TRANSFORM_180:
+                dst_rects[i].x1 = width - src_rects[i].x2;
+                dst_rects[i].y1 = height - src_rects[i].y2;
+                dst_rects[i].x2 = width - src_rects[i].x1;
+                dst_rects[i].y2 = height - src_rects[i].y1;
+                break;
+            case WL_OUTPUT_TRANSFORM_270:
+                dst_rects[i].x1 = src_rects[i].y1;
+                dst_rects[i].y1 = width - src_rects[i].x2;
+                dst_rects[i].x2 = src_rects[i].y2;
+                dst_rects[i].y2 = width - src_rects[i].x1;
+                break;
+            case WL_OUTPUT_TRANSFORM_FLIPPED:
+                dst_rects[i].x1 = width - src_rects[i].x2;
+                dst_rects[i].y1 = src_rects[i].y1;
+                dst_rects[i].x2 = width - src_rects[i].x1;
+                dst_rects[i].y2 = src_rects[i].y2;
+                break;
+            case WL_OUTPUT_TRANSFORM_FLIPPED_90:
+                dst_rects[i].x1 = src_rects[i].y1;
+                dst_rects[i].y1 = src_rects[i].x1;
+                dst_rects[i].x2 = src_rects[i].y2;
+                dst_rects[i].y2 = src_rects[i].x2;
+                break;
+            case WL_OUTPUT_TRANSFORM_FLIPPED_180:
+                dst_rects[i].x1 = src_rects[i].x1;
+                dst_rects[i].y1 = height - src_rects[i].y2;
+                dst_rects[i].x2 = src_rects[i].x2;
+                dst_rects[i].y2 = height - src_rects[i].y1;
+                break;
+            case WL_OUTPUT_TRANSFORM_FLIPPED_270:
+                dst_rects[i].x1 = height - src_rects[i].y2;
+                dst_rects[i].y1 = width - src_rects[i].x2;
+                dst_rects[i].x2 = height - src_rects[i].y1;
+                dst_rects[i].y2 = width - src_rects[i].x1;
+                break;
+       }
+    }
+
+    pixman_region32_fini(dst);
+    pixman_region32_init_rects(dst, dst_rects, nrects);
+    free(dst_rects);
+}
+
+void
+ds_region_scale_xy(pixman_region32_t *dst, pixman_region32_t *src,
+        float scale_x, float scale_y)
+{
+    pixman_box32_t *src_rects, *dst_rects;
+    int nrects, i;
+
+    if (scale_x == 1.0 && scale_y == 1.0) {
+        pixman_region32_copy(dst, src);
+    }
+
+    src_rects = pixman_region32_rectangles(src, &nrects);
+    dst_rects = malloc(nrects * sizeof *dst_rects);
+    if (!dst_rects)
+        return;
+
+    for (i = 0; i < nrects; i++) {
+        dst_rects[i].x1 = floor(src_rects[i].x1 * scale_x);
+        dst_rects[i].x2 = ceil(src_rects[i].x2 * scale_x);
+        dst_rects[i].y1 = floor(src_rects[i].y1 * scale_y);
+        dst_rects[i].y2 = ceil(src_rects[i].y2 * scale_y);
+    }
+
+    pixman_region32_fini(dst);
+    pixman_region32_init_rects(dst, dst_rects, nrects);
+    free(dst_rects);
+}
+
+void
+ds_region_scale(pixman_region32_t *dst, pixman_region32_t *src, float scale)
+{
+    ds_region_scale_xy(dst, src, scale, scale);
+}
+
+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)
+{
+    pixman_region32_t *region = wl_resource_get_user_data(resource);
+    pixman_region32_union_rect(region, 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)
+{
+    pixman_region32_t *region = wl_resource_get_user_data(resource);
+    pixman_region32_t rect;
+
+    pixman_region32_union_rect(region, region, x, y, width, height);
+    pixman_region32_init_rect(&rect, x, y, width, height);
+    pixman_region32_subtract(region, region, &rect);
+    pixman_region32_fini(&rect);
+}
+
+static const struct wl_region_interface region_impl = {
+    .destroy = region_destroy,
+    .add = region_add,
+    .subtract = region_subtract,
+};
+
+static void
+region_handle_resource_destroy(struct wl_resource *resource)
+{
+    pixman_region32_t *region = wl_resource_get_user_data(resource);
+    pixman_region32_fini(region);
+    free(region);
+}
diff --git a/src/libds/region.h b/src/libds/region.h
new file mode 100644 (file)
index 0000000..b27963c
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef DS_REGION_H
+#define DS_REGION_H
+
+#include <pixman.h>
+#include <wayland-server.h>
+
+void 
+ds_region_add(struct wl_client *client, uint32_t version, uint32_t id);
+
+pixman_region32_t *
+ds_region_from_resource(struct wl_resource *resource);
+
+void
+ds_region_transform(pixman_region32_t *dst, pixman_region32_t *src,
+        enum wl_output_transform transform, int width, int height);
+
+void
+ds_region_scale_xy(pixman_region32_t *dst, pixman_region32_t *src,
+        float scale_x, float scale_y);
+
+void
+ds_region_scale(pixman_region32_t *dst, pixman_region32_t *src, float scale);
+
+#endif
diff --git a/src/libds/subcompositor.c b/src/libds/subcompositor.c
new file mode 100644 (file)
index 0000000..483fefb
--- /dev/null
@@ -0,0 +1,98 @@
+#include "subcompositor.h"
+#include "surface.h"
+#include "libds/log.h"
+
+#define SUBCOMPOSITOR_VERSION 1
+
+static void subcompositor_bind(struct wl_client *client, void *data,
+        uint32_t version, uint32_t id);
+
+bool
+ds_subcompositor_init(ds_subcompositor_t *subcomp,
+        struct wl_display *display)
+{
+    subcomp->global = wl_global_create(display, &wl_subcompositor_interface,
+            SUBCOMPOSITOR_VERSION, subcomp, subcompositor_bind);
+    if (!subcomp->global) {
+        ds_log_errno(DS_ERR, "Could not create global");
+        return false;
+    }
+
+    return true;
+}
+
+void
+ds_subcompositor_finish(ds_subcompositor_t *subcomp)
+{
+    wl_global_destroy(subcomp->global);
+}
+
+static void
+subcompositor_handle_destroy(struct wl_client *client,
+        struct wl_resource *resource)
+{
+    wl_resource_destroy(resource);
+}
+
+static void
+subcompositor_handle_get_subsurface(struct wl_client *client,
+        struct wl_resource *resource, uint32_t id,
+        struct wl_resource *surface_resource,
+        struct wl_resource *parent_resource)
+{
+    struct ds_surface *surface = ds_surface_from_resource(surface_resource);
+    struct ds_surface *parent = ds_surface_from_resource(parent_resource);
+
+    if (surface == parent) {
+        ds_inf("ds_surface(%p) cannot be its own parent", surface);
+        wl_resource_post_error(resource,
+                WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE,
+                "%d: wl_surface@%d cannot be its own parent",
+                id, wl_resource_get_id(surface_resource));
+        return;
+    }
+
+    if (ds_surface_is_subsurface(surface)) {
+        ds_inf("ds_surface(%p) is already a sub-surface", surface);
+        wl_resource_post_error(resource,
+                WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE,
+                "%d: wl_surface@%d is already a sub-surface",
+                id, wl_resource_get_id(surface_resource));
+        return;
+    }
+
+    if (ds_surface_is_ancestor_of(surface, parent)) {
+        ds_inf("ds_surface(%p) is an ancestor of given parent(%p)",
+                surface, parent);
+        wl_resource_post_error(resource,
+                WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE,
+                "%d: wl_surface@%d is an ancestor of parent",
+                id, wl_resource_get_id(surface_resource));
+        return;
+    }
+
+    ds_subsurface_create(resource, surface, parent,
+            wl_resource_get_version(resource), id);
+}
+
+static const struct wl_subcompositor_interface subcompositor_impl = {
+    .destroy = subcompositor_handle_destroy,
+    .get_subsurface = subcompositor_handle_get_subsurface,
+};
+
+static void
+subcompositor_bind(struct wl_client *client, void *data,
+        uint32_t version, uint32_t id)
+{
+    ds_subcompositor_t *subcomp = data;
+    struct wl_resource *resource;
+
+    resource = wl_resource_create(client, &wl_subcompositor_interface, 1, id);
+    if (resource == NULL) {
+        wl_client_post_no_memory(client);
+        return;
+    }
+
+    wl_resource_set_implementation(resource, &subcompositor_impl,
+            subcomp, NULL);
+}
diff --git a/src/libds/subcompositor.h b/src/libds/subcompositor.h
new file mode 100644 (file)
index 0000000..362727e
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef DS_SUBCOMPOSITOR_H
+#define DS_SUBCOMPOSITOR_H
+
+#include <wayland-server.h>
+
+typedef struct ds_subcompositor ds_subcompositor_t;
+
+struct ds_subcompositor
+{
+    struct wl_global *global;
+};
+
+bool
+ds_subcompositor_init(ds_subcompositor_t *subcomp,
+        struct wl_display *display);
+
+void
+ds_subcompositor_finish(ds_subcompositor_t *subcomp);
+
+#endif
diff --git a/src/libds/surface.h b/src/libds/surface.h
new file mode 100644 (file)
index 0000000..8f3ceac
--- /dev/null
@@ -0,0 +1,49 @@
+#ifndef DS_SURFACE_H
+#define DS_SURFACE_H
+
+#include <wayland-server.h>
+
+#include "libds/surface.h"
+
+struct ds_surface_role
+{
+    const char *name;
+    void (*commit)(struct ds_surface *surface);
+};
+
+struct ds_surface *
+ds_surface_create(struct wl_client *client, uint32_t version, uint32_t id);
+
+struct ds_surface *
+ds_surface_from_resource(struct wl_resource *resource);
+
+bool
+ds_surface_set_role(struct ds_surface *surface,
+        const struct ds_surface_role *role, void *role_data,
+        struct wl_resource *error_resource, uint32_t error_code);
+
+const struct ds_surface_role *
+ds_surface_get_role(struct ds_surface *surface);
+
+void *
+ds_surface_get_role_data(struct ds_surface *surface);
+
+void
+ds_surface_reset_role_data(struct ds_surface *surface);
+
+bool
+ds_surface_has_buffer(struct ds_surface *surface);
+
+bool
+ds_surface_is_ancestor_of(struct ds_surface *surface,
+        struct ds_surface *target_surface);
+
+bool
+ds_surface_is_subsurface(struct ds_surface *surface);
+
+struct ds_subsurface *
+ds_subsurface_create(struct wl_resource *subcomp_resource,
+        struct ds_surface *surface, struct ds_surface *parent,
+        uint32_t version, uint32_t id);
+
+#endif
diff --git a/src/libds/surface/subsurface.c b/src/libds/surface/subsurface.c
new file mode 100644 (file)
index 0000000..4cc1ad8
--- /dev/null
@@ -0,0 +1,333 @@
+#include <assert.h>
+#include <stdlib.h>
+
+#include "libds/log.h"
+#include "libds/surface.h"
+#include "surface-private.h"
+
+static const struct wl_subsurface_interface subsurface_impl;
+static const struct ds_surface_role subsurface_role;
+
+static void subsurface_handle_resource_destroy(struct wl_resource *resource);
+static void subsurface_handle_surface_destroy(struct wl_listener *listener,
+        void *data);
+static void subsurface_link_surface(struct ds_subsurface *subsurface,
+        struct ds_surface *surface);
+static void subsurface_unlink_surface(struct ds_subsurface *subsurface);
+static void subsurface_link_parent(struct ds_subsurface *subsurface,
+        struct ds_surface *parent);
+static void subsurface_unlink_parent(struct ds_subsurface *subsurface);
+static struct ds_subsurface *subsurface_find_sibling(struct ds_subsurface *subsurface,
+        struct ds_surface *surface);
+
+struct ds_subsurface *
+ds_subsurface_create(struct wl_resource *subcomp_resource,
+        struct ds_surface *surface, struct ds_surface *parent,
+        uint32_t version, uint32_t id)
+{
+    struct wl_client *client;
+    struct ds_subsurface *subsurface;
+
+    client = wl_resource_get_client(subcomp_resource);
+
+    subsurface = calloc(1, sizeof *subsurface);
+    if (!subsurface) {
+        ds_log_errno(DS_ERR, "Could not allocate memory");
+        wl_client_post_no_memory(client);
+        return NULL;
+    }
+
+    if (!ds_surface_set_role(surface, &subsurface_role, subsurface,
+            subcomp_resource, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE)) {
+        free(subsurface);
+        return NULL;
+    }
+
+    subsurface->resource = wl_resource_create(client, &wl_subsurface_interface,
+            version, id);
+    if (!subsurface->resource) {
+        ds_log_errno(DS_ERR, "Could not create resource");
+        free(subsurface);
+        wl_client_post_no_memory(client);
+        return NULL;
+    }
+    wl_resource_set_implementation(subsurface->resource, &subsurface_impl,
+            subsurface, subsurface_handle_resource_destroy);
+
+    subsurface->synchronized = true;
+
+    wl_signal_init(&subsurface->events.destroy);
+
+    subsurface_link_surface(subsurface, surface);
+    subsurface_link_parent(subsurface, parent);
+
+    ds_inf("New ds_subsurface %p: surface %p, parent surface %p",
+            subsurface, surface, parent);
+
+    wl_signal_emit(&parent->events.new_subsurface, subsurface);
+
+    return subsurface;
+}
+
+bool
+ds_surface_is_subsurface(struct ds_surface *surface)
+{
+    return ds_surface_get_role(surface) == &subsurface_role;
+}
+
+struct ds_subsurface *
+ds_subsurface_from_ds_surface(struct ds_surface *surface)
+{
+    assert(ds_surface_is_subsurface(surface));
+    return (struct ds_subsurface *)surface->role_data;
+}
+
+struct ds_surface *
+ds_subsurface_get_parent(struct ds_subsurface *subsurface)
+{
+    return subsurface->parent;
+}
+
+static const struct ds_surface_role subsurface_role = {
+    .name = "wl_subsurface",
+};
+
+static void
+subsurface_handle_destroy(struct wl_client *client,
+        struct wl_resource *resource)
+{
+    wl_resource_destroy(resource);
+}
+
+static void
+subsurface_handle_set_position(struct wl_client *client,
+        struct wl_resource *resource, int32_t x, int32_t y)
+{
+    struct ds_subsurface *subsurface;
+
+    subsurface = wl_resource_get_user_data(resource);
+    if (!subsurface)
+        return;
+
+    subsurface->pending.x = x;
+    subsurface->pending.y = y;
+}
+
+static void
+subsurface_handle_place_above(struct wl_client *client,
+        struct wl_resource *resource, struct wl_resource *sibling_resource)
+{
+    struct ds_subsurface *subsurface, *sibling;
+    struct ds_surface *sibling_surface;
+    struct wl_list *node;
+
+    subsurface = wl_resource_get_user_data(resource);
+    if (!subsurface)
+        return;
+
+    sibling_surface = ds_surface_from_resource(sibling_resource);
+    if (sibling_surface == subsurface->parent) {
+        node = &subsurface->parent->pending.subsurfaces_above;
+    }
+    else {
+        sibling = subsurface_find_sibling(subsurface, sibling_surface);
+        if (!sibling) {
+            wl_resource_post_error(subsurface->resource,
+                    WL_SUBSURFACE_ERROR_BAD_SURFACE,
+                    "%s: wl_surface@%d is not a parent or sibling",
+                    "place_above", wl_resource_get_id(sibling_resource));
+            return;
+        }
+        node = &sibling->pending.link;
+    }
+
+    wl_list_remove(&subsurface->pending.link);
+    wl_list_insert(node, &subsurface->pending.link);
+
+    subsurface->reordered = true;
+}
+
+static void
+subsurface_handle_place_below(struct wl_client *client,
+        struct wl_resource *resource, struct wl_resource *sibling_resource)
+{
+    struct ds_subsurface *subsurface, *sibling;
+    struct ds_surface *sibling_surface;
+    struct wl_list *node;
+
+    subsurface = wl_resource_get_user_data(resource);
+    if (!subsurface)
+        return;
+
+    sibling_surface = ds_surface_from_resource(sibling_resource);
+    if (sibling_surface == subsurface->parent) {
+        node = &subsurface->parent->pending.subsurfaces_below;
+    }
+    else {
+        sibling = subsurface_find_sibling(subsurface, sibling_surface);
+        if (!sibling) {
+            wl_resource_post_error(subsurface->resource,
+                    WL_SUBSURFACE_ERROR_BAD_SURFACE,
+                    "%s: wl_surface@%d is not a parent or sibling",
+                    "place_below", wl_resource_get_id(sibling_resource));
+            return;
+        }
+        node = &sibling->pending.link;
+
+    }
+
+    wl_list_remove(&subsurface->pending.link);
+    wl_list_insert(node->prev, &subsurface->pending.link);
+
+    subsurface->reordered = true;
+}
+
+static void
+subsurface_handle_set_sync(struct wl_client *client,
+        struct wl_resource *resource)
+{
+    struct ds_subsurface *subsurface;
+
+    subsurface = wl_resource_get_user_data(resource);
+    if (!subsurface)
+        return;
+
+    subsurface->synchronized = true;
+}
+
+static void
+subsurface_handle_set_desync(struct wl_client *client,
+        struct wl_resource *resource)
+{
+    struct ds_subsurface *subsurface;
+
+    subsurface = wl_resource_get_user_data(resource);
+    if (!subsurface)
+        return;
+
+    if (subsurface->synchronized) {
+        subsurface->synchronized = false;
+
+        // TODO: flush
+    }
+}
+
+static const struct wl_subsurface_interface subsurface_impl = {
+    .destroy = subsurface_handle_destroy,
+    .set_position = subsurface_handle_set_position,
+    .place_above = subsurface_handle_place_above,
+    .place_below = subsurface_handle_place_below,
+    .set_sync = subsurface_handle_set_sync,
+    .set_desync = subsurface_handle_set_desync,
+};
+
+static void
+subsurface_destroy(struct ds_subsurface *subsurface)
+{
+    wl_signal_emit(&subsurface->events.destroy, subsurface);
+
+    if (subsurface->parent)
+        subsurface_unlink_parent(subsurface);
+
+    if (subsurface->surface)
+        subsurface_unlink_surface(subsurface);
+
+    wl_resource_set_user_data(subsurface->resource, NULL);
+
+    free(subsurface);
+}
+
+static void
+subsurface_handle_resource_destroy(struct wl_resource *resource)
+{
+    struct ds_subsurface *subsurface;
+
+    subsurface = wl_resource_get_user_data(resource);
+    if (!subsurface)
+        return;
+
+    subsurface_destroy(subsurface);
+}
+
+static
+void subsurface_handle_surface_destroy(struct wl_listener *listener,
+        void *data)
+{
+    struct ds_subsurface *subsurface;
+
+    subsurface = wl_container_of(listener, subsurface,
+            listener.surface_destroy);
+    subsurface_destroy(subsurface);
+}
+
+static void
+subsurface_handle_parent_destroy(struct wl_listener *listener,
+        void *data)
+{
+    struct ds_subsurface *subsurface;
+
+    subsurface = wl_container_of(listener, subsurface,
+            listener.parent_destroy);
+    subsurface_unlink_parent(subsurface);
+}
+
+static void
+subsurface_link_surface(struct ds_subsurface *subsurface, struct ds_surface *surface)
+{
+    subsurface->surface = surface;
+    subsurface->listener.surface_destroy.notify =
+        subsurface_handle_surface_destroy;
+    ds_surface_add_destroy_listener(surface,
+            &subsurface->listener.surface_destroy);
+}
+
+static void
+subsurface_unlink_surface(struct ds_subsurface *subsurface)
+{
+    wl_list_remove(&subsurface->listener.surface_destroy.link);
+    subsurface->surface->role_data = NULL;
+    subsurface->surface = NULL;
+}
+
+static void
+subsurface_link_parent(struct ds_subsurface *subsurface, struct ds_surface *parent)
+{
+    subsurface->parent = parent;
+    subsurface->listener.parent_destroy.notify =
+        subsurface_handle_parent_destroy;
+    ds_surface_add_destroy_listener(parent,
+            &subsurface->listener.parent_destroy);
+    wl_list_insert(parent->current.subsurfaces_above.prev,
+            &subsurface->current.link);
+    wl_list_insert(parent->pending.subsurfaces_above.prev,
+            &subsurface->pending.link);
+}
+
+static void
+subsurface_unlink_parent(struct ds_subsurface *subsurface)
+{
+    wl_list_remove(&subsurface->current.link);
+    wl_list_remove(&subsurface->pending.link);
+    wl_list_remove(&subsurface->listener.parent_destroy.link);
+    subsurface->parent = NULL;
+}
+
+static struct ds_subsurface *
+subsurface_find_sibling(struct ds_subsurface *subsurface, struct ds_surface *surface)
+{
+    struct ds_surface *parent = subsurface->parent;
+    struct ds_subsurface *sibling;
+
+    wl_list_for_each(sibling, &parent->pending.subsurfaces_below, pending.link) {
+        if (sibling->surface == surface && sibling != subsurface)
+            return sibling;
+    }
+
+    wl_list_for_each(sibling, &parent->pending.subsurfaces_above, pending.link) {
+        if (sibling->surface == surface && sibling != subsurface) {
+            return sibling;
+        }
+    }
+
+    return NULL;
+}
diff --git a/src/libds/surface/surface-private.h b/src/libds/surface/surface-private.h
new file mode 100644 (file)
index 0000000..570dcc2
--- /dev/null
@@ -0,0 +1,109 @@
+#ifndef DS_SURFACE_PRIVATE_H
+#define DS_SURFACE_PRIVATE_H
+
+#include <stdint.h>
+#include <pixman.h>
+#include <wayland-server.h>
+
+#include "addon.h"
+#include "buffer.h"
+#include "surface.h"
+
+typedef enum
+{
+    DS_SURFACE_STATE_BUFFER = 1 << 0,
+    DS_SURFACE_STATE_SURFACE_DAMAGE = 1 << 1,
+    DS_SURFACE_STATE_BUFFER_DAMAGE = 1 << 2,
+    DS_SURFACE_STATE_OPAQUE_REGION = 1 << 3,
+    DS_SURFACE_STATE_INPUT_REGION = 1 << 4,
+    DS_SURFACE_STATE_TRANSFORM = 1 << 5,
+    DS_SURFACE_STATE_SCALE = 1 << 6,
+    DS_SURFACE_STATE_FRAME_CALLBACK_LIST = 1 << 7,
+    DS_SURFACE_STATE_VIEWPORT = 1 << 8,
+} ds_surface_state_field_t;
+
+struct ds_surface_state
+{
+    ds_surface_state_field_t committed;
+
+    struct ds_buffer *buffer;
+    int32_t dx, dy;
+    pixman_region32_t surface_damage, buffer_damage;
+    pixman_region32_t opaque, input;
+    enum wl_output_transform transform;
+    int32_t scale;
+    struct wl_list frame_callback_list;
+
+    int width, height;
+    int buffer_width, buffer_height;
+
+    struct wl_list subsurfaces_below;
+    struct wl_list subsurfaces_above;
+
+    // TODO viewport
+};
+
+struct ds_surface
+{
+    struct wl_resource *resource;
+
+    struct ds_buffer *buffer;
+
+    pixman_region32_t buffer_damage;
+    pixman_region32_t opaque_region;
+    pixman_region32_t input_region;
+
+    struct ds_surface_state current, pending;
+
+    const struct ds_surface_role *role;
+    void *role_data;
+
+    int sx, sy;
+
+    struct {
+        struct wl_signal commit;
+        struct wl_signal new_subsurface;
+        struct wl_signal destroy;
+    } events;
+
+    struct ds_addon_set addons;
+};
+
+struct ds_subsurface_parent_state
+{
+    int32_t x, y;
+    struct wl_list link;
+};
+
+struct ds_subsurface
+{
+    struct wl_resource *resource;
+    struct ds_surface *surface;
+    struct ds_surface *parent;
+
+    struct ds_subsurface_parent_state current, pending;
+
+    struct {
+        struct wl_signal destroy;
+    } events;
+
+    struct {
+        struct wl_listener surface_destroy;
+        struct wl_listener parent_destroy;
+    } listener;
+
+    bool synchronized;
+    bool reordered;
+    bool mapped;
+};
+
+struct ds_surface *
+ds_surface_get_root_surface(struct ds_surface *surface);
+
+struct ds_subsurface *
+ds_subsurface_from_ds_surface(struct ds_surface *surface);
+
+struct ds_surface *
+ds_subsurface_get_parent(struct ds_subsurface *subsurface);
+
+#endif
diff --git a/src/libds/surface/surface.c b/src/libds/surface/surface.c
new file mode 100644 (file)
index 0000000..32ab232
--- /dev/null
@@ -0,0 +1,740 @@
+#include <assert.h>
+#include <stdlib.h>
+
+#include "surface-private.h"
+#include "region.h"
+#include "util.h"
+
+#include "libds/log.h"
+#include "libds/surface.h"
+
+#define CALLBACK_VERSION 1
+
+static const struct wl_surface_interface surface_impl;
+
+static void surface_handle_resource_destroy(struct wl_resource *resource);
+static void surface_state_init(struct ds_surface_state *state);
+static void surface_state_finish(struct ds_surface_state *state);
+static void surface_state_move(struct ds_surface_state *state,
+        struct ds_surface_state *next);
+static void surface_finalize_pending(struct ds_surface *surface);
+static void surface_commit_state(struct ds_surface *surface,
+        struct ds_surface_state *next);
+static bool surface_for_each(struct ds_surface *surface, int x, int y,
+        ds_surface_for_each_func_t iterator, void *data);
+
+WL_EXPORT void
+ds_surface_add_destroy_listener(struct ds_surface *surface,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&surface->events.destroy, listener);
+}
+
+WL_EXPORT void
+ds_surface_add_commit_listener(struct ds_surface *surface,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&surface->events.commit, listener);
+}
+
+WL_EXPORT void
+ds_surface_add_new_subsurface_listener(struct ds_surface *surface,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&surface->events.new_subsurface, listener);
+}
+
+WL_EXPORT struct ds_buffer *
+ds_surface_get_buffer(struct ds_surface *surface)
+{
+    return surface->buffer;
+}
+
+WL_EXPORT void
+ds_surface_for_each(struct ds_surface *surface,
+        ds_surface_for_each_func_t iterator, void *data)
+{
+    surface_for_each(surface, 0, 0, iterator, data);
+}
+
+WL_EXPORT void
+ds_surface_send_frame_done(struct ds_surface *surface,
+        const struct timespec *when)
+{
+    struct wl_resource *resource, *tmp;
+
+    wl_resource_for_each_safe(resource, tmp,
+            &surface->current.frame_callback_list) {
+        wl_callback_send_done(resource, timespec_to_msec(when));
+        wl_resource_destroy(resource);
+    }
+}
+
+struct ds_surface *
+ds_surface_create(struct wl_client *client, uint32_t version, uint32_t id)
+{
+    struct ds_surface *surface;
+
+    surface = calloc(1, sizeof *surface);
+    if (!surface) {
+        wl_client_post_no_memory(client);
+        return NULL;
+    }
+
+    surface->resource =
+        wl_resource_create(client, &wl_surface_interface, version, id);
+    if (!surface->resource) {
+        free(surface);
+        wl_client_post_no_memory(client);
+        return NULL;
+    }
+
+    wl_resource_set_implementation(surface->resource, &surface_impl,
+            surface, surface_handle_resource_destroy);
+
+    surface_state_init(&surface->current);
+    surface_state_init(&surface->pending);
+
+    wl_signal_init(&surface->events.commit);
+    wl_signal_init(&surface->events.destroy);
+    wl_signal_init(&surface->events.new_subsurface);
+
+    pixman_region32_init(&surface->buffer_damage);
+    pixman_region32_init(&surface->opaque_region);
+    pixman_region32_init(&surface->input_region);
+
+    ds_inf("New ds_surface %p (res %p)", surface, surface->resource);
+
+    return surface;
+}
+
+struct ds_surface *
+ds_surface_from_resource(struct wl_resource *resource)
+{
+    assert(wl_resource_instance_of(resource, &wl_surface_interface,
+                &surface_impl));
+    return wl_resource_get_user_data(resource);
+}
+
+bool
+ds_surface_set_role(struct ds_surface *surface,
+        const struct ds_surface_role *role, void *role_data,
+        struct wl_resource *error_resource, uint32_t error_code)
+{
+    assert(role != NULL);
+
+    if (surface->role != NULL && surface->role != role) {
+        ds_err("Cannot assign role %s to ds_surface(%p) "
+                "already has role %s\n",
+                role->name, surface, surface->role->name);
+        if (error_resource != NULL) {
+            wl_resource_post_error(error_resource, error_code,
+                    "Cannot assign role %s to wl_surface@%d "
+                    "already has role %s\n",
+                    role->name, wl_resource_get_id(surface->resource),
+                    surface->role->name);
+        }
+        return false;
+    }
+
+    if (surface->role_data != NULL && surface->role_data != role_data) {
+        ds_err("Cannot reassign role %s to ds_surface(%p), "
+                "role object still exists", role->name, surface);
+        wl_resource_post_error(error_resource, error_code,
+                "Cannot reassign role %s to wl_surface@%d, "
+                "role object still exists", role->name,
+                wl_resource_get_id(surface->resource));
+        return false;
+    }
+
+    surface->role = role;
+    surface->role_data = role_data;
+
+    ds_inf("Set ds_surface(%p) role: %s", surface, role->name);
+
+    return true;
+}
+
+const struct ds_surface_role *
+ds_surface_get_role(struct ds_surface *surface)
+{
+    return surface->role;
+}
+
+void *
+ds_surface_get_role_data(struct ds_surface *surface)
+{
+    return surface->role_data;
+}
+
+void
+ds_surface_reset_role_data(struct ds_surface *surface)
+{
+    surface->role_data = NULL;
+}
+
+bool
+ds_surface_is_ancestor_of(struct ds_surface *surface,
+        struct ds_surface *target_surface)
+{
+    struct ds_subsurface *target_subsurface;
+    struct ds_surface *parent_surface;
+
+    while (target_surface &&
+            ds_surface_is_subsurface(target_surface)) {
+        target_subsurface = ds_subsurface_from_ds_surface(target_surface);
+        if (!target_subsurface)
+            break;
+
+        parent_surface = ds_subsurface_get_parent(target_subsurface);
+        if (surface == parent_surface)
+            return true;
+
+        target_surface = parent_surface;
+    }
+
+    return false;
+}
+
+struct ds_surface *
+ds_surface_get_root_surface(struct ds_surface *surface)
+{
+    struct ds_subsurface *subsurface;
+
+    while (ds_surface_is_subsurface(surface)) {
+        subsurface = ds_subsurface_from_ds_surface(surface);
+        if (!subsurface)
+            break;
+
+        surface = ds_subsurface_get_parent(subsurface);
+        if (!surface)
+            return NULL;
+    }
+
+    return surface;
+}
+
+bool
+ds_surface_has_buffer(struct ds_surface *surface)
+{
+    return !!surface->buffer;
+}
+
+static void
+surface_handle_destroy(struct wl_client *client, struct wl_resource *resource)
+{
+    wl_resource_destroy(resource);
+}
+
+static void
+surface_handle_attach(struct wl_client *client,
+        struct wl_resource *resource,
+        struct wl_resource *buffer_resource,
+        int32_t dx, int32_t dy)
+{
+    struct ds_surface *surface;
+    struct ds_buffer *buffer = NULL;
+
+    if (buffer_resource) {
+        buffer = ds_buffer_from_resource(buffer_resource);
+        if (!buffer) {
+            wl_resource_post_error(buffer_resource, 0, "unknown buffer type");
+            return;
+        }
+    }
+
+    surface = wl_resource_get_user_data(resource);
+    surface->pending.committed |= DS_SURFACE_STATE_BUFFER;
+    surface->pending.dx = dx;
+    surface->pending.dy = dy;
+
+    if (surface->pending.buffer)
+        ds_buffer_unlock(surface->pending.buffer);
+
+    surface->pending.buffer = buffer;
+
+    ds_dbg("ds_surface(%p) attach buffer(%p)", surface, buffer);
+}
+
+static void
+surface_handle_damage(struct wl_client *client,
+        struct wl_resource *resource,
+        int32_t x, int32_t y, int32_t width, int32_t height)
+{
+    struct ds_surface *surface;
+
+    surface = wl_resource_get_user_data(resource);
+
+    ds_dbg("ds_surface(%p) damage: x %d y %d width %d height %d",
+            surface, x, y, width, height);
+
+    if (width < 0 || height < 0)
+        return;
+
+    surface->pending.committed |= DS_SURFACE_STATE_SURFACE_DAMAGE;
+    pixman_region32_union_rect(&surface->pending.surface_damage,
+            &surface->pending.surface_damage,
+            x, y, width, height);
+}
+
+static void
+callback_handle_resource_destroy(struct wl_resource *resource)
+{
+    wl_list_remove(wl_resource_get_link(resource));
+}
+
+static void
+surface_handle_frame(struct wl_client *client,
+        struct wl_resource *resource, uint32_t callback)
+{
+    struct ds_surface *surface;
+    struct wl_resource *callback_resource;
+
+    surface = wl_resource_get_user_data(resource);
+
+    ds_dbg("ds_surface(%p) frame", surface);
+
+    callback_resource = wl_resource_create(client, &wl_callback_interface,
+            CALLBACK_VERSION, callback);
+    if (!callback_resource) {
+        wl_resource_post_no_memory(resource);
+        return;
+    }
+
+    wl_resource_set_implementation(callback_resource, NULL, NULL,
+            callback_handle_resource_destroy);
+
+    wl_list_insert(surface->pending.frame_callback_list.prev,
+            wl_resource_get_link(callback_resource));
+
+    surface->pending.committed |= DS_SURFACE_STATE_FRAME_CALLBACK_LIST;
+}
+
+static void
+surface_handle_set_opaque_region(struct wl_client *client,
+        struct wl_resource *resource, struct wl_resource *region_resource)
+{
+    struct ds_surface *surface;
+    pixman_region32_t *region;
+
+    surface = wl_resource_get_user_data(resource);
+
+    ds_dbg("ds_surface(%p) set opaque region", surface);
+
+    surface->pending.committed |= DS_SURFACE_STATE_OPAQUE_REGION;
+    if (region_resource) {
+        region = ds_region_from_resource(region_resource);
+        pixman_region32_copy(&surface->pending.opaque, region);
+    }
+    else {
+        pixman_region32_clear(&surface->pending.opaque);
+    }
+}
+
+static void
+surface_handle_set_input_region(struct wl_client *client,
+        struct wl_resource *resource, struct wl_resource *region_resource)
+{
+    struct ds_surface *surface = wl_resource_get_user_data(resource);
+    pixman_region32_t *region;
+
+    ds_dbg("ds_surface(%p) set input region", surface);
+
+    surface->pending.committed |= DS_SURFACE_STATE_INPUT_REGION;
+    if (region_resource) {
+        region = ds_region_from_resource(region_resource);
+        pixman_region32_copy(&surface->pending.input, region);
+    }
+    else {
+        pixman_region32_fini(&surface->pending.input);
+        pixman_region32_init_rect(&surface->pending.input,
+                INT32_MIN, INT32_MIN, UINT32_MAX, UINT32_MAX);
+    }
+}
+
+static void
+surface_handle_commit(struct wl_client *client, struct wl_resource *resource)
+{
+    struct ds_surface *surface;
+
+    surface = wl_resource_get_user_data(resource);
+
+    ds_dbg("ds_surface(%p) commit", surface);
+
+    // TODO handle subsurface
+
+    surface_finalize_pending(surface);
+
+    surface_commit_state(surface, &surface->pending);
+
+    // TODO handle subsurfaces of a given surface
+
+    if (surface->role && surface->role->commit)
+        surface->role->commit(surface);
+
+    wl_signal_emit(&surface->events.commit, surface);
+}
+
+static void
+surface_handle_set_buffer_transform(struct wl_client *client,
+        struct wl_resource *resource, int32_t transform)
+{
+    struct ds_surface *surface = wl_resource_get_user_data(resource);
+
+    ds_dbg("ds_surface(%p) set buffer transform(%d)", surface, transform);
+
+    if (transform < WL_OUTPUT_TRANSFORM_NORMAL ||
+            transform > WL_OUTPUT_TRANSFORM_FLIPPED_270) {
+        ds_err("Specified transform value(%d) is invalid. ds_surface(%p)",
+                transform, surface);
+        wl_resource_post_error(resource, WL_SURFACE_ERROR_INVALID_TRANSFORM,
+                "Specified transform value(%d) is invalid", transform);
+        return;
+    }
+
+    surface->pending.committed |= DS_SURFACE_STATE_TRANSFORM;
+    surface->pending.transform = transform;
+}
+
+static void
+surface_handle_set_buffer_scale(struct wl_client *client,
+        struct wl_resource *resource, int32_t scale)
+{
+    struct ds_surface *surface = wl_resource_get_user_data(resource);
+
+    ds_dbg("ds_surface(%p) set buffer scale(%d)", surface, scale);
+
+    if (scale <= 0) {
+        ds_err("Specified scale value(%d) is not positive. ds_surface(%p)",
+                scale, surface);
+        wl_resource_post_error(resource, WL_SURFACE_ERROR_INVALID_SCALE,
+                "Specified scale value(%d) is not positive", scale);
+        return;
+    }
+
+    surface->pending.committed |= DS_SURFACE_STATE_SCALE;
+    surface->pending.scale = scale;
+}
+
+static void
+surface_handle_damage_buffer(struct wl_client *client,
+        struct wl_resource *resource,
+        int32_t x, int32_t y, int32_t width, int32_t height)
+{
+    struct ds_surface *surface = wl_resource_get_user_data(resource);
+
+    ds_dbg("ds_surface(%p) damage: x %d y %d width %d height %d",
+            surface, x, y, width, height);
+
+    if (width < 0 || height < 0) {
+        ds_err("Size cannot be less than zero. ds_surface(%p) "
+                "width %d height %d", surface, width, height);
+        return;
+    }
+
+    surface->pending.committed |= DS_SURFACE_STATE_BUFFER_DAMAGE;
+    pixman_region32_union_rect(&surface->pending.buffer_damage,
+            &surface->pending.buffer_damage,
+            x, y, width, height);
+}
+
+static const struct wl_surface_interface surface_impl = {
+    .destroy = surface_handle_destroy,
+    .attach = surface_handle_attach,
+    .damage = surface_handle_damage,
+    .frame = surface_handle_frame,
+    .set_opaque_region = surface_handle_set_opaque_region,
+    .set_input_region = surface_handle_set_input_region,
+    .commit = surface_handle_commit,
+    .set_buffer_transform = surface_handle_set_buffer_transform,
+    .set_buffer_scale = surface_handle_set_buffer_scale,
+    .damage_buffer = surface_handle_damage_buffer,
+};
+
+static void
+surface_handle_resource_destroy(struct wl_resource *resource)
+{
+    struct ds_surface *surface = wl_resource_get_user_data(resource);
+
+    ds_inf("Destroy ds_surface %p (res %p)", surface, surface->resource);
+
+    wl_signal_emit(&surface->events.destroy, surface);
+
+    if (surface->buffer)
+        ds_buffer_unlock(surface->buffer);
+
+    surface_state_finish(&surface->pending);
+    surface_state_finish(&surface->current);
+
+    pixman_region32_fini(&surface->buffer_damage);
+    pixman_region32_fini(&surface->opaque_region);
+    pixman_region32_fini(&surface->input_region);
+
+    free(surface);
+}
+
+static void
+surface_state_init(struct ds_surface_state *state)
+{
+    state->scale = 1;
+    state->transform = WL_OUTPUT_TRANSFORM_NORMAL;
+
+    wl_list_init(&state->subsurfaces_above);
+    wl_list_init(&state->subsurfaces_below);
+
+    wl_list_init(&state->frame_callback_list);
+
+    pixman_region32_init(&state->surface_damage);
+    pixman_region32_init(&state->buffer_damage);
+    pixman_region32_init(&state->opaque);
+    pixman_region32_init_rect(&state->input,
+            INT32_MIN, INT32_MIN, UINT32_MAX, UINT32_MAX);
+}
+
+static void
+surface_state_finish(struct ds_surface_state *state)
+{
+    struct wl_resource *resource, *tmp;
+
+    if (state->buffer)
+        ds_buffer_unlock(state->buffer);
+
+    wl_resource_for_each_safe(resource, tmp, &state->frame_callback_list)
+        wl_resource_destroy(resource);
+
+    pixman_region32_fini(&state->surface_damage);
+    pixman_region32_fini(&state->buffer_damage);
+    pixman_region32_fini(&state->opaque);
+    pixman_region32_fini(&state->input);
+}
+
+static void
+surface_state_move(struct ds_surface_state *state, struct ds_surface_state *next)
+{
+    state->width = next->width;
+    state->height = next->height;
+    state->buffer_width = next->buffer_width;
+    state->buffer_height = next->buffer_height;
+
+    if (next->committed & DS_SURFACE_STATE_SCALE)
+        state->scale = next->scale;
+
+    if (next->committed & DS_SURFACE_STATE_TRANSFORM)
+        state->transform = next->transform;
+
+    if (next->committed & DS_SURFACE_STATE_BUFFER) {
+        state->dx = next->dx;
+        state->dy = next->dy;
+        next->dx = next->dy = 0;
+
+        if (state->buffer) {
+            ds_buffer_unlock(state->buffer);
+            state->buffer = NULL;
+        }
+
+        if (next->buffer) {
+            state->buffer = ds_buffer_lock(next->buffer);
+            ds_buffer_unlock(next->buffer);
+            next->buffer = NULL;
+        }
+    }
+    else {
+        state->dx = state->dy = 0;
+    }
+
+    if (next->committed & DS_SURFACE_STATE_SURFACE_DAMAGE) {
+        pixman_region32_copy(&state->surface_damage, &next->surface_damage);
+        pixman_region32_clear(&next->surface_damage);
+    }
+    else
+        pixman_region32_clear(&state->surface_damage);
+
+    if (next->committed & DS_SURFACE_STATE_BUFFER_DAMAGE) {
+        pixman_region32_copy(&state->buffer_damage, &next->buffer_damage);
+        pixman_region32_clear(&next->buffer_damage);
+    }
+    else
+        pixman_region32_clear(&state->buffer_damage);
+
+    if (next->committed & DS_SURFACE_STATE_OPAQUE_REGION)
+        pixman_region32_copy(&state->opaque, &next->opaque);
+
+    if (next->committed & DS_SURFACE_STATE_INPUT_REGION)
+        pixman_region32_copy(&state->input, &next->input);
+
+    if (next->committed & DS_SURFACE_STATE_FRAME_CALLBACK_LIST) {
+        wl_list_insert_list(&state->frame_callback_list,
+                &next->frame_callback_list);
+        wl_list_init(&next->frame_callback_list);
+    }
+
+    // FIXME 
+    // state->committed |= next->committed; ??
+    state->committed = next->committed;
+    next->committed = 0;
+}
+
+static void
+surface_state_viewport_src_size(struct ds_surface_state *state,
+        int *out_width, int *out_height)
+{
+    int width, height, tmp;
+
+    if (state->buffer_width == 0 && state->buffer_height == 0) {
+        *out_width = 0;
+        *out_height = 0;
+        return;
+    }
+
+    width = state->buffer_width / state->scale;
+    height = state->buffer_height / state->scale;
+    if ((state->transform & WL_OUTPUT_TRANSFORM_90) != 0) {
+        tmp = width;
+        width =  height;
+        height = tmp;
+    }
+
+    *out_width = width;
+    *out_height = height;
+}
+
+static void
+surface_finalize_pending(struct ds_surface *surface)
+{
+    struct ds_surface_state *pending = &surface->pending;
+
+    if ((pending->committed & DS_SURFACE_STATE_BUFFER)) {
+        if (pending->buffer) {
+            ds_buffer_get_size(pending->buffer,
+                    &pending->buffer_width, &pending->buffer_height);
+        }
+        else {
+            pending->buffer_width = 0;
+            pending->buffer_height = 0;
+        }
+    }
+
+    surface_state_viewport_src_size(pending,
+            &pending->width, &pending->height);
+
+    pixman_region32_intersect_rect(&pending->surface_damage,
+            &pending->surface_damage,
+            0, 0, pending->width, pending->height);
+
+    pixman_region32_intersect_rect(&pending->buffer_damage,
+            &pending->buffer_damage,
+            0, 0, pending->buffer_width, pending->buffer_height);
+}
+
+// FIXME
+static enum wl_output_transform
+ds_output_transform_invert(enum wl_output_transform tr)
+{
+    if ((tr & WL_OUTPUT_TRANSFORM_90) && !(tr & WL_OUTPUT_TRANSFORM_FLIPPED)) {
+        tr ^= WL_OUTPUT_TRANSFORM_180;
+    }
+    return tr;
+}
+
+static void
+surface_update_damage(pixman_region32_t *buffer_damage,
+        struct ds_surface_state *current, struct ds_surface_state *pending)
+{
+    pixman_region32_clear(buffer_damage);
+
+    if (pending->width != current->width ||
+            pending->height != current->height) {
+        pixman_region32_union_rect(buffer_damage, buffer_damage,
+                0, 0, pending->buffer_width, pending->buffer_height);
+    }
+    else {
+        pixman_region32_t surface_damage;
+
+        pixman_region32_init(&surface_damage);
+        pixman_region32_copy(&surface_damage, &pending->surface_damage);
+
+        // TODO viewport
+
+        ds_region_transform(&surface_damage, &surface_damage,
+                ds_output_transform_invert(pending->transform),
+                pending->width, pending->height);
+        ds_region_scale(&surface_damage, &surface_damage, pending->scale);
+
+        pixman_region32_union(buffer_damage,
+                &pending->buffer_damage, &surface_damage);
+
+        pixman_region32_fini(&surface_damage);
+    }
+}
+
+static void
+surface_update_buffer(struct ds_surface *surface)
+{
+    if (!surface->current.buffer) {
+        if (surface->buffer) {
+            ds_buffer_unlock(surface->buffer);
+            surface->buffer = NULL;
+        }
+        return;
+    }
+
+    if (surface->buffer) {
+        ds_buffer_unlock(surface->buffer);
+        surface->buffer = NULL;
+    }
+
+    if (surface->current.buffer) {
+        surface->buffer = ds_buffer_lock(surface->current.buffer);
+        ds_buffer_unlock(surface->current.buffer);
+        surface->current.buffer = NULL;
+    }
+}
+
+static void
+surface_commit_state(struct ds_surface *surface, struct ds_surface_state *next)
+{
+    surface->sx += next->dx;
+    surface->sy += next->dy;
+    surface_update_damage(&surface->buffer_damage, &surface->current, next);
+
+    surface_state_move(&surface->current, next);
+
+    // FIXME no need?
+    if (surface->current.committed & DS_SURFACE_STATE_BUFFER)
+        surface_update_buffer(surface);
+
+    wl_signal_emit(&surface->events.commit, surface);
+}
+
+static bool
+surface_for_each(struct ds_surface *surface, int x, int y,
+        ds_surface_for_each_func_t iterator, void *data)
+{
+    struct ds_subsurface *subsurface;
+    struct ds_subsurface_parent_state *state;
+    bool stop = false;
+
+    wl_list_for_each(subsurface,
+            &surface->current.subsurfaces_below, current.link) {
+        state = &subsurface->current;
+        stop = surface_for_each(subsurface->surface,
+                x + state->x, y + state->y, iterator, data);
+        if (stop)
+            return true;
+    }
+
+    stop = iterator(surface, x, y, data);
+    if (stop)
+        return true;
+
+    wl_list_for_each(subsurface,
+            &surface->current.subsurfaces_above, current.link) {
+        state = &subsurface->current;
+        stop = surface_for_each(subsurface->surface,
+                x + state->x, y + state->y, iterator, data);
+        if (stop)
+            return true;
+    }
+
+    return false;
+}
diff --git a/src/libds/swapchain.c b/src/libds/swapchain.c
new file mode 100644 (file)
index 0000000..54e64cf
--- /dev/null
@@ -0,0 +1,202 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include "libds/log.h"
+#include "libds/allocator.h"
+
+#define DS_SWAPCHAIN_CAP    4
+
+struct ds_swapchain_slot
+{
+    struct ds_buffer *buffer;
+
+    struct wl_listener buffer_release;
+
+    int age;
+    bool acquired;
+};
+
+struct ds_swapchain
+{
+    struct ds_allocator *allocator;
+
+    struct ds_swapchain_slot slots[DS_SWAPCHAIN_CAP];
+
+    struct wl_listener allocator_destroy;
+
+    int width, height;
+    uint32_t format;
+};
+
+static void swapchain_handle_allocator_destroy(struct wl_listener *listener,
+        void *data);
+static bool swapchain_has_buffer(struct ds_swapchain *swapchain,
+        struct ds_buffer *buffer);
+static struct ds_buffer *swapchain_slot_acquire(struct ds_swapchain *swapchain,
+        struct ds_swapchain_slot *slot, int *age);
+static void swapchain_slot_reset(struct ds_swapchain_slot *slot);
+
+struct ds_swapchain *
+ds_swapchain_create(struct ds_allocator *alloc, int width, int height,
+        uint32_t format)
+{
+    struct ds_swapchain *swapchain = NULL;
+
+    swapchain = calloc(1, sizeof *swapchain);
+    if (!swapchain)
+        return NULL;
+
+    swapchain->allocator = alloc;
+    swapchain->width = width;
+    swapchain->height = height;
+    swapchain->format = format;
+
+    swapchain->allocator_destroy.notify =
+        swapchain_handle_allocator_destroy;
+    ds_allocator_add_destroy_listener(alloc,
+            &swapchain->allocator_destroy);
+
+    ds_inf("Swapchain(%p) created", swapchain);
+
+    return swapchain;
+}
+
+void
+ds_swapchain_destroy(struct ds_swapchain *swapchain)
+{
+    size_t i;
+
+    ds_dbg("Destroy swapchain(%p)", swapchain);
+
+    for (i = 0; i < DS_SWAPCHAIN_CAP; i++)
+        swapchain_slot_reset(&swapchain->slots[i]);
+
+    wl_list_remove(&swapchain->allocator_destroy.link);
+    free(swapchain);
+}
+
+struct ds_buffer *
+ds_swapchain_acquire(struct ds_swapchain *swapchain, int *age)
+{
+    struct ds_swapchain_slot *slot, *free_slot = NULL;
+    size_t i;
+
+    for (i = 0; i < DS_SWAPCHAIN_CAP; i++) {
+        slot = &swapchain->slots[i];
+        if (slot->acquired)
+            continue;
+
+        if (slot->buffer != NULL)
+            return swapchain_slot_acquire(swapchain, slot, age);
+
+        free_slot = slot;
+    }
+
+    if (free_slot == NULL) {
+        ds_err("No free output buffer slot");
+        return NULL;
+    }
+
+    if (!swapchain->allocator)
+        return NULL;
+
+    free_slot->buffer = ds_allocator_create_buffer(swapchain->allocator,
+            swapchain->width, swapchain->height, swapchain->format);
+    if (!free_slot->buffer) {
+        ds_err("Failed to allocate buffer");
+        return NULL;
+    }
+
+    ds_dbg("Allocating new swapchain buffer(%p)", free_slot->buffer);
+
+    return swapchain_slot_acquire(swapchain, free_slot, age);
+}
+
+void
+ds_swapchain_set_buffer_submitted(struct ds_swapchain *swapchain,
+        struct ds_buffer *buffer)
+{
+    struct ds_swapchain_slot *slot;
+    size_t i;
+
+    assert(buffer);
+
+    if (!swapchain_has_buffer(swapchain, buffer))
+        return;
+
+    for (i = 0; i < DS_SWAPCHAIN_CAP; i++) {
+        slot = &swapchain->slots[i];
+        if (slot->buffer == buffer)
+            slot->age = 1;
+        else if (slot->age > 0)
+            slot->age++;
+    }
+}
+
+static void
+swapchain_handle_allocator_destroy(struct wl_listener *listener, void *data)
+{
+    struct ds_swapchain *swapchain;
+
+    swapchain = wl_container_of(listener, swapchain, allocator_destroy);
+    swapchain->allocator = NULL;
+}
+
+static bool swapchain_has_buffer(struct ds_swapchain *swapchain,
+        struct ds_buffer *buffer)
+{
+    struct ds_swapchain_slot *slot;
+    size_t i;
+
+    for (i = 0; i < DS_SWAPCHAIN_CAP; i++) {
+        slot = &swapchain->slots[i];
+        if (slot->buffer == buffer)
+            return true;
+    }
+
+    return false;
+}
+
+static void
+swapchain_slot_handle_buffer_release(struct wl_listener *listener, void *data)
+{
+    struct ds_swapchain_slot *slot;
+
+    slot = wl_container_of(listener, slot, buffer_release);
+
+    ds_dbg("Buffer(%p) released.", slot->buffer);
+
+    wl_list_remove(&slot->buffer_release.link);
+    slot->acquired = false;
+}
+
+static struct ds_buffer *
+swapchain_slot_acquire(struct ds_swapchain *swapchain, struct ds_swapchain_slot *slot,
+        int *age)
+{
+    assert(!slot->acquired);
+    assert(slot->buffer);
+
+    slot->acquired = true;
+
+    slot->buffer_release.notify = swapchain_slot_handle_buffer_release;
+    ds_buffer_add_release_listener(slot->buffer, &slot->buffer_release);
+
+    if (age != NULL)
+        *age = slot->age;
+
+    return ds_buffer_lock(slot->buffer);
+}
+
+static void
+swapchain_slot_reset(struct ds_swapchain_slot *slot)
+{
+    if (slot->acquired)
+        wl_list_remove(&slot->buffer_release.link);
+
+    if (slot->buffer)
+        ds_buffer_drop(slot->buffer);
+
+    memset(slot, 0, sizeof *slot);
+}
diff --git a/src/libds/util.h b/src/libds/util.h
new file mode 100644 (file)
index 0000000..3b7448b
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef DS_UTIL_H
+#define DS_UTIL_H
+
+#include <stdint.h>
+#include <time.h>
+
+int64_t
+timespec_to_msec(const struct timespec *a);
+
+int
+allocate_shm_file(size_t size);
+
+#endif
diff --git a/src/libds/util/shm.c b/src/libds/util/shm.c
new file mode 100644 (file)
index 0000000..c8c84e3
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * Copyright Â© 2012 Collabora, Ltd.
+ *
+ * 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.
+ */
+
+#define _POSIX_C_SOURCE 200809L
+
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+int
+os_fd_set_cloexec(int fd)
+{
+       long flags;
+
+       if (fd == -1)
+               return -1;
+
+       flags = fcntl(fd, F_GETFD);
+       if (flags == -1)
+               return -1;
+
+       if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
+               return -1;
+
+       return 0;
+}
+
+static int
+set_cloexec_or_close(int fd)
+{
+       if (os_fd_set_cloexec(fd) != 0) {
+               close(fd);
+               return -1;
+       }
+       return fd;
+}
+
+static int
+create_tmpfile_cloexec(char *tmpname)
+{
+       int fd;
+
+#ifdef HAVE_MKOSTEMP
+       fd = mkostemp(tmpname, O_CLOEXEC);
+       if (fd >= 0)
+               unlink(tmpname);
+#else
+       fd = mkstemp(tmpname);
+       if (fd >= 0) {
+               fd = set_cloexec_or_close(fd);
+               unlink(tmpname);
+       }
+#endif
+
+       return fd;
+}
+
+/*
+ * Create a new, unique, anonymous file of the given size, and
+ * return the file descriptor for it. The file descriptor is set
+ * CLOEXEC. The file is immediately suitable for mmap()'ing
+ * the given size at offset zero.
+ *
+ * The file should not have a permanent backing store like a disk,
+ * but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
+ *
+ * The file name is deleted from the file system.
+ *
+ * The file is suitable for buffer sharing between processes by
+ * transmitting the file descriptor over Unix sockets using the
+ * SCM_RIGHTS methods.
+ *
+ * If the C library implements posix_fallocate(), it is used to
+ * guarantee that disk space is available for the file at the
+ * given size. If disk space is insufficient, errno is set to ENOSPC.
+ * If posix_fallocate() is not supported, program may receive
+ * SIGBUS on accessing mmap()'ed file contents instead.
+ *
+ * If the C library implements memfd_create(), it is used to create the
+ * file purely in memory, without any backing file name on the file
+ * system, and then sealing off the possibility of shrinking it.  This
+ * can then be checked before accessing mmap()'ed file contents, to
+ * make sure SIGBUS can't happen.  It also avoids requiring
+ * XDG_RUNTIME_DIR.
+ */
+int
+allocate_shm_file(off_t size)
+{
+       static const char template[] = "/weston-shared-XXXXXX";
+       const char *path;
+       char *name;
+       int fd;
+       int ret;
+
+#ifdef HAVE_MEMFD_CREATE
+       fd = memfd_create("weston-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING);
+       if (fd >= 0) {
+               /* We can add this seal before calling posix_fallocate(), as
+                * the file is currently zero-sized anyway.
+                *
+                * There is also no need to check for the return value, we
+                * couldn't do anything with it anyway.
+                */
+               fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK);
+       } else
+#endif
+       {
+               path = getenv("XDG_RUNTIME_DIR");
+               if (!path) {
+                       errno = ENOENT;
+                       return -1;
+               }
+
+               name = malloc(strlen(path) + sizeof(template));
+               if (!name)
+                       return -1;
+
+               strcpy(name, path);
+               strcat(name, template);
+
+               fd = create_tmpfile_cloexec(name);
+
+               free(name);
+
+               if (fd < 0)
+                       return -1;
+       }
+
+#ifdef HAVE_POSIX_FALLOCATE
+       do {
+               ret = posix_fallocate(fd, 0, size);
+       } while (ret == EINTR);
+       if (ret != 0) {
+               close(fd);
+               errno = ret;
+               return -1;
+       }
+#else
+       do {
+               ret = ftruncate(fd, size);
+       } while (ret < 0 && errno == EINTR);
+       if (ret < 0) {
+               close(fd);
+               return -1;
+       }
+#endif
+
+       return fd;
+}
diff --git a/src/libds/util/time.c b/src/libds/util/time.c
new file mode 100644 (file)
index 0000000..1b17516
--- /dev/null
@@ -0,0 +1,8 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include <stdint.h>
+#include <time.h>
+
+int64_t timespec_to_msec(const struct timespec *a) {
+    return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000;
+}
diff --git a/src/libds/xdg_shell/xdg_shell.c b/src/libds/xdg_shell/xdg_shell.c
new file mode 100644 (file)
index 0000000..b7cc8ff
--- /dev/null
@@ -0,0 +1,214 @@
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "xdg_shell.h"
+#include "libds/log.h"
+#include "libds/xdg_shell.h"
+
+#define XDG_WM_BASE_VERSION 2
+#define XDG_SHELL_PING_TIMEOUT  10000
+
+static void xdg_shell_handle_display_destroy(struct wl_listener *listener, void *data);
+static void xdg_shell_bind(struct wl_client *wl_client, void *data,
+        uint32_t verison, uint32_t id);
+
+WL_EXPORT struct ds_xdg_shell *
+ds_xdg_shell_create(struct wl_display *display)
+{
+    struct ds_xdg_shell *shell;
+
+    shell = calloc(1, sizeof *shell);
+    if (!shell) {
+        return NULL;
+    }
+
+    shell->ping_timeout = XDG_SHELL_PING_TIMEOUT;
+
+    wl_list_init(&shell->clients);
+
+    shell->global = wl_global_create(display, &xdg_wm_base_interface,
+            XDG_WM_BASE_VERSION, shell, xdg_shell_bind);
+    if (!shell->global) {
+        free(shell);
+        return NULL;
+    }
+
+    wl_signal_init(&shell->events.destroy);
+    wl_signal_init(&shell->events.new_surface);
+
+    shell->display_destroy.notify = xdg_shell_handle_display_destroy;
+    wl_display_add_destroy_listener(display, &shell->display_destroy);
+
+    ds_inf("Global created: xdg_wm_base shell(%p)", shell);
+
+    return shell;
+}
+
+WL_EXPORT void                                                    
+ds_xdg_shell_add_destroy_listener(struct ds_xdg_shell *shell,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&shell->events.destroy, listener);
+}
+
+void
+ds_xdg_shell_add_new_surface_listener(struct ds_xdg_shell *shell,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&shell->events.new_surface, listener);
+}
+
+static void
+xdg_shell_handle_display_destroy(struct wl_listener *listener, void *data)
+{
+    struct ds_xdg_shell *shell;
+
+    shell = wl_container_of(listener, shell, display_destroy);
+
+    ds_inf("Global destroy: xdg_wm_base shell(%p)", shell);
+
+    wl_signal_emit(&shell->events.destroy, shell);
+    wl_list_remove(&shell->display_destroy.link);
+    wl_global_destroy(shell->global);
+    free(shell);
+}
+
+static void
+xdg_shell_handle_destroy(struct wl_client *wl_client,
+        struct wl_resource *resource)
+{
+    struct ds_xdg_client *client;
+
+    client = wl_resource_get_user_data(resource);
+
+    if (!wl_list_empty(&client->surfaces)) {
+        wl_resource_post_error(client->resource,
+                XDG_WM_BASE_ERROR_DEFUNCT_SURFACES,
+                "xdg_wm_base was destroyed before children");
+        return;
+    }
+
+    wl_resource_destroy(resource);
+}
+
+static void
+xdg_shell_handle_create_positioner(struct wl_client *wl_client,
+        struct wl_resource *resource, uint32_t id)
+{
+    // TODO
+}
+
+static void
+xdg_shell_handle_get_xdg_surface(struct wl_client *wl_client,
+        struct wl_resource *resource, uint32_t id,
+        struct wl_resource *surface_resource)
+{
+    struct ds_xdg_client *client;
+    struct ds_surface *surface;
+
+    client = wl_resource_get_user_data(resource);
+    surface = ds_surface_from_resource(surface_resource);
+    create_xdg_surface(client, surface, id);
+}
+
+static void
+xdg_shell_handle_pong(struct wl_client *wl_client,
+        struct wl_resource *resource, uint32_t serial)
+{
+    struct ds_xdg_client *client;
+
+    client = wl_resource_get_user_data(resource);
+    if (client->ping_serial != serial)
+        return;
+
+    wl_event_source_timer_update(client->ping_timer, 0);
+    client->ping_serial = 0;
+}
+
+static const struct xdg_wm_base_interface xdg_shell_impl = {
+    .destroy = xdg_shell_handle_destroy,
+    .create_positioner = xdg_shell_handle_create_positioner,
+    .get_xdg_surface = xdg_shell_handle_get_xdg_surface,
+    .pong = xdg_shell_handle_pong,
+};
+
+static void
+xdg_client_handle_resource_destroy(struct wl_resource *resource)
+{
+    struct ds_xdg_client *client;
+
+    client = wl_resource_get_user_data(resource);
+
+    if (client->ping_timer != NULL)
+        wl_event_source_remove(client->ping_timer);
+
+    wl_list_remove(&client->link);
+    free(client);
+}
+
+static int
+xdg_client_handle_ping_timeout(void *user_data)
+{
+    struct ds_xdg_client *client;
+    struct ds_xdg_surface *surface;
+
+    client = user_data;
+
+    wl_list_for_each(surface, &client->surfaces, link)
+        wl_signal_emit(&surface->events.ping_timeout, surface);
+
+    client->ping_serial = 0;
+
+    return 1;
+}
+
+static void
+xdg_client_init_ping_timer(struct ds_xdg_client *client)
+{
+    struct wl_display *display;
+    struct wl_event_loop *loop;
+
+    display = wl_client_get_display(client->wl_client);
+    loop = wl_display_get_event_loop(display);
+    client->ping_timer = wl_event_loop_add_timer(loop,
+            xdg_client_handle_ping_timeout, client);
+    if (client->ping_timer == NULL)
+        wl_client_post_no_memory(client->wl_client);
+}
+
+static void
+xdg_shell_bind(struct wl_client *wl_client, void *data, uint32_t version,
+        uint32_t id)
+{
+    struct ds_xdg_shell *shell;
+    struct ds_xdg_client *client;
+
+    shell = data;
+
+    client = calloc(1, sizeof *client);
+    if (client == NULL) {
+        wl_client_post_no_memory(wl_client);
+        return;
+    }
+
+    client->wl_client = wl_client;
+    client->shell = shell;
+
+    wl_list_init(&client->surfaces);
+
+    client->resource =
+        wl_resource_create(wl_client, &xdg_wm_base_interface, version, id);
+    if (client->resource == NULL) {
+        free(client);
+        wl_client_post_no_memory(wl_client);
+        return;
+    }
+
+    wl_resource_set_implementation(client->resource, &xdg_shell_impl, client,
+            xdg_client_handle_resource_destroy);
+
+    wl_list_insert(&shell->clients, &client->link);
+
+    xdg_client_init_ping_timer(client);
+}
diff --git a/src/libds/xdg_shell/xdg_shell.h b/src/libds/xdg_shell/xdg_shell.h
new file mode 100644 (file)
index 0000000..0f99d6d
--- /dev/null
@@ -0,0 +1,187 @@
+#ifndef DS_XDG_SHELL_H
+#define DS_XDG_SHELL_H
+
+#include <wayland-server.h>
+
+#include "xdg-shell-server-protocol.h"
+
+#include "surface.h"
+#include "libds/output.h"
+
+typedef enum
+{
+    DS_XDG_SURFACE_ROLE_NONE,
+    DS_XDG_SURFACE_ROLE_TOPLEVEL,
+    DS_XDG_SURFACE_ROLE_POPUP,
+} ds_xdg_surface_role_t;
+
+struct ds_xdg_shell
+{
+    struct wl_global *global;
+
+    struct wl_list clients;
+
+    struct wl_listener display_destroy;
+
+    struct {
+        struct wl_signal destroy;
+        struct wl_signal new_surface;
+    } events;
+
+    uint32_t ping_timeout;
+};
+
+struct ds_xdg_client
+{
+    struct ds_xdg_shell *shell;
+
+    struct wl_resource *resource;
+    struct wl_client *wl_client;
+    struct wl_event_source *ping_timer;
+
+    struct wl_list surfaces;
+
+    struct wl_list link; // ds_xdg_shell::clients
+
+    uint32_t ping_serial;
+};
+
+struct ds_xdg_toplevel_state
+{
+    bool maximized, fullscreen, resizing, activated;
+    uint32_t tiled;
+    uint32_t width, height;
+    uint32_t max_width, max_height;
+    uint32_t min_width, min_height;
+};
+
+struct ds_xdg_toplevel_configure
+{
+    bool maximized, fullscreen, resizing, activated;
+    uint32_t tiled;
+    uint32_t width, height;
+};
+
+struct ds_xdg_toplevel_requested
+{
+    bool maximized, minimized, fullscreen;
+    struct ds_output *fullscreen_output;
+    struct wl_listener fullscreen_output_destroy;
+};
+
+struct ds_xdg_toplevel
+{
+    struct wl_resource *resource;
+    struct ds_xdg_surface *base;
+    bool added;
+
+    struct ds_xdg_surface *parent;
+    struct wl_listener parent_unmap;
+
+    struct ds_xdg_toplevel_state current, pending;
+    struct ds_xdg_toplevel_configure scheduled;
+    struct ds_xdg_toplevel_requested requested;
+
+    char *title;
+    char *app_id;
+
+    struct {
+        struct wl_signal request_maximize;
+        struct wl_signal request_fullscreen;
+        struct wl_signal request_minimize;
+        struct wl_signal request_move;
+        struct wl_signal request_resize;
+        struct wl_signal request_show_window_menu;
+        struct wl_signal set_parent;
+        struct wl_signal set_title;
+        struct wl_signal set_app_id;
+    } events;
+};
+
+struct ds_xdg_popup
+{
+
+};
+
+struct ds_xdg_surface_state
+{
+    uint32_t configure_serial;
+    struct {
+        int x, y;
+        int width, height;
+    } geometry;
+};
+
+struct ds_xdg_surface
+{
+    struct ds_xdg_client *client;
+    struct ds_surface *ds_surface;
+
+    ds_xdg_surface_role_t role;
+
+    union {
+        struct ds_xdg_toplevel *toplevel;
+        struct ds_xdg_popup *popup;
+    };
+
+    struct wl_resource *resource;
+
+    struct wl_event_source *configure_idle;
+    uint32_t scheduled_serial;
+    struct wl_list configure_list;
+
+    struct ds_xdg_surface_state current, pending;
+
+    struct wl_list link; // ds_xdg_client::surfaces
+
+    struct {
+        struct wl_listener surface_destroy;
+        struct wl_listener surface_commit;
+    } listener;
+
+    struct {
+        struct wl_signal destroy;
+        struct wl_signal ping_timeout;
+        struct wl_signal new_popup;
+        struct wl_signal map;
+        struct wl_signal unmap;
+        struct wl_signal configure;
+        struct wl_signal ack_configure;
+    } events;
+
+    bool added, configured, mapped;
+};
+
+struct ds_xdg_surface_configure
+{
+    struct ds_xdg_surface *surface;
+    struct wl_list link;
+    uint32_t serial;
+
+    struct ds_xdg_toplevel_configure *toplevel_configure;
+};
+
+uint32_t
+ds_xdg_surface_schedule_configure(struct ds_xdg_surface *surface);
+
+struct ds_xdg_surface *
+create_xdg_surface(struct ds_xdg_client *client, struct ds_surface *surface,
+        uint32_t id);
+
+void
+reset_xdg_surface(struct ds_xdg_surface *surface);
+
+void
+create_xdg_toplevel(struct ds_xdg_surface *surface, uint32_t id);
+
+void
+handle_xdg_surface_commit(struct ds_surface *ds_surface);
+
+void
+handle_xdg_surface_toplevel_committed(struct ds_xdg_surface *surface);
+
+void
+send_xdg_toplevel_configure(struct ds_xdg_surface *surface,
+        struct ds_xdg_surface_configure *configure);
+
+#endif
diff --git a/src/libds/xdg_shell/xdg_surface.c b/src/libds/xdg_shell/xdg_surface.c
new file mode 100644 (file)
index 0000000..523438d
--- /dev/null
@@ -0,0 +1,485 @@
+#include <assert.h>
+#include <stdlib.h>
+
+#include "xdg_shell.h"
+#include "libds/log.h"
+
+static const struct xdg_surface_interface xdg_surface_impl;
+static void xdg_surface_handle_surface_destroy(struct wl_listener *listener,
+        void *data);
+static void xdg_surface_handle_surface_commit(struct wl_listener *listener,
+        void *data);
+static void xdg_surface_handle_resource_destroy(struct wl_resource *resource);
+static void xdg_surface_configure_destroy(struct ds_xdg_surface_configure *configure);
+static void surface_send_configure(void *user_data);
+
+void
+ds_xdg_surface_add_destroy_listener(struct ds_xdg_surface *surface,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&surface->events.destroy, listener);
+}
+
+WL_EXPORT void
+ds_xdg_surface_add_map_listener(struct ds_xdg_surface *surface,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&surface->events.map, listener);
+}
+
+WL_EXPORT void
+ds_xdg_surface_add_unmap_listener(struct ds_xdg_surface *surface,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&surface->events.unmap, listener);
+}
+
+struct ds_surface *
+ds_xdg_surface_get_surface(struct ds_xdg_surface *surface)
+{
+    return surface->ds_surface;
+}
+
+struct ds_xdg_surface *
+create_xdg_surface(struct ds_xdg_client *client, struct ds_surface *ds_surface,
+        uint32_t id)
+{
+    struct ds_xdg_surface *surface;
+
+    surface = calloc(1, sizeof *surface);
+    if (!surface) {
+        wl_client_post_no_memory(client->wl_client);
+        return NULL;
+    }
+
+    surface->client = client;
+    surface->role = DS_XDG_SURFACE_ROLE_NONE;
+    surface->ds_surface = ds_surface;
+    surface->resource = wl_resource_create(client->wl_client,
+            &xdg_surface_interface, wl_resource_get_version(client->resource),
+            id);
+    if (!surface->resource) {
+        free(surface);
+        wl_client_post_no_memory(client->wl_client);
+        return NULL;
+    }
+
+    if (ds_surface_has_buffer(surface->ds_surface)) {
+        wl_resource_destroy(surface->resource);
+        free(surface);
+        wl_resource_post_error(client->resource,
+                XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER,
+                "xdg_surface must not have a buffer at creation");
+        return NULL;
+    }
+
+    wl_list_init(&surface->configure_list);
+
+    wl_signal_init(&surface->events.destroy);
+    wl_signal_init(&surface->events.ping_timeout);
+    wl_signal_init(&surface->events.new_popup);
+    wl_signal_init(&surface->events.map);
+    wl_signal_init(&surface->events.unmap);
+    wl_signal_init(&surface->events.configure);
+    wl_signal_init(&surface->events.ack_configure);
+
+    surface->listener.surface_destroy.notify =
+        xdg_surface_handle_surface_destroy;
+    ds_surface_add_destroy_listener(ds_surface,
+            &surface->listener.surface_destroy);
+
+    surface->listener.surface_commit.notify =
+        xdg_surface_handle_surface_commit;
+    ds_surface_add_commit_listener(ds_surface,
+            &surface->listener.surface_commit);
+
+    wl_resource_set_implementation(surface->resource, &xdg_surface_impl,
+            surface, xdg_surface_handle_resource_destroy);
+
+    wl_list_insert(&client->surfaces, &surface->link);
+
+    ds_inf("New xdg_surface %p (res %p)", surface, surface->resource);
+
+    return surface;
+}
+
+void
+unmap_xdg_surface(struct ds_xdg_surface *surface)
+{
+    struct ds_xdg_surface_configure *configure, *tmp;
+
+    // TODO handle popup
+
+    if (surface->mapped)
+        wl_signal_emit(&surface->events.unmap, surface);
+
+    switch (surface->role) {
+        case DS_XDG_SURFACE_ROLE_TOPLEVEL:
+            if (surface->toplevel->parent) {
+                wl_list_remove(&surface->toplevel->parent_unmap.link);
+                surface->toplevel->parent = NULL;
+            }
+            free(surface->toplevel->title);
+            surface->toplevel->title = NULL;
+            free(surface->toplevel->app_id);
+            surface->toplevel->app_id = NULL;
+            break;
+        case DS_XDG_SURFACE_ROLE_POPUP:
+            // TODO
+            break;
+        case DS_XDG_SURFACE_ROLE_NONE:
+            assert(false && "not reached");
+    }
+
+    wl_list_for_each_safe(configure, tmp, &surface->configure_list, link)
+        xdg_surface_configure_destroy(configure);
+
+    if (surface->configure_idle) {
+        wl_event_source_remove(surface->configure_idle);
+        surface->configure_idle = NULL;
+    }
+
+    surface->configured = false;
+    surface->mapped = false;
+}
+
+void
+reset_xdg_surface(struct ds_xdg_surface *surface)
+{
+    struct ds_xdg_toplevel_requested *req;
+
+    if (surface->role != DS_XDG_SURFACE_ROLE_NONE)
+        unmap_xdg_surface(surface);
+
+    if (surface->added) {
+        wl_signal_emit(&surface->events.destroy, surface);
+        surface->added = false;
+    }
+
+    switch (surface->role) {
+        case DS_XDG_SURFACE_ROLE_TOPLEVEL:
+            wl_resource_set_user_data(surface->toplevel->resource, NULL);
+            surface->toplevel->resource = NULL;
+            req = &surface->toplevel->requested;
+            if (req->fullscreen_output)
+                wl_list_remove(&req->fullscreen_output_destroy.link);
+            free(surface->toplevel);
+            surface->toplevel = NULL;
+            break;
+        case DS_XDG_SURFACE_ROLE_POPUP:
+            // TODO
+            break;
+        case DS_XDG_SURFACE_ROLE_NONE:
+            // This space is intentionally left blank
+            break;
+    }
+
+    surface->role = DS_XDG_SURFACE_ROLE_NONE;
+}
+
+void
+destroy_xdg_surface(struct ds_xdg_surface *surface)
+{
+    reset_xdg_surface(surface);
+
+    wl_resource_set_user_data(surface->resource, NULL);
+
+    ds_surface_reset_role_data(surface->ds_surface);
+
+    wl_list_remove(&surface->link);
+    wl_list_remove(&surface->listener.surface_destroy.link);
+    wl_list_remove(&surface->listener.surface_commit.link);
+
+    free(surface);
+}
+
+void
+handle_xdg_surface_commit(struct ds_surface *ds_surface)
+{
+    struct ds_xdg_surface *surface;
+
+    surface = ds_surface_get_role_data(ds_surface);
+    surface->current = surface->pending;
+
+    switch (surface->role) {
+        case DS_XDG_SURFACE_ROLE_NONE:
+            // inert toplevel or popup
+            break;
+        case DS_XDG_SURFACE_ROLE_TOPLEVEL:
+            handle_xdg_surface_toplevel_committed(surface);
+            // TODO
+            break;
+        case DS_XDG_SURFACE_ROLE_POPUP:
+            // TODO
+            break;
+    }
+
+    if (!surface->added) {
+        surface->added = true;
+        wl_signal_emit(&surface->client->shell->events.new_surface, surface);
+    }
+
+    if (surface->configured &&
+            ds_surface_has_buffer(surface->ds_surface) &&
+            !surface->mapped) {
+        surface->mapped = true;
+        wl_signal_emit(&surface->events.map, surface);
+    }
+}
+
+void handle_xdg_surface_precommit(struct ds_surface *ds_surface)
+{
+    struct ds_xdg_surface *surface;
+
+    surface = ds_surface_get_role_data(ds_surface);
+
+    // TODO
+    (void)surface;
+}
+
+uint32_t
+ds_xdg_surface_schedule_configure(struct ds_xdg_surface *surface)
+{
+    struct wl_display *display;
+    struct wl_event_loop *loop;
+
+    display = wl_client_get_display(surface->client->wl_client);
+    loop = wl_display_get_event_loop(display);
+
+    if (!surface->configure_idle) {
+        surface->scheduled_serial = wl_display_next_serial(display);
+        surface->configure_idle = wl_event_loop_add_idle(loop,
+                surface_send_configure, surface);
+        if (!surface->configure_idle)
+            wl_client_post_no_memory(surface->client->wl_client);
+    }
+
+    return surface->scheduled_serial;
+}
+
+void
+ds_xdg_surface_ping(struct ds_xdg_surface *surface)
+{
+}
+
+static void
+xdg_surface_handle_surface_destroy(struct wl_listener *listener, void *data)
+{
+    struct ds_xdg_surface *surface;
+
+    surface = wl_container_of(listener, surface, listener.surface_destroy);
+    destroy_xdg_surface(surface);
+}
+
+static void
+xdg_surface_handle_surface_commit(struct wl_listener *listener, void *data)
+{
+    struct ds_xdg_surface *surface;
+
+    surface = wl_container_of(listener, surface, listener.surface_commit);
+
+    if (ds_surface_has_buffer(surface->ds_surface) &&
+            !surface->configured) {
+        wl_resource_post_error(surface->resource,
+                XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER,
+                "xdg_surface has never been configured");
+        return;
+    }
+
+    if (!ds_surface_get_role(surface->ds_surface)) {
+        wl_resource_post_error(surface->resource,
+                XDG_SURFACE_ERROR_NOT_CONSTRUCTED,
+                "xdg_surface must have a role");
+        return;
+    }
+}
+
+static void
+xdg_surface_handle_resource_destroy(struct wl_resource *resource)
+{
+    struct ds_xdg_surface *surface;
+
+    surface = wl_resource_get_user_data(resource);
+    if (!surface)
+        return;
+
+    destroy_xdg_surface(surface);
+}
+
+static void
+xdg_surface_configure_destroy(struct ds_xdg_surface_configure *configure)
+{
+    wl_list_remove(&configure->link);
+    free(configure->toplevel_configure);
+    free(configure);
+}
+
+static void
+xdg_surface_handle_destroy(struct wl_client *client,
+        struct wl_resource *resource)
+{
+    struct ds_xdg_surface *surface;
+
+    surface = wl_resource_get_user_data(resource);
+
+    if (surface->role != DS_XDG_SURFACE_ROLE_NONE) {
+        ds_err("Tried to destroy an xdg_surface before its role object");
+        return;
+    }
+
+    wl_resource_destroy(resource);
+}
+
+static void
+xdg_surface_handle_get_toplevel(struct wl_client *client,
+        struct wl_resource *resource, uint32_t id)
+{
+    struct ds_xdg_surface *surface;
+
+    surface = wl_resource_get_user_data(resource);
+    create_xdg_toplevel(surface, id);
+}
+
+static void
+xdg_surface_handle_get_popup(struct wl_client *client,
+        struct wl_resource *resource, uint32_t id,
+        struct wl_resource *parent_resource,
+        struct wl_resource *positioner_resource)
+{
+    struct ds_xdg_surface *surface;
+
+    surface = wl_resource_get_user_data(resource);
+
+    // TODO
+    (void)surface;
+}
+
+static void
+xdg_surface_handle_ack_configure(struct wl_client *client,
+        struct wl_resource *resource, uint32_t serial)
+{
+    struct ds_xdg_surface *surface;
+
+    surface = wl_resource_get_user_data(resource);
+
+    if (surface->role == DS_XDG_SURFACE_ROLE_NONE) {
+        wl_resource_post_error(surface->resource,
+                XDG_SURFACE_ERROR_NOT_CONSTRUCTED,
+                "xdg_surface must have a role");
+        return;
+    }
+
+    bool found = false;
+    struct ds_xdg_surface_configure *configure, *tmp;
+    wl_list_for_each(configure, &surface->configure_list, link) {
+        if (configure->serial == serial) {
+            found = true;
+            break;
+        }
+    }
+
+    if (!found) {
+        wl_resource_post_error(surface->client->resource,
+                XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE,
+                "wrong configure serial: %u", serial);
+        return;
+    }
+
+    wl_list_for_each_safe(configure, tmp, &surface->configure_list, link) {
+        if (configure->serial == serial)
+            break;
+
+        wl_signal_emit(&surface->events.ack_configure, configure);
+        xdg_surface_configure_destroy(configure);
+    }
+
+    switch (surface->role) {
+        case DS_XDG_SURFACE_ROLE_NONE:
+            assert(0 && "not reached");
+            break;
+        case DS_XDG_SURFACE_ROLE_TOPLEVEL:
+            // TODO
+            break;
+        case DS_XDG_SURFACE_ROLE_POPUP:
+            break;
+    }
+
+    surface->configured = true;
+    surface->pending.configure_serial = serial;
+
+    wl_signal_emit(&surface->events.ack_configure, configure);
+    xdg_surface_configure_destroy(configure);
+}
+
+static void
+xdg_surface_handle_set_window_geometry(struct wl_client *client,
+        struct wl_resource *resource,
+        int32_t x, int32_t y, int32_t width, int32_t height)
+{
+    struct ds_xdg_surface *surface;
+
+    surface = wl_resource_get_user_data(resource);
+
+    if (surface->role == DS_XDG_SURFACE_ROLE_NONE) {
+        wl_resource_post_error(surface->resource,
+                XDG_SURFACE_ERROR_NOT_CONSTRUCTED,
+                "xdg_surface must have a role");
+        return;
+    }
+
+    if (width <= 0 || height <= 0) {
+        ds_err("Client tried to set invalid geometry");
+        wl_resource_post_error(resource, -1,
+                "Tried to set invalid xdg_surface geometry");
+        return;
+    }
+
+    surface->pending.geometry.x = x;
+    surface->pending.geometry.y = y;
+    surface->pending.geometry.width = width;
+    surface->pending.geometry.height = height;
+}
+
+static const struct xdg_surface_interface xdg_surface_impl =
+{
+    .destroy = xdg_surface_handle_destroy,
+    .get_toplevel = xdg_surface_handle_get_toplevel,
+    .get_popup = xdg_surface_handle_get_popup,
+    .ack_configure = xdg_surface_handle_ack_configure,
+    .set_window_geometry = xdg_surface_handle_set_window_geometry,
+};
+
+static void
+surface_send_configure(void *user_data)
+{
+    struct ds_xdg_surface *surface;
+    struct ds_xdg_surface_configure *configure;
+
+    surface = user_data;
+    surface->configure_idle = NULL;
+
+    configure = calloc(1, sizeof *configure);
+    if (!configure) {
+        wl_client_post_no_memory(surface->client->wl_client);
+        return;
+    }
+
+    wl_list_insert(surface->configure_list.prev, &configure->link);
+    configure->serial = surface->scheduled_serial;
+    configure->surface = surface;
+
+    switch (surface->role) {
+        case DS_XDG_SURFACE_ROLE_NONE:
+            assert(0 && "not reached");
+            break;
+        case DS_XDG_SURFACE_ROLE_TOPLEVEL:
+            send_xdg_toplevel_configure(surface, configure);
+            break;
+        case DS_XDG_SURFACE_ROLE_POPUP:
+            break;
+    }
+
+    wl_signal_emit(&surface->events.configure, configure);
+
+    xdg_surface_send_configure(surface->resource, configure->serial);
+}
diff --git a/src/libds/xdg_shell/xdg_toplevel.c b/src/libds/xdg_shell/xdg_toplevel.c
new file mode 100644 (file)
index 0000000..8e78fe7
--- /dev/null
@@ -0,0 +1,327 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "xdg_shell.h"
+
+const struct ds_surface_role xdg_toplevel_surface_role =  {
+    .name = "xdg_toplevel",
+    .commit = handle_xdg_surface_commit,
+};
+
+static const struct xdg_toplevel_interface xdg_toplevel_impl;
+
+static void xdg_toplevel_handle_resource_destroy(struct wl_resource *resource);
+
+void
+create_xdg_toplevel(struct ds_xdg_surface *surface, uint32_t id)
+{
+    if (!ds_surface_set_role(surface->ds_surface, &xdg_toplevel_surface_role,
+                surface, surface->resource, XDG_WM_BASE_ERROR_ROLE)) {
+        return;
+    }
+
+    if (surface->role != DS_XDG_SURFACE_ROLE_NONE) {
+        wl_resource_post_error(surface->resource,
+                XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED,
+                "xdg_surface has already been constructed");
+        return;
+    }
+
+    assert(surface->toplevel == NULL);
+
+    surface->toplevel = calloc(1, sizeof *surface->toplevel);
+    if (!surface->toplevel) {
+        wl_resource_post_no_memory(surface->resource);
+        return;
+    }
+
+    surface->toplevel->base = surface;
+
+    wl_signal_init(&surface->toplevel->events.request_maximize);
+    wl_signal_init(&surface->toplevel->events.request_fullscreen);
+    wl_signal_init(&surface->toplevel->events.request_minimize);
+    wl_signal_init(&surface->toplevel->events.request_move);
+    wl_signal_init(&surface->toplevel->events.request_resize);
+    wl_signal_init(&surface->toplevel->events.request_show_window_menu);
+    wl_signal_init(&surface->toplevel->events.set_parent);
+    wl_signal_init(&surface->toplevel->events.set_title);
+    wl_signal_init(&surface->toplevel->events.set_app_id);
+
+    surface->toplevel->resource = wl_resource_create(
+            surface->client->wl_client, &xdg_toplevel_interface,
+            wl_resource_get_version(surface->resource), id);
+    if (!surface->toplevel->resource) {
+        free(surface->toplevel);
+        wl_resource_post_no_memory(surface->resource);
+        return;
+    }
+
+    wl_resource_set_implementation(surface->toplevel->resource,
+            &xdg_toplevel_impl, surface,
+            xdg_toplevel_handle_resource_destroy);
+
+    surface->role = DS_XDG_SURFACE_ROLE_TOPLEVEL;
+}
+
+void
+handle_xdg_surface_toplevel_committed(struct ds_xdg_surface *surface)
+{
+    if (!surface->toplevel->added) {
+        ds_xdg_surface_schedule_configure(surface);
+        surface->toplevel->added = true;
+        return;
+    }
+
+    surface->toplevel->current = surface->toplevel->pending;
+}
+
+void
+send_xdg_toplevel_configure(struct ds_xdg_surface *surface,
+        struct ds_xdg_surface_configure *configure)
+{
+    struct wl_array states;
+
+    configure->toplevel_configure =
+        malloc(sizeof *configure->toplevel_configure);
+    if (!configure->toplevel_configure) {
+        wl_resource_post_no_memory(surface->toplevel->resource);
+        return;
+    }
+
+    *configure->toplevel_configure = surface->toplevel->scheduled;
+
+    wl_array_init(&states);
+    if (surface->toplevel->scheduled.maximized) {
+        uint32_t *s = wl_array_add(&states, sizeof(uint32_t));
+        if (!s)
+            goto error_out;
+        *s = XDG_TOPLEVEL_STATE_MAXIMIZED;
+    }
+
+    if (surface->toplevel->scheduled.fullscreen) {
+        uint32_t *s = wl_array_add(&states, sizeof(uint32_t));
+        if (!s)
+            goto error_out;
+        *s = XDG_TOPLEVEL_STATE_FULLSCREEN;
+    }
+
+    if (surface->toplevel->scheduled.resizing) {
+        uint32_t *s = wl_array_add(&states, sizeof(uint32_t));
+        if (!s)
+            goto error_out;
+        *s = XDG_TOPLEVEL_STATE_RESIZING;
+    }
+
+    if (surface->toplevel->scheduled.activated) {
+        uint32_t *s = wl_array_add(&states, sizeof(uint32_t));
+        if (!s)
+            goto error_out;
+        *s = XDG_TOPLEVEL_STATE_ACTIVATED;
+    }
+
+    if (surface->toplevel->scheduled.tiled) {
+        ;;;
+        // TODO
+    }
+
+    uint32_t width = surface->toplevel->scheduled.width;
+    uint32_t height = surface->toplevel->scheduled.height;
+
+    xdg_toplevel_send_configure(surface->toplevel->resource, width, height,
+            &states);
+    wl_array_release(&states);
+
+    return;
+
+error_out:
+    wl_array_release(&states);
+    wl_resource_post_no_memory(surface->toplevel->resource);
+}
+
+void
+destroy_xdg_toplevel(struct ds_xdg_surface *xdg_surface)
+{
+    reset_xdg_surface(xdg_surface);
+}
+
+static void
+xdg_toplevel_handle_destroy(struct wl_client *client,
+        struct wl_resource *resource)
+{
+    wl_resource_destroy(resource);
+}
+
+static void
+xdg_toplevel_handle_set_parent(struct wl_client *client,
+        struct wl_resource *resource, struct wl_resource *parent_resource)
+{
+    // TODO
+}
+
+static void
+xdg_toplevel_handle_set_title(struct wl_client *client,
+        struct wl_resource *resource, const char *title)
+{
+    struct ds_xdg_surface *surface;
+    char *tmp;
+
+    surface = wl_resource_get_user_data(resource);
+    tmp = strdup(title);
+    if (!tmp) {
+        wl_resource_post_no_memory(resource);
+        return;
+    }
+
+    if (surface->toplevel->title)
+        free(surface->toplevel->title);
+
+    surface->toplevel->title = tmp;
+    wl_signal_emit(&surface->toplevel->events.set_title, surface);
+}
+
+static void
+xdg_toplevel_handle_set_app_id(struct wl_client *client,
+        struct wl_resource *resource, const char *app_id)
+{
+    struct ds_xdg_surface *surface;
+    char *tmp;
+
+    surface = wl_resource_get_user_data(resource);
+    tmp = strdup(app_id);
+    if (!tmp) {
+        wl_resource_post_no_memory(resource);
+        return;
+    }
+
+    if (surface->toplevel->app_id)
+        free(surface->toplevel->app_id);
+
+    surface->toplevel->app_id= tmp;
+    wl_signal_emit(&surface->toplevel->events.set_app_id, surface);
+
+}
+
+static void
+xdg_toplevel_handle_show_window_menu(struct wl_client *client,
+        struct wl_resource *resource, struct wl_resource *seat_resource,
+        uint32_t serial, int32_t x, int32_t y)
+{
+    // TODO
+}
+
+static void
+xdg_toplevel_handle_move(struct wl_client *client,
+        struct wl_resource *resource, struct wl_resource *seat_resource,
+        uint32_t serial)
+{
+    // TODO
+}
+
+static void
+xdg_toplevel_handle_resize(struct wl_client *client,
+        struct wl_resource *resource, struct wl_resource *seat_resource,
+        uint32_t serial, uint32_t edges)
+{
+    // TODO
+}
+
+static void
+xdg_toplevel_handle_set_max_size(struct wl_client *client,
+        struct wl_resource *resource, int32_t width, int32_t height)
+{
+    struct ds_xdg_surface *surface;
+
+    surface = wl_resource_get_user_data(resource);
+    surface->toplevel->pending.max_width = width;
+    surface->toplevel->pending.max_height = height;
+}
+
+static void
+xdg_toplevel_handle_set_min_size(struct wl_client *client,
+        struct wl_resource *resource, int32_t width, int32_t height)
+{
+    struct ds_xdg_surface *surface;
+
+    surface = wl_resource_get_user_data(resource);
+    surface->toplevel->pending.min_width = width;
+    surface->toplevel->pending.min_height = height;
+}
+
+static void
+xdg_toplevel_handle_set_maximized(struct wl_client *client,
+        struct wl_resource *resource)
+{
+    struct ds_xdg_surface *surface;
+
+    surface = wl_resource_get_user_data(resource);
+    surface->toplevel->requested.maximized = true;
+    wl_signal_emit(&surface->toplevel->events.request_maximize, surface);
+    ds_xdg_surface_schedule_configure(surface);
+}
+
+static void
+xdg_toplevel_handle_unset_maximized(struct wl_client *client,
+        struct wl_resource *resource)
+{
+    struct ds_xdg_surface *surface;
+
+    surface = wl_resource_get_user_data(resource);
+    surface->toplevel->requested.maximized = false;
+    wl_signal_emit(&surface->toplevel->events.request_maximize, surface);
+    ds_xdg_surface_schedule_configure(surface);
+}
+
+static void
+xdg_toplevel_handle_set_fullscreen(struct wl_client *client,
+        struct wl_resource *resource, struct wl_resource  *output_resource)
+{
+    // TODO
+}
+
+static void
+xdg_toplevel_handle_unset_fullscreen(struct wl_client *client,
+        struct wl_resource *resource)
+{
+    // TODO
+}
+
+static void
+xdg_toplevel_handle_set_minimized(struct wl_client *client,
+        struct wl_resource *resource)
+{
+    struct ds_xdg_surface *surface;
+
+    surface = wl_resource_get_user_data(resource);
+    surface->toplevel->requested.minimized = true;
+    wl_signal_emit(&surface->toplevel->events.request_maximize, surface);
+}
+
+static const struct xdg_toplevel_interface xdg_toplevel_impl = {
+    xdg_toplevel_handle_destroy,
+    xdg_toplevel_handle_set_parent,
+    xdg_toplevel_handle_set_title,
+    xdg_toplevel_handle_set_app_id,
+    xdg_toplevel_handle_show_window_menu,
+    xdg_toplevel_handle_move,
+    xdg_toplevel_handle_resize,
+    xdg_toplevel_handle_set_max_size,
+    xdg_toplevel_handle_set_min_size,
+    xdg_toplevel_handle_set_maximized,
+    xdg_toplevel_handle_unset_maximized,
+    xdg_toplevel_handle_set_fullscreen,
+    xdg_toplevel_handle_unset_fullscreen,
+    xdg_toplevel_handle_set_minimized,
+};
+
+static void
+xdg_toplevel_handle_resource_destroy(struct wl_resource *resource)
+{
+    struct ds_xdg_surface *surface;
+
+    surface = wl_resource_get_user_data(resource);
+    if (!surface)
+        return;
+
+    destroy_xdg_toplevel(surface);
+}
diff --git a/src/meson.build b/src/meson.build
new file mode 100644 (file)
index 0000000..2a6ba37
--- /dev/null
@@ -0,0 +1,14 @@
+wayland_protos = dependency('wayland-protocols',
+  fallback: ['wayland-protocols', 'wayland_protocols'],
+  default_options: ['tests=false'],
+)
+wl_protocol_dir = wayland_protos.get_variable('pkgdatadir')
+
+wayland_scanner_dep = dependency('wayland-scanner', native: true)
+wayland_scanner = find_program(
+  wayland_scanner_dep.get_variable('wayland_scanner'),
+  native: true,
+)
+
+subdir('libds')
+subdir('tests')
diff --git a/src/tests/meson.build b/src/tests/meson.build
new file mode 100644 (file)
index 0000000..dd52176
--- /dev/null
@@ -0,0 +1,25 @@
+tests = [
+  { 'name': 'test-compositor' },
+  { 'name': 'test-backend' },
+  { 
+    'name': 'test-surface',
+    'deps': [ dependency('wayland-client') ],
+  },
+  {
+    'name': 'test-subsurface',
+    'deps': [ dependency('wayland-client') ],
+  },
+]
+
+foreach t : tests
+  t_deps = [ dep_libds ]
+  t_deps += t.get('deps', [])
+
+  test('libds-' + t.get('name'),
+    executable('libds-' + t.get('name'), t.get('name') + '.c',
+      dependencies: t_deps ,
+      include_directories: common_inc,
+      install: false
+    )
+  )
+endforeach
diff --git a/src/tests/test-backend.c b/src/tests/test-backend.c
new file mode 100644 (file)
index 0000000..69417b6
--- /dev/null
@@ -0,0 +1,80 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <wayland-server.h>
+#include <libds/backend/wayland.h>
+
+static struct ds_backend *
+create_possible_wl_backend(struct wl_display *display)
+{
+    struct ds_backend *backend;
+    char wl_name[512];
+
+    for (int i = 0; i < 5; i++) {
+        snprintf(wl_name, sizeof wl_name, "wayland-%d", i);
+        backend = ds_wl_backend_create(display, wl_name);
+        if (backend)
+            break;
+    }
+
+    return backend;
+}
+
+static void
+test_wl_backend_create(void)
+{
+    struct wl_display *display;
+    struct ds_backend *backend;
+
+    display = wl_display_create();
+    
+    backend = create_possible_wl_backend(display);
+    assert(backend);
+
+    ds_backend_destroy(backend);
+}
+
+struct test
+{
+    struct wl_listener backend_destroy;
+    bool cb_called;
+};
+
+static void
+cb_backend_destroy(struct wl_listener *listener, void *data)
+{
+    struct test *test;
+
+    test = wl_container_of(listener, test, backend_destroy);
+    test->cb_called = true;
+}
+
+static void
+test_wl_backend_destroy_signal(void)
+{
+    struct wl_display *display;
+    struct ds_backend *backend;
+    struct test test;
+
+    display = wl_display_create();
+
+    backend = create_possible_wl_backend(display);
+    assert(backend);
+
+    test.cb_called = false;
+    test.backend_destroy.notify = cb_backend_destroy;
+    ds_backend_add_destroy_listener(backend, &test.backend_destroy);
+
+    wl_display_destroy(display);
+    assert(test.cb_called);
+}
+
+int
+main(void)
+{
+    test_wl_backend_create();
+    test_wl_backend_destroy_signal();
+
+    return 0;
+}
diff --git a/src/tests/test-compositor.c b/src/tests/test-compositor.c
new file mode 100644 (file)
index 0000000..b5883a5
--- /dev/null
@@ -0,0 +1,63 @@
+#include <assert.h>
+#include <stdbool.h>
+
+#include <wayland-server.h>
+#include <libds/compositor.h>
+#include <libds/log.h>
+
+static void
+test_compositor_create(void)
+{
+    struct wl_display *display;
+    struct ds_compositor *compositor;
+
+    display = wl_display_create();
+    compositor = ds_compositor_create(display);
+    assert(compositor);
+
+    wl_display_destroy(display);
+}
+
+struct test
+{
+    struct wl_listener compositor_destroy;
+    bool destroyed;
+};
+
+static void
+cb_compositor_destroy(struct wl_listener *listener, void *data)
+{
+    struct test *test;
+
+    test = wl_container_of(listener, test, compositor_destroy);
+    test->destroyed = true;
+}
+
+static void
+test_compositor_destroy_signal(void)
+{
+    struct wl_display *display;
+    struct ds_compositor *compositor;
+    struct test test;
+
+    display = wl_display_create();
+    compositor = ds_compositor_create(display);
+    assert(compositor);
+
+    test.destroyed = false;
+    test.compositor_destroy.notify = cb_compositor_destroy;
+    ds_compositor_add_destroy_listener(compositor,
+            &test.compositor_destroy);
+
+    wl_display_destroy(display);
+    assert(test.destroyed == true);
+}
+
+int
+main(void)
+{
+    test_compositor_create();
+    test_compositor_destroy_signal();
+
+    return 0;
+}
diff --git a/src/tests/test-subsurface.c b/src/tests/test-subsurface.c
new file mode 100644 (file)
index 0000000..98e830d
--- /dev/null
@@ -0,0 +1,211 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <wayland-server.h>
+#include <wayland-client.h>
+#include <libds/compositor.h>
+#include <libds/surface.h>
+#include <libds/subsurface.h>
+#include <libds/log.h>
+
+struct server_base
+{
+    struct wl_display *display;
+    struct ds_compositor *compositor;
+    const char *socket;
+};
+
+const char *
+test_server_init(struct server_base *server)
+{
+    server->display = wl_display_create();
+    assert(server->display);
+    server->compositor = ds_compositor_create(server->display);
+    assert(server->compositor);
+    server->socket = wl_display_add_socket_auto(server->display);
+    assert(server->socket);
+
+    return server->socket;
+}
+
+void
+test_server_finish(struct server_base *server)
+{
+    wl_display_destroy(server->display);
+}
+
+struct client
+{
+    struct wl_display *display;
+    struct wl_registry *registry;
+    struct wl_compositor *compositor;
+    struct wl_subcompositor *subcompositor;
+    struct wl_surface *surface;
+    struct wl_subsurface *subsurface;
+};
+
+static void
+handle_global(void *data, struct wl_registry *registry, uint32_t id,
+        const char *interface, uint32_t version)
+{
+    struct client *client = data;
+
+    if (strcmp(interface, "wl_compositor") == 0) {
+        client->compositor =
+            wl_registry_bind(registry, id, &wl_compositor_interface, version);
+    }
+    else if (strcmp(interface, "wl_subcompositor") == 0) {
+        client->subcompositor =
+            wl_registry_bind(registry, id, &wl_subcompositor_interface, version);
+    }
+}
+
+static const struct wl_registry_listener registry_listener = {
+    .global = handle_global,
+};
+
+void
+test_client_init(struct client *client, const char *name)
+{
+    client->display = wl_display_connect(name);
+    assert(client->display);
+    client->registry = wl_display_get_registry(client->display);
+    assert(client->registry);
+
+    wl_registry_add_listener(client->registry, &registry_listener, client);
+
+    wl_display_roundtrip(client->display);
+
+    assert(client->compositor);
+    assert(client->subcompositor);
+}
+
+void
+test_client_finish(struct client *client)
+{
+    wl_subcompositor_destroy(client->subcompositor);
+    wl_compositor_destroy(client->compositor);
+    wl_registry_destroy(client->registry);
+    wl_display_disconnect(client->display);
+}
+
+struct test_server
+{
+    struct server_base base;
+    bool cb_called;
+
+    struct wl_listener new_surface;
+    struct wl_listener new_subsurface1;
+    struct wl_listener new_subsurface2;
+};
+
+static void
+cb_new_subsurface1(struct wl_listener *listener, void *data)
+{
+    struct ds_subsurface *subsurface = data;
+    struct test_server *server;
+
+    assert(subsurface);
+
+    server = wl_container_of(listener, server, new_subsurface1);
+    server->cb_called = true;
+    wl_display_terminate(server->base.display);
+}
+
+static void
+cb_new_subsurface2(struct wl_listener *listener, void *data)
+{
+    struct ds_subsurface *subsurface = data;
+    struct test_server *server;
+
+    assert(subsurface);
+
+    server = wl_container_of(listener, server, new_subsurface2);
+    server->cb_called = true;
+    wl_display_terminate(server->base.display);
+}
+
+static void
+cb_new_surface(struct wl_listener *listener, void *data)
+{
+    struct ds_surface *surface = data;
+    struct test_server *server;
+
+    server = wl_container_of(listener, server, new_surface);
+    if (!server->new_subsurface1.notify) {
+        server->new_subsurface1.notify = cb_new_subsurface1;
+        ds_surface_add_new_subsurface_listener(surface,
+                &server->new_subsurface1);
+    }
+    else {
+        server->new_subsurface2.notify = cb_new_subsurface2;
+        ds_surface_add_new_subsurface_listener(surface,
+                &server->new_subsurface2);
+    }
+}
+
+static void
+run_client(const char *name)
+{
+    struct client client;
+
+    test_client_init(&client, name);
+
+    struct wl_surface *surface =
+        wl_compositor_create_surface(client.compositor);
+
+    struct wl_surface *child_surface =
+        wl_compositor_create_surface(client.compositor);
+
+    struct wl_subsurface *subsurface =
+        wl_subcompositor_get_subsurface(client.subcompositor,
+                child_surface, surface);
+
+    wl_display_roundtrip(client.display);
+
+    wl_subsurface_destroy(subsurface);
+    wl_surface_destroy(child_surface);
+    wl_surface_destroy(surface);
+
+    test_client_finish(&client);
+}
+
+static void
+test_subsurface_create(void)
+{
+    struct test_server server = {
+        .new_subsurface1 = { .notify = NULL },
+        .cb_called = false
+    };
+    pid_t pid;
+
+    const char *socket_name = test_server_init(&server.base);
+
+    pid = fork();
+    assert(pid != -1);
+
+    if (pid == 0) {
+        run_client(socket_name);
+        exit(0);
+    }
+
+    server.new_surface.notify = cb_new_surface;
+    ds_compositor_add_new_surface_listener(server.base.compositor,
+            &server.new_surface);
+
+    wl_display_run(server.base.display);
+
+    assert(server.cb_called);
+
+    test_server_finish(&server.base);
+}
+
+int
+main(void)
+{
+    test_subsurface_create();
+    return 0;
+}
diff --git a/src/tests/test-surface.c b/src/tests/test-surface.c
new file mode 100644 (file)
index 0000000..eab2b77
--- /dev/null
@@ -0,0 +1,162 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <wayland-server.h>
+#include <wayland-client.h>
+#include <libds/compositor.h>
+#include <libds/surface.h>
+#include <libds/log.h>
+
+struct server_base
+{
+    struct wl_display *display;
+    struct ds_compositor *compositor;
+    const char *socket;
+};
+
+void
+test_server_init(struct server_base *server)
+{
+    server->display = wl_display_create();
+    assert(server->display);
+    server->compositor = ds_compositor_create(server->display);
+    assert(server->compositor);
+    server->socket = wl_display_add_socket_auto(server->display);
+    assert(server->socket);
+}
+
+void
+test_server_finish(struct server_base *server)
+{
+    wl_display_destroy(server->display);
+}
+
+struct client
+{
+    struct wl_display *display;
+    struct wl_registry *registry;
+    struct wl_compositor *compositor;
+    struct wl_surface *surface;
+};
+
+static void
+handle_global(void *data, struct wl_registry *registry, uint32_t id,
+        const char *interface, uint32_t version)
+{
+    struct client *client = data;
+
+    if (strcmp(interface, "wl_compositor") == 0) {
+        client->compositor =
+            wl_registry_bind(registry, id, &wl_compositor_interface, version);
+    }
+}
+
+static const struct wl_registry_listener registry_listener = {
+    .global = handle_global,
+};
+
+void
+test_client_init(struct client *client, const char *name)
+{
+    client->display = wl_display_connect(name);
+    assert(client->display);
+    client->registry = wl_display_get_registry(client->display);
+    assert(client->registry);
+
+    wl_registry_add_listener(client->registry, &registry_listener, client);
+
+    wl_display_roundtrip(client->display);
+
+    assert(client->compositor);
+}
+
+void
+test_client_finish(struct client *client)
+{
+    wl_compositor_destroy(client->compositor);
+    wl_registry_destroy(client->registry);
+    wl_display_disconnect(client->display);
+}
+
+struct test_server {
+    struct server_base base;
+    bool cb_called;
+
+    struct wl_listener new_surface;
+    struct wl_listener surface_destroy;
+};
+
+static void
+cb_surface_destroy(struct wl_listener *listener, void *data)
+{
+    struct test_server *server;
+
+    server = wl_container_of(listener, server, surface_destroy);
+    server->cb_called = true;
+    wl_display_terminate(server->base.display);
+}
+
+static void
+cb_new_surface(struct wl_listener *listener, void *data)
+{
+    struct ds_surface *surface = data;
+    struct test_server *server;
+
+    server = wl_container_of(listener, server, new_surface);
+    server->surface_destroy.notify = cb_surface_destroy;
+    ds_surface_add_destroy_listener(surface, &server->surface_destroy);
+}
+
+static void
+run_client(const char *name)
+{
+    struct client client;
+
+    test_client_init(&client, name);
+
+    struct wl_surface *surface =
+        wl_compositor_create_surface(client.compositor);
+
+    wl_display_roundtrip(client.display);
+
+    wl_surface_destroy(surface);
+
+    test_client_finish(&client);
+}
+
+static void
+test_surface_create_and_destroy(void)
+{
+    struct test_server server = { .cb_called = false };
+    pid_t pid;
+
+    test_server_init(&server.base);
+
+    pid = fork();
+    assert(pid != -1);
+
+    if (pid == 0) {
+        run_client(server.base.socket);
+        exit(0);
+    }
+
+    server.new_surface.notify = cb_new_surface;
+    ds_compositor_add_new_surface_listener(server.base.compositor,
+            &server.new_surface);
+
+    wl_display_run(server.base.display);
+
+    assert(server.cb_called);
+
+    test_server_finish(&server.base);
+}
+
+int
+main(void)
+{
+    test_surface_create_and_destroy();
+    return 0;
+}