/* 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 "compositor.h" #include "12to11-test.h" #define TestSurfaceFromRole(role) ((TestSurface *) (role)) #define DefaultEventMask \ (ExposureMask | StructureNotifyMask | PropertyChangeMask) enum { IsSurfaceMapped = 1, PendingBufferRelease = 1 << 1, PendingFrameCallback = 1 << 2, }; typedef struct _TestSurface TestSurface; struct _TestSurface { /* The associated role. */ Role role; /* The associated subcompositor. */ Subcompositor *subcompositor; /* The associated buffer release helper. */ BufferReleaseHelper *release_helper; /* The associated window. */ Window window; /* The associated rendering target. */ RenderTarget target; /* The number of references to this test surface, and flags. */ int refcount, flags; /* The last known width and height. */ int bounds_width, bounds_height; }; /* The test surface manager global. */ static struct wl_global *test_manager_global; /* Hash table of all surfaces. */ static XLAssocTable *surfaces; static void DestroyBacking (TestSurface *test) { if (--test->refcount) return; /* Release all allocated resources. */ RenderDestroyRenderTarget (test->target); XDestroyWindow (compositor.display, test->window); /* And the buffer release helper. */ FreeBufferReleaseHelper (test->release_helper); /* Delete the association. */ XLDeleteAssoc (surfaces, test->window); /* Free the subcompositor. */ SubcompositorFree (test->subcompositor); /* And since there are no C level references to the role anymore, it can be freed. */ XLFree (test); } static void RunFrameCallbacks (TestSurface *test) { struct timespec time; clock_gettime (CLOCK_MONOTONIC, &time); XLSurfaceRunFrameCallbacks (test->role.surface, time); test->flags &= ~PendingFrameCallback; } static void RunFrameCallbacksConditionally (TestSurface *test) { if (!test->role.surface) return; if (test->flags & PendingBufferRelease) /* Wait for all buffers to be released first. */ test->flags |= PendingFrameCallback; else RunFrameCallbacks (test); } static void AllBuffersReleased (void *data) { TestSurface *test; test = data; if (!test->role.surface) return; test->flags &= ~PendingBufferRelease; /* Run pending frame callbacks. */ if (test->flags & PendingFrameCallback) RunFrameCallbacks (test); } static void NoteBounds (void *data, int min_x, int min_y, int max_x, int max_y) { TestSurface *test; int bounds_width, bounds_height; test = data; /* 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 (test->bounds_width != bounds_width || test->bounds_height != bounds_height) { /* Resize the window to fit. */ XResizeWindow (compositor.display, test->window, bounds_width, bounds_height); test->bounds_width = bounds_width; test->bounds_height = bounds_height; } } static void NoteFrame (FrameMode mode, uint64_t id, void *data) { if (mode != ModeComplete && mode != ModePresented) return; /* Run the frame callbacks. With the test surface, this also serves to mean that painting has completed. */ RunFrameCallbacksConditionally (data); } static void MapTestSurface (TestSurface *test) { /* Set the bounds width and height. */ test->bounds_width = SubcompositorWidth (test->subcompositor); test->bounds_height = SubcompositorHeight (test->subcompositor); /* First, resize the window to the current bounds. */ XResizeWindow (compositor.display, test->window, test->bounds_width, test->bounds_height); /* Next, map the window and raise it. Wait for a subsequent MapNotify before sending the map event. */ XMapRaised (compositor.display, test->window); /* And say that the window is now mapped. */ test->flags |= IsSurfaceMapped; } static void UnmapTestSurface (TestSurface *test) { if (test->flags & IsSurfaceMapped) /* Unmap the surface. */ XUnmapWindow (compositor.display, test->window); } static void Commit (Surface *surface, Role *role) { TestSurface *test; test = TestSurfaceFromRole (role); if (surface->current_state.buffer && !(test->flags & IsSurfaceMapped)) /* Map the surface now. */ MapTestSurface (test); else if (!surface->current_state.buffer) /* Unmap the surface now. */ UnmapTestSurface (test); /* Finally, do a subcompositor update if the surface is now mapped. */ if (test->flags & IsSurfaceMapped) SubcompositorUpdate (test->subcompositor); } static Bool Setup (Surface *surface, Role *role) { TestSurface *test; test = TestSurfaceFromRole (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 = TestSurfaceType; /* Attach the views to the subcompositor. */ ViewSetSubcompositor (surface->view, test->subcompositor); ViewSetSubcompositor (surface->under, test->subcompositor); /* Make sure the under view ends up beneath surface->view. */ SubcompositorInsert (test->subcompositor, surface->under); SubcompositorInsert (test->subcompositor, surface->view); /* Retain the backing data. */ test->refcount++; return True; } static void Teardown (Surface *surface, Role *role) { TestSurface *test; /* Clear role->surface here, since this is where the refcounting is done as well. */ role->surface = NULL; test = TestSurfaceFromRole (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. */ DestroyBacking (test); } static void Destroy (struct wl_client *client, struct wl_resource *resource) { TestSurface *test; test = wl_resource_get_user_data (resource); /* Now detach the role from its surface, which can be reused in the future. */ if (test->role.surface) XLSurfaceReleaseRole (test->role.surface, &test->role); /* And destroy the resource. */ wl_resource_destroy (resource); } static void ReleaseBuffer (Surface *surface, Role *role, ExtBuffer *buffer) { TestSurface *test; RenderBuffer render_buffer; test = TestSurfaceFromRole (role); render_buffer = XLRenderBufferFromBuffer (buffer); if (RenderIsBufferIdle (render_buffer, test->target)) /* Release the buffer now -- it is already idle. */ XLReleaseBuffer (buffer); else { /* Release the buffer once it becomes idle, or is destroyed. */ ReleaseBufferWithHelper (test->release_helper, buffer, test->target); /* Mark the surface as pending buffer release, so frame callbacks can be deferred until all buffers are released. */ test->flags |= PendingBufferRelease; } } static void SubsurfaceUpdate (Surface *surface, Role *role) { TestSurface *test; test = TestSurfaceFromRole (role); SubcompositorUpdate (test->subcompositor); } static Window GetWindow (Surface *surface, Role *role) { TestSurface *test; test = TestSurfaceFromRole (role); return test->window; } static const struct test_surface_interface test_surface_impl = { .destroy = Destroy, }; static void HandleResourceDestroy (struct wl_resource *resource) { TestSurface *test; test = wl_resource_get_user_data (resource); test->role.resource = NULL; /* Dereference the backing data. */ DestroyBacking (test); } static void GetTestSurface (struct wl_client *client, struct wl_resource *resource, uint32_t id, struct wl_resource *surface_resource) { Surface *surface; TestSurface *test; XSetWindowAttributes attrs; unsigned long flags; surface = wl_resource_get_user_data (surface_resource); if (surface->role_type != AnythingType && surface->role_type != TestSurfaceType) { /* The client is trying to create a test surface for a surface that has or had some other role. */ wl_resource_post_error (resource, TEST_MANAGER_ERROR_ROLE_PRESENT, "a role is/was already present on the given surface"); return; } test = XLSafeMalloc (sizeof *test); if (!test) { wl_resource_post_no_memory (resource); return; } memset (test, 0, sizeof *test); /* Now create the associated resource. */ test->role.resource = wl_resource_create (client, &test_surface_interface, wl_resource_get_version (resource), id); if (!test->role.resource) { wl_resource_post_no_memory (resource); XLFree (test); return; } /* Create the window. */ attrs.colormap = compositor.colormap; attrs.border_pixel = border_pixel; attrs.event_mask = DefaultEventMask; attrs.cursor = InitDefaultCursor (); flags = CWColormap | CWBorderPixel | CWEventMask | CWCursor; test->window = XCreateWindow (compositor.display, DefaultRootWindow (compositor.display), 0, 0, 20, 20, 0, compositor.n_planes, InputOutput, compositor.visual, flags, &attrs); /* And the subcompositor and rendering target. */ test->subcompositor = MakeSubcompositor (); test->target = RenderTargetFromWindow (test->window, DefaultEventMask); /* And a buffer release helper. */ test->release_helper = MakeBufferReleaseHelper (AllBuffersReleased, test); /* Set the subcompositor target. */ SubcompositorSetTarget (test->subcompositor, &test->target); /* Set some callbacks. The note frame callback is not useful as test surfaces have no frame clock. */ SubcompositorSetBoundsCallback (test->subcompositor, NoteBounds, test); SubcompositorSetNoteFrameCallback (test->subcompositor, NoteFrame, test); /* Create the hash table used to look up test surfaces if necessary. */ if (!surfaces) surfaces = XLCreateAssocTable (16); /* Associate the window with the role. */ XLMakeAssoc (surfaces, test->window, test); /* Set the role implementation. */ test->role.funcs.commit = Commit; test->role.funcs.teardown = Teardown; test->role.funcs.setup = Setup; test->role.funcs.release_buffer = ReleaseBuffer; test->role.funcs.subsurface_update = SubsurfaceUpdate; test->role.funcs.get_window = GetWindow; /* Add the resource implementation. */ wl_resource_set_implementation (test->role.resource, &test_surface_impl, test, HandleResourceDestroy); test->refcount++; /* Attach the role. */ if (!XLSurfaceAttachRole (surface, &test->role)) abort (); } static const struct test_manager_interface test_manager_impl = { .get_test_surface = GetTestSurface, }; static void HandleBind (struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct wl_resource *resource; char *name; resource = wl_resource_create (client, &test_manager_interface, version, id); if (!resource) { wl_client_post_no_memory (client); return; } wl_resource_set_implementation (resource, &test_manager_impl, NULL, NULL); /* Send the display name to the client. */ name = DisplayString (compositor.display); test_manager_send_display_string (resource, name); } void XLInitTest (void) { test_manager_global = wl_global_create (compositor.wl_display, &test_manager_interface, 1, NULL, HandleBind); } static Bool DispatchMapNotify (XEvent *event) { TestSurface *test; /* Try to look up the surface. */ test = XLLookUpAssoc (surfaces, event->xmap.window); if (!test) return False; /* The surface is now mapped. Dispatch the mapped event. */ if (test->flags & IsSurfaceMapped && test->role.resource) test_surface_send_mapped (test->role.resource, test->window, DisplayString (compositor.display)); return True; } static Bool DispatchExpose (XEvent *event) { TestSurface *test; /* Try to look up the surface. */ test = XLLookUpAssoc (surfaces, event->xexpose.window); if (!test) return False; /* Expose the subcompositor. */ SubcompositorExpose (test->subcompositor, event); return True; } Bool XLHandleOneXEventForTest (XEvent *event) { if (!surfaces) return False; switch (event->type) { case MapNotify: return DispatchMapNotify (event); case Expose: return DispatchExpose (event); } return False; }