/* 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
#include "compositor.h"
#include "xdg-shell.h"
#include
#define XdgRoleFromRole(role) ((XdgRole *) (role))
enum
{
StatePendingFrameCallback = 1,
StateLateFrame = (1 << 1),
StatePendingWindowGeometry = (1 << 2),
StateWaitingForAckConfigure = (1 << 3),
StateWaitingForAckCommit = (1 << 4),
StateLateFrameAcked = (1 << 5),
StateMaybeConfigure = (1 << 6),
};
typedef struct _XdgRole XdgRole;
typedef struct _XdgState XdgState;
typedef struct _ReleaseLaterRecord ReleaseLaterRecord;
typedef struct _ReconstrainCallback ReconstrainCallback;
/* Association between XIDs and surfaces. */
static XLAssocTable *surfaces;
/* The default border color of a window. Not actually used for
anything other than preventing BadMatch errors during window
creation. */
unsigned long border_pixel;
struct _ReconstrainCallback
{
/* Function called when a configure event is received. */
void (*configure) (void *, XEvent *);
/* Function called when we are certain a frame moved or resized. */
void (*resized) (void *);
/* Data the functions is called with. */
void *data;
/* The next and last callbacks in this list. */
ReconstrainCallback *next, *last;
};
struct _XdgState
{
int window_geometry_x;
int window_geometry_y;
int window_geometry_width;
int window_geometry_height;
};
struct _XdgRole
{
/* The role object. */
Role role;
/* Number of references to this role. Used when the client
terminates and the Wayland library destroys objects out of
order. */
int refcount;
/* The window backing this role. */
Window window;
/* The render target backing this role. */
RenderTarget target;
/* The subcompositor backing this role. */
Subcompositor *subcompositor;
/* The implementation of this role. */
XdgRoleImplementation *impl;
/* Various role state. */
int state;
/* Queue of buffers to release later (when the X server is done with
them). */
ReleaseLaterRecord *release_records;
/* The frame clock. */
FrameClock *clock;
/* The pending xdg_surface state. */
XdgState pending_state;
/* The current xdg_surface state. */
XdgState current_state;
/* Configure event serial. */
uint32_t conf_serial;
/* The current bounds of the subcompositor. */
int min_x, min_y, max_x, max_y;
/* The bounds width and bounds height of the subcompositor. */
int bounds_width, bounds_height;
/* The pending root window position of the subcompositor. */
int pending_root_x, pending_root_y;
/* How many synthetic (in the case of toplevels) ConfigureNotify
events to wait for before ignoring those coordinates. */
int pending_synth_configure;
/* The type of the attached role. */
XdgRoleImplementationType type;
/* The input region of the attached subsurface. */
pixman_region32_t input_region;
/* List of callbacks run upon a ConfigureNotify event. */
ReconstrainCallback reconstrain_callbacks;
/* The number of desynchronous children of this toplevel. */
int n_desync_children;
};
struct _ReleaseLaterRecord
{
/* A monotonically (overflow aside) increasing identifier. */
uint64_t id;
/* Key for the free func. */
void *free_func_key;
/* The buffer that should be released upon receiving this
message. */
ExtBuffer *buffer;
/* The next and last records. */
ReleaseLaterRecord *next, *last;
};
/* Event base of the XShape extension. */
int shape_base;
static void
RemoveRecord (ReleaseLaterRecord *record)
{
/* Removing the sentinel record is invalid. */
XLAssert (record->buffer != NULL);
/* First, make the rest of the list skip RECORD. */
record->last->next = record->next;
record->next->last = record->last;
/* Next, remove the buffer listener. */
XLBufferCancelRunOnFree (record->buffer,
record->free_func_key);
/* Finally, free RECORD. */
XLFree (record);
}
static void
DeleteRecord (ReleaseLaterRecord *record)
{
/* Removing the sentinel record is invalid. */
XLAssert (record->buffer != NULL);
/* First, make the rest of the list skip RECORD. */
record->last->next = record->next;
record->next->last = record->last;
/* Finally, free RECORD. */
XLFree (record);
}
static void
FreeRecords (ReleaseLaterRecord *records)
{
ReleaseLaterRecord *last, *tem;
tem = records->next;
while (tem != records)
{
last = tem;
tem = tem->next;
/* Release the buffer now. */
XLReleaseBuffer (last->buffer);
/* And cancel the destroy listener. */
XLBufferCancelRunOnFree (last->buffer,
last->free_func_key);
/* Before freeing the record itself. */
XLFree (last);
}
XLFree (records);
}
static ReleaseLaterRecord *
AddRecordAfter (ReleaseLaterRecord *start)
{
ReleaseLaterRecord *record;
record = XLMalloc (sizeof *record);
record->next = start->next;
record->last = start;
start->next->last = record;
start->next = record;
return record;
}
static ReconstrainCallback *
AddCallbackAfter (ReconstrainCallback *start)
{
ReconstrainCallback *callback;
callback = XLMalloc (sizeof *callback);
callback->next = start->next;
callback->last = start;
start->next->last = callback;
start->next = callback;
return callback;
}
static void
UnlinkReconstrainCallback (ReconstrainCallback *callback)
{
callback->last->next = callback->next;
callback->next->last = callback->last;
XLFree (callback);
}
static void
RunReconstrainCallbacksForXEvent (XdgRole *role, XEvent *event)
{
ReconstrainCallback *callback;
callback = role->reconstrain_callbacks.next;
while (callback != &role->reconstrain_callbacks)
{
callback->configure (callback->data, event);
callback = callback->next;
}
}
static void
RunReconstrainCallbacks (XdgRole *role)
{
ReconstrainCallback *callback;
callback = role->reconstrain_callbacks.next;
while (callback != &role->reconstrain_callbacks)
{
callback->resized (callback->data);
callback = callback->next;
}
}
static void
FreeReconstrainCallbacks (XdgRole *role)
{
ReconstrainCallback *callback, *last;
callback = role->reconstrain_callbacks.next;
while (callback != &role->reconstrain_callbacks)
{
last = callback;
callback = callback->next;
XLFree (last);
}
}
static void
ReleaseLaterExtBufferFunc (ExtBuffer *buffer, void *data)
{
DeleteRecord (data);
}
static void
RunFrameCallbacks (Surface *surface, XdgRole *role)
{
struct timespec time;
/* Surface can be NULL for various reasons, especially events
arriving after the shell surface is detached. */
if (!surface)
return;
clock_gettime (CLOCK_MONOTONIC, &time);
XLSurfaceRunFrameCallbacks (surface, time);
}
static void
RunFrameCallbacksConditionally (XdgRole *role)
{
if (role->release_records->last == role->release_records
&& role->role.surface)
RunFrameCallbacks (role->role.surface, role);
else if (role->role.surface)
/* weston-simple-shm seems to assume that a frame callback can
only arrive after all buffers have been released. */
role->state |= StatePendingFrameCallback;
}
static void
HandleReleaseLaterMessage (XdgRole *role, uint64_t id)
{
ReleaseLaterRecord *record;
Surface *surface;
record = role->release_records->last;
if (record == role->release_records)
return;
/* Since the list of release records is a (circular) queue, ID will
either be the last element or invalid as the buffer has been
destroyed. */
if (record->id == id)
{
XLReleaseBuffer (record->buffer);
RemoveRecord (record);
}
surface = role->role.surface;
/* Run frame callbacks now, if no more buffers are waiting to be
released. */
if (surface && role->state & StatePendingFrameCallback
&& role->release_records->next == role->release_records)
{
RunFrameCallbacks (surface, role);
role->state &= ~StatePendingFrameCallback;
}
}
/* It shouldn't be possible for billions of frames to pile up without
a response from the X server, so relying on wraparound to handle id
overflow should be fine. */
static void
ReleaseLater (XdgRole *role, ExtBuffer *buffer)
{
ReleaseLaterRecord *record;
XEvent event;
static uint64_t id;
/* Send a message to the X server; once it is received, release the
given buffer. This is necessary because the connection to the X
server does not behave synchronously, and receiving the message
tells us that the X server has finished processing all requests
that access the buffer. */
record = AddRecordAfter (role->release_records);
record->id = ++id;
record->buffer = buffer;
record->free_func_key
= XLBufferRunOnFree (buffer, ReleaseLaterExtBufferFunc,
record);
memset (&event, 0, sizeof event);
event.xclient.type = ClientMessage;
event.xclient.window = role->window;
event.xclient.message_type = _XL_BUFFER_RELEASE;
event.xclient.format = 32;
event.xclient.data.l[0] = id >> 31 >> 1;
event.xclient.data.l[1] = id & 0xffffffff;
XSendEvent (compositor.display, role->window, False,
NoEventMask, &event);
}
Bool
XLHandleXEventForXdgSurfaces (XEvent *event)
{
XdgRole *role;
uint64_t id, low, high;
Window window;
if (event->type == ClientMessage
&& event->xclient.message_type == _XL_BUFFER_RELEASE)
{
role = XLLookUpAssoc (surfaces, event->xclient.window);
if (role)
{
/* These values are masked because Xlib sign-extends 32-bit
values to fit potentially 64-bit long. */
low = event->xclient.data.l[0] & 0xffffffff;
high = event->xclient.data.l[1] & 0xffffffff;
id = (low << 32) | high;
HandleReleaseLaterMessage (role, id);
}
return True;
}
if (event->type == ClientMessage
&& ((event->xclient.message_type == _NET_WM_FRAME_DRAWN
|| event->xclient.message_type == _NET_WM_FRAME_TIMINGS)
|| (event->xclient.message_type == WM_PROTOCOLS
&& event->xclient.data.l[0] == _NET_WM_SYNC_REQUEST)))
{
role = XLLookUpAssoc (surfaces, event->xclient.window);
if (role)
{
XLFrameClockHandleFrameEvent (role->clock, event);
return True;
}
return False;
}
if (event->type == Expose)
{
role = XLLookUpAssoc (surfaces, event->xexpose.window);
if (role)
{
/* If resizing is in progress with WM synchronization, don't
handle exposure events, since that causes updates outside
a frame. */
if (!XLFrameClockNeedConfigure (role->clock))
SubcompositorExpose (role->subcompositor, event);
return True;
}
return False;
}
window = XLGetGEWindowForSeats (event);
if (window != None)
{
role = XLLookUpAssoc (surfaces, window);
if (role && role->role.surface)
{
XLDispatchGEForSeats (event, role->role.surface,
role->subcompositor);
return True;
}
return False;
}
return False;
}
static void
Destroy (struct wl_client *client, struct wl_resource *resource)
{
XdgRole *role;
role = wl_resource_get_user_data (resource);
if (role->impl)
{
wl_resource_post_error (resource, XDG_WM_BASE_ERROR_ROLE,
"trying to destroy xdg surface with role");
return;
}
wl_resource_destroy (resource);
}
static void
GetToplevel (struct wl_client *client, struct wl_resource *resource,
uint32_t id)
{
XdgRole *role;
role = wl_resource_get_user_data (resource);
if (!role->role.surface)
/* This object is inert. */
return;
if (role->type == TypePopup)
{
wl_resource_post_error (resource, XDG_WM_BASE_ERROR_ROLE,
"surface was previously a popup");
return;
}
role->type = TypeToplevel;
XLGetXdgToplevel (client, resource, id);
}
static void
GetPopup (struct wl_client *client, struct wl_resource *resource,
uint32_t id, struct wl_resource *parent_resource,
struct wl_resource *positioner_resource)
{
XdgRole *role;
role = wl_resource_get_user_data (resource);
if (!role->role.surface)
/* This object is inert. */
return;
if (role->type == TypeToplevel)
{
wl_resource_post_error (resource, XDG_WM_BASE_ERROR_ROLE,
"surface was previously a toplevel");
return;
}
role->type = TypePopup;
XLGetXdgPopup (client, resource, id, parent_resource,
positioner_resource);
}
static void
SetWindowGeometry (struct wl_client *client, struct wl_resource *resource,
int32_t x, int32_t y, int32_t width, int32_t height)
{
XdgRole *role;
role = wl_resource_get_user_data (resource);
if (x == role->current_state.window_geometry_x
&& y == role->pending_state.window_geometry_y
&& width == role->pending_state.window_geometry_width
&& height == role->pending_state.window_geometry_height)
return;
role->state |= StatePendingWindowGeometry;
role->pending_state.window_geometry_x = x;
role->pending_state.window_geometry_y = y;
role->pending_state.window_geometry_width = width;
role->pending_state.window_geometry_height = height;
}
static void
AckConfigure (struct wl_client *client, struct wl_resource *resource,
uint32_t serial)
{
XdgRole *xdg_role;
xdg_role = wl_resource_get_user_data (resource);
if (!xdg_role->role.surface)
return;
if (serial == xdg_role->conf_serial)
{
xdg_role->state &= ~StateWaitingForAckConfigure;
/* Garbage the subcompositor too, since contents could be
exposed due to changes in bounds. */
SubcompositorGarbage (xdg_role->subcompositor);
/* Also run frame callbacks if the frame clock is frozen. */
if (XLFrameClockIsFrozen (xdg_role->clock)
&& xdg_role->role.surface)
RunFrameCallbacksConditionally (xdg_role);
#ifdef DEBUG_GEOMETRY_CALCULATION
fprintf (stderr, "Client acknowledged configuration\n");
#endif
}
if (xdg_role->impl)
xdg_role->impl->funcs.ack_configure (&xdg_role->role,
xdg_role->impl,
serial);
}
static const struct xdg_surface_interface xdg_surface_impl =
{
.get_toplevel = GetToplevel,
.get_popup = GetPopup,
.destroy = Destroy,
.set_window_geometry = SetWindowGeometry,
.ack_configure = AckConfigure,
};
static void
Unfreeze (XdgRole *role)
{
XLFrameClockUnfreeze (role->clock);
}
static void
Commit (Surface *surface, Role *role)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
if (!xdg_role->impl)
return;
if (xdg_role->state & StatePendingWindowGeometry)
{
xdg_role->current_state.window_geometry_x
= xdg_role->pending_state.window_geometry_x;
xdg_role->current_state.window_geometry_y
= xdg_role->pending_state.window_geometry_y;
xdg_role->current_state.window_geometry_width
= xdg_role->pending_state.window_geometry_width;
xdg_role->current_state.window_geometry_height
= xdg_role->pending_state.window_geometry_height;
}
xdg_role->impl->funcs.commit (role, surface,
xdg_role->impl);
if (xdg_role->state & StatePendingWindowGeometry)
{
if (xdg_role->impl->funcs.handle_geometry_change)
xdg_role->impl->funcs.handle_geometry_change (role,
xdg_role->impl);
xdg_role->state &= ~StatePendingWindowGeometry;
}
/* This flag means no commit has happened after an
ack_configure. */
xdg_role->state &= ~StateWaitingForAckCommit;
/* A frame is already in progress, so instead say that an urgent
update is needed immediately after the frame completes. In any
case, don't run frame callbacks upon buffer release anymore. */
if (XLFrameClockFrameInProgress (xdg_role->clock))
{
if (XLFrameClockCanBatch (xdg_role->clock))
/* But if we can squeeze the frame inside the vertical
blanking period, go ahead. */
goto just_draw_then_return;
xdg_role->state |= StateLateFrame;
xdg_role->state &= ~StatePendingFrameCallback;
if (xdg_role->state & StateWaitingForAckConfigure)
xdg_role->state &= ~StateLateFrameAcked;
else
xdg_role->state |= StateLateFrameAcked;
return;
}
/* If the frame clock is frozen but we are no longer waiting for the
configure event to be acknowledged by the client, unfreeze the
frame clock. */
if (!(xdg_role->state & StateWaitingForAckConfigure))
Unfreeze (xdg_role);
XLFrameClockStartFrame (xdg_role->clock, False);
SubcompositorUpdate (xdg_role->subcompositor);
/* Also run role "commit inside frame" hook. */
if (xdg_role->impl && xdg_role->impl->funcs.commit_inside_frame)
xdg_role->impl->funcs.commit_inside_frame (role, xdg_role->impl);
XLFrameClockEndFrame (xdg_role->clock);
/* Clients which commit when a configure event that has not yet been
acked still expect frame callbacks to be called; however, frame
callbacks are not provided by the frame clock while it is frozen.
If that happens, just run the frame callback immediately. */
if (XLFrameClockIsFrozen (xdg_role->clock))
RunFrameCallbacksConditionally (xdg_role);
return;
just_draw_then_return:
SubcompositorUpdate (xdg_role->subcompositor);
}
static Bool
Setup (Surface *surface, Role *role)
{
XdgRole *xdg_role;
/* Set role->surface here, since this is where the refcounting is
done as well. */
role->surface = surface;
/* Prevent the surface from ever holding another kind of role. */
surface->role_type = XdgType;
xdg_role = XdgRoleFromRole (role);
ViewSetSubcompositor (surface->view,
xdg_role->subcompositor);
ViewSetSubcompositor (surface->under,
xdg_role->subcompositor);
/* Make sure the under view ends up beneath surface->view. */
SubcompositorInsert (xdg_role->subcompositor,
surface->under);
SubcompositorInsert (xdg_role->subcompositor,
surface->view);
/* Count the number of desynchronous children attached to this
surface, directly or indirectly. This number is then updated as
surfaces are attached, made desynchronous and/or removed in
NoteChildSynced and NoteDesyncChild. */
XLUpdateDesynchronousChildren (surface, &xdg_role->n_desync_children);
/* If there are desynchronous children, enable frame refresh
prediction in the frame clock, which batches subframes from
multiple subsurfaces together if they arrive in time. */
if (xdg_role->n_desync_children)
XLFrameClockSetPredictRefresh (xdg_role->clock);
/* Retain the backing data. */
xdg_role->refcount++;
return True;
}
static void
ReleaseBacking (XdgRole *role)
{
if (--role->refcount)
return;
/* Sync, and then release all buffers pending release. The sync is
necessary because the X server does not perform operations
immediately after the Xlib function is called. */
XSync (compositor.display, False);
FreeRecords (role->release_records);
/* Now release the reference to any toplevel implementation that
might be attached. */
if (role->impl)
XLXdgRoleDetachImplementation (&role->role, role->impl);
/* Release all allocated resources. */
RenderDestroyRenderTarget (role->target);
XDestroyWindow (compositor.display, role->window);
/* And the association. */
XLDeleteAssoc (surfaces, role->window);
/* There shouldn't be any children of the subcompositor at this
point. */
SubcompositorFree (role->subcompositor);
/* The frame clock is no longer useful. */
XLFreeFrameClock (role->clock);
/* Free the input region. */
pixman_region32_fini (&role->input_region);
/* Free reconstrain callbacks. */
FreeReconstrainCallbacks (role);
/* And since there are no C level references to the role anymore, it
can be freed. */
XLFree (role);
}
static void
Teardown (Surface *surface, Role *role)
{
XdgRole *xdg_role;
/* Clear role->surface here, since this is where the refcounting is
done as well. */
role->surface = NULL;
xdg_role = XdgRoleFromRole (role);
/* Unparent the surface's views as well. */
ViewUnparent (surface->view);
ViewUnparent (surface->under);
/* Detach the surface's views from the subcompositor. */
ViewSetSubcompositor (surface->view, NULL);
ViewSetSubcompositor (surface->under, NULL);
/* Release the backing data. */
ReleaseBacking (xdg_role);
}
static void
ReleaseBuffer (Surface *surface, Role *role, ExtBuffer *buffer)
{
ReleaseLater (XdgRoleFromRole (role), buffer);
}
static Bool
Subframe (Surface *surface, Role *role)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
/* If the frame clock is frozen, return False. */
if (XLFrameClockIsFrozen (xdg_role->clock))
{
/* However, run frame callbacks. */
RunFrameCallbacksConditionally (xdg_role);
return False;
}
/* If a frame is already in progress, return False. Then, require a
late frame. */
if (XLFrameClockFrameInProgress (xdg_role->clock))
{
if (XLFrameClockCanBatch (xdg_role->clock))
/* But if we can squeeze the frame inside the vertical
blanking period, go ahead. */
return True;
xdg_role->state |= StateLateFrame;
xdg_role->state &= ~StatePendingFrameCallback;
if (xdg_role->state & StateWaitingForAckConfigure)
xdg_role->state &= ~StateLateFrameAcked;
return False;
}
/* I guess subsurface updates don't count as urgent frames? */
XLFrameClockStartFrame (xdg_role->clock, False);
return True;
}
static void
EndSubframe (Surface *surface, Role *role)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
XLFrameClockEndFrame (xdg_role->clock);
}
static Window
GetWindow (Surface *surface, Role *role)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
return xdg_role->window;
}
static void
HandleResourceDestroy (struct wl_resource *resource)
{
XdgRole *role;
role = wl_resource_get_user_data (resource);
role->role.resource = NULL;
/* Release the backing data. */
ReleaseBacking (role);
}
static void
AfterFrame (FrameClock *clock, void *data)
{
XdgRole *role;
role = data;
if (role->state & StateLateFrame)
{
role->state &= ~StateLateFrame;
if (role->state & StateLateFrameAcked)
XLFrameClockUnfreeze (role->clock);
/* Since we are running late, make the compositor draw the frame
now. */
XLFrameClockStartFrame (clock, True);
SubcompositorUpdate (role->subcompositor);
/* Also run role "commit inside frame" hook. */
if (role->impl
&& role->impl->funcs.commit_inside_frame)
role->impl->funcs.commit_inside_frame (&role->role,
role->impl);
XLFrameClockEndFrame (clock);
/* See the comment in Commit about frame callbacks being
attached while the frame clock is frozen. */
if (XLFrameClockIsFrozen (role->clock)
&& role->role.surface)
RunFrameCallbacksConditionally (role);
return;
}
/* If all pending frames have been drawn, run frame callbacks.
Unless some buffers have not yet been released, in which case the
callbacks will be run when they are. */
RunFrameCallbacksConditionally (role);
}
static void
OpaqueRegionChanged (Subcompositor *subcompositor,
void *client_data,
pixman_region32_t *opaque_region)
{
XdgRole *role;
long *data;
int nrects, i;
pixman_box32_t *boxes;
boxes = pixman_region32_rectangles (opaque_region, &nrects);
role = client_data;
if (nrects < 64)
data = alloca (4 * sizeof *data * nrects);
else
data = XLMalloc (4 * sizeof *data * nrects);
for (i = 0; i < nrects; ++i)
{
data[i * 4 + 0] = BoxStartX (boxes[i]);
data[i * 4 + 1] = BoxStartY (boxes[i]);
data[i * 4 + 2] = BoxWidth (boxes[i]);
data[i * 4 + 3] = BoxHeight (boxes[i]);
}
XChangeProperty (compositor.display, role->window,
_NET_WM_OPAQUE_REGION, XA_CARDINAL,
32, PropModeReplace,
(unsigned char *) data, nrects * 4);
if (nrects >= 64)
XLFree (data);
}
static void
InputRegionChanged (Subcompositor *subcompositor,
void *data,
pixman_region32_t *input_region)
{
XdgRole *role;
int nrects, i;
pixman_box32_t *boxes;
XRectangle *rects;
role = data;
boxes = pixman_region32_rectangles (input_region, &nrects);
/* If the number of rectangles is small (<= 256), allocate them on
the stack. Otherwise, use the heap instead. */
if (nrects < 256)
rects = alloca (sizeof *rects * nrects);
else
rects = XLMalloc (sizeof *rects * nrects);
/* Convert the boxes into proper XRectangles and make them the input
region of the window. */
for (i = 0; i < nrects; ++i)
{
rects[i].x = BoxStartX (boxes[i]);
rects[i].y = BoxStartY (boxes[i]);
rects[i].width = BoxWidth (boxes[i]);
rects[i].height = BoxHeight (boxes[i]);
}
XShapeCombineRectangles (compositor.display,
role->window, ShapeInput,
0, 0, rects, nrects,
/* pixman uses the same region
representation as the X server, which is
YXBanded. */
ShapeSet, YXBanded);
if (nrects >= 256)
XLFree (rects);
/* Also save the input region for future use. */
pixman_region32_copy (&role->input_region, input_region);
}
static void
NoteConfigure (XdgRole *role, XEvent *event)
{
if (role->pending_synth_configure)
role->pending_synth_configure--;
/* Update the surface that the surface is inside. */
if (role->role.surface)
XLUpdateSurfaceOutputs (role->role.surface,
event->xconfigure.x + role->min_x,
event->xconfigure.y + role->min_y,
-1, -1);
RunReconstrainCallbacksForXEvent (role, event);
}
static void
CurrentRootPosition (XdgRole *role, int *root_x, int *root_y)
{
Window child_return;
if (role->pending_synth_configure)
{
*root_x = role->pending_root_x;
*root_y = role->pending_root_y;
return;
}
XTranslateCoordinates (compositor.display, role->window,
DefaultRootWindow (compositor.display),
0, 0, root_x, root_y, &child_return);
}
static void
NoteBounds (void *data, int min_x, int min_y,
int max_x, int max_y)
{
XdgRole *role;
int bounds_width, bounds_height, root_x, root_y;
Bool run_reconstrain_callbacks;
role = data;
run_reconstrain_callbacks = False;
if (XLFrameClockIsFrozen (role->clock))
/* We are waiting for the acknowledgement of a configure event.
Don't resize the window until it's acknowledged. */
return;
if (role->state & StateWaitingForAckCommit)
/* Don't resize the window until all configure events are
acknowledged. We wait for a commit on the xdg_toplevel to do
this, because Firefox updates subsurfaces while the old size is
still in effect. */
return;
/* Avoid resizing the window should its actual size not have
changed. */
bounds_width = max_x - min_x + 1;
bounds_height = max_y - min_y + 1;
if (role->bounds_width != bounds_width
|| role->bounds_height != bounds_height)
{
#ifdef DEBUG_GEOMETRY_CALCULATION
fprintf (stderr, "Resizing to: %d %d\n", bounds_width, bounds_height);
#endif
if (role->impl->funcs.note_window_pre_resize)
role->impl->funcs.note_window_pre_resize (&role->role,
role->impl,
bounds_width,
bounds_height);
XResizeWindow (compositor.display, role->window,
bounds_width, bounds_height);
run_reconstrain_callbacks = True;
if (role->impl->funcs.note_window_resized)
role->impl->funcs.note_window_resized (&role->role,
role->impl,
bounds_width,
bounds_height);
}
/* Now, make sure the window stays at the same position relative to
the origin of the view. */
if (min_x != role->min_x || min_y != role->min_y)
{
/* Move the window by the opposite of the amount the min_x and
min_y changed. */
CurrentRootPosition (role, &root_x, &root_y);
XMoveWindow (compositor.display, role->window,
root_x + min_x + role->min_x,
root_y + min_y + role->min_y);
/* Set pending root window positions. These positions will be
used until the movement really happens, to avoid outdated
positions being used after the minimum positions change in
quick succession. */
role->pending_root_x = root_x + min_x + role->min_x;
role->pending_root_y = root_y + min_y + role->min_y;
role->pending_synth_configure++;
}
/* Finally, record the current bounds. */
role->min_x = min_x;
role->max_x = max_x;
role->min_y = min_y;
role->max_y = max_y;
role->bounds_width = bounds_width;
role->bounds_height = bounds_height;
/* Tell the role implementation about the change in window size. */
if (role->impl && role->impl->funcs.note_size)
role->impl->funcs.note_size (&role->role, role->impl,
max_x - min_x + 1,
max_y - min_y + 1);
/* Run reconstrain callbacks if a resize happened. */
if (run_reconstrain_callbacks)
RunReconstrainCallbacks (role);
}
static void
ResizeForMap (XdgRole *role)
{
int min_x, min_y, max_x, max_y;
SubcompositorBounds (role->subcompositor, &min_x,
&min_y, &max_x, &max_y);
NoteBounds (role, min_x, min_y, max_x, max_y);
}
static void
GetResizeDimensions (Surface *surface, Role *role, int *x_out,
int *y_out)
{
XLXdgRoleGetCurrentGeometry (role, NULL, NULL, x_out, y_out);
*x_out *= surface->factor;
*y_out *= surface->factor;
}
static void
PostResize (Surface *surface, Role *role, int west_motion,
int north_motion, int new_width, int new_height)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
if (!xdg_role->impl || !xdg_role->impl->funcs.post_resize)
return;
xdg_role->impl->funcs.post_resize (role, xdg_role->impl,
west_motion, north_motion,
new_width, new_height);
}
static void
MoveBy (Surface *surface, Role *role, int west, int north)
{
XLXdgRoleMoveBy (role, west, north);
}
static void
Rescale (Surface *surface, Role *role)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
/* The window geometry actually applied to the X window (in the form
of frame extents, etc) heavily depends on the output scale. */
if (xdg_role->impl->funcs.handle_geometry_change)
xdg_role->impl->funcs.handle_geometry_change (role, xdg_role->impl);
}
static void
NoteChildSynced (Surface *surface, Role *role)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
if (xdg_role->n_desync_children)
xdg_role->n_desync_children--;
if (!xdg_role->n_desync_children)
XLFrameClockDisablePredictRefresh (xdg_role->clock);
}
static void
NoteDesyncChild (Surface *surface, Role *role)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
xdg_role->n_desync_children++;
XLFrameClockSetPredictRefresh (xdg_role->clock);
}
static void
WriteRedirectProperty (XdgRole *role)
{
unsigned long bypass_compositor;
bypass_compositor = 2;
XChangeProperty (compositor.display, role->window,
_NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL,
32, PropModeReplace,
(unsigned char *) &bypass_compositor, 1);
}
static void
HandleFreeze (void *data)
{
XdgRole *role;
role = data;
/* _NET_WM_SYNC_REQUEST events should be succeeded by a
ConfigureNotify event. */
role->state |= StateWaitingForAckConfigure;
role->state |= StateWaitingForAckCommit;
/* This flag means the WaitingForAckConfigure was caused by a
_NET_WM_SYNC_REQUEST, and the following ConfigureNotify event
might not lead to a configure event being sent. */
role->state |= StateMaybeConfigure;
}
void
XLGetXdgSurface (struct wl_client *client, struct wl_resource *resource,
uint32_t id, struct wl_resource *surface_resource)
{
XdgRole *role;
XSetWindowAttributes attrs;
unsigned int flags;
Surface *surface;
surface = wl_resource_get_user_data (surface_resource);
if (surface->role || (surface->role_type != AnythingType
&& surface->role_type != XdgType))
{
/* A role already exists on that surface. */
wl_resource_post_error (resource, XDG_WM_BASE_ERROR_ROLE,
"surface already has attached role");
return;
}
role = XLSafeMalloc (sizeof *role);
if (!role)
{
wl_client_post_no_memory (client);
return;
}
memset (role, 0, sizeof *role);
role->release_records
= XLSafeMalloc (sizeof *role->release_records);
if (!role->release_records)
{
XLFree (role);
wl_client_post_no_memory (client);
return;
}
role->role.resource = wl_resource_create (client, &xdg_surface_interface,
wl_resource_get_version (resource),
id);
if (!role->role.resource)
{
XLFree (role->release_records);
XLFree (role);
wl_client_post_no_memory (client);
return;
}
wl_resource_set_implementation (role->role.resource, &xdg_surface_impl,
role, HandleResourceDestroy);
/* Add a reference to this role struct since a wl_resource now
refers to it. */
role->refcount++;
role->role.funcs.commit = Commit;
role->role.funcs.teardown = Teardown;
role->role.funcs.setup = Setup;
role->role.funcs.release_buffer = ReleaseBuffer;
role->role.funcs.subframe = Subframe;
role->role.funcs.end_subframe = EndSubframe;
role->role.funcs.get_window = GetWindow;
role->role.funcs.get_resize_dimensions = GetResizeDimensions;
role->role.funcs.post_resize = PostResize;
role->role.funcs.move_by = MoveBy;
role->role.funcs.rescale = Rescale;
role->role.funcs.note_desync_child = NoteDesyncChild;
role->role.funcs.note_child_synced = NoteChildSynced;
attrs.colormap = compositor.colormap;
attrs.border_pixel = border_pixel;
attrs.event_mask = (ExposureMask | StructureNotifyMask
| PropertyChangeMask);
attrs.cursor = InitDefaultCursor ();
flags = (CWColormap | CWBorderPixel | CWEventMask
| CWCursor);
/* Sentinel node. */
role->release_records->next = role->release_records;
role->release_records->last = role->release_records;
role->release_records->buffer = NULL;
role->window = XCreateWindow (compositor.display,
DefaultRootWindow (compositor.display),
0, 0, 20, 20, 0, compositor.n_planes,
InputOutput, compositor.visual, flags,
&attrs);
role->target = RenderTargetFromWindow (role->window);
role->subcompositor = MakeSubcompositor ();
role->clock = XLMakeFrameClockForWindow (role->window);
XLFrameClockSetFreezeCallback (role->clock, HandleFreeze, role);
SubcompositorSetTarget (role->subcompositor, &role->target);
SubcompositorSetInputCallback (role->subcompositor,
InputRegionChanged, role);
SubcompositorSetOpaqueCallback (role->subcompositor,
OpaqueRegionChanged, role);
SubcompositorSetBoundsCallback (role->subcompositor,
NoteBounds, role);
XLSelectStandardEvents (role->window);
XLMakeAssoc (surfaces, role->window, role);
/* Tell the compositing manager to never un-redirect this window.
If it does, frame synchronization will not work. */
WriteRedirectProperty (role);
/* Initialize frame callbacks. */
XLFrameClockAfterFrame (role->clock, AfterFrame, role);
if (!XLSurfaceAttachRole (surface, &role->role))
abort ();
/* Initialize the input region. */
pixman_region32_init (&role->input_region);
/* Initialize the sentinel node of the reconstrain callbacks
list. */
role->reconstrain_callbacks.next = &role->reconstrain_callbacks;
role->reconstrain_callbacks.last = &role->reconstrain_callbacks;
}
Window
XLWindowFromXdgRole (Role *role)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
return xdg_role->window;
}
Subcompositor *
XLSubcompositorFromXdgRole (Role *role)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
return xdg_role->subcompositor;
}
void
XLXdgRoleAttachImplementation (Role *role, XdgRoleImplementation *impl)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
XLAssert (!xdg_role->impl && role->surface);
impl->funcs.attach (role, impl);
xdg_role->impl = impl;
}
void
XLXdgRoleDetachImplementation (Role *role, XdgRoleImplementation *impl)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
XLAssert (xdg_role->impl == impl);
impl->funcs.detach (role, impl);
xdg_role->impl = NULL;
}
void
XLXdgRoleSendConfigure (Role *role, uint32_t serial)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
xdg_role->conf_serial = serial;
xdg_role->state |= StateWaitingForAckConfigure;
xdg_role->state |= StateWaitingForAckCommit;
/* We know know that the ConfigureNotify event following any
_NET_WM_SYNC_REQUEST event was accepted, so clear the maybe
configure flag. */
xdg_role->state &= ~StateMaybeConfigure;
#ifdef DEBUG_GEOMETRY_CALCULATION
fprintf (stderr, "Waiting for ack_configure...\n");
#endif
xdg_surface_send_configure (role->resource, serial);
}
void
XLXdgRoleCalcNewWindowSize (Role *role, int width, int height,
int *new_width, int *new_height)
{
XdgRole *xdg_role;
int temp, temp1, geometry_width, geometry_height;
int current_width, current_height, min_x, min_y, max_x, max_y;
xdg_role = XdgRoleFromRole (role);
if (!xdg_role->current_state.window_geometry_width
/* If no surface exists, we might as well return immediately,
since the scale factor will not be obtainable. */
|| !role->surface)
{
*new_width = width;
*new_height = height;
return;
}
SubcompositorBounds (xdg_role->subcompositor,
&min_x, &min_y, &max_x, &max_y);
/* Adjust the current_width and current_height by the global scale
factor. */
current_width = (max_x - min_x + 1) / role->surface->factor;
current_height = (max_y - min_y + 1) / role->surface->factor;
XLXdgRoleGetCurrentGeometry (role, NULL, NULL, &geometry_width,
&geometry_height);
/* Now, temp and temp1 become the difference between the current
window geometry and the size of the surface (incl. subsurfaces)
in both axes. */
temp = current_width - geometry_width;
temp1 = current_height - geometry_height;
*new_width = width - temp;
*new_height = height - temp1;
#ifdef DEBUG_GEOMETRY_CALCULATION
fprintf (stderr,
"Configure event width, height: %d %d\n"
"Generated width, height: %d %d\n",
width, height, *new_width, *new_height);
#endif
}
int
XLXdgRoleGetWidth (Role *role)
{
XdgRole *xdg_role;
int x, y, x1, y1;
xdg_role = XdgRoleFromRole (role);
SubcompositorBounds (xdg_role->subcompositor,
&x, &y, &x1, &y1);
return x1 - x + 1;
}
int
XLXdgRoleGetHeight (Role *role)
{
XdgRole *xdg_role;
int x, y, x1, y1;
xdg_role = XdgRoleFromRole (role);
SubcompositorBounds (xdg_role->subcompositor,
&x, &y, &x1, &y1);
return y1 - y + 1;
}
void
XLXdgRoleSetBoundsSize (Role *role, int bounds_width, int bounds_height)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
xdg_role->bounds_width = bounds_width;
xdg_role->bounds_height = bounds_height;
}
void
XLXdgRoleGetCurrentGeometry (Role *role, int *x_return, int *y_return,
int *width, int *height)
{
XdgRole *xdg_role;
int x, y, x1, y1, min_x, max_x, min_y, max_y;
xdg_role = XdgRoleFromRole (role);
SubcompositorBounds (xdg_role->subcompositor,
&min_x, &min_y, &max_x, &max_y);
if (!xdg_role->current_state.window_geometry_width)
{
if (x_return)
*x_return = min_x;
if (y_return)
*y_return = min_y;
if (width)
*width = max_x - min_x + 1;
if (height)
*height = max_y - min_y + 1;
return;
}
x = xdg_role->current_state.window_geometry_x;
y = xdg_role->current_state.window_geometry_y;
x1 = (xdg_role->current_state.window_geometry_x
+ xdg_role->current_state.window_geometry_width - 1);
y1 = (xdg_role->current_state.window_geometry_y
+ xdg_role->current_state.window_geometry_height - 1);
x1 = MIN (x1, max_x);
y1 = MIN (y1, max_y);
x = MAX (min_x, x);
y = MAX (min_y, y);
if (x_return)
*x_return = x;
if (y_return)
*y_return = y;
if (width)
*width = x1 - x + 1;
if (height)
*height = y1 - y + 1;
}
void
XLXdgRoleNoteConfigure (Role *role, XEvent *event)
{
NoteConfigure (XdgRoleFromRole (role), event);
}
void
XLRetainXdgRole (Role *role)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
xdg_role->refcount++;
}
void
XLReleaseXdgRole (Role *role)
{
ReleaseBacking (XdgRoleFromRole (role));
}
void
XLXdgRoleCurrentRootPosition (Role *role, int *root_x, int *root_y)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
CurrentRootPosition (xdg_role, root_x, root_y);
}
XdgRoleImplementationType
XLTypeOfXdgRole (Role *role)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
return xdg_role->type;
}
XdgRoleImplementation *
XLImplementationOfXdgRole (Role *role)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
return xdg_role->impl;
}
Bool
XLXdgRoleInputRegionContains (Role *role, int x, int y)
{
XdgRole *xdg_role;
pixman_box32_t dummy_box;
xdg_role = XdgRoleFromRole (role);
return pixman_region32_contains_point (&xdg_role->input_region,
x, y, &dummy_box);
}
void
XLXdgRoleResizeForMap (Role *role)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
ResizeForMap (xdg_role);
}
void *
XLXdgRoleRunOnReconstrain (Role *role, void (*configure_func) (void *,
XEvent *),
void (*resize_func) (void *), void *data)
{
ReconstrainCallback *callback;
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
callback = AddCallbackAfter (&xdg_role->reconstrain_callbacks);
callback->configure = configure_func;
callback->resized = resize_func;
callback->data = data;
return callback;
}
void
XLXdgRoleCancelReconstrainCallback (void *key)
{
ReconstrainCallback *callback;
callback = key;
UnlinkReconstrainCallback (callback);
}
void
XLXdgRoleReconstrain (Role *role, XEvent *event)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
RunReconstrainCallbacksForXEvent (xdg_role, event);
}
void
XLXdgRoleMoveBy (Role *role, int west, int north)
{
int root_x, root_y;
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
/* Move the window by the opposite of west and north. */
CurrentRootPosition (xdg_role, &root_x, &root_y);
XMoveWindow (compositor.display, xdg_role->window,
root_x - west, root_y - north);
/* Set pending root window positions. These positions will be
used until the movement really happens, to avoid outdated
positions being used after the minimum positions change in
quick succession. */
xdg_role->pending_root_x = root_x - west;
xdg_role->pending_root_y = root_y - north;
xdg_role->pending_synth_configure++;
}
FrameClock *
XLXdgRoleGetFrameClock (Role *role)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
return xdg_role->clock;
}
void
XLInitXdgSurfaces (void)
{
XColor alloc;
int shape_minor, shape_major, shape_error;
surfaces = XLCreateAssocTable (2048);
alloc.red = 0;
alloc.green = 65535;
alloc.blue = 0;
if (!XAllocColor (compositor.display, compositor.colormap,
&alloc))
{
fprintf (stderr, "Failed to allocate green pixel\n");
exit (1);
}
border_pixel = alloc.pixel;
/* Now initialize the nonrectangular window shape extension. We
need a version that supports input shapes, which means 1.1 or
later. */
if (!XShapeQueryExtension (compositor.display, &shape_base,
&shape_error))
{
fprintf (stderr, "The Nonrectangular Window Shape extension is not"
" present on the X server\n");
exit (1);
}
if (!XShapeQueryVersion (compositor.display,
&shape_major, &shape_minor))
{
fprintf (stderr, "A supported version of the Nonrectangular Window"
" Shape extension is not present on the X server\n");
exit (1);
}
if (shape_major < 1 || (shape_major == 1 && shape_minor < 1))
{
fprintf (stderr, "The version of the Nonrectangular Window Shape"
" extension is too old\n");
exit (1);
}
}
XdgRoleImplementation *
XLLookUpXdgToplevel (Window window)
{
XdgRole *role;
role = XLLookUpAssoc (surfaces, window);
if (!role)
return NULL;
if (role->type != TypeToplevel)
return NULL;
return role->impl;
}
XdgRoleImplementation *
XLLookUpXdgPopup (Window window)
{
XdgRole *role;
role = XLLookUpAssoc (surfaces, window);
if (!role)
return NULL;
if (role->type != TypePopup)
return NULL;
return role->impl;
}
void
XLXdgRoleNoteRejectedConfigure (Role *role)
{
XdgRole *xdg_role;
xdg_role = XdgRoleFromRole (role);
if (xdg_role->state & StateMaybeConfigure)
{
/* A configure event immediately following _NET_WM_SYNC_REQUEST
was rejected, meaning that we do not have to change anything
before unfreezing the frame clock. */
xdg_role->state &= ~StateWaitingForAckConfigure;
xdg_role->state &= ~StateWaitingForAckCommit;
xdg_role->state &= ~StateMaybeConfigure;
/* Unfreeze the frame clock now. */
XLFrameClockUnfreeze (xdg_role->clock);
}
}