12to11/subsurface.c
hujianwei e7b89cec3b Fix crashes during scale changes
* compositor.h (struct _XdgRoleImplementationFuncs): New
function `rescale'.
* subsurface.c (Rescale): If the parent has been detached, don't
call MoveFractional.
* xdg_surface.c (Rescale): Call rescale hook.
* xdg_toplevel.c (SendOutputBounds): Check that some fields are
present.  Scale geometry correctly.
(Rescale): New function.
(ShowWindowMenu): Scale coordinates correctly.
(XLGetXdgToplevel): Add Rescale hook.
2022-11-15 12:17:26 +00:00

1237 lines
32 KiB
C

/* 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 <https://www.gnu.org/licenses/>. */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#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 should be desynchronous. */
Bool should_be_desync;
/* 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;
/* Whether or not this subsurface was just added to a parent that
has not yet committed. */
Bool pending;
/* 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)
{
Subsurface *subsurface;
subsurface = wl_resource_get_user_data (resource);
/* Now detach the role from its surface, which can be reused in the
future. */
if (subsurface->role.surface)
XLSurfaceReleaseRole (subsurface->role.surface,
&subsurface->role);
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
SetSync1 (Subsurface *subsurface)
{
Surface *child;
Subsurface *child_subsurface;
XLList *list;
/* Note that the given subsurface has become synchronous by setting
its synchronous flag to True. */
subsurface->synchronous = True;
if (subsurface->role.surface)
{
list = subsurface->role.surface->subsurfaces;
for (; list; list = list->next)
{
child = list->data;
child_subsurface = SubsurfaceFromRole (child->role);
SetSync1 (child_subsurface);
}
}
}
static void
SetSync (struct wl_client *client, struct wl_resource *resource)
{
Subsurface *subsurface;
subsurface = wl_resource_get_user_data (resource);
/* This subsurface should not actually be desynchronous. */
subsurface->should_be_desync = False;
/* Now, make each child synchronous recursively. */
SetSync1 (subsurface);
}
static Bool
IsParentSynchronous (Subsurface *subsurface)
{
Surface *surface;
Subsurface *parent;
surface = subsurface->parent;
if (!surface || surface->role_type != SubsurfaceType)
return False;
parent = SubsurfaceFromRole (surface->role);
return parent->synchronous;
}
static void
NoteSubsurfaceDesynchronous (Subsurface *subsurface, Bool apply_state)
{
Surface *child;
Subsurface *child_subsurface;
XLList *list;
/* Note that the given subsurface has become desynchronous, and
apply pending state. Make each of its children that should be
desynchronous desynchronous as well, but avoid applying their
pending state. */
subsurface->synchronous = False;
if (subsurface->pending_commit && subsurface->role.surface
&& apply_state)
{
XLCommitSurface (subsurface->role.surface, False);
/* Set pending_commit to False only here, where it is certain
that the cached state has been applied. */
subsurface->pending_commit = False;
}
if (subsurface->role.surface)
{
list = subsurface->role.surface->subsurfaces;
for (; list; list = list->next)
{
child = list->data;
child_subsurface = SubsurfaceFromRole (child->role);
if (child_subsurface->should_be_desync)
NoteSubsurfaceDesynchronous (child_subsurface,
False);
}
}
}
static void
NoteSubsurfaceTeardown (Subsurface *subsurface)
{
Surface *child;
Subsurface *child_subsurface;
XLList *list;
/* The same, but it avoids applying any pending state. Used during
teardown. */
list = subsurface->role.surface->subsurfaces;
subsurface->synchronous = False;
for (; list; list = list->next)
{
child = list->data;
child_subsurface = SubsurfaceFromRole (child->role);
if (child_subsurface->should_be_desync)
NoteSubsurfaceTeardown (child_subsurface);
}
}
static void
SetDesync (struct wl_client *client, struct wl_resource *resource)
{
Subsurface *subsurface;
subsurface = wl_resource_get_user_data (resource);
/* Set it so that this subsurface should be desynchronous. If the
parent is synchronous, then it does not actually become
desynchronous until the pending state is applied. */
subsurface->should_be_desync = True;
/* Return if the parent is synchronous, as Wayland specifies
children of synchronous subsurfaces are always synchronous. */
if (IsParentSynchronous (subsurface))
return;
/* Make subsurface desynchronous and apply its pending state. If
any of its children are supposed to be desynchronous, make them
desynchronous as well, but do not apply the pending state. This
is how the documentation for the set_desync request is worded:
If cached state exists when wl_surface.commit is called in
desynchronized mode, the pending state is added to the cached
state, and applied as a whole. This invalidates the cache.
Note: even if a sub-surface is set to desynchronized, a parent
sub-surface may override it to behave as synchronized. For
details, see wl_subsurface.
If a surface's parent surface behaves as desynchronized, then
the cached state is applied on set_desync.
Notice how the last paragraph tries to stress that only surfaces
that are made desynchronous at the time of a set_desync request
made on them are supposed to have their cached state applied at
the time of that request.
Normally, applying the cached state of the desynchronous
subsurface will cause the cached state of its children to be
applied. However, there could be no cached state at all on the
surface specified as the argument to the set_desync request, in
which case children should not have their pending state applied.
This behavior is subject to tests in subsurface_test.c. */
NoteSubsurfaceDesynchronous (subsurface, True);
}
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;
}
else if (subsurface->pending_commit)
{
/* There is still cached state. Merge the state into the
surface first, before SubcompositorUpdate is called by
InternalCommit. */
XLSurfaceMergeCachedState (surface);
/* As the state is merged, there is no more cached state. */
subsurface->pending_commit = 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);
}
/* Attach the views to the subcompositor if they have not yet been
attached, as the parent's state has been applied. This must come
before XLCommitSurface, as doing so will apply the pending state,
which will fail to update the subcompositor bounds if the
subsurface is not present. */
if (subsurface->pending)
{
/* 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 (subsurface->role.surface->under,
ViewGetSubcompositor (surface->view));
ViewInsert (surface->view, subsurface->role.surface->under);
ViewSetSubcompositor (subsurface->role.surface->view,
ViewGetSubcompositor (surface->view));
ViewInsert (surface->view, subsurface->role.surface->view);
subsurface->pending = False;
}
/* 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);
}
subsurface->pending_commit = False;
subsurface->pending_substate.flags = 0;
}
static void
SubsurfaceUpdate (Surface *surface, Role *role)
{
Subsurface *subsurface;
subsurface = SubsurfaceFromRole (role);
if (!subsurface->parent || !subsurface->parent->role
|| !subsurface->parent->role->funcs.subsurface_update)
return;
subsurface->parent->role->funcs.subsurface_update (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)
{
/* Tell the parent that a subsurface changed. It should then do
whatever is appropriate to update the subsurface. */
SubsurfaceUpdate (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;
surface->role_type = SubsurfaceType;
subsurface = SubsurfaceFromRole (role);
subsurface->refcount++;
subsurface->output_x = INT_MIN;
subsurface->output_y = INT_MIN;
role->surface = surface;
/* 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 subsurface as pending. A pending subsurface is not
inserted into any subcompositor, but will be inserted upon the
parent commit callback being run.
The specification states that the "effect of adding a subsurface"
will take effect after its parent is applied.
The interpretation previously used was that the the subsurface
would be made visible upon the parent's state being applied. But
that interpretation led to ambiguities, and contradicted with
common sense and the implementation in Weston. */
subsurface->pending = True;
/* Subsurfaces are synchronous by default. Make every child
synchronous. */
SetSync1 (subsurface);
return True;
}
static void
Rescale (Surface *surface, Role *role)
{
Subsurface *subsurface;
subsurface = SubsurfaceFromRole (role);
/* If the parent has been detached, return immediately. */
if (!subsurface->parent)
return;
/* 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, *last;
Subcompositor *subcompositor;
subsurface = SubsurfaceFromRole (role);
/* Make each of the surface's children that should be desynchronous
desynchronous. */
NoteSubsurfaceTeardown (subsurface);
role->surface = NULL;
subcompositor = NULL;
if (subsurface->parent)
{
if (!subsurface->pending)
{
subcompositor = ViewGetSubcompositor (surface->view);
/* Detach the views if the subsurface is not pending. */
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)
{
last = action;
action = action->next;
if (last->subsurface == subsurface)
DestroySurfaceAction (last);
}
}
subsurface->parent->subsurfaces
= XLListRemove (subsurface->parent->subsurfaces, surface);
XLSurfaceCancelCommitCallback (subsurface->commit_callback);
/* According to the spec, this removal should take effect
immediately. */
if (subcompositor)
SubsurfaceUpdate (surface, role);
}
/* Destroy the backing data of the subsurface. */
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.subsurface_update = SubsurfaceUpdate;
subsurface->role.funcs.early_commit = EarlyCommit;
subsurface->role.funcs.get_window = GetWindow;
subsurface->role.funcs.rescale = Rescale;
subsurface->role.funcs.parent_rescale = ParentRescale;
subsurface->parent = parent;
/* Note that for subsurfaces to be attached in the correct order,
commit callbacks must be run in the order they were created. */
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);
/* The callback is freed with the parent. */
subsurface->commit_callback = NULL;
if (subsurface->role.surface)
{
/* Unparent the view. The parent is responsible for clearing
the subcompositor. */
ViewUnparent (subsurface->role.surface->view);
ViewUnparent (subsurface->role.surface->under);
}
subsurface->parent = NULL;
}
void
XLSubsurfaceHandleParentCommit (Surface *parent)
{
SurfaceActionClientData *client;
/* Note that these actions will also work for pending subsurfaces,
as they will be attached by the time this is called. */
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);
}