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