forked from 12to11/12to11

* 12to11.c (HandleCmdline): Improve messages printed during -help. (XLMain): Initialize DRM leasing. * 12to11.man: Document changes. * Imakefile (LOCAL_LIBRARIES): Add xcb-randr, xf86drm and XPresent. (SRCS): Add drm_lease.c. (OBJS): Add drm_lease.o. * atoms.c (names): Add CONNECTOR_ID. (CONNECTOR_ID): New atom. (XLInitAtoms): Intern CONNECTOR_ID. * compositor.h (struct _RenderFuncs): Require event mask to be passed to target_from_window. New functions `set_standard_event_mask', `present_to_window', `cancel_presentation_callback', and `cancel_presentation'. (struct _BufferFuncs): Move buffer release machinery here. (enum _FrameMode): New enum. * egl.c (TargetFromWindow, SetStandardEventMask, egl_render_funcs): Adjust functions accordingly. * frame_clock.c (struct _FrameClock): New fields `end_frame_called' and `pending_sync_value'. (HandleEndFrame, PostEndFrame, StartFrame, EndFrame): Clean up frame synchronization code. Do not end upon predicted presentation time if EndFrame was not called in time. (FreezeForValue): New function. (XLFrameClockHandleFrameEvent): Defer actually freezing until StartFrame happens. (XLFrameClockGetFrameTime): New function. * icon_surface.c (ReleaseBuffer): Use RenderWaitForIdle. (RunFrameCallbacks, AfterFrame): Pass frame clock to callbacks. Use frame time if available. (XLGetIconSurface): Require wait_for_idle to work. * output.c (change_hook): New hook. (XLHandleOneXEventForOutputs): Run hook if set. (XLOutputSetChangeFunction): New function. (XLInitRROutputs): Select for RRResourceChangeNotify if providers are supported. * picture_renderer.c (struct _PresentRecord) (struct _BufferActivityRecord, struct _IdleCallback) (struct _PictureBuffer, struct _PictureTarget) (struct _DrmFormatInfo, struct _DmaBufRecord) (struct _PresentCompletionCallback): New record structures. (all_formats, SendRoundtripMessage, FindBufferActivityRecord) (RecordBufferActivity, RunIdleCallbacks, MaybeRunIdleCallbacks) (UnlinkActivityRecord, HandleActivityEvent, InitRenderFuncs) (TargetFromDrawable, TargetFromPixmap, TargetFromWindow) (SetStandardEventMask, NoteTargetSize, PictureFromTarget) (DestroyRenderTarget, FillBoxesWithTransparency) (ServerRegionFromRegion, ClearRectangle, Composite) (FindPresentRecord, AllocateRecord, PresentToWindow) (CancelPresentationCallback, CancelPresentation) (picture_render_funcs, FindSupportedModifiers, InitDrmFormats) (PictFormatIsPresentable, BufferFromDmaBuf, FinishDmaBufRecord) (BufferFromDmaBufAsync, BufferFromShm, BufferFromSinglePixel) (FreeShmBuffer, FreeDmabufBuffer, FreeSinglePixelBuffer) (AddIdleCallback, CancelIdleCallback, IsBufferIdle) (IdleEventPredicate, WaitForIdle, SetNeedWaitForIdle) (picture_buffer_funcs, HandlePresentCompleteNotify) (HandlePresentIdleNotify, HandlePresentationEvent) (HandleOneXEventForPictureRenderer, InitPictureRenderer): Allow presenting pixmaps directly, and move buffer release tracking machinery here. * renderer.c (RenderTargetFromWindow): Update signature. (RenderPresentToWindow, RenderCancelPresentationCallback) (RenderCancelPresentation, RenderAddIdleCallback) (RegisterStaticRenderer): New wrapper functions. * run.c (HandleOneXEvent): Handle picture renderer events earlier. * seat.c (MaybeCreateCursor): Require wait_for_idle. (ReleaseBuffer): Wait for buffer to become idle on each target in the cursor ring. * subcompositor.c (struct _Subcompositor): New callback `note_frame'. (SubcompositorSetNoteFrameCallback, NoViewsAfter) (PresentCompletedCallback): New functions. (SubcompositorUpdate): Try to present the buffer if possible, and run completion callbacks. (SubcompositorFree): Free presentation key. * surface.c (XLSurfaceRunFrameCallbacksMs): New function. * text_input.c: Improve commentary. * xdg-shell.xml: Update from wayland-protocols. * xdg_surface.c (struct _XdgRole): New fields `pending_frame', `last_specified_serial'. (struct _ReleaseLaterRecord): Replace free function with idle callback key and xdg role pointers. (RemoveRecord): Delete function. (FreeRecords): Stop cancelling buffer destroy listener. (ReleaseLaterExtBufferFunc): Delete function. (RunFrameCallbacks): Use frame clock time if it is set. (HandleReleaseLaterMessage): Delete function. (BufferIdleCallback): New function. (ReleaseLater): Delete function. (XLHandleXEventForXdgSurfaces): Stop handling buffer release events here. (AckConfigure): Improve debug code and reject duplicate serials. (Commit): Unfreeze earlier; also, in general... (NoteFrame): Move frame handling implementation here. (ReleaseBuffer, Subframe, EndSubframe, AfterFrame, ResizeForMap) (SelectExtraEvents): Set standard event mask. (XLGetXdgSurface, XLXdgRoleSendConfigure): ...Replace frame clock logic with that in NoteFrame.
1968 lines
49 KiB
C
1968 lines
49 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,
|
|
StateLateFrame = (1 << 1),
|
|
StatePendingWindowGeometry = (1 << 2),
|
|
StateWaitingForAckConfigure = (1 << 3),
|
|
StateWaitingForAckCommit = (1 << 4),
|
|
StateLateFrameAcked = (1 << 5),
|
|
StateMaybeConfigure = (1 << 6),
|
|
StateDirtyFrameExtents = (1 << 7),
|
|
StateTemporaryBounds = (1 << 8),
|
|
StateFrameStarted = (1 << 9),
|
|
};
|
|
|
|
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;
|
|
|
|
/* The pending frame ID. */
|
|
uint64_t pending_frame;
|
|
|
|
/* 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, 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 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;
|
|
|
|
/* The buffer that should be released upon receiving this
|
|
message. */
|
|
ExtBuffer *buffer;
|
|
|
|
/* The idle callback. */
|
|
IdleCallbackKey key;
|
|
|
|
/* The XdgRole. */
|
|
XdgRole *role;
|
|
|
|
/* The next and last records. */
|
|
ReleaseLaterRecord *next, *last;
|
|
};
|
|
|
|
/* Event base of the XShape extension. */
|
|
int shape_base;
|
|
|
|
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);
|
|
|
|
/* 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
|
|
RunFrameCallbacks (Surface *surface, XdgRole *role)
|
|
{
|
|
struct timespec time;
|
|
uint64_t last_drawn_time;
|
|
|
|
/* Surface can be NULL for various reasons, especially events
|
|
arriving after the shell surface is detached. */
|
|
if (!surface)
|
|
return;
|
|
|
|
last_drawn_time = XLFrameClockGetFrameTime (role->clock);
|
|
|
|
if (!last_drawn_time)
|
|
{
|
|
clock_gettime (CLOCK_MONOTONIC, &time);
|
|
XLSurfaceRunFrameCallbacks (surface, time);
|
|
}
|
|
else
|
|
XLSurfaceRunFrameCallbacksMs (surface, last_drawn_time / 1000);
|
|
}
|
|
|
|
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
|
|
BufferIdleCallback (RenderBuffer buffer, void *data)
|
|
{
|
|
ReleaseLaterRecord *record;
|
|
XdgRole *role;
|
|
Surface *surface;
|
|
|
|
record = data;
|
|
role = record->role;
|
|
|
|
|
|
XLReleaseBuffer (record->buffer);
|
|
DeleteRecord (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;
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
#ifdef DEBUG_GEOMETRY_CALCULATION
|
|
fprintf (stderr, "ack_configure: %"PRIu32"\n", serial);
|
|
#endif
|
|
|
|
if (serial < xdg_role->conf_serial)
|
|
{
|
|
/* The client specified an outdated serial. */
|
|
wl_resource_post_error (resource, XDG_SURFACE_ERROR_INVALID_SERIAL,
|
|
"serial specified not monotonic");
|
|
return;
|
|
}
|
|
|
|
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);
|
|
|
|
/* 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 Bool
|
|
IsRoleMapped (XdgRole *role)
|
|
{
|
|
if (!role->impl)
|
|
/* This can happen when destroying subsurfaces as part of client
|
|
cleanup. */
|
|
return False;
|
|
|
|
return role->impl->funcs.is_window_mapped (&role->role,
|
|
role->impl);
|
|
}
|
|
|
|
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",
|
|
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);
|
|
#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 the window is unmapped, skip all of this code! Once the
|
|
window is mapped again, the compositor will send _NET_FRAME_DRAWN
|
|
should a frame still be in progress. */
|
|
if (!IsRoleMapped (xdg_role))
|
|
goto start_drawing;
|
|
|
|
/* 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);
|
|
|
|
/* 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, or a frame is in progress but EndFrame has
|
|
not yet been called, go ahead. */
|
|
goto start_drawing;
|
|
|
|
xdg_role->state |= StateLateFrame;
|
|
xdg_role->state &= ~StatePendingFrameCallback;
|
|
|
|
if (xdg_role->state & StateWaitingForAckConfigure)
|
|
xdg_role->state &= ~StateLateFrameAcked;
|
|
else
|
|
xdg_role->state |= StateLateFrameAcked;
|
|
|
|
return;
|
|
}
|
|
|
|
start_drawing:
|
|
|
|
SubcompositorUpdate (xdg_role->subcompositor);
|
|
|
|
/* Do not end frames explicitly. Instead, wait for the
|
|
NoteFrameCallback to end the frame. */
|
|
|
|
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);
|
|
|
|
/* 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)
|
|
{
|
|
RenderBuffer render_buffer;
|
|
ReleaseLaterRecord *record;
|
|
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. */
|
|
record = AddRecordAfter (xdg_role->release_records);
|
|
record->buffer = buffer;
|
|
record->key = RenderAddIdleCallback (render_buffer,
|
|
xdg_role->target,
|
|
BufferIdleCallback,
|
|
record);
|
|
record->role = xdg_role;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* Similarly, return False if the role is unmapped. */
|
|
if (!IsRoleMapped (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, or a frame is in progress but EndFrame has
|
|
not yet been called, go ahead. */
|
|
return True;
|
|
|
|
xdg_role->state |= StateLateFrame;
|
|
xdg_role->state &= ~StatePendingFrameCallback;
|
|
|
|
if (xdg_role->state & StateWaitingForAckConfigure)
|
|
xdg_role->state &= ~StateLateFrameAcked;
|
|
|
|
return False;
|
|
}
|
|
|
|
return True;
|
|
}
|
|
|
|
static void
|
|
EndSubframe (Surface *surface, Role *role)
|
|
{
|
|
/* Don't end the frame here; instead, wait for the frame callback to
|
|
note that drawing the frame has finished. */
|
|
}
|
|
|
|
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 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;
|
|
|
|
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
|
|
|
|
if (role->impl->funcs.note_window_pre_resize)
|
|
role->impl->funcs.note_window_pre_resize (&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;
|
|
}
|
|
|
|
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
|
|
NoteFrame (FrameMode mode, uint64_t id, void *data)
|
|
{
|
|
XdgRole *role;
|
|
|
|
role = data;
|
|
|
|
switch (mode)
|
|
{
|
|
case ModeStarted:
|
|
/* Record this frame counter as the pending frame. */
|
|
role->pending_frame = id;
|
|
|
|
if (!(role->state & StateFrameStarted))
|
|
{
|
|
role->state |= StateFrameStarted;
|
|
XLFrameClockStartFrame (role->clock, False);
|
|
}
|
|
|
|
/* 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);
|
|
|
|
break;
|
|
|
|
case ModePresented:
|
|
case ModeComplete:
|
|
/* The frame was completed. */
|
|
if (id == role->pending_frame)
|
|
{
|
|
/* End the frame. */
|
|
XLFrameClockEndFrame (role->clock);
|
|
|
|
/* Clear the frame completed flag. */
|
|
role->state &= ~StateFrameStarted;
|
|
|
|
/* 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 (role->clock)
|
|
/* If the window is not mapped, then the native frame
|
|
clock will not draw frames. Some clients do commit
|
|
before the initial configure event and wait for the
|
|
frame callback to be called after or before
|
|
ack_configure, leading to the mapping commit never
|
|
being performed. */
|
|
|| !IsRoleMapped (role))
|
|
RunFrameCallbacksConditionally (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);
|
|
|
|
/* 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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
role->role.funcs.select_extra_events = SelectExtraEvents;
|
|
|
|
attrs.colormap = compositor.colormap;
|
|
attrs.border_pixel = border_pixel;
|
|
attrs.event_mask = DefaultEventMask;
|
|
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, DefaultEventMask);
|
|
|
|
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);
|
|
SubcompositorSetNoteFrameCallback (role->subcompositor,
|
|
NoteFrame, 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;
|
|
|
|
/* 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);
|
|
}
|
|
|
|
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 (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;
|
|
|
|
/* Unfreeze the frame clock now. */
|
|
XLFrameClockUnfreeze (xdg_role->clock);
|
|
}
|
|
}
|