From 1160a8390334c6f111b55329753a74cf50e1ff91 Mon Sep 17 00:00:00 2001 From: Scott Anderson Date: Tue, 30 Apr 2019 20:23:16 +1200 Subject: [PATCH] Add wlr_subsurface for new wlr_surface API --- include/wlr/types/wlr_compositor.h | 67 +++- include/wlr/types/wlr_subcompositor.h | 52 +++ types/wlr_compositor.c | 168 +++------ types/wlr_subcompositor.c | 476 ++++++++++++++++++++++++++ 4 files changed, 641 insertions(+), 122 deletions(-) create mode 100644 include/wlr/types/wlr_subcompositor.h create mode 100644 types/wlr_subcompositor.c diff --git a/include/wlr/types/wlr_compositor.h b/include/wlr/types/wlr_compositor.h index 0cff2ba04..adb97762c 100644 --- a/include/wlr/types/wlr_compositor.h +++ b/include/wlr/types/wlr_compositor.h @@ -16,18 +16,13 @@ struct wlr_commit; struct wlr_output; struct wlr_surface; -struct wlr_subcompositor { - struct wl_global *global; -}; - struct wlr_compositor { + struct wl_display *display; struct wl_global *global; struct wlr_renderer *renderer; uint32_t ids; - struct wlr_subcompositor subcompositor; - struct { struct wl_signal new_surface; struct wl_signal new_surface_2; @@ -41,17 +36,63 @@ struct wlr_surface_2 { struct wl_resource *resource; struct wlr_compositor *compositor; + /* + * This should contain the name of the protocol to prevent conflicts. + * e.g. "xdg_toplevel". + * Also, it should point to statically allocated memory. + */ + const char *role_name; + void *role_data; + + struct wl_list committed; + struct wl_list frame_callbacks; + struct { struct wl_signal commit; struct wl_signal destroy; } events; struct wlr_commit *pending; - struct wl_list committed; - - struct wl_list frame_callbacks; }; +/* + * wlr_commit + * + * A wlr_commit is a description for a "coherent presentation" of a surface's + * contents, or could also be thought of as a "snaphot" of the surface at + * commit time. It should contain all of the information to correctly display + * a surface. + * + * A wlr_commit can be extended to add extra state for each commit to be + * managed properly by the commit queue. Every wayland protocol that has state + * synchronized by wl_surface.commit should use this extension mechanism. + * + * A commit can exist in 3 states: + * - Pending: The client has not commited the contents yet. + * - Committed: The client has commited the contents, but something is + * inhibiting the presentation of this surface. + * - Complete: The commit is ready to be presented. + * + * For a surface, there will always be exactly 1 pending commit for a surface. + * see wlr_surface_get_pending + * + * Excluding the time when a before the first complete commit is ready, there + * will always be at least 1 complete commit for a surface. The latest complete + * commit is called the current commit. + * see wlr_surface_get_commit + * + * wlr_commits are reference counted. If you wish to save the contents of a + * commit for future use, simply do not unreference the commit. A referenced + * wlr_commit will even persist after the surface is destroyed, which can be + * useful for fade-out animations. Any extensions to wlr_surface should be + * prepared for the associated wlr_surface to not be available. + * + * Upon commit destruction, several actions may be performed on objects which + * will affect clients. For example, wl_buffer.release may be called for the + * attached buffer. Holding onto too many commits for too long may cause + * resource starvation in clients, so care should be taken to unreference + * commits as soon as they're no longer needed. + */ struct wlr_commit { struct wl_list link; struct wlr_surface_2 *surface; @@ -103,15 +144,23 @@ struct wlr_surface_2 *wlr_surface_from_resource_2(struct wl_resource *resource); struct wlr_commit *wlr_surface_get_commit(struct wlr_surface_2 *surface); struct wlr_commit *wlr_surface_get_pending(struct wlr_surface_2 *surface); +struct wlr_commit *wlr_surface_get_latest(struct wlr_surface_2 *surface); void wlr_surface_send_enter_2(struct wlr_surface_2 *surf, struct wlr_output *output); void wlr_surface_send_leave_2(struct wlr_surface_2 *surf, struct wlr_output *output); +struct wlr_commit *wlr_commit_ref(struct wlr_commit *commit); void wlr_commit_unref(struct wlr_commit *commit); +bool wlr_commit_is_complete(struct wlr_commit *c); + void wlr_commit_inhibit(struct wlr_commit *commit); void wlr_commit_uninhibit(struct wlr_commit *commit); +bool wlr_surface_set_role_2(struct wlr_surface_2 *surf, const char *name); +void wlr_commit_set(struct wlr_commit *commit, uint32_t id, void *data); +void *wlr_commit_get(struct wlr_commit *commit, uint32_t id); + bool wlr_surface_is_subsurface(struct wlr_surface *surface); /** diff --git a/include/wlr/types/wlr_subcompositor.h b/include/wlr/types/wlr_subcompositor.h new file mode 100644 index 000000000..9f65f5cf4 --- /dev/null +++ b/include/wlr/types/wlr_subcompositor.h @@ -0,0 +1,52 @@ +#ifndef WLR_TYPES_WLR_SUBCOMPOSITOR_H +#define WLR_TYPES_WLR_SUBCOMPOSITOR_H + +#include +#include +#include + +struct wlr_compositor; +struct wlr_commit; +struct wlr_surface_2; + +struct wlr_subcompositor { + struct wl_global *global; + struct wlr_compositor *compositor; + + uint32_t root_id; + + struct { + struct wl_signal new_subsurface; + } events; + + struct wl_listener new_state; + struct wl_listener display_destroy; +}; + +struct wlr_subsurface { + struct wl_resource *resource; + struct wlr_surface_2 *surface; + struct wlr_surface_2 *parent; + + bool synchronized; + + struct { + struct wl_signal destroy; + } events; + + struct wl_listener surface_destroy; + struct wl_listener parent_destroy; +}; + +struct wlr_subcompositor *wlr_subcompositor_create(struct wlr_compositor *compositor); + +struct wlr_subsurface *wlr_surface_to_subsurface(struct wlr_surface_2 *surf); +struct wlr_surface_2 *wlr_surface_get_root_surface(struct wlr_surface_2 *surf); + +typedef void (*wlr_subsurface_iter_t)(void *userdata, + struct wlr_commit *commit, int32_t x, int32_t y); + +void wlr_commit_for_each_subsurface(struct wlr_subcompositor *sc, + struct wlr_commit *commit, wlr_subsurface_iter_t func, void *userdata); + +#endif diff --git a/types/wlr_compositor.c b/types/wlr_compositor.c index 0c121f8e8..a0d3b763f 100644 --- a/types/wlr_compositor.c +++ b/types/wlr_compositor.c @@ -9,97 +9,6 @@ #include "util/signal.h" #define COMPOSITOR_VERSION 4 -#define SUBCOMPOSITOR_VERSION 1 - -extern const struct wlr_surface_role subsurface_role; - -bool wlr_surface_is_subsurface(struct wlr_surface *surface) { - return surface->role == &subsurface_role; -} - -struct wlr_subsurface *wlr_subsurface_from_wlr_surface( - struct wlr_surface *surface) { - assert(wlr_surface_is_subsurface(surface)); - return (struct wlr_subsurface *)surface->role_data; -} - -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 wlr_surface *surface = wlr_surface_from_resource(surface_resource); - struct wlr_surface *parent = wlr_surface_from_resource(parent_resource); - - static const char msg[] = "get_subsurface: wl_subsurface@"; - - if (surface == parent) { - wl_resource_post_error(resource, - WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, - "%s%d: wl_surface@%d cannot be its own parent", - msg, id, wl_resource_get_id(surface_resource)); - return; - } - - if (wlr_surface_is_subsurface(surface) && - wlr_subsurface_from_wlr_surface(surface) != NULL) { - wl_resource_post_error(resource, - WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, - "%s%d: wl_surface@%d is already a sub-surface", - msg, id, wl_resource_get_id(surface_resource)); - return; - } - - if (wlr_surface_get_root_surface(parent) == surface) { - wl_resource_post_error(resource, - WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, - "%s%d: wl_surface@%d is an ancestor of parent", - msg, id, wl_resource_get_id(surface_resource)); - return; - } - - if (!wlr_surface_set_role(surface, &subsurface_role, NULL, - resource, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE)) { - return; - } - - wlr_subsurface_create(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) { - struct wl_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, NULL, NULL); -} - -static void subcompositor_init(struct wlr_subcompositor *sc, - struct wl_display *display) { - sc->global = wl_global_create(display, &wl_subcompositor_interface, - SUBCOMPOSITOR_VERSION, NULL, subcompositor_bind); - if (sc->global == NULL) { - wlr_log_errno(WLR_ERROR, "Failed to create subcompositor global"); - return; - } -} - -static void subcompositor_finish(struct wlr_subcompositor *sc) { - wl_global_destroy(sc->global); -} static const struct wl_region_interface region_impl; @@ -153,17 +62,18 @@ static struct wlr_commit *commit_create(struct wlr_surface_2 *surf) { } // Prevent a zero allocation - size_t len = comp->ids ? comp->ids : 1; - - c->state = calloc(len, sizeof(*c->state)); + c->state_len = comp->ids ? comp->ids : 1; + c->state = calloc(c->state_len, sizeof(*c->state)); if (!c->state) { wlr_log_errno(WLR_ERROR, "Allocation failed"); free(c); return NULL; } - c->state_len = len; c->surface = surf; + wl_signal_init(&c->events.commit); + wl_signal_init(&c->events.complete); + wl_signal_init(&c->events.destroy); pixman_region32_init(&c->surface_damage); wl_list_init(&c->frame_callbacks); @@ -188,7 +98,7 @@ static void commit_destroy(struct wlr_commit *c) { free(c); } -static bool commit_is_complete(struct wlr_commit *c) { +bool wlr_commit_is_complete(struct wlr_commit *c) { return c->committed && c->inhibit == 0; } @@ -202,14 +112,13 @@ static bool commit_is_latest(struct wlr_commit *c) { return true; } - if (commit_is_complete(iter)) { + if (wlr_commit_is_complete(iter)) { return false; } } // You shouldn't be able to get here. - assert(0); - return false; + abort(); } static void surface_prune_commits(struct wlr_surface_2 *surf) { @@ -217,15 +126,16 @@ static void surface_prune_commits(struct wlr_surface_2 *surf) { struct wlr_commit *iter, *tmp; wl_list_for_each_safe(iter, tmp, &surf->committed, link) { if (!complete) { - complete = commit_is_complete(iter); + complete = wlr_commit_is_complete(iter); } else if (iter->ref_cnt == 0) { + wl_list_remove(&iter->link); commit_destroy(iter); } } } void wlr_commit_inhibit(struct wlr_commit *commit) { - assert(commit && !commit_is_complete(commit)); + assert(commit && !wlr_commit_is_complete(commit)); ++commit->inhibit; } @@ -233,7 +143,7 @@ void wlr_commit_uninhibit(struct wlr_commit *commit) { assert(commit && commit->inhibit > 0); --commit->inhibit; - if (commit_is_complete(commit)) { + if (wlr_commit_is_complete(commit)) { wlr_signal_emit_safe(&commit->events.complete, commit); if (commit_is_latest(commit)) { @@ -435,7 +345,7 @@ static void surface_commit(struct wl_client *client, struct wl_resource *res) { surface_prune_commits(surf); - if (commit_is_complete(commit)) { + if (wlr_commit_is_complete(commit)) { wlr_signal_emit_safe(&commit->events.complete, commit); wlr_signal_emit_safe(&surf->events.commit, surf); } @@ -455,7 +365,7 @@ static struct wl_surface_interface surface_impl = { }; struct wlr_surface_2 *wlr_surface_from_resource_2(struct wl_resource *resource) { - assert(wl_resource_instance_of(resource, &wl_compositor_interface, + assert(wl_resource_instance_of(resource, &wl_surface_interface, &surface_impl)); return wl_resource_get_user_data(resource); } @@ -466,10 +376,14 @@ static void surface_resource_destroy(struct wl_resource *resource) { wlr_signal_emit_safe(&surf->events.destroy, surf); struct wlr_commit *iter, *tmp; - wl_list_for_each_reverse_safe(iter, tmp, &surf->committed, link) { - commit_destroy(iter); + wl_list_for_each_safe(iter, tmp, &surf->committed, link) { + if (iter->ref_cnt == 0) { + commit_destroy(iter); + } } + commit_destroy(surf->pending); + free(surf); } @@ -500,8 +414,11 @@ static void compositor_create_surface(struct wl_client *client, wl_resource_set_implementation(surf->resource, &surface_impl, surf, surface_resource_destroy); - wl_signal_init(&surf->events.destroy); + surf->compositor = compositor; wl_list_init(&surf->committed); + wl_list_init(&surf->frame_callbacks); + wl_signal_init(&surf->events.commit); + wl_signal_init(&surf->events.destroy); surf->pending = commit_create(surf); if (!surf->pending) { @@ -571,10 +488,7 @@ static void compositor_bind(struct wl_client *client, void *data, static void compositor_display_destroy(struct wl_listener *listener, void *data) { struct wlr_compositor *comp = wl_container_of(listener, comp, display_destroy); - subcompositor_finish(&comp->subcompositor); - wl_list_remove(&comp->display_destroy.link); wl_global_destroy(comp->global); - free(comp); } @@ -586,6 +500,7 @@ struct wlr_compositor *wlr_compositor_create(struct wl_display *display, return NULL; } + comp->display = display; comp->global = wl_global_create(display, &wl_compositor_interface, COMPOSITOR_VERSION, comp, compositor_bind); if (!comp->global) { @@ -599,8 +514,6 @@ struct wlr_compositor *wlr_compositor_create(struct wl_display *display, wl_signal_init(&comp->events.new_surface_2); wl_signal_init(&comp->events.new_state); - subcompositor_init(&comp->subcompositor, display); - comp->display_destroy.notify = compositor_display_destroy; wl_display_add_destroy_listener(display, &comp->display_destroy); @@ -617,7 +530,7 @@ struct wlr_commit *wlr_surface_get_commit(struct wlr_surface_2 *surf) { struct wlr_commit *iter; wl_list_for_each(iter, &surf->committed, link) { - if (commit_is_complete(iter)) { + if (wlr_commit_is_complete(iter)) { ++iter->ref_cnt; return iter; } @@ -630,6 +543,16 @@ struct wlr_commit *wlr_surface_get_pending(struct wlr_surface_2 *surf) { return surf->pending; } +struct wlr_commit *wlr_surface_get_latest(struct wlr_surface_2 *surf) { + if (wl_list_empty(&surf->committed)) { + return NULL; + } + + struct wlr_commit *first = wl_container_of(surf->committed.next, first, link); + ++first->ref_cnt; + return first; +} + void wlr_surface_send_enter_2(struct wlr_surface_2 *surf, struct wlr_output *output) { struct wl_client *client = wl_resource_get_client(surf->resource); struct wl_resource *iter; @@ -663,9 +586,28 @@ void wlr_surface_send_frame_done_2(struct wlr_surface_2 *surf, } } +struct wlr_commit *wlr_commit_ref(struct wlr_commit *commit) { + assert(commit); + ++commit->ref_cnt; + + return commit; +} + void wlr_commit_unref(struct wlr_commit *commit) { assert(commit && commit->ref_cnt > 0); --commit->ref_cnt; surface_prune_commits(commit->surface); } + +bool wlr_surface_set_role_2(struct wlr_surface_2 *surf, const char *name) { + assert(surf && name); + + /* Already has role */ + if (surf->role_name && strcmp(surf->role_name, name) != 0) { + return false; + } + + surf->role_name = name; + return true; +} diff --git a/types/wlr_subcompositor.c b/types/wlr_subcompositor.c new file mode 100644 index 000000000..4b4327e78 --- /dev/null +++ b/types/wlr_subcompositor.c @@ -0,0 +1,476 @@ +#include +#include +#include +#include + +#include +#include +#include +#include "util/signal.h" + +#include + +#define SUBCOMPOSITOR_VERSION 1 + +struct subsurface_state { + struct wl_list link; + + struct wlr_subsurface *subsurface; + struct wlr_commit *commit; // Does have ref count + struct wlr_commit *parent; // Does NOT have ref count + + bool synchronized; + bool inhibiting_parent; + + int32_t x; + int32_t y; + + struct wl_listener subsurface_destroy; + struct wl_listener commit_complete; +}; + +struct subsurface_root { + /* This list is in stacking order. + * The first element is the bottom of the stack (i.e. drawn first). + * The last element is the top of the stack (i.e. drawn last). + */ + struct wl_list subsurfaces; // subsurface_state.link + + /* This represents the root surface. + * - It will always appear in the above subsurfaces list + * - subsurface will always be NULL + * - commit will always be NULL + * - (x, y) will always be (0, 0) + */ + struct subsurface_state root; + + struct wl_listener commit_destroy; + struct wl_listener commit_committed; +}; + +static const struct wl_subsurface_interface subsurface_impl; + +static struct wlr_subsurface *subsurface_from_resource(struct wl_resource *res) { + assert(wl_resource_instance_of(res, &wl_subsurface_interface, + &subsurface_impl)); + return wl_resource_get_user_data(res); +} + +static void subsurface_destroy(struct wl_client *client, struct wl_resource *res) { + wl_resource_destroy(res); +} + +static void subsurface_set_position(struct wl_client *client, + struct wl_resource *res, int32_t x, int32_t y) { + struct wlr_subsurface *ss = subsurface_from_resource(res); + struct wlr_commit *commit = wlr_surface_get_pending(ss->parent); +} + +static void subsurface_place_above(struct wl_client *client, + struct wl_resource *res, struct wl_resource *sibling_res) { + struct wlr_subsurface *ss = subsurface_from_resource(res); + if (!ss) { + return; + } +} + +static void subsurface_place_below(struct wl_client *client, + struct wl_resource *res, struct wl_resource *sibling_res) { + struct wlr_subsurface *ss = subsurface_from_resource(res); + if (!ss) { + return; + } +} + +static void subsurface_set_sync(struct wl_client *client, struct wl_resource *res) { + struct wlr_subsurface *ss = subsurface_from_resource(res); + if (!ss) { + return; + } + + ss->synchronized = true; +} + +static void subsurface_set_desync(struct wl_client *client, struct wl_resource *res) { + struct wlr_subsurface *ss = subsurface_from_resource(res); + if (!ss) { + return; + } + + ss->synchronized = false; +} + +static const struct wl_subsurface_interface subsurface_impl = { + .destroy = subsurface_destroy, + .set_position = subsurface_set_position, + .place_above = subsurface_place_above, + .place_below = subsurface_place_below, + .set_sync = subsurface_set_sync, + .set_desync = subsurface_set_desync, +}; + +static void subcompositor_destroy(struct wl_client *client, + struct wl_resource *res) { + wl_resource_destroy(res); +} + +static const struct wl_subcompositor_interface subcompositor_impl; + +static struct wlr_subcompositor *subcompositor_from_resource(struct wl_resource *res) { + assert(wl_resource_instance_of(res, &wl_subcompositor_interface, + &subcompositor_impl)); + return wl_resource_get_user_data(res); +} + +static void wlr_subsurface_destroy(struct wlr_subsurface *ss) +{ + wlr_signal_emit_safe(&ss->events.destroy, ss); + + ss->surface->role_data = NULL; + wl_list_remove(&ss->surface_destroy.link); + if (ss->parent) { + wl_list_remove(&ss->parent_destroy.link); + } + free(ss); +} + +static void subsurface_resource_destroy(struct wl_resource *res) { + struct wlr_subsurface *ss = subsurface_from_resource(res); + + if (ss) { + wlr_subsurface_destroy(ss); + } +} + +static void subsurface_surface_destroy(struct wl_listener *listener, void *data) { + struct wlr_subsurface *ss = wl_container_of(listener, ss, surface_destroy); + + /* Make resource inert */ + wl_resource_set_user_data(ss->resource, NULL); + + wlr_subsurface_destroy(ss); +} + +static void subsurface_parent_destroy(struct wl_listener *listener, void *data) { + struct wlr_subsurface *ss = wl_container_of(listener, ss, parent_destroy); + + wl_list_remove(&ss->parent_destroy.link); + ss->parent = NULL; +} + +static void state_subsurface_destroy(struct wl_listener *listener, void *data) { + struct subsurface_state *st = wl_container_of(listener, st, subsurface_destroy); + + wl_list_remove(&st->subsurface_destroy.link); + st->subsurface = NULL; + + /* We don't have a surface or saved commit, so this is now useless */ + if (!st->commit) { + wl_list_remove(&st->link); + free(st); + } +} + +static void subcompositor_get_subsurface(struct wl_client *client, + struct wl_resource *sc_res, uint32_t id, + struct wl_resource *surface_res, struct wl_resource *parent_res) { + struct wlr_subcompositor *sc = subcompositor_from_resource(sc_res); + struct wlr_surface_2 *surface = wlr_surface_from_resource_2(surface_res); + struct wlr_surface_2 *parent = wlr_surface_from_resource_2(parent_res); + + /* Check for protocol violations */ + + if (surface == parent) { + wl_resource_post_error(sc_res, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, + "surface cannot be its own parent"); + return; + } + + if (!wlr_surface_set_role_2(surface, "wl_subsurface")) { + wl_resource_post_error(sc_res, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, + "surface already has role"); + return; + } + + if (surface->role_data != NULL) { + wl_resource_post_error(sc_res, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, + "surface is already a sub-surface"); + return; + } + + if (wlr_surface_get_root_surface(parent) == surface) { + wl_resource_post_error(sc_res, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, + "surface is an ancestor of parent"); + return; + } + + /* Add role state for surface */ + + struct wlr_subsurface *ss = calloc(1, sizeof(*ss)); + if (!ss) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto error_post; + } + + ss->resource = wl_resource_create(client, &wl_subsurface_interface, + wl_resource_get_version(sc_res), id); + if (!ss->resource) { + wlr_log_errno(WLR_ERROR, "Failed to create subsurface resource"); + goto error_ss; + } + + ss->surface = surface; + ss->parent = parent; + ss->synchronized = true; + wl_signal_init(&ss->events.destroy); + ss->surface_destroy.notify = subsurface_surface_destroy; + wl_signal_add(&surface->events.destroy, &ss->surface_destroy); + ss->parent_destroy.notify = subsurface_parent_destroy; + wl_signal_add(&parent->events.destroy, &ss->parent_destroy); + + surface->role_data = ss; + + /* Insert into parent's sub-surface tree */ + + struct wlr_commit *parent_commit = wlr_surface_get_pending(parent); + struct subsurface_root *root = wlr_commit_get(parent_commit, sc->root_id); + assert(root); + + struct subsurface_state *st = calloc(1, sizeof(*st)); + if (!st) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto error_res; + } + + st->subsurface = ss; + st->subsurface_destroy.notify = state_subsurface_destroy; + wl_signal_add(&ss->events.destroy, &st->subsurface_destroy); + wl_list_insert(root->subsurfaces.prev, &st->link); + + wl_resource_set_implementation(ss->resource, &subsurface_impl, + ss, subsurface_resource_destroy); + return; + +error_res: + wl_resource_destroy(ss->resource); + wl_list_remove(&ss->surface_destroy.link); + wl_list_remove(&ss->parent_destroy.link); +error_ss: + free(ss); +error_post: + wl_resource_post_no_memory(sc_res); +} + +static const struct wl_subcompositor_interface subcompositor_impl = { + .destroy = subcompositor_destroy, + .get_subsurface = subcompositor_get_subsurface, +}; + +static void subcompositor_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) { + struct wlr_subcompositor *sc = data; + + struct wl_resource *res = + wl_resource_create(client, &wl_subcompositor_interface, version, id); + if (!res) { + wlr_log_errno(WLR_ERROR, "Failed to create subcompositor resource"); + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(res, &subcompositor_impl, sc, NULL); +} + +static void subsurface_complete(struct wl_listener *listener, void *data) { + struct subsurface_state *st = wl_container_of(listener, st, commit_complete); + + st->inhibiting_parent = false; + wl_list_remove(&st->commit_complete.link); + + wlr_commit_uninhibit(st->parent); + st->parent = NULL; +} + +static void commit_committed(struct wl_listener *listener, void *data) { + struct subsurface_root *root = wl_container_of(listener, root, commit_committed); + struct wlr_commit *parent = data; + + struct subsurface_state *iter; + wl_list_for_each(iter, &root->subsurfaces, link) { + if (iter == &root->root) { + continue; + } + + struct wlr_subsurface *ss = iter->subsurface; + + iter->synchronized = ss->synchronized; + if (!iter->synchronized) { + continue; + } + + iter->commit = wlr_surface_get_latest(ss->surface); + if (iter->commit && !wlr_commit_is_complete(iter->commit)) { + iter->parent = parent; + iter->inhibiting_parent = true; + wlr_commit_inhibit(parent); + + iter->commit_complete.notify = subsurface_complete; + wl_signal_add(&iter->commit->events.complete, &iter->commit_complete); + } + } +} + +static void commit_destroy(struct wl_listener *listener, void *data) { + struct subsurface_root *root = wl_container_of(listener, root, commit_destroy); + + struct subsurface_state *iter, *tmp; + wl_list_for_each_safe(iter, tmp, &root->subsurfaces, link) { + if (iter == &root->root) { + continue; + } + + if (iter->subsurface) { + wl_list_remove(&iter->subsurface_destroy.link); + } + if (iter->inhibiting_parent) { + wl_list_remove(&iter->commit_complete.link); + } + if (iter->commit) { + wlr_commit_unref(iter->commit); + } + free(iter); + } + + wl_list_remove(&root->commit_destroy.link); + free(root); +} + +static void subcompositor_new_state(struct wl_listener *listener, void *data) { + struct wlr_subcompositor *sc = wl_container_of(listener, sc, new_state); + struct wlr_compositor_new_state_args *args = data; + struct wlr_commit *new = args->new; + + struct subsurface_root *root = calloc(1, sizeof(*root)); + wl_list_init(&root->subsurfaces); + root->commit_committed.notify = commit_committed; + wl_signal_add(&new->events.commit, &root->commit_committed); + root->commit_destroy.notify = commit_destroy; + wl_signal_add(&new->events.destroy, &root->commit_destroy); + + if (args->old) { + struct subsurface_root *old_root = wlr_commit_get(args->old, sc->root_id); + + struct subsurface_state *iter; + wl_list_for_each(iter, &old_root->subsurfaces, link) { + struct subsurface_state *new; + if (iter == &old_root->root) { + new = &root->root; + new->commit = NULL; + } else { + new = calloc(1, sizeof(*new)); + if (new->commit) { + new->commit = wlr_commit_ref(iter->commit); + } + } + + new->subsurface = iter->subsurface; + new->x = iter->x; + new->y = iter->y; + + wl_list_insert(root->subsurfaces.prev, &new->link); + } + } else { + wl_list_insert(&root->subsurfaces, &root->root.link); + root->root.subsurface = NULL; + root->root.commit = NULL; + root->root.x = 0; + root->root.y = 0; + } + + wlr_commit_set(new, sc->root_id, root); +} + +static void subcompositor_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_subcompositor *sc = + wl_container_of(listener, sc, display_destroy); + + wl_global_destroy(sc->global); + free(sc); +} + +struct wlr_subcompositor *wlr_subcompositor_create(struct wlr_compositor *compositor) { + struct wlr_subcompositor *sc = calloc(1, sizeof(*sc)); + if (!sc) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + + sc->global = wl_global_create(compositor->display, &wl_subcompositor_interface, + SUBCOMPOSITOR_VERSION, sc, subcompositor_bind); + if (!sc->global) { + wlr_log_errno(WLR_ERROR, "Failed to create wayland global"); + free(sc); + return NULL; + } + + sc->compositor = compositor; + sc->root_id = wlr_compositor_register(compositor); + + wl_signal_init(&sc->events.new_subsurface); + + sc->new_state.notify = subcompositor_new_state; + wl_signal_add(&compositor->events.new_state, &sc->new_state); + + sc->display_destroy.notify = subcompositor_display_destroy; + wl_display_add_destroy_listener(compositor->display, &sc->display_destroy); + + return sc; +} + +struct wlr_subsurface *wlr_surface_to_subsurface(struct wlr_surface_2 *surf) { + if (surf->role_name && strcmp(surf->role_name, "wl_subsurface") == 0) { + return surf->role_data; + } + + return NULL; +} + +struct wlr_surface_2 *wlr_surface_get_root_surface(struct wlr_surface_2 *surf) { + struct wlr_subsurface *ss; + + while (surf && (ss = wlr_surface_to_subsurface(surf))) { + surf = ss->parent; + } + + return surf; +} + +static void commit_for_each(struct wlr_subcompositor *sc, struct wlr_commit *commit, + wlr_subsurface_iter_t func, void *userdata, int32_t x, int32_t y) { + assert(wlr_commit_is_complete(commit)); + + struct subsurface_root *root = wlr_commit_get(commit, sc->root_id); + struct subsurface_state *iter; + wl_list_for_each(iter, &root->subsurfaces, link) { + int32_t new_x = iter->x + x; + int32_t new_y = iter->y + y; + + if (iter == &root->root) { + func(userdata, commit, new_x, new_y); + } else if (iter->synchronized && iter->commit) { + commit_for_each(sc, iter->commit, func, userdata, new_x, new_y); + } else if (!iter->synchronized) { + struct wlr_surface_2 *s = iter->subsurface->surface; + struct wlr_commit *c = wlr_surface_get_commit(s); + if (c) { + commit_for_each(sc, c, func, userdata, new_x, new_y); + wlr_commit_unref(c); + } + } + } +} + +void wlr_commit_for_each_subsurface(struct wlr_subcompositor *sc, + struct wlr_commit *commit, wlr_subsurface_iter_t func, void *userdata) { + commit_for_each(sc, commit, func, userdata, 0, 0); +}