/* Wayland compositor running on top of an X server. Copyright (C) 2022 to various contributors. This file is part of 12to11. 12to11 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 12to11 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with 12to11. If not, see . */ #include #include #include "compositor.h" enum { PendingPosition = 1, }; enum _SurfaceActionType { Sentinel, PlaceAboveOther, PlaceBelowOther, }; typedef enum _SurfaceActionType SurfaceActionType; typedef struct _Subsurface Subsurface; typedef struct _Substate Substate; typedef struct _SurfaceAction SurfaceAction; typedef struct _SurfaceActionClientData SurfaceActionClientData; #define SubsurfaceFromRole(role) ((Subsurface *) (role)) struct _SurfaceAction { /* What this action is. */ SurfaceActionType type; /* What subsurface this action applies to. */ Subsurface *subsurface; /* What surface is the "other" surface. */ Surface *other; /* Surface destroy listener. */ DestroyCallback *destroy_listener; /* The next and last surface actions in this list. */ SurfaceAction *next, *last; }; struct _Substate { /* The position of the subsurface relative to the parent. */ int x, y; /* Various flags. */ int flags; }; struct _Subsurface { /* The role object itself. */ Role role; /* The parent surface. */ Surface *parent; /* The number of references to this subsurface. */ int refcount; /* Pending substate. */ Substate pending_substate; /* Current substate. */ Substate current_substate; /* Commit callback attached to the parent. */ CommitCallback *commit_callback; /* Whether or not this is synchronous. */ Bool synchronous; /* Whether or not a commit is pending. */ Bool pending_commit; /* Whether or not this subsurface is mapped. */ Bool mapped; /* The last dimensions and position that were used to update this surface's outputs. */ int output_x, output_y, output_width, output_height; }; struct _SurfaceActionClientData { /* Any pending subsurface actions. */ SurfaceAction actions; }; /* The global wl_subcompositor resource. */ struct wl_global *global_subcompositor; static void UnlinkSurfaceAction (SurfaceAction *subaction) { subaction->last->next = subaction->next; subaction->next->last = subaction->last; } static void HandleOtherSurfaceDestroyed (void *data) { SurfaceAction *action; action = data; UnlinkSurfaceAction (action); XLFree (action); } static void DestroySurfaceAction (SurfaceAction *subaction) { XLSurfaceCancelRunOnFree (subaction->destroy_listener); UnlinkSurfaceAction (subaction); XLFree (subaction); } static Bool CheckSiblingRelationship (Subsurface *subsurface, Surface *other) { Subsurface *other_subsurface; if (other->role_type != SubsurfaceType /* The role might've been detached from the other surface. */ || !other->role) return False; other_subsurface = SubsurfaceFromRole (other->role); if (other_subsurface->parent != subsurface->parent) return False; return True; } static void ParentBelow (View *parent, View *below, Surface *surface) { ViewInsertBefore (parent, surface->view, below); ViewInsertBefore (parent, surface->under, surface->view); } static void ParentAbove (View *parent, View *above, Surface *surface) { ViewInsertAfter (parent, surface->under, above); ViewInsertAfter (parent, surface->view, surface->under); } static void ParentStart (View *parent, Surface *surface) { ViewInsert (parent, surface->under); ViewInsert (parent, surface->view); } static void RunOneSurfaceAction (Subsurface *subsurface, SurfaceAction *subaction) { View *target; if (!subsurface->role.surface || !subsurface->parent) return; if (subaction->type == PlaceAboveOther) { if (subaction->other != subsurface->parent && !CheckSiblingRelationship (subsurface, subaction->other)) /* The hierarchy changed in some unacceptable way between the action being recorded and the commit of the parent. Ignore. */ return; /* Determine the target under which to place the view. If subaction->other is underneath the parent, then this will actually be subsurface->parent->under. */ target = ViewGetParent (subaction->other->view); /* After that, unparent the views. */ ViewUnparent (subsurface->role.surface->view); ViewUnparent (subsurface->role.surface->under); if (subaction->other == subsurface->parent) /* Re-insert this view at the beginning of the parent. */ ParentStart (subsurface->parent->view, subsurface->role.surface); else /* Re-insert this view in front of the other surface. */ ParentAbove (target, subaction->other->view, subsurface->role.surface); } else if (subaction->type == PlaceBelowOther) { if (subaction->other != subsurface->parent && !CheckSiblingRelationship (subsurface, subaction->other)) return; target = ViewGetParent (subaction->other->view); ViewUnparent (subsurface->role.surface->view); ViewUnparent (subsurface->role.surface->under); if (subaction->other != subsurface->parent) /* Re-insert this view before the other surface. */ ParentBelow (target, subaction->other->under, subsurface->role.surface); else /* Re-insert this view below the parent surface. */ ParentStart (subsurface->parent->under, subsurface->role.surface); } } static void FreeSurfaceActions (SurfaceAction *first) { SurfaceAction *action, *last; action = first->next; while (action != first) { last = action; action = action->next; DestroySurfaceAction (last); } } static void FreeSubsurfaceData (void *data) { SurfaceActionClientData *client; client = data; FreeSurfaceActions (&client->actions); } static SurfaceAction * AddSurfaceAction (Subsurface *subsurface, Surface *other, SurfaceActionType type) { SurfaceAction *action; SurfaceActionClientData *client; action = XLMalloc (sizeof *action); action->subsurface = subsurface; action->type = type; action->other = other; action->destroy_listener = XLSurfaceRunOnFree (other, HandleOtherSurfaceDestroyed, action); client = XLSurfaceGetClientData (subsurface->parent, SubsurfaceData, sizeof *client, FreeSubsurfaceData); if (!client->actions.next) { /* Client is not yet initialized, so initialize the sentinel node. */ client->actions.next = &client->actions; client->actions.last = &client->actions; client->actions.type = Sentinel; } action->next = client->actions.next; action->last = &client->actions; client->actions.next->last = action; client->actions.next = action; return action; } static void RunSurfaceActions (SurfaceAction *first) { SurfaceAction *action, *last; action = first->last; while (action != first) { last = action; /* Run the actions backwards so they appear in the right order. */ action = action->last; RunOneSurfaceAction (last->subsurface, last); DestroySurfaceAction (last); } } static void DestroySubsurface (struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy (resource); } static void SetPosition (struct wl_client *client, struct wl_resource *resource, int32_t x, int32_t y) { Subsurface *subsurface; subsurface = wl_resource_get_user_data (resource); subsurface->pending_substate.x = x; subsurface->pending_substate.y = y; subsurface->pending_substate.flags |= PendingPosition; } static void PlaceAbove (struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface_resource) { Subsurface *subsurface; Surface *other; subsurface = wl_resource_get_user_data (resource); other = wl_resource_get_user_data (surface_resource); if (other != subsurface->parent && !CheckSiblingRelationship (subsurface, other)) { wl_resource_post_error (resource, WL_SUBSURFACE_ERROR_BAD_SURFACE, "surface is not a sibling or the parent"); return; } AddSurfaceAction (subsurface, other, PlaceAboveOther); } static void PlaceBelow (struct wl_client *client, struct wl_resource *resource, struct wl_resource *surface_resource) { Subsurface *subsurface; Surface *other; subsurface = wl_resource_get_user_data (resource); other = wl_resource_get_user_data (surface_resource); if (other != subsurface->parent || !CheckSiblingRelationship (subsurface, other)) { wl_resource_post_error (resource, WL_SUBSURFACE_ERROR_BAD_SURFACE, "surface is not a sibling or the parent"); return; } AddSurfaceAction (subsurface, other, PlaceBelowOther); } static void NoteDesyncChild (Surface *surface, Role *role) { Subsurface *subsurface; subsurface = SubsurfaceFromRole (role); if (!subsurface->parent || !subsurface->parent->role || !subsurface->parent->role->funcs.note_desync_child) return; subsurface->parent->role->funcs.note_desync_child (subsurface->parent, subsurface->parent->role); } static void NoteChildSynced (Surface *surface, Role *role) { Subsurface *subsurface; subsurface = SubsurfaceFromRole (role); if (!subsurface->parent || !subsurface->parent->role || !subsurface->parent->role->funcs.note_child_synced) return; subsurface->parent->role->funcs.note_child_synced (subsurface->parent, subsurface->parent->role); } static void SetSync (struct wl_client *client, struct wl_resource *resource) { Subsurface *subsurface; subsurface = wl_resource_get_user_data (resource); if (subsurface->role.surface && !subsurface->synchronous) NoteChildSynced (subsurface->role.surface, &subsurface->role); subsurface->synchronous = True; } static void SetDesync (struct wl_client *client, struct wl_resource *resource) { Subsurface *subsurface; subsurface = wl_resource_get_user_data (resource); if (subsurface->role.surface && subsurface->synchronous) NoteDesyncChild (subsurface->role.surface, &subsurface->role); subsurface->synchronous = False; if (subsurface->pending_commit && subsurface->role.surface) XLCommitSurface (subsurface->role.surface, False); subsurface->pending_commit = False; } static const struct wl_subsurface_interface wl_subsurface_impl = { .destroy = DestroySubsurface, .set_position = SetPosition, .place_above = PlaceAbove, .place_below = PlaceBelow, .set_sync = SetSync, .set_desync = SetDesync, }; static void DestroyBacking (Subsurface *subsurface) { if (--subsurface->refcount) return; XLFree (subsurface); } static Bool EarlyCommit (Surface *surface, Role *role) { Subsurface *subsurface; subsurface = SubsurfaceFromRole (role); /* If the role is synchronous, don't commit until the parent commits. */ if (subsurface->synchronous) { subsurface->pending_commit = True; return False; } return True; } static void MaybeUpdateOutputs (Subsurface *subsurface) { int x, y, width, height, base_x, base_y; if (subsurface->role.surface->output_x == INT_MIN || subsurface->role.surface->output_y == INT_MIN) /* Valid base coordinates are not yet available. */ return; if (!subsurface->parent) /* A valid scale factor is not available. */ return; /* Compute the positions relative to the parent. */ x = floor (subsurface->current_substate.x * subsurface->parent->factor); y = floor (subsurface->current_substate.y * subsurface->parent->factor); /* And the base X and Y. */ base_x = subsurface->role.surface->output_x; base_y = subsurface->role.surface->output_y; /* Compute the absolute width and height of the surface contents. */ width = ViewWidth (subsurface->role.surface->view); height = ViewHeight (subsurface->role.surface->view); /* If nothing really changed, return. */ if (x == subsurface->output_x && y == subsurface->output_y && width == subsurface->output_width && height == subsurface->output_height) return; /* Otherwise, recompute the outputs this subsurface overlaps and record those values. */ subsurface->output_x = x; subsurface->output_y = y; subsurface->output_width = width; subsurface->output_height = height; /* Recompute overlaps. */ XLUpdateSurfaceOutputs (subsurface->role.surface, x + base_x, y + base_y, width, height); } static void MoveFractional (Subsurface *subsurface) { double x, y; int x_int, y_int; /* Move the surface to a fractional window (subcompositor) coordinate relative to the parent. This is done by placing the surface at the floor of the coordinates, and then offsetting the image and input by the remainder during rendering. */ SurfaceToWindow (subsurface->parent, subsurface->current_substate.x, subsurface->current_substate.y, &x, &y); x_int = floor (x); y_int = floor (y); /* Move the subsurface to x_int, y_int. */ ViewMove (subsurface->role.surface->view, x_int, y_int); ViewMove (subsurface->role.surface->under, x_int, y_int); /* Apply the fractional offset. */ ViewMoveFractional (subsurface->role.surface->view, x - x_int, y - y_int); ViewMoveFractional (subsurface->role.surface->under, x - x_int, y - y_int); /* And set the fractional offset on the surface for input handling purposes. */ subsurface->role.surface->input_delta_x = x - x_int; subsurface->role.surface->input_delta_y = y - y_int; /* Apply pointer constraints. */ XLPointerConstraintsSubsurfaceMoved (subsurface->role.surface); } static void AfterParentCommit (Surface *surface, void *data) { Subsurface *subsurface; subsurface = data; /* The surface might've been destroyed already. */ if (!subsurface->role.surface) return; /* Apply pending state. */ if (subsurface->pending_substate.flags & PendingPosition) { /* Apply the new position. */ subsurface->current_substate.x = subsurface->pending_substate.x; subsurface->current_substate.y = subsurface->pending_substate.y; /* And move the views. */ MoveFractional (subsurface); } /* And any cached surface state too. */ if (subsurface->pending_commit) { XLCommitSurface (subsurface->role.surface, False); /* If the size changed, update the outputs this surface is in the scanout area of. */ MaybeUpdateOutputs (subsurface); } /* Mark the subsurface as unskipped. (IOW, make it visible). */ ViewUnskip (subsurface->role.surface->view); ViewUnskip (subsurface->role.surface->under); subsurface->pending_commit = False; subsurface->pending_substate.flags = 0; } static Bool Subframe (Surface *surface, Role *role) { Subsurface *subsurface; subsurface = SubsurfaceFromRole (role); if (!subsurface->parent || !subsurface->parent->role || !subsurface->parent->role->funcs.subframe) return True; return subsurface->parent->role->funcs.subframe (subsurface->parent, subsurface->parent->role); } static void EndSubframe (Surface *surface, Role *role) { Subsurface *subsurface; subsurface = SubsurfaceFromRole (role); if (!subsurface->parent || !subsurface->parent->role || !subsurface->parent->role->funcs.end_subframe) return; subsurface->parent->role->funcs.end_subframe (subsurface->parent, subsurface->parent->role); } static Window GetWindow (Surface *surface, Role *role) { Subsurface *subsurface; subsurface = SubsurfaceFromRole (role); if (!subsurface->parent || !subsurface->parent->role || !subsurface->parent->role->funcs.get_window) return None; return subsurface->parent->role->funcs.get_window (subsurface->parent, subsurface->parent->role); } static void Commit (Surface *surface, Role *role) { Subcompositor *subcompositor; Subsurface *subsurface; subcompositor = ViewGetSubcompositor (surface->view); subsurface = SubsurfaceFromRole (role); if (!subcompositor) return; /* If no buffer is attached, unmap the views. */ if (!surface->current_state.buffer) { ViewUnmap (surface->under); ViewUnmap (surface->view); if (subsurface->mapped) /* Check for idle inhibition changes. */ XLDetectSurfaceIdleInhibit (); subsurface->mapped = False; } else { /* Once a buffer is attached to the view, it is automatically mapped. */ ViewMap (surface->under); if (!subsurface->mapped) /* Check if this subsurface being mapped would cause idle inhibitors to change. */ XLDetectSurfaceIdleInhibit (); subsurface->mapped = True; } if (!subsurface->synchronous) { /* If the surface is asynchronous, draw this subframe now. Otherwise it is synchronous, so we should wait for the toplevel to end the frame. */ if (Subframe (surface, role)) { SubcompositorUpdate (subcompositor); EndSubframe (surface, role); } /* If the size changed, update the outputs this surface is in the scanout area of. */ MaybeUpdateOutputs (subsurface); } } static Bool Setup (Surface *surface, Role *role) { Subsurface *subsurface; View *parent_view; surface->role_type = SubsurfaceType; subsurface = SubsurfaceFromRole (role); subsurface->refcount++; subsurface->output_x = INT_MIN; subsurface->output_y = INT_MIN; role->surface = surface; parent_view = subsurface->parent->view; /* Set the subcompositor here. If the role providing the subcompositor hasn't been attached to the parent, then when it is it will call ViewSetSubcompositor on the parent's view. */ ViewSetSubcompositor (surface->under, ViewGetSubcompositor (parent_view)); ViewInsert (parent_view, surface->under); ViewSetSubcompositor (surface->view, ViewGetSubcompositor (parent_view)); ViewInsert (parent_view, surface->view); /* Now move the subsurface to its initial location (0, 0) */ MoveFractional (subsurface); /* Now add the subsurface to the parent's list of subsurfaces. */ subsurface->parent->subsurfaces = XLListPrepend (subsurface->parent->subsurfaces, surface); /* And mark the view as "skipped"; this differs from unmapping, which we cannot simply use, in that children remain visible, as the specification says the following: Adding sub-surfaces to a parent is a double-buffered operation on the parent (see wl_surface.commit). The effect of adding a sub-surface becomes visible on the next time the state of the parent surface is applied. So if a child is added to a desynchronized subsurface whose parent toplevel has not yet committed, and commit is called on the desynchronized subsurface, the child should become indirectly visible on the parent toplevel through the child. */ ViewSkip (surface->view); ViewSkip (surface->under); return True; } static void Rescale (Surface *surface, Role *role) { Subsurface *subsurface; subsurface = SubsurfaceFromRole (role); /* The scale factor changed; move the subsurface to the new correct position. */ MoveFractional (subsurface); } static void ParentRescale (Surface *surface, Role *role) { /* This is called when the scale factor of the parent changes. */ Rescale (surface, role); } static void Teardown (Surface *surface, Role *role) { Subsurface *subsurface; SurfaceActionClientData *client; SurfaceAction *action; Subcompositor *subcompositor; subsurface = SubsurfaceFromRole (role); /* If this subsurface is desynchronous, tell the toplevel parent that it is now gone. */ if (!subsurface->synchronous) NoteDesyncChild (role->surface, role); role->surface = NULL; if (subsurface->parent) { subcompositor = ViewGetSubcompositor (surface->view); ViewUnparent (surface->view); ViewSetSubcompositor (surface->view, NULL); ViewUnparent (surface->under); ViewSetSubcompositor (surface->under, NULL); client = XLSurfaceFindClientData (subsurface->parent, SubsurfaceData); if (client) { /* Free all subsurface actions involving this subsurface. */ action = client->actions.next; while (action != &client->actions) { if (action->subsurface == subsurface) DestroySurfaceAction (action); } } subsurface->parent->subsurfaces = XLListRemove (subsurface->parent->subsurfaces, surface); XLSurfaceCancelCommitCallback (subsurface->commit_callback); /* According to the spec, this removal should take effect immediately. */ if (subcompositor && Subframe (surface, role)) { SubcompositorUpdate (subcompositor); EndSubframe (surface, role); } } DestroyBacking (subsurface); /* Update whether or not idle inhibition should continue. */ XLDetectSurfaceIdleInhibit (); } static void ReleaseBuffer (Surface *surface, Role *role, ExtBuffer *buffer) { Subsurface *subsurface; subsurface = SubsurfaceFromRole (role); if (!subsurface->parent || !subsurface->parent->role) { XLReleaseBuffer (buffer); return; } subsurface->parent->role->funcs.release_buffer (subsurface->parent, subsurface->parent->role, buffer); } static void HandleSubsurfaceResourceDestroy (struct wl_resource *resource) { Subsurface *subsurface; subsurface = wl_resource_get_user_data (resource); DestroyBacking (subsurface); } static Surface * GetRootSurface (Surface *surface) { Subsurface *subsurface; if (surface->role_type != SubsurfaceType || !surface->role) return surface; subsurface = SubsurfaceFromRole (surface->role); if (!subsurface->parent) return surface; return GetRootSurface (subsurface->parent); } static void GetSubsurface (struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *surface_resource, struct wl_resource *parent_resource) { Surface *surface, *parent; Subsurface *subsurface; surface = wl_resource_get_user_data (surface_resource); parent = wl_resource_get_user_data (parent_resource); /* If the surface already has a role, don't attach this subsurface. Likewise if the surface previously held some other role. */ if (surface->role || (surface->role_type != AnythingType && surface->role_type != SubsurfaceType)) { wl_resource_post_error (resource, WL_SUBCOMPOSITOR_ERROR_BAD_SURFACE, "trying to attach subsurface to surface with role"); return; } /* Check that a parent loop won't happen. */ if (parent == surface) { wl_resource_post_error (resource, WL_SUBCOMPOSITOR_ERROR_BAD_PARENT, "trying to attach subsurface to itself"); return; } if (GetRootSurface (parent) == surface) { wl_resource_post_error (resource, WL_SUBCOMPOSITOR_ERROR_BAD_PARENT, "specified parent is ancestor of subsurface"); return; } subsurface = XLSafeMalloc (sizeof *subsurface); if (!subsurface) { wl_resource_post_no_memory (resource); return; } memset (subsurface, 0, sizeof *subsurface); subsurface->role.resource = wl_resource_create (client, &wl_subsurface_interface, wl_resource_get_version (resource), id); if (!subsurface->role.resource) { XLFree (subsurface); wl_resource_post_no_memory (resource); return; } wl_resource_set_implementation (subsurface->role.resource, &wl_subsurface_impl, subsurface, HandleSubsurfaceResourceDestroy); /* Now the wl_resource holds a reference to the subsurface. */ subsurface->refcount++; subsurface->role.funcs.commit = Commit; subsurface->role.funcs.teardown = Teardown; subsurface->role.funcs.setup = Setup; subsurface->role.funcs.release_buffer = ReleaseBuffer; subsurface->role.funcs.subframe = Subframe; subsurface->role.funcs.end_subframe = EndSubframe; subsurface->role.funcs.early_commit = EarlyCommit; subsurface->role.funcs.get_window = GetWindow; subsurface->role.funcs.rescale = Rescale; subsurface->role.funcs.parent_rescale = ParentRescale; subsurface->role.funcs.note_child_synced = NoteChildSynced; subsurface->role.funcs.note_desync_child = NoteDesyncChild; subsurface->parent = parent; subsurface->commit_callback = XLSurfaceRunAtCommit (parent, AfterParentCommit, subsurface); subsurface->synchronous = True; if (!XLSurfaceAttachRole (surface, &subsurface->role)) abort (); } static void DestroySubcompositor (struct wl_client *client, struct wl_resource *resource) { wl_resource_destroy (resource); } static const struct wl_subcompositor_interface wl_subcompositor_impl = { .destroy = DestroySubcompositor, .get_subsurface = GetSubsurface, }; static void HandleBind (struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource; resource = wl_resource_create (client, &wl_subcompositor_interface, version, id); if (!resource) { wl_client_post_no_memory (client); return; } wl_resource_set_implementation (resource, &wl_subcompositor_impl, NULL, NULL); } void XLInitSubsurfaces (void) { global_subcompositor = wl_global_create (compositor.wl_display, &wl_subcompositor_interface, 1, NULL, HandleBind); } void XLSubsurfaceParentDestroyed (Role *role) { Subsurface *subsurface; subsurface = SubsurfaceFromRole (role); subsurface->parent = NULL; /* The callback is freed with the parent. */ subsurface->commit_callback = NULL; if (subsurface->role.surface) { ViewUnparent (subsurface->role.surface->view); ViewUnparent (subsurface->role.surface->under); } } void XLSubsurfaceHandleParentCommit (Surface *parent) { SurfaceActionClientData *client; client = XLSurfaceFindClientData (parent, SubsurfaceData); if (client) RunSurfaceActions (&client->actions); } void XLUpdateOutputsForChildren (Surface *parent, int base_x, int base_y) { XLList *item; Subsurface *subsurface; Surface *child; int output_x, output_y, output_width, output_height; for (item = parent->subsurfaces; item; item = item->next) { child = item->data; subsurface = SubsurfaceFromRole (child->role); output_x = (subsurface->current_substate.x * parent->factor); output_y = (subsurface->current_substate.y * parent->factor); output_width = ViewWidth (child->view); output_height = ViewHeight (child->view); XLUpdateSurfaceOutputs (child, base_x + output_x, base_y + output_y, output_width, output_height); /* Record those values in the child. */ subsurface->output_x = output_x; subsurface->output_y = output_y; subsurface->output_width = output_width; subsurface->output_height = output_height; } } void XLUpdateDesynchronousChildren (Surface *parent, int *n_children) { XLList *item; Subsurface *subsurface; Surface *child; for (item = parent->subsurfaces; item; item = item->next) { child = item->data; subsurface = SubsurfaceFromRole (child->role); if (!subsurface->synchronous) /* The subsurface is desynchronous, so add it to the number of desynchronous children. */ *n_children += 1; /* Update these numbers recursively as well. */ XLUpdateDesynchronousChildren (child, n_children); } } Surface * XLSubsurfaceGetRoot (Surface *surface) { return GetRootSurface (surface); }