implement the xdg_shell_v6 05/278105/1
authorSooChan Lim <sc1.lim@samsung.com>
Mon, 4 Jul 2022 10:12:59 +0000 (19:12 +0900)
committerSooChan Lim <sc1.lim@samsung.com>
Mon, 18 Jul 2022 05:08:56 +0000 (14:08 +0900)
It provides the wayland server implementation for zxdg_shell_v6_unstable
protocol.

Change-Id: I154bba10ba3cd9b7a35f93ac309808a172ee48df

include/libds/xdg_shell_v6.h [new file with mode: 0644]
packaging/libds.spec
src/meson.build
src/xdg_shell_v6/meson.build [new file with mode: 0644]
src/xdg_shell_v6/xdg_shell_v6.c [new file with mode: 0644]
src/xdg_shell_v6/xdg_shell_v6.h [new file with mode: 0644]
src/xdg_shell_v6/xdg_surface_v6.c [new file with mode: 0644]
src/xdg_shell_v6/xdg_toplevel_v6.c [new file with mode: 0644]

diff --git a/include/libds/xdg_shell_v6.h b/include/libds/xdg_shell_v6.h
new file mode 100644 (file)
index 0000000..40c9c5a
--- /dev/null
@@ -0,0 +1,58 @@
+#ifndef LIBDS_XDG_SHELL_V6_H
+#define LIBDS_XDG_SHELL_V6_H
+
+#include <stdint.h>
+#include <wayland-server.h>
+
+#include "surface.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ds_xdg_shell_v6;
+
+struct ds_xdg_surface_v6;
+
+struct ds_xdg_shell_v6 *
+ds_xdg_shell_v6_create(struct wl_display *display);
+
+void
+ds_xdg_shell_v6_add_destroy_listener(struct ds_xdg_shell_v6 *shell,
+        struct wl_listener *listener);
+
+void
+ds_xdg_shell_v6_add_new_surface_listener(struct ds_xdg_shell_v6 *shell,
+        struct wl_listener *listener);
+
+void
+ds_xdg_surface_v6_add_destroy_listener(struct ds_xdg_surface_v6 *surface,
+        struct wl_listener *listener);
+
+void
+ds_xdg_surface_v6_add_map_listener(struct ds_xdg_surface_v6 *surface,
+        struct wl_listener *listener);
+
+void
+ds_xdg_surface_v6_add_unmap_listener(struct ds_xdg_surface_v6 *surface,
+        struct wl_listener *listener);
+
+void
+ds_xdg_surface_v6_ping(struct ds_xdg_surface_v6 *surface);
+
+struct ds_surface *
+ds_xdg_surface_v6_get_surface(struct ds_xdg_surface_v6 *surface);
+
+uint32_t
+ds_xdg_toplevel_set_size(struct ds_xdg_surface_v6 *surface,
+        uint32_t width, uint32_t height);
+
+uint32_t
+ds_xdg_toplevel_set_activated(struct ds_xdg_surface_v6 *surface,
+        bool activated);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
index 1a7951e..13d59cf 100644 (file)
@@ -11,6 +11,7 @@ BuildRequires:  meson
 BuildRequires:  pkgconfig(wayland-server)
 BuildRequires:  pkgconfig(wayland-client)
 BuildRequires:  pkgconfig(wayland-protocols)
+BuildRequires:  pkgconfig(xdg-shell-unstable-v6-server)
 BuildRequires:  pkgconfig(pixman-1)
 BuildRequires:  pkgconfig(libdrm)
 BuildRequires:  pkgconfig(xkbcommon)
@@ -28,6 +29,21 @@ Requires:   %{name} = %{version}-%{release}
 %description devel
 Development package of Wayland Compositor Library
 
+## libds-xdg-shell-v6
+%package xdg-shell-v6
+Summary: Wayland Compositor Library for xdg-shell-v6
+Group:   Development/Libraries
+
+%description xdg-shell-v6
+Wayland Compositor Library for xdg-shell-v6
+
+%package xdg-shell-v6-devel
+Summary: Wayland Compositor Development package for xdg-shell-v6
+Group:   Development/Libraries
+
+%description xdg-shell-v6-devel
+Wayland Compositor Development package for xdg-shell-v6
+
 %prep
 %setup -q
 cp %{SOURCE1001} .
@@ -62,3 +78,17 @@ ninja -C builddir install
 %{_bindir}/input-device-test
 %{_bindir}/libinput-backend
 %{_bindir}/ds-simple-shm-shell
+
+%files xdg-shell-v6
+%manifest %{name}.manifest
+%defattr(-,root,root,-)
+%license LICENSE
+%{_libdir}/libds-xdg-shell-v6.so.*
+
+%files xdg-shell-v6-devel
+%manifest %{name}.manifest
+%defattr(-,root,root,-)
+%license LICENSE
+%{_includedir}/libds/xdg_shell_v6.h
+%{_libdir}/pkgconfig/libds-xdg-shell-v6.pc
+%{_libdir}/libds-xdg-shell-v6.so
index b59fda2..7cde232 100644 (file)
@@ -99,3 +99,5 @@ pkgconfig.generate(lib_libds,
   name: meson.project_name(),
   description: 'Wayland compositor library',
 )
+
+subdir('xdg_shell_v6')
diff --git a/src/xdg_shell_v6/meson.build b/src/xdg_shell_v6/meson.build
new file mode 100644 (file)
index 0000000..55c9b85
--- /dev/null
@@ -0,0 +1,31 @@
+libds_xdg_shell_v6_files = [
+  'xdg_shell_v6.c',
+  'xdg_surface_v6.c',
+  'xdg_toplevel_v6.c',
+]
+
+libds_xdg_shell_v6_deps = [
+  dep_libds,
+  dependency('xdg-shell-unstable-v6-server', required: true),
+]
+
+lib_libds_xdg_shell_v6 = shared_library('ds-xdg-shell-v6', libds_xdg_shell_v6_files,
+  dependencies: libds_xdg_shell_v6_deps,
+  include_directories: [ common_inc, include_directories('.'), include_directories('..') ],
+  version: meson.project_version(),
+  install: true
+)
+
+deps_libds_xdg_shell_v6 = declare_dependency(
+  link_with: lib_libds_xdg_shell_v6,
+  dependencies: libds_xdg_shell_v6_deps,
+  include_directories: [ common_inc, include_directories('.') ],
+)
+
+pkgconfig = import('pkgconfig')
+pkgconfig.generate(lib_libds_xdg_shell_v6,
+  version: meson.project_version(),
+  filebase: 'libds-xdg-shell-v6',
+  name: 'libds-xdg-shell-v6',
+  description: 'wayland server implementation of xdg-shel-v6 unstable protocol',
+)
diff --git a/src/xdg_shell_v6/xdg_shell_v6.c b/src/xdg_shell_v6/xdg_shell_v6.c
new file mode 100644 (file)
index 0000000..7702d73
--- /dev/null
@@ -0,0 +1,217 @@
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "libds/log.h"
+#include "libds/xdg_shell_v6.h"
+
+#include "xdg_shell_v6.h"
+
+#define XDG_SHELL_V6_BASE_VERSION 1
+#define XDG_SHELL_V6_PING_TIMEOUT  10000
+
+static void xdg_shell_v6_handle_display_destroy(struct wl_listener *listener,
+        void *data);
+static void xdg_shell_v6_bind(struct wl_client *wl_client, void *data,
+        uint32_t verison, uint32_t id);
+
+WL_EXPORT struct ds_xdg_shell_v6 *
+ds_xdg_shell_v6_create(struct wl_display *display)
+{
+    struct ds_xdg_shell_v6 *shell;
+
+    shell = calloc(1, sizeof *shell);
+    if (!shell) {
+        return NULL;
+    }
+
+    shell->ping_timeout = XDG_SHELL_V6_PING_TIMEOUT;
+
+    wl_list_init(&shell->clients);
+
+    shell->global = wl_global_create(display, &zxdg_shell_v6_interface,
+            XDG_SHELL_V6_BASE_VERSION, shell, xdg_shell_v6_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_v6_handle_display_destroy;
+    wl_display_add_destroy_listener(display, &shell->display_destroy);
+
+    ds_inf("Global created: xdg_shell_v6(%p)", shell);
+
+    return shell;
+}
+
+WL_EXPORT void
+ds_xdg_shell_v6_add_destroy_listener(struct ds_xdg_shell_v6 *shell,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&shell->events.destroy, listener);
+}
+
+void
+ds_xdg_shell_v6_add_new_surface_listener(struct ds_xdg_shell_v6 *shell,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&shell->events.new_surface, listener);
+}
+
+static void
+xdg_shell_v6_handle_display_destroy(struct wl_listener *listener, void *data)
+{
+    struct ds_xdg_shell_v6 *shell;
+
+    shell = wl_container_of(listener, shell, display_destroy);
+
+    ds_inf("Global destroy: xdg_shell_v6(%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_v6_handle_destroy(struct wl_client *wl_client,
+        struct wl_resource *resource)
+{
+    struct ds_xdg_client_v6 *client;
+
+    client = wl_resource_get_user_data(resource);
+
+    if (!wl_list_empty(&client->surfaces)) {
+        wl_resource_post_error(client->resource,
+                ZXDG_SHELL_V6_ERROR_DEFUNCT_SURFACES,
+                "xdg_shell_v6 was destroyed before children");
+        return;
+    }
+
+    wl_resource_destroy(resource);
+}
+
+static void
+xdg_shell_v6_handle_create_positioner(struct wl_client *wl_client,
+        struct wl_resource *resource, uint32_t id)
+{
+    // TODO
+}
+
+static void
+xdg_shell_v6_handle_get_xdg_surface_v6(struct wl_client *wl_client,
+        struct wl_resource *resource, uint32_t id,
+        struct wl_resource *surface_resource)
+{
+    struct ds_xdg_client_v6 *client;
+    struct ds_surface *surface;
+
+    client = wl_resource_get_user_data(resource);
+    surface = ds_surface_from_resource(surface_resource);
+    create_xdg_surface_v6(client, surface, id);
+}
+
+static void
+xdg_shell_v6_handle_pong(struct wl_client *wl_client,
+        struct wl_resource *resource, uint32_t serial)
+{
+    struct ds_xdg_client_v6 *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 zxdg_shell_v6_interface xdg_shell_v6_impl =
+{
+    .destroy = xdg_shell_v6_handle_destroy,
+    .create_positioner = xdg_shell_v6_handle_create_positioner,
+    .get_xdg_surface = xdg_shell_v6_handle_get_xdg_surface_v6,
+    .pong = xdg_shell_v6_handle_pong,
+};
+
+static void
+xdg_client_v6_handle_resource_destroy(struct wl_resource *resource)
+{
+    struct ds_xdg_client_v6 *client;
+    struct ds_xdg_surface_v6 *surface, *tmp;
+
+    client = wl_resource_get_user_data(resource);
+
+    wl_list_for_each_safe(surface, tmp, &client->surfaces, link)
+        destroy_xdg_surface_v6(surface);
+
+    if (client->ping_timer != NULL)
+        wl_event_source_remove(client->ping_timer);
+
+    wl_list_remove(&client->link);
+    free(client);
+}
+
+static int
+xdg_client_v6_handle_ping_timeout(void *user_data)
+{
+    struct ds_xdg_client_v6 *client = user_data;
+    struct ds_xdg_surface_v6 *surface;
+
+    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_v6_init_ping_timer(struct ds_xdg_client_v6 *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_v6_handle_ping_timeout, client);
+    if (client->ping_timer == NULL)
+        wl_client_post_no_memory(client->wl_client);
+}
+
+static void
+xdg_shell_v6_bind(struct wl_client *wl_client, void *data, uint32_t version,
+        uint32_t id)
+{
+    struct ds_xdg_shell_v6 *shell = data;
+    struct ds_xdg_client_v6 *client;
+
+    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, &zxdg_shell_v6_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_v6_impl, client,
+            xdg_client_v6_handle_resource_destroy);
+
+    wl_list_insert(&shell->clients, &client->link);
+
+    xdg_client_v6_init_ping_timer(client);
+}
diff --git a/src/xdg_shell_v6/xdg_shell_v6.h b/src/xdg_shell_v6/xdg_shell_v6.h
new file mode 100644 (file)
index 0000000..ee8de5e
--- /dev/null
@@ -0,0 +1,189 @@
+#ifndef DS_XDG_SHELL_V6_H
+#define DS_XDG_SHELL_V6_H
+
+#include <wayland-server.h>
+#include <xdg-shell-unstable-v6-server-protocol.h>
+
+#include "libds/output.h"
+
+#include "surface.h"
+
+enum ds_xdg_surface_v6_role
+{
+    DS_XDG_SURFACE_V6_ROLE_NONE,
+    DS_XDG_SURFACE_V6_ROLE_TOPLEVEL,
+    DS_XDG_SURFACE_V6_ROLE_POPUP,
+};
+
+struct ds_xdg_shell_v6
+{
+    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_v6
+{
+    struct ds_xdg_shell_v6 *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_v6::clients
+
+    uint32_t ping_serial;
+};
+
+struct ds_xdg_toplevle_v6_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_toplevle_v6_configure
+{
+    bool maximized, fullscreen, resizing, activated;
+    uint32_t tiled;
+    uint32_t width, height;
+};
+
+struct ds_xdg_toplevel_v6_requested
+{
+    bool maximized, minimized, fullscreen;
+    struct ds_output *fullscreen_output;
+    struct wl_listener fullscreen_output_destroy;
+};
+
+struct ds_xdg_toplevle_v6
+{
+    struct wl_resource *resource;
+    struct ds_xdg_surface_v6 *base;
+    bool added;
+
+    struct ds_xdg_surface_v6 *parent;
+    struct wl_listener parent_unmap;
+
+    struct ds_xdg_toplevle_v6_state current, pending;
+    struct ds_xdg_toplevle_v6_configure scheduled;
+    struct ds_xdg_toplevel_v6_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_v6_state
+{
+    uint32_t configure_serial;
+    struct {
+        int x, y;
+        int width, height;
+    } geometry;
+};
+
+struct ds_xdg_surface_v6
+{
+    struct ds_xdg_client_v6 *client;
+    struct ds_surface *ds_surface;
+
+    enum ds_xdg_surface_v6_role role;
+
+    union {
+        struct ds_xdg_toplevle_v6 *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_v6_state current, pending;
+
+    struct wl_list link; // ds_xdg_client_v6::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_v6_configure
+{
+    struct ds_xdg_surface_v6 *surface;
+    struct wl_list link;
+    uint32_t serial;
+
+    struct ds_xdg_toplevle_v6_configure *toplevel_configure;
+};
+
+uint32_t
+ds_xdg_surface_v6_schedule_configure(struct ds_xdg_surface_v6 *surface);
+
+struct ds_xdg_surface_v6 *
+create_xdg_surface_v6(struct ds_xdg_client_v6 *client, struct ds_surface *surface,
+        uint32_t id);
+
+void destroy_xdg_surface_v6(struct ds_xdg_surface_v6 *surface);
+
+void
+reset_xdg_surface_v6(struct ds_xdg_surface_v6 *surface);
+
+void
+create_xdg_toplevel_v6(struct ds_xdg_surface_v6 *surface, uint32_t id);
+
+void
+handle_xdg_surface_v6_commit(struct ds_surface *ds_surface);
+
+void
+handle_xdg_surface_v6_toplevel_committed(struct ds_xdg_surface_v6 *surface);
+
+void
+send_xdg_toplevel_v6_configure(struct ds_xdg_surface_v6 *surface,
+        struct ds_xdg_surface_v6_configure *configure);
+
+#endif
diff --git a/src/xdg_shell_v6/xdg_surface_v6.c b/src/xdg_shell_v6/xdg_surface_v6.c
new file mode 100644 (file)
index 0000000..69ab595
--- /dev/null
@@ -0,0 +1,493 @@
+#include <assert.h>
+#include <stdlib.h>
+
+#include "libds/log.h"
+
+#include "xdg_shell_v6.h"
+
+static const struct zxdg_surface_v6_interface xdg_surface_v6_impl;
+
+static void xdg_surface_v6_handle_surface_destroy(struct wl_listener *listener,
+        void *data);
+static void xdg_surface_v6_handle_surface_commit(struct wl_listener *listener,
+        void *data);
+static void xdg_surface_v6_handle_resource_destroy(struct wl_resource *resource);
+static void xdg_surface_v6_configure_destroy(struct ds_xdg_surface_v6_configure *configure);
+static void surface_send_configure(void *user_data);
+
+void
+ds_xdg_surface_v6_add_destroy_listener(struct ds_xdg_surface_v6 *surface,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&surface->events.destroy, listener);
+}
+
+WL_EXPORT void
+ds_xdg_surface_v6_add_map_listener(struct ds_xdg_surface_v6 *surface,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&surface->events.map, listener);
+}
+
+WL_EXPORT void
+ds_xdg_surface_v6_add_unmap_listener(struct ds_xdg_surface_v6 *surface,
+        struct wl_listener *listener)
+{
+    wl_signal_add(&surface->events.unmap, listener);
+}
+
+struct ds_surface *
+ds_xdg_surface_v6_get_surface(struct ds_xdg_surface_v6 *surface)
+{
+    return surface->ds_surface;
+}
+
+struct ds_xdg_surface_v6 *
+create_xdg_surface_v6(struct ds_xdg_client_v6 *client, struct ds_surface *ds_surface,
+        uint32_t id)
+{
+    struct ds_xdg_surface_v6 *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_V6_ROLE_NONE;
+    surface->ds_surface = ds_surface;
+    surface->resource = wl_resource_create(client->wl_client,
+            &zxdg_surface_v6_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,
+                ZXDG_SURFACE_V6_ERROR_UNCONFIGURED_BUFFER,
+                "xdg_surface_v6 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_v6_handle_surface_destroy;
+    ds_surface_add_destroy_listener(ds_surface,
+            &surface->listener.surface_destroy);
+
+    surface->listener.surface_commit.notify =
+        xdg_surface_v6_handle_surface_commit;
+    ds_surface_add_commit_listener(ds_surface,
+            &surface->listener.surface_commit);
+
+    wl_resource_set_implementation(surface->resource, &xdg_surface_v6_impl,
+            surface, xdg_surface_v6_handle_resource_destroy);
+
+    wl_list_insert(&client->surfaces, &surface->link);
+
+    ds_inf("New xdg_surface_v6 %p (res %p)", surface, surface->resource);
+
+    return surface;
+}
+
+void
+unmap_xdg_surface_v6(struct ds_xdg_surface_v6 *surface)
+{
+    struct ds_xdg_surface_v6_configure *configure, *tmp;
+
+    // TODO handle popup
+
+    if (surface->mapped)
+        wl_signal_emit(&surface->events.unmap, surface);
+
+    switch (surface->role) {
+        case DS_XDG_SURFACE_V6_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_V6_ROLE_POPUP:
+            // TODO
+            break;
+        case DS_XDG_SURFACE_V6_ROLE_NONE:
+            assert(false && "not reached");
+    }
+
+    wl_list_for_each_safe(configure, tmp, &surface->configure_list, link)
+        xdg_surface_v6_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_v6(struct ds_xdg_surface_v6 *surface)
+{
+    struct ds_xdg_toplevel_v6_requested *req;
+
+    if (surface->role != DS_XDG_SURFACE_V6_ROLE_NONE)
+        unmap_xdg_surface_v6(surface);
+
+    if (surface->added) {
+        wl_signal_emit(&surface->events.destroy, surface);
+        surface->added = false;
+    }
+
+    switch (surface->role) {
+        case DS_XDG_SURFACE_V6_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_V6_ROLE_POPUP:
+            // TODO
+            break;
+        case DS_XDG_SURFACE_V6_ROLE_NONE:
+            // This space is intentionally left blank
+            break;
+    }
+
+    surface->role = DS_XDG_SURFACE_V6_ROLE_NONE;
+}
+
+void
+destroy_xdg_surface_v6(struct ds_xdg_surface_v6 *surface)
+{
+    reset_xdg_surface_v6(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_v6_commit(struct ds_surface *ds_surface)
+{
+    struct ds_xdg_surface_v6 *surface;
+
+    surface = ds_surface_get_role_data(ds_surface);
+    surface->current = surface->pending;
+
+    switch (surface->role) {
+        case DS_XDG_SURFACE_V6_ROLE_NONE:
+            // inert toplevel or popup
+            break;
+        case DS_XDG_SURFACE_V6_ROLE_TOPLEVEL:
+            handle_xdg_surface_v6_toplevel_committed(surface);
+            // TODO
+            break;
+        case DS_XDG_SURFACE_V6_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_v6_precommit(struct ds_surface *ds_surface)
+{
+    struct ds_xdg_surface_v6 *surface;
+
+    surface = ds_surface_get_role_data(ds_surface);
+
+    // TODO
+    (void)surface;
+}
+
+uint32_t
+ds_xdg_surface_v6_schedule_configure(struct ds_xdg_surface_v6 *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_v6_ping(struct ds_xdg_surface_v6 *surface)
+{
+    struct wl_display *display;
+
+    display = wl_client_get_display(surface->client->wl_client);
+
+    surface->client->ping_serial = wl_display_next_serial(display);
+    zxdg_shell_v6_send_ping(surface->resource, surface->client->ping_serial);
+}
+
+static void
+xdg_surface_v6_handle_surface_destroy(struct wl_listener *listener, void *data)
+{
+    struct ds_xdg_surface_v6 *surface;
+
+    surface = wl_container_of(listener, surface, listener.surface_destroy);
+    destroy_xdg_surface_v6(surface);
+}
+
+static void
+xdg_surface_v6_handle_surface_commit(struct wl_listener *listener, void *data)
+{
+    struct ds_xdg_surface_v6 *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,
+                ZXDG_SURFACE_V6_ERROR_UNCONFIGURED_BUFFER,
+                "xdg_surface_v6 has never been configured");
+        return;
+    }
+
+    if (!ds_surface_get_role(surface->ds_surface)) {
+        wl_resource_post_error(surface->resource,
+                ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED,
+                "xdg_surface_v6 must have a role");
+        return;
+    }
+}
+
+static void
+xdg_surface_v6_handle_resource_destroy(struct wl_resource *resource)
+{
+    struct ds_xdg_surface_v6 *surface;
+
+    surface = wl_resource_get_user_data(resource);
+    if (!surface)
+        return;
+
+    destroy_xdg_surface_v6(surface);
+}
+
+static void
+xdg_surface_v6_configure_destroy(struct ds_xdg_surface_v6_configure *configure)
+{
+    wl_list_remove(&configure->link);
+    free(configure->toplevel_configure);
+    free(configure);
+}
+
+static void
+xdg_surface_v6_handle_destroy(struct wl_client *client,
+        struct wl_resource *resource)
+{
+    struct ds_xdg_surface_v6 *surface;
+
+    surface = wl_resource_get_user_data(resource);
+
+    if (surface->role != DS_XDG_SURFACE_V6_ROLE_NONE) {
+        ds_err("Tried to destroy an xdg_surface_v6 before its role object");
+        return;
+    }
+
+    wl_resource_destroy(resource);
+}
+
+static void
+xdg_surface_v6_handle_get_toplevel(struct wl_client *client,
+        struct wl_resource *resource, uint32_t id)
+{
+    struct ds_xdg_surface_v6 *surface;
+
+    surface = wl_resource_get_user_data(resource);
+    create_xdg_toplevel_v6(surface, id);
+}
+
+static void
+xdg_surface_v6_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_v6 *surface;
+
+    surface = wl_resource_get_user_data(resource);
+
+    // TODO
+    (void)surface;
+}
+
+static void
+xdg_surface_v6_handle_ack_configure(struct wl_client *client,
+        struct wl_resource *resource, uint32_t serial)
+{
+    struct ds_xdg_surface_v6 *surface;
+
+    surface = wl_resource_get_user_data(resource);
+
+    if (surface->role == DS_XDG_SURFACE_V6_ROLE_NONE) {
+        wl_resource_post_error(surface->resource,
+                ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED,
+                "xdg_surface_v6 must have a role");
+        return;
+    }
+
+    bool found = false;
+    struct ds_xdg_surface_v6_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,
+                ZXDG_SHELL_V6_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_v6_configure_destroy(configure);
+    }
+
+    switch (surface->role) {
+        case DS_XDG_SURFACE_V6_ROLE_NONE:
+            assert(0 && "not reached");
+            break;
+        case DS_XDG_SURFACE_V6_ROLE_TOPLEVEL:
+            // TODO
+            break;
+        case DS_XDG_SURFACE_V6_ROLE_POPUP:
+            break;
+    }
+
+    surface->configured = true;
+    surface->pending.configure_serial = serial;
+
+    wl_signal_emit(&surface->events.ack_configure, configure);
+    xdg_surface_v6_configure_destroy(configure);
+}
+
+static void
+xdg_surface_v6_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_v6 *surface;
+
+    surface = wl_resource_get_user_data(resource);
+
+    if (surface->role == DS_XDG_SURFACE_V6_ROLE_NONE) {
+        wl_resource_post_error(surface->resource,
+                ZXDG_SURFACE_V6_ERROR_NOT_CONSTRUCTED,
+                "xdg_surface_v6 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_v6 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 zxdg_surface_v6_interface xdg_surface_v6_impl =
+{
+    .destroy = xdg_surface_v6_handle_destroy,
+    .get_toplevel = xdg_surface_v6_handle_get_toplevel,
+    .get_popup = xdg_surface_v6_handle_get_popup,
+    .ack_configure = xdg_surface_v6_handle_ack_configure,
+    .set_window_geometry = xdg_surface_v6_handle_set_window_geometry,
+};
+
+static void
+surface_send_configure(void *user_data)
+{
+    struct ds_xdg_surface_v6 *surface;
+    struct ds_xdg_surface_v6_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_V6_ROLE_NONE:
+            assert(0 && "not reached");
+            break;
+        case DS_XDG_SURFACE_V6_ROLE_TOPLEVEL:
+            send_xdg_toplevel_v6_configure(surface, configure);
+            break;
+        case DS_XDG_SURFACE_V6_ROLE_POPUP:
+            break;
+    }
+
+    wl_signal_emit(&surface->events.configure, configure);
+
+    zxdg_surface_v6_send_configure(surface->resource, configure->serial);
+}
diff --git a/src/xdg_shell_v6/xdg_toplevel_v6.c b/src/xdg_shell_v6/xdg_toplevel_v6.c
new file mode 100644 (file)
index 0000000..463b65a
--- /dev/null
@@ -0,0 +1,329 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "xdg_shell_v6.h"
+
+static const struct ds_surface_role xdg_toplevel_v6_surface_role =
+{
+    .name = "xdg_toplevel_v6",
+    .commit = handle_xdg_surface_v6_commit,
+};
+
+static const struct zxdg_toplevel_v6_interface xdg_toplevel_v6_impl;
+
+static void xdg_toplevel_v6_handle_resource_destroy(struct wl_resource *resource);
+
+void
+create_xdg_toplevel_v6(struct ds_xdg_surface_v6 *surface, uint32_t id)
+{
+    if (!ds_surface_set_role(surface->ds_surface, &xdg_toplevel_v6_surface_role,
+                surface, surface->resource, ZXDG_SHELL_V6_ERROR_ROLE))
+        return;
+
+    if (surface->role != DS_XDG_SURFACE_V6_ROLE_NONE) {
+        wl_resource_post_error(surface->resource,
+                ZXDG_SURFACE_V6_ERROR_ALREADY_CONSTRUCTED,
+                "xdg_surface_v6 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, &zxdg_toplevel_v6_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_v6_impl, surface,
+            xdg_toplevel_v6_handle_resource_destroy);
+
+    surface->role = DS_XDG_SURFACE_V6_ROLE_TOPLEVEL;
+}
+
+void
+handle_xdg_surface_v6_toplevel_committed(struct ds_xdg_surface_v6 *surface)
+{
+    if (!surface->toplevel->added) {
+        ds_xdg_surface_v6_schedule_configure(surface);
+        surface->toplevel->added = true;
+        return;
+    }
+
+    surface->toplevel->current = surface->toplevel->pending;
+}
+
+void
+send_xdg_toplevel_v6_configure(struct ds_xdg_surface_v6 *surface,
+        struct ds_xdg_surface_v6_configure *configure)
+{
+    struct wl_array states;
+    uint32_t width, height;
+
+    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 = ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED;
+    }
+
+    if (surface->toplevel->scheduled.fullscreen) {
+        uint32_t *s = wl_array_add(&states, sizeof(uint32_t));
+        if (!s)
+            goto error_out;
+        *s = ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN;
+    }
+
+    if (surface->toplevel->scheduled.resizing) {
+        uint32_t *s = wl_array_add(&states, sizeof(uint32_t));
+        if (!s)
+            goto error_out;
+        *s = ZXDG_TOPLEVEL_V6_STATE_RESIZING;
+    }
+
+    if (surface->toplevel->scheduled.activated) {
+        uint32_t *s = wl_array_add(&states, sizeof(uint32_t));
+        if (!s)
+            goto error_out;
+        *s = ZXDG_TOPLEVEL_V6_STATE_ACTIVATED;
+    }
+
+    if (surface->toplevel->scheduled.tiled) {
+        ;;;
+        // TODO
+    }
+
+    width = surface->toplevel->scheduled.width;
+    height = surface->toplevel->scheduled.height;
+
+    zxdg_toplevel_v6_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_v6(struct ds_xdg_surface_v6 *xdg_surface_v6)
+{
+    reset_xdg_surface_v6(xdg_surface_v6);
+}
+
+static void
+xdg_toplevel_v6_handle_destroy(struct wl_client *client,
+        struct wl_resource *resource)
+{
+    wl_resource_destroy(resource);
+}
+
+static void
+xdg_toplevel_v6_handle_set_parent(struct wl_client *client,
+        struct wl_resource *resource, struct wl_resource *parent_resource)
+{
+    // TODO
+}
+
+static void
+xdg_toplevel_v6_handle_set_title(struct wl_client *client,
+        struct wl_resource *resource, const char *title)
+{
+    struct ds_xdg_surface_v6 *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_v6_handle_set_app_id(struct wl_client *client,
+        struct wl_resource *resource, const char *app_id)
+{
+    struct ds_xdg_surface_v6 *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_v6_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_v6_handle_move(struct wl_client *client,
+        struct wl_resource *resource, struct wl_resource *seat_resource,
+        uint32_t serial)
+{
+    // TODO
+}
+
+static void
+xdg_toplevel_v6_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_v6_handle_set_max_size(struct wl_client *client,
+        struct wl_resource *resource, int32_t width, int32_t height)
+{
+    struct ds_xdg_surface_v6 *surface;
+
+    surface = wl_resource_get_user_data(resource);
+    surface->toplevel->pending.max_width = width;
+    surface->toplevel->pending.max_height = height;
+}
+
+static void
+xdg_toplevel_v6_handle_set_min_size(struct wl_client *client,
+        struct wl_resource *resource, int32_t width, int32_t height)
+{
+    struct ds_xdg_surface_v6 *surface;
+
+    surface = wl_resource_get_user_data(resource);
+    surface->toplevel->pending.min_width = width;
+    surface->toplevel->pending.min_height = height;
+}
+
+static void
+xdg_toplevel_v6_handle_set_maximized(struct wl_client *client,
+        struct wl_resource *resource)
+{
+    struct ds_xdg_surface_v6 *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_v6_schedule_configure(surface);
+}
+
+static void
+xdg_toplevel_v6_handle_unset_maximized(struct wl_client *client,
+        struct wl_resource *resource)
+{
+    struct ds_xdg_surface_v6 *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_v6_schedule_configure(surface);
+}
+
+static void
+xdg_toplevel_v6_handle_set_fullscreen(struct wl_client *client,
+        struct wl_resource *resource, struct wl_resource  *output_resource)
+{
+    // TODO
+}
+
+static void
+xdg_toplevel_v6_handle_unset_fullscreen(struct wl_client *client,
+        struct wl_resource *resource)
+{
+    // TODO
+}
+
+static void
+xdg_toplevel_v6_handle_set_minimized(struct wl_client *client,
+        struct wl_resource *resource)
+{
+    struct ds_xdg_surface_v6 *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 zxdg_toplevel_v6_interface xdg_toplevel_v6_impl =
+{
+    xdg_toplevel_v6_handle_destroy,
+    xdg_toplevel_v6_handle_set_parent,
+    xdg_toplevel_v6_handle_set_title,
+    xdg_toplevel_v6_handle_set_app_id,
+    xdg_toplevel_v6_handle_show_window_menu,
+    xdg_toplevel_v6_handle_move,
+    xdg_toplevel_v6_handle_resize,
+    xdg_toplevel_v6_handle_set_max_size,
+    xdg_toplevel_v6_handle_set_min_size,
+    xdg_toplevel_v6_handle_set_maximized,
+    xdg_toplevel_v6_handle_unset_maximized,
+    xdg_toplevel_v6_handle_set_fullscreen,
+    xdg_toplevel_v6_handle_unset_fullscreen,
+    xdg_toplevel_v6_handle_set_minimized,
+};
+
+static void
+xdg_toplevel_v6_handle_resource_destroy(struct wl_resource *resource)
+{
+    struct ds_xdg_surface_v6 *surface;
+
+    surface = wl_resource_get_user_data(resource);
+    if (!surface)
+        return;
+
+    destroy_xdg_toplevel_v6(surface);
+}