forked from 12to11/12to11

This is in preparation for a migration to the Present extension on systems that do not support frame synchronization. * 12to11.man: Remove option that is no longer useful. * Imakefile (SRCS, OBJS): Add `sync_source.c.'. * compositor.h (enum _RenderMode): New enum. (struct _RenderFuncs): Add some new functions. (enum _FrameMode): Remove ModeNotifyDisablePresent. * frame_clock.c (struct _FrameClockCallback): Add draw time to frame callback. (struct _FrameClock): Rearrange for alignment and remove many unused members. (BumpFrame): New function. (FreezeForValue): Remove code that is no longer useful, as clock->frozen no longer exists. (StartFrame, EndFrame): Remove clock "freezing" logic. Only `need_configure' remains. (RunFrameCallbacks, NoteFakeFrame): Add frame drawn time. (XLFrameClockAfterFrame): Adjust for new type of callback. (XLFrameClockFreeze): Remove function. (XLFrameClockFrameInProgress): Remove test for `frozen_until_end_frame'. (XLFrameClockHandleFrameEvent): Improve code in accordance with above changes. (XLFrameClockUnfreeze, XLFrameClockNeedConfigure): Remove functions. (XLFrameClockIsFrozen): Remove function. (XLFrameClockSetFreezeCallback): Accept new callback `fast_forward_callback'. (XLFrameClockGetFrameTime): Remove unused function. * icon_surface.c (struct _IconSurface, ReleaseBacking) (ReleaseBuffer, RunFrameCallbacks, AfterFrame, Commit) (SubsurfaceUpdate, XLGetIconSurface) (XLHandleOneXEventForIconSurfaces): Switch the icon surface to the sync helper abstraction, and allow asynch buffer release from inside. * picture_renderer.c (struct _PictureTarget): New field `next_msc' and `render_mode'. (SwapBackBuffers): Respect the render mode. (InitSynchronizedPresentation): Delete function. (InitAdditionalModifiers): Remove incorrect comment. (InitRenderFuncs): Stop initializing obsolete option. (SetRenderMode): New function. (PresentToWindow): Respect the render mode. (NotifyMsc): New function. (picture_render_funcs): Add notify_msc. (HandlePresentCompleteNotify): Call completion callback with the fraame counter. * renderer.c (RenderSetRenderMode): (RenderNotifyMsc): New functions. * subcompositor.c (struct _Subcompositor) (SubcompositorSetNoteFrameCallback): Add msc and ust to note frame callback. (PresentCompletedCallback, RenderCompletedCallback): Call with msc and ust. (BeginFrame): When presentation is being synchronized and there is no existing presentation callback, ask for an event to be sent on the next frame. (EndFrame): Do not clear the presentation callbacks, as the update might not touch anything. * test.c (NoteFrame): Update prototype. * xdg_popup.c (InternalReposition): Stop "freezing" the frame clock. * xdg_surface.c (struct _XdgRole): Replace the frame clock with the sync helper abstraction. (RunFrameCallbacks): Run with the frame time. (RunFrameCallbacksConditionally): Save the pending frame time. (UpdateFrameRefreshPrediction): Delete function. (XLHandleXEventForXdgSurfaces): Give events to the sync helper instead. (Unfreeze, IsRoleMapped, CheckFrame): Remove functions. (Commit, SubsurfaceUpdate): Update using the sync helper instead, only if not pending ack commit. (MaybeRunLateFrame, AfterFrame): Delete functions. (NoteConfigure, NoteBounds): Update for the sync helper. (WriteRedirectProperty): Always require redirection for now. Disabling redirection requires sorting out some Present problems on the X server side. (WasFrameQueued, NoteFrame): Delete functions. (HandleFreeze): Delete function. (HandleResize, CheckFastForward, HandleFrameCallback): New functions. (XLGetXdgSurface): Use the sync helper for most things. (XLXdgRoleSendConfigure, XLXdgRoleReconstrain) (XLXdgRoleGetFrameClock): Delete function. (XLXdgRoleNoteRejectedConfigure): Clean up for using the sync clock instead.
1906 lines
48 KiB
C
1906 lines
48 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"
|
|
#include "xdg-shell.h"
|
|
|
|
#include <X11/extensions/shape.h>
|
|
|
|
#define XdgRoleFromRole(role) ((XdgRole *) (role))
|
|
|
|
/* This is the default core event mask used by our windows. */
|
|
#define DefaultEventMask \
|
|
(ExposureMask | StructureNotifyMask | PropertyChangeMask)
|
|
|
|
enum
|
|
{
|
|
StatePendingFrameCallback = 1,
|
|
StatePendingWindowGeometry = (1 << 2),
|
|
StateWaitingForAckConfigure = (1 << 3),
|
|
StateWaitingForAckCommit = (1 << 4),
|
|
StateMaybeConfigure = (1 << 5),
|
|
StateDirtyFrameExtents = (1 << 6),
|
|
StateTemporaryBounds = (1 << 7),
|
|
StatePendingBufferRelease = (1 << 8),
|
|
};
|
|
|
|
typedef struct _XdgRole XdgRole;
|
|
typedef struct _XdgState XdgState;
|
|
typedef struct _ReleaseLaterRecord ReleaseLaterRecord;
|
|
typedef struct _ReconstrainCallback ReconstrainCallback;
|
|
typedef struct _PingEvent PingEvent;
|
|
|
|
/* 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;
|
|
|
|
/* The link to the wm_base's list of surfaces. */
|
|
XdgRoleList link;
|
|
|
|
/* The attached XdgWmBase. Not valid if link->next is NULL. */
|
|
XdgWmBase *wm_base;
|
|
|
|
/* 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;
|
|
|
|
/* The pending frame ID. */
|
|
uint64_t pending_frame;
|
|
|
|
/* List of pending ping events. */
|
|
XLList *ping_events;
|
|
|
|
/* Number of references to this role. Used when the client
|
|
terminates and the Wayland library destroys objects out of
|
|
order. */
|
|
int refcount;
|
|
|
|
/* Various role state. */
|
|
int state;
|
|
|
|
/* Buffer release helper. */
|
|
BufferReleaseHelper *release_helper;
|
|
|
|
/* The synchronization helper. */
|
|
SyncHelper *sync_helper;
|
|
|
|
/* The pending xdg_surface state. */
|
|
XdgState pending_state;
|
|
|
|
/* The current xdg_surface state. */
|
|
XdgState current_state;
|
|
|
|
/* List of callbacks run upon a ConfigureNotify event. */
|
|
ReconstrainCallback reconstrain_callbacks;
|
|
|
|
/* Configure event serial. */
|
|
uint32_t conf_serial, last_specified_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 pending frame time. */
|
|
uint32_t pending_frame_time;
|
|
|
|
/* The input region of the attached subsurface. */
|
|
pixman_region32_t input_region;
|
|
|
|
/* The type of the attached role. */
|
|
XdgRoleImplementationType type;
|
|
};
|
|
|
|
struct _PingEvent
|
|
{
|
|
/* Function called to reply to this event. */
|
|
void (*reply_func) (XEvent *);
|
|
|
|
/* The event. */
|
|
XEvent event;
|
|
};
|
|
|
|
/* Event base of the XShape extension. */
|
|
int shape_base;
|
|
|
|
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
|
|
RunFrameCallbacks (Surface *surface, XdgRole *role, uint32_t frame_time)
|
|
{
|
|
/* Surface can be NULL for various reasons, especially events
|
|
arriving after the shell surface is detached. */
|
|
|
|
if (!surface)
|
|
return;
|
|
|
|
XLSurfaceRunFrameCallbacksMs (surface, frame_time);
|
|
}
|
|
|
|
static void
|
|
RunFrameCallbacksConditionally (XdgRole *role, uint32_t frame_time)
|
|
{
|
|
if (!(role->state & StatePendingBufferRelease))
|
|
RunFrameCallbacks (role->role.surface, role, frame_time);
|
|
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;
|
|
role->pending_frame_time = frame_time;
|
|
}
|
|
}
|
|
|
|
static void
|
|
AllBuffersReleased (void *data)
|
|
{
|
|
XdgRole *role;
|
|
Surface *surface;
|
|
|
|
role = data;
|
|
surface = role->role.surface;
|
|
|
|
/* Clear the buffer release flag. */
|
|
role->state &= ~StatePendingBufferRelease;
|
|
|
|
/* Run frame callbacks now, as no more buffers are waiting to be
|
|
released. */
|
|
if (surface && role->state & StatePendingFrameCallback)
|
|
{
|
|
RunFrameCallbacks (surface, role,
|
|
role->pending_frame_time);
|
|
|
|
role->state &= ~StatePendingFrameCallback;
|
|
}
|
|
}
|
|
|
|
Bool
|
|
XLHandleXEventForXdgSurfaces (XEvent *event)
|
|
{
|
|
XdgRole *role;
|
|
Window window;
|
|
|
|
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)
|
|
{
|
|
SyncHelperHandleFrameEvent (role->sync_helper, event);
|
|
return True;
|
|
}
|
|
|
|
return False;
|
|
}
|
|
|
|
if (event->type == Expose)
|
|
{
|
|
role = XLLookUpAssoc (surfaces, event->xexpose.window);
|
|
|
|
if (role)
|
|
{
|
|
SubcompositorExpose (role->subcompositor, event);
|
|
|
|
return True;
|
|
}
|
|
|
|
return False;
|
|
}
|
|
|
|
if (event->type == KeyPress || event->type == KeyRelease)
|
|
{
|
|
/* These events are actually sent by the input method library
|
|
upon receiving XIM_COMMIT messages. */
|
|
|
|
role = XLLookUpAssoc (surfaces, event->xkey.window);
|
|
|
|
if (role && role->role.surface)
|
|
{
|
|
XLTextInputDispatchCoreEvent (role->role.surface, 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;
|
|
}
|
|
|
|
/* Now detach the role from its surface, which can be reused in the
|
|
future. */
|
|
if (role->role.surface)
|
|
XLSurfaceReleaseRole (role->role.surface, &role->role);
|
|
|
|
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;
|
|
|
|
#ifdef DEBUG_GEOMETRY_CALCULATION
|
|
fprintf (stderr, "Client requested geometry: [%d %d %d %d]\n",
|
|
role->pending_state.window_geometry_x,
|
|
role->pending_state.window_geometry_y,
|
|
role->pending_state.window_geometry_width,
|
|
role->pending_state.window_geometry_height);
|
|
#endif
|
|
}
|
|
|
|
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;
|
|
|
|
#ifdef DEBUG_GEOMETRY_CALCULATION
|
|
fprintf (stderr, "ack_configure: %"PRIu32"\n", serial);
|
|
#endif
|
|
|
|
if (serial && serial <= xdg_role->last_specified_serial)
|
|
{
|
|
/* The client specified the same serial twice. */
|
|
wl_resource_post_error (resource, XDG_SURFACE_ERROR_INVALID_SERIAL,
|
|
"same serial specified twice");
|
|
return;
|
|
}
|
|
|
|
if (serial == xdg_role->conf_serial)
|
|
{
|
|
xdg_role->last_specified_serial = serial;
|
|
xdg_role->state &= ~StateWaitingForAckConfigure;
|
|
|
|
/* Garbage the subcompositor too, since contents could be
|
|
exposed due to changes in bounds. */
|
|
SubcompositorGarbage (xdg_role->subcompositor);
|
|
|
|
#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
|
|
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;
|
|
|
|
#ifdef DEBUG_GEOMETRY_CALCULATION
|
|
fprintf (stderr, "Client set window geometry to: [%d %d %d %d]\n"
|
|
"State is: %d\n",
|
|
xdg_role->current_state.window_geometry_x,
|
|
xdg_role->current_state.window_geometry_y,
|
|
xdg_role->current_state.window_geometry_width,
|
|
xdg_role->current_state.window_geometry_height,
|
|
xdg_role->state & StateWaitingForAckConfigure);
|
|
#endif
|
|
|
|
/* Now, clear the "pending window geometry" flag. */
|
|
xdg_role->state &= ~StatePendingWindowGeometry;
|
|
|
|
/* Next, set the "dirty frame extents" flag; this is then used
|
|
to update the window geometry the next time the window is
|
|
resized. */
|
|
xdg_role->state |= StateDirtyFrameExtents;
|
|
}
|
|
|
|
xdg_role->impl->funcs.commit (role, surface,
|
|
xdg_role->impl);
|
|
|
|
/* This flag means no commit has happened after an
|
|
ack_configure. */
|
|
if (!(xdg_role->state & StateWaitingForAckConfigure)
|
|
&& xdg_role->state & StateWaitingForAckCommit)
|
|
{
|
|
#ifdef DEBUG_GEOMETRY_CALCULATION
|
|
fprintf (stderr, "Client aknowledged commit\n");
|
|
#endif
|
|
xdg_role->state &= ~StateWaitingForAckCommit;
|
|
}
|
|
|
|
if (!(xdg_role->state & StateWaitingForAckCommit))
|
|
{
|
|
/* Tell the sync helper to update the frame. This will also
|
|
complete any resize if necessary. */
|
|
SyncHelperUpdate (xdg_role->sync_helper);
|
|
|
|
/* Run the after_commit function of the role implementation,
|
|
which peforms actions such as posting pending configure
|
|
events for built-in resize. */
|
|
|
|
if (xdg_role->impl->funcs.after_commit)
|
|
xdg_role->impl->funcs.after_commit (role, surface,
|
|
xdg_role->impl);
|
|
}
|
|
else
|
|
/* Now, tell the sync helper to generate a frame.
|
|
Many clients do this:
|
|
|
|
wl_surface@1.frame (new id wl_callback@2)
|
|
wl_surface@1.commit ()
|
|
|
|
and upon receiving a configure event, potentially call:
|
|
|
|
xdg_surface@3.ack_configure (1)
|
|
|
|
but do not commit (or even ack_configure) until the frame
|
|
callback is triggered.
|
|
|
|
That is problematic because the frame clock is not unfrozen
|
|
until the commit happens. To work around the problem, tell the
|
|
sync helper to check for this situation, and run frame
|
|
callbacks if necessary. */
|
|
SyncHelperCheckFrameCallback (xdg_role->sync_helper);
|
|
|
|
return;
|
|
}
|
|
|
|
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);
|
|
|
|
/* Retain the backing data. */
|
|
xdg_role->refcount++;
|
|
|
|
return True;
|
|
}
|
|
|
|
static void
|
|
ReleaseBacking (XdgRole *role)
|
|
{
|
|
if (--role->refcount)
|
|
return;
|
|
|
|
/* Unlink the role if it is still linked. */
|
|
|
|
if (role->link.next)
|
|
{
|
|
role->link.next->last = role->link.last;
|
|
role->link.last->next = role->link.next;
|
|
}
|
|
|
|
/* Release all buffers pending release. The sync is necessary
|
|
because the X server does not perform operations immediately
|
|
after the Xlib function is called. */
|
|
FreeBufferReleaseHelper (role->release_helper);
|
|
|
|
/* 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);
|
|
|
|
/* Free associated ping events. */
|
|
XLListFree (role->ping_events, XLFree);
|
|
|
|
/* And the association. */
|
|
XLDeleteAssoc (surfaces, role->window);
|
|
|
|
/* Destroy the sync helper. */
|
|
FreeSyncHelper (role->sync_helper);
|
|
|
|
/* There shouldn't be any children of the subcompositor at this
|
|
point. */
|
|
SubcompositorFree (role->subcompositor);
|
|
|
|
/* 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)
|
|
{
|
|
RenderBuffer render_buffer;
|
|
XdgRole *xdg_role;
|
|
|
|
render_buffer = XLRenderBufferFromBuffer (buffer);
|
|
xdg_role = XdgRoleFromRole (role);
|
|
|
|
if (RenderIsBufferIdle (render_buffer, xdg_role->target))
|
|
/* If the buffer is already idle, release it now. */
|
|
XLReleaseBuffer (buffer);
|
|
else
|
|
{
|
|
/* Release the buffer once it is destroyed or becomes idle. */
|
|
ReleaseBufferWithHelper (xdg_role->release_helper,
|
|
buffer, xdg_role->target);
|
|
xdg_role->state |= StatePendingBufferRelease;
|
|
}
|
|
}
|
|
|
|
static void
|
|
SubsurfaceUpdate (Surface *surface, Role *role)
|
|
{
|
|
XdgRole *xdg_role;
|
|
|
|
xdg_role = XdgRoleFromRole (role);
|
|
|
|
if (xdg_role->state & StateWaitingForAckCommit)
|
|
{
|
|
/* Updates are being postponed until the next commit after
|
|
ack_configure.
|
|
|
|
Now, tell the sync helper to generate a frame.
|
|
Many clients do this:
|
|
|
|
wl_surface@1.frame (new id wl_callback@2)
|
|
wl_surface@1.commit ()
|
|
|
|
and upon receiving a configure event, potentially call:
|
|
|
|
xdg_surface@3.ack_configure (1)
|
|
|
|
but do not commit (or even ack_configure) until the frame
|
|
callback is triggered.
|
|
|
|
That is problematic because the frame clock is not unfrozen
|
|
until the commit happens. To work around the problem, tell
|
|
the sync helper to check for this situation, and run frame
|
|
callbacks if necessary. */
|
|
SyncHelperCheckFrameCallback (xdg_role->sync_helper);
|
|
return;
|
|
}
|
|
|
|
/* Tell the sync helper to do an update. */
|
|
SyncHelperUpdate (xdg_role->sync_helper);
|
|
}
|
|
|
|
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
|
|
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--;
|
|
|
|
if (role->role.surface)
|
|
{
|
|
/* Update the list of outputs that the surface is inside. */
|
|
XLUpdateSurfaceOutputs (role->role.surface,
|
|
event->xconfigure.x + role->min_x,
|
|
event->xconfigure.y + role->min_y,
|
|
-1, -1);
|
|
|
|
/* Update pointer constraints. */
|
|
XLPointerConstraintsSurfaceMovedTo (role->role.surface,
|
|
event->xconfigure.x,
|
|
event->xconfigure.y);
|
|
}
|
|
|
|
/* Tell the frame clock how many WM-generated configure events have
|
|
arrived. */
|
|
SyncHelperNoteConfigureEvent (role->sync_helper);
|
|
|
|
/* Run reconstrain callbacks. */
|
|
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, root_position_initialized;
|
|
|
|
role = data;
|
|
run_reconstrain_callbacks = False;
|
|
root_position_initialized = False;
|
|
|
|
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;
|
|
|
|
if (role->state & StateTemporaryBounds)
|
|
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 (from: %d %d)\n",
|
|
bounds_width, bounds_height, role->bounds_width,
|
|
role->bounds_height);
|
|
#endif
|
|
|
|
/* Update the list of outputs that the surface is inside.
|
|
First, get the root window position. */
|
|
CurrentRootPosition (role, &root_x, &root_y);
|
|
root_position_initialized = True;
|
|
|
|
/* Next, update the output set. */
|
|
XLUpdateSurfaceOutputs (role->role.surface, root_x + min_x,
|
|
root_y + min_y, -1, -1);
|
|
|
|
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);
|
|
}
|
|
|
|
if (role->state & StateDirtyFrameExtents)
|
|
{
|
|
/* Only handle window geometry changes once a commit happens and
|
|
the window is really resized. */
|
|
|
|
if (role->impl->funcs.handle_geometry_change)
|
|
role->impl->funcs.handle_geometry_change (&role->role,
|
|
role->impl);
|
|
|
|
role->state &= ~StateDirtyFrameExtents;
|
|
}
|
|
|
|
/* 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. */
|
|
|
|
if (!root_position_initialized)
|
|
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);
|
|
run_reconstrain_callbacks = True;
|
|
|
|
/* 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
|
|
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
|
|
ResizeForMap (XdgRole *role)
|
|
{
|
|
int min_x, min_y, max_x, max_y;
|
|
|
|
SubcompositorBounds (role->subcompositor, &min_x,
|
|
&min_y, &max_x, &max_y);
|
|
|
|
/* At this point, we are probably still waiting for ack_commit; as a
|
|
result, NoteBounds will not really resize the window. */
|
|
NoteBounds (role, min_x, min_y, max_x, max_y);
|
|
|
|
#ifdef DEBUG_GEOMETRY_CALCULATION
|
|
fprintf (stderr, "ResizeForMap: %d %d\n",
|
|
max_x - min_x + 1, max_y - min_y + 1);
|
|
#endif
|
|
|
|
if (role->state & StateDirtyFrameExtents)
|
|
{
|
|
/* Only handle window geometry changes once a commit
|
|
happens. */
|
|
|
|
if (role->impl->funcs.handle_geometry_change)
|
|
role->impl->funcs.handle_geometry_change (&role->role,
|
|
role->impl);
|
|
|
|
role->state &= ~StateDirtyFrameExtents;
|
|
}
|
|
|
|
/* Resize the window pre-map. This should generate a
|
|
ConfigureNotify event once the resize completes. */
|
|
XResizeWindow (compositor.display, role->window,
|
|
max_x - min_x + 1, max_y - min_y + 1);
|
|
|
|
if (role->impl->funcs.note_window_resized)
|
|
role->impl->funcs.note_window_resized (&role->role,
|
|
role->impl,
|
|
max_x - min_x + 1,
|
|
max_y - min_y + 1);
|
|
}
|
|
|
|
static void
|
|
GetResizeDimensions (Surface *surface, Role *role, int *x_out,
|
|
int *y_out)
|
|
{
|
|
XLXdgRoleGetCurrentGeometry (role, NULL, NULL, x_out, y_out);
|
|
|
|
/* Scale these surface-local dimensions to window-local ones. */
|
|
TruncateSurfaceToWindow (surface, *x_out, *y_out, x_out, y_out);
|
|
}
|
|
|
|
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);
|
|
|
|
/* Also update the configure bounds if necessary. */
|
|
|
|
if (xdg_role->impl->funcs.rescale)
|
|
xdg_role->impl->funcs.rescale (role, xdg_role->impl);
|
|
}
|
|
|
|
static void
|
|
HandleResize (void *data, Bool only_frame)
|
|
{
|
|
XdgRole *role;
|
|
|
|
role = data;
|
|
|
|
if (only_frame)
|
|
{
|
|
SyncHelperCheckFrameCallback (role->sync_helper);
|
|
return;
|
|
}
|
|
|
|
/* _NET_WM_SYNC_REQUEST events should be succeeded by a
|
|
ConfigureNotify event. */
|
|
role->state |= StateWaitingForAckConfigure;
|
|
role->state |= StateWaitingForAckCommit;
|
|
|
|
/* Cancel any pending frame. Nothing should be displayed while an
|
|
ack_configure is pending. */
|
|
SyncHelperClearPendingFrame (role->sync_helper);
|
|
|
|
/* 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;
|
|
|
|
/* If a freeze comes between commit and configure, then clients will
|
|
hang indefinitely waiting for _NET_WM_FRAME_DRAWN. Make the sync
|
|
helper check for this situation. */
|
|
SyncHelperCheckFrameCallback (role->sync_helper);
|
|
|
|
#ifdef DEBUG_GEOMETRY_CALCULATION
|
|
fprintf (stderr, "Waiting for ack_configure (?)...\n");
|
|
#endif
|
|
}
|
|
|
|
static Bool
|
|
CheckFastForward (void *data)
|
|
{
|
|
XdgRole *role;
|
|
|
|
/* Return whether or not it is ok to fast forward the frame
|
|
counter while ending a frame. */
|
|
|
|
role = data;
|
|
return !(role->state & StateWaitingForAckCommit);
|
|
}
|
|
|
|
static void
|
|
SelectExtraEvents (Surface *surface, Role *role,
|
|
unsigned long event_mask)
|
|
{
|
|
XdgRole *xdg_role;
|
|
|
|
xdg_role = XdgRoleFromRole (role);
|
|
|
|
/* Select extra events for the input method. */
|
|
XSelectInput (compositor.display, xdg_role->window,
|
|
DefaultEventMask | event_mask);
|
|
|
|
/* Set the target standard event mask. */
|
|
RenderSetStandardEventMask (xdg_role->target,
|
|
DefaultEventMask | event_mask);
|
|
}
|
|
|
|
static void
|
|
NoteFocus (Surface *surface, Role *role, FocusMode focus)
|
|
{
|
|
XdgRole *xdg_role;
|
|
|
|
xdg_role = XdgRoleFromRole (role);
|
|
|
|
if (xdg_role->impl && xdg_role->impl->funcs.note_focus)
|
|
xdg_role->impl->funcs.note_focus (role, xdg_role->impl,
|
|
focus);
|
|
}
|
|
|
|
static void
|
|
OutputsChanged (Surface *surface, Role *role)
|
|
{
|
|
XdgRole *xdg_role;
|
|
|
|
xdg_role = XdgRoleFromRole (role);
|
|
|
|
if (xdg_role->impl && xdg_role->impl->funcs.outputs_changed)
|
|
xdg_role->impl->funcs.outputs_changed (role, xdg_role->impl);
|
|
}
|
|
|
|
static void
|
|
Activate (Surface *surface, Role *role, int deviceid,
|
|
Timestamp timestamp, Surface *activator_surface)
|
|
{
|
|
XdgRole *xdg_role;
|
|
|
|
xdg_role = XdgRoleFromRole (role);
|
|
|
|
if (xdg_role->impl && xdg_role->impl->funcs.activate)
|
|
xdg_role->impl->funcs.activate (role, xdg_role->impl,
|
|
deviceid,
|
|
timestamp.milliseconds,
|
|
activator_surface);
|
|
}
|
|
|
|
static void
|
|
HandleFrameCallback (void *data, uint32_t frame_time)
|
|
{
|
|
XdgRole *role;
|
|
|
|
role = data;
|
|
RunFrameCallbacksConditionally (role, frame_time);
|
|
}
|
|
|
|
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;
|
|
XdgWmBase *wm_base;
|
|
|
|
surface = wl_resource_get_user_data (surface_resource);
|
|
wm_base = wl_resource_get_user_data (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->role.resource = wl_resource_create (client, &xdg_surface_interface,
|
|
wl_resource_get_version (resource),
|
|
id);
|
|
|
|
if (!role->role.resource)
|
|
{
|
|
XLFree (role);
|
|
wl_client_post_no_memory (client);
|
|
|
|
return;
|
|
}
|
|
|
|
wl_resource_set_implementation (role->role.resource, &xdg_surface_impl,
|
|
role, HandleResourceDestroy);
|
|
|
|
/* Link the role onto the wm base. */
|
|
role->link.next = wm_base->list.next;
|
|
role->link.last = &wm_base->list;
|
|
role->link.role = &role->role;
|
|
wm_base->list.next->last = &role->link;
|
|
wm_base->list.next = &role->link;
|
|
role->wm_base = wm_base;
|
|
|
|
/* 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.subsurface_update = SubsurfaceUpdate;
|
|
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.select_extra_events = SelectExtraEvents;
|
|
role->role.funcs.note_focus = NoteFocus;
|
|
role->role.funcs.outputs_changed = OutputsChanged;
|
|
role->role.funcs.activate = Activate;
|
|
|
|
attrs.colormap = compositor.colormap;
|
|
attrs.border_pixel = border_pixel;
|
|
attrs.event_mask = DefaultEventMask;
|
|
attrs.cursor = InitDefaultCursor ();
|
|
flags = CWColormap | CWBorderPixel | CWEventMask | CWCursor;
|
|
|
|
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, DefaultEventMask);
|
|
role->release_helper = MakeBufferReleaseHelper (AllBuffersReleased,
|
|
role);
|
|
|
|
/* Set the client. */
|
|
RenderSetClient (role->target, client);
|
|
|
|
role->subcompositor = MakeSubcompositor ();
|
|
role->sync_helper = MakeSyncHelper (role->subcompositor,
|
|
role->window,
|
|
role->target,
|
|
HandleFrameCallback,
|
|
&role->role);
|
|
SyncHelperSetResizeCallback (role->sync_helper, HandleResize,
|
|
CheckFastForward);
|
|
|
|
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);
|
|
|
|
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;
|
|
|
|
/* Cancel any pending frame. Nothing should be displayed while an
|
|
ack_configure is pending. */
|
|
SyncHelperClearPendingFrame (xdg_role->sync_helper);
|
|
|
|
/* See the comment under XLXdgRoleSetBoundsSize. */
|
|
xdg_role->state &= ~StateTemporaryBounds;
|
|
|
|
/* 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 (%"PRIu32")...\n",
|
|
xdg_role->conf_serial);
|
|
#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);
|
|
|
|
/* Calculate the current width and height. */
|
|
current_width = (max_x - min_x + 1);
|
|
current_height = (max_y - min_y + 1);
|
|
|
|
/* Adjust the current_width and current_height by the scale
|
|
factor. */
|
|
TruncateScaleToSurface (role->surface, current_width,
|
|
current_height, ¤t_width,
|
|
¤t_height);
|
|
|
|
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;
|
|
|
|
#ifdef DEBUG_GEOMETRY_CALCULATION
|
|
fprintf (stderr, "Set new bounds size: %d %d\n", bounds_width,
|
|
bounds_height);
|
|
#endif
|
|
|
|
/* Now, a temporary bounds_width and bounds_height has been
|
|
recorded. This means that if a configure event has not yet been
|
|
delivered, then any subsequent SubcompositorUpdate will cause
|
|
NoteBounds to resize back to the old width and height, confusing
|
|
the window manager and possibly causing it to maximize us.
|
|
|
|
Set a flag that tells NoteBounds to abstain from resizing the
|
|
window. This flag is then cleared once a configure event is
|
|
delivered, or the next time the role is mapped. */
|
|
xdg_role->state |= StateTemporaryBounds;
|
|
}
|
|
|
|
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);
|
|
|
|
/* Clear the StateTemporaryBounds flag; it should not persist after
|
|
mapping, as a configure event is no longer guaranteed to be sent
|
|
if the toplevel is unmapped immediately after
|
|
XLXdgRoleSetBoundsSize. */
|
|
xdg_role->state &= ~StateTemporaryBounds;
|
|
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);
|
|
|
|
/* If event is a configure event, tell the frame clock about it. */
|
|
if (event->type == ConfigureNotify)
|
|
SyncHelperNoteConfigureEvent (xdg_role->sync_helper);
|
|
}
|
|
|
|
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++;
|
|
}
|
|
|
|
void
|
|
XLInitXdgSurfaces (void)
|
|
{
|
|
XColor alloc;
|
|
int shape_minor, shape_major, shape_error;
|
|
|
|
surfaces = XLCreateAssocTable (1024);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
void
|
|
XLXdgRoleHandlePing (Role *role, XEvent *event,
|
|
void (*reply_func) (XEvent *))
|
|
{
|
|
XdgRole *xdg_role;
|
|
PingEvent *record;
|
|
|
|
xdg_role = XdgRoleFromRole (role);
|
|
|
|
/* If the role's xdg_wm_base is detached, just reply to the ping
|
|
message. */
|
|
if (!xdg_role->link.next)
|
|
reply_func (event);
|
|
else
|
|
{
|
|
/* Otherwise, save the event and ping the client. Then, send
|
|
replies once the client replies. */
|
|
record = XLMalloc (sizeof *record);
|
|
record->event = *event;
|
|
record->reply_func = reply_func;
|
|
xdg_role->ping_events = XLListPrepend (xdg_role->ping_events,
|
|
record);
|
|
XLXdgWmBaseSendPing (xdg_role->wm_base);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ReplyPingEvent (void *data)
|
|
{
|
|
PingEvent *event;
|
|
|
|
event = data;
|
|
event->reply_func (&event->event);
|
|
XLFree (event);
|
|
}
|
|
|
|
void
|
|
XLXdgRoleReplyPing (Role *role)
|
|
{
|
|
XdgRole *xdg_role;
|
|
|
|
xdg_role = XdgRoleFromRole (role);
|
|
|
|
/* Free the ping event list, calling the reply functions along the
|
|
way. */
|
|
XLListFree (xdg_role->ping_events, ReplyPingEvent);
|
|
xdg_role->ping_events = NULL;
|
|
}
|