584 lines
13 KiB
C
584 lines
13 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 <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sys/mman.h>
|
|
#include <sys/fcntl.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "compositor.h"
|
|
|
|
typedef struct _Pool
|
|
{
|
|
/* The file descriptor corresponding to this pool. */
|
|
int fd;
|
|
|
|
/* The size of this pool. */
|
|
size_t size;
|
|
|
|
/* The number of references to this pool. */
|
|
int refcount;
|
|
|
|
/* Pointer to the raw data in this pool. */
|
|
void *data;
|
|
|
|
/* The wl_resource corresponding to this pool. */
|
|
struct wl_resource *resource;
|
|
} Pool;
|
|
|
|
typedef struct _Buffer
|
|
{
|
|
/* The ExtBuffer associated with this buffer. */
|
|
ExtBuffer buffer;
|
|
|
|
/* The pixmap associated with this buffer. */
|
|
Pixmap pixmap;
|
|
|
|
/* The picture associated with this buffer. */
|
|
Picture picture;
|
|
|
|
/* The width and height of this buffer. */
|
|
unsigned int width, height;
|
|
|
|
/* The stride of this buffer. */
|
|
int stride;
|
|
|
|
/* The offset of this buffer. */
|
|
int offset;
|
|
|
|
/* The format of this buffer. */
|
|
uint32_t format;
|
|
|
|
/* The wl_resource corresponding to this buffer. */
|
|
struct wl_resource *resource;
|
|
|
|
/* The pool corresponding to this buffer. */
|
|
Pool *pool;
|
|
|
|
/* The number of references to this buffer. */
|
|
int refcount;
|
|
} Buffer;
|
|
|
|
/* List of all resources for our shared memory global. */
|
|
static XLList *all_shms;
|
|
|
|
/* The shared memory global. */
|
|
static struct wl_global *global_shm;
|
|
|
|
static void
|
|
DereferencePool (Pool *pool)
|
|
{
|
|
if (--pool->refcount)
|
|
return;
|
|
|
|
munmap (pool->data, pool->size);
|
|
close (pool->fd);
|
|
XLFree (pool);
|
|
}
|
|
|
|
static void
|
|
RetainPool (Pool *pool)
|
|
{
|
|
pool->refcount++;
|
|
}
|
|
|
|
static void
|
|
RetainBuffer (Buffer *buffer)
|
|
{
|
|
buffer->refcount++;
|
|
}
|
|
|
|
static void
|
|
DereferenceBuffer (Buffer *buffer)
|
|
{
|
|
if (--buffer->refcount)
|
|
return;
|
|
|
|
XRenderFreePicture (compositor.display, buffer->picture);
|
|
XFreePixmap (compositor.display, buffer->pixmap);
|
|
DereferencePool (buffer->pool);
|
|
|
|
ExtBufferDestroy (&buffer->buffer);
|
|
XLFree (buffer);
|
|
}
|
|
|
|
static void
|
|
ReleaseBufferFunc (ExtBuffer *buffer)
|
|
{
|
|
if (((Buffer *) buffer)->resource)
|
|
wl_buffer_send_release (((Buffer *) buffer)->resource);
|
|
}
|
|
|
|
static void
|
|
RetainBufferFunc (ExtBuffer *buffer)
|
|
{
|
|
RetainBuffer ((Buffer *) buffer);
|
|
}
|
|
|
|
static void
|
|
DereferenceBufferFunc (ExtBuffer *buffer)
|
|
{
|
|
DereferenceBuffer ((Buffer *) buffer);
|
|
}
|
|
|
|
static Picture
|
|
GetPictureFunc (ExtBuffer *buffer)
|
|
{
|
|
return ((Buffer *) buffer)->picture;
|
|
}
|
|
|
|
static Pixmap
|
|
GetPixmapFunc (ExtBuffer *buffer)
|
|
{
|
|
return ((Buffer *) buffer)->pixmap;
|
|
}
|
|
|
|
static unsigned int
|
|
WidthFunc (ExtBuffer *buffer)
|
|
{
|
|
return ((Buffer *) buffer)->width;
|
|
}
|
|
|
|
static unsigned int
|
|
HeightFunc (ExtBuffer *buffer)
|
|
{
|
|
return ((Buffer *) buffer)->height;
|
|
}
|
|
|
|
static void
|
|
DestroyBuffer (struct wl_client *client, struct wl_resource *resource)
|
|
{
|
|
wl_resource_destroy (resource);
|
|
}
|
|
|
|
static void
|
|
PrintBuffer (Buffer *buffer)
|
|
{
|
|
int x, y;
|
|
char *base;
|
|
unsigned int *base32;
|
|
|
|
for (y = 0; y < buffer->height; ++y)
|
|
{
|
|
base = buffer->pool->data;
|
|
base += buffer->stride * y;
|
|
base32 = (unsigned int *) base;
|
|
|
|
for (x = 0; x < buffer->width; ++x)
|
|
fprintf (stderr, "#x%8x ", base32[x]);
|
|
fprintf (stderr, "\n");
|
|
}
|
|
}
|
|
|
|
static void
|
|
PrintBufferFunc (ExtBuffer *buffer)
|
|
{
|
|
PrintBuffer ((Buffer *) buffer);
|
|
}
|
|
|
|
static int
|
|
DepthForFormat (uint32_t format)
|
|
{
|
|
switch (format)
|
|
{
|
|
case WL_SHM_FORMAT_ARGB8888:
|
|
return 32;
|
|
|
|
case WL_SHM_FORMAT_XRGB8888:
|
|
return 24;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static XRenderPictFormat *
|
|
PictFormatForFormat (uint32_t format)
|
|
{
|
|
switch (format)
|
|
{
|
|
case WL_SHM_FORMAT_ARGB8888:
|
|
return compositor.argb_format;
|
|
|
|
case WL_SHM_FORMAT_XRGB8888:
|
|
return compositor.xrgb_format;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
HandleBufferResourceDestroy (struct wl_resource *resource)
|
|
{
|
|
Buffer *buffer;
|
|
|
|
buffer = wl_resource_get_user_data (resource);
|
|
buffer->resource = NULL;
|
|
|
|
DereferenceBuffer (buffer);
|
|
}
|
|
|
|
static const struct wl_buffer_interface wl_shm_buffer_impl =
|
|
{
|
|
.destroy = DestroyBuffer,
|
|
};
|
|
|
|
static void
|
|
CreateBuffer (struct wl_client *client, struct wl_resource *resource,
|
|
uint32_t id, int32_t offset, int32_t width, int32_t height,
|
|
int32_t stride, uint32_t format)
|
|
{
|
|
XRenderPictureAttributes picture_attrs;
|
|
Pool *pool;
|
|
Buffer *buffer;
|
|
xcb_shm_seg_t seg;
|
|
Pixmap pixmap;
|
|
Picture picture;
|
|
int fd, depth;
|
|
|
|
depth = DepthForFormat (format);
|
|
|
|
if (!depth)
|
|
{
|
|
wl_resource_post_error (resource, WL_SHM_ERROR_INVALID_FORMAT,
|
|
"The specified format is not supported");
|
|
return;
|
|
}
|
|
|
|
pool = wl_resource_get_user_data (resource);
|
|
|
|
if (pool->size < offset || stride != width * 4
|
|
|| offset + stride * height > pool->size)
|
|
{
|
|
wl_resource_post_error (resource, WL_SHM_ERROR_INVALID_STRIDE,
|
|
"Invalid offset or stride, or pool too small");
|
|
return;
|
|
}
|
|
|
|
if (width > 32768 || height > 32768)
|
|
{
|
|
/* X doesn't support larger drawables. */
|
|
wl_resource_post_no_memory (resource);
|
|
return;
|
|
}
|
|
|
|
if (width < 1 || height < 1)
|
|
{
|
|
/* X doesn't support smaller drawables. */
|
|
wl_resource_post_error (resource, WL_SHM_ERROR_INVALID_STRIDE,
|
|
"Invalid size, X server does not support zero-width drawables");
|
|
return;
|
|
}
|
|
|
|
buffer = XLSafeMalloc (sizeof *buffer);
|
|
|
|
if (!buffer)
|
|
{
|
|
wl_resource_post_no_memory (resource);
|
|
return;
|
|
}
|
|
|
|
memset (buffer, 0, sizeof *buffer);
|
|
buffer->resource = wl_resource_create (client, &wl_buffer_interface,
|
|
wl_resource_get_version (resource),
|
|
id);
|
|
|
|
if (!buffer->resource)
|
|
{
|
|
wl_resource_post_no_memory (resource);
|
|
XLFree (buffer);
|
|
return;
|
|
}
|
|
|
|
/* XCB closes fds after sending them. */
|
|
fd = fcntl (pool->fd, F_DUPFD_CLOEXEC, 0);
|
|
|
|
if (fd < 0)
|
|
{
|
|
wl_resource_destroy (buffer->resource);
|
|
XLFree (buffer);
|
|
wl_resource_post_error (resource, WL_SHM_ERROR_INVALID_FD,
|
|
"fcntl: %s", strerror (errno));
|
|
return;
|
|
}
|
|
|
|
seg = xcb_generate_id (compositor.conn);
|
|
pixmap = xcb_generate_id (compositor.conn);
|
|
|
|
xcb_shm_attach_fd (compositor.conn, seg, fd, false);
|
|
xcb_shm_create_pixmap (compositor.conn, pixmap,
|
|
DefaultRootWindow (compositor.display),
|
|
width, height, depth, seg, offset);
|
|
xcb_shm_detach (compositor.conn, seg);
|
|
|
|
picture = XRenderCreatePicture (compositor.display, pixmap,
|
|
PictFormatForFormat (format),
|
|
0, &picture_attrs);
|
|
|
|
buffer->pixmap = pixmap;
|
|
buffer->picture = picture;
|
|
buffer->width = width;
|
|
buffer->height = height;
|
|
buffer->stride = stride;
|
|
buffer->offset = offset;
|
|
buffer->format = format;
|
|
buffer->pool = pool;
|
|
buffer->refcount = 1;
|
|
|
|
/* Initialize function pointers. */
|
|
buffer->buffer.funcs.retain = RetainBufferFunc;
|
|
buffer->buffer.funcs.dereference = DereferenceBufferFunc;
|
|
buffer->buffer.funcs.get_picture = GetPictureFunc;
|
|
buffer->buffer.funcs.get_pixmap = GetPixmapFunc;
|
|
buffer->buffer.funcs.width = WidthFunc;
|
|
buffer->buffer.funcs.height = HeightFunc;
|
|
buffer->buffer.funcs.release = ReleaseBufferFunc;
|
|
buffer->buffer.funcs.print_buffer = PrintBufferFunc;
|
|
|
|
RetainPool (pool);
|
|
|
|
wl_resource_set_implementation (buffer->resource,
|
|
&wl_shm_buffer_impl,
|
|
buffer,
|
|
HandleBufferResourceDestroy);
|
|
}
|
|
|
|
static void
|
|
HandlePoolResourceDestroy (struct wl_resource *resource)
|
|
{
|
|
Pool *pool;
|
|
|
|
pool = wl_resource_get_user_data (resource);
|
|
DereferencePool (pool);
|
|
}
|
|
|
|
static void
|
|
DestroyPool (struct wl_client *client, struct wl_resource *resource)
|
|
{
|
|
wl_resource_destroy (resource);
|
|
}
|
|
|
|
static void
|
|
ResizePool (struct wl_client *client, struct wl_resource *resource,
|
|
int32_t size)
|
|
{
|
|
Pool *pool;
|
|
void *data;
|
|
|
|
pool = wl_resource_get_user_data (resource);
|
|
data = mremap (pool->data, pool->size, size, MREMAP_MAYMOVE);
|
|
|
|
if (data == MAP_FAILED)
|
|
{
|
|
wl_resource_post_error (resource, WL_SHM_ERROR_INVALID_FD,
|
|
"mremap: %s", strerror (errno));
|
|
return;
|
|
}
|
|
|
|
pool->size = size;
|
|
pool->data = data;
|
|
}
|
|
|
|
static const struct wl_shm_pool_interface wl_shm_pool_impl =
|
|
{
|
|
.destroy = DestroyPool,
|
|
.resize = ResizePool,
|
|
.create_buffer = CreateBuffer,
|
|
};
|
|
|
|
static void
|
|
HandleResourceDestroy (struct wl_resource *resource)
|
|
{
|
|
all_shms = XLListRemove (all_shms, resource);
|
|
}
|
|
|
|
static void
|
|
CreatePool (struct wl_client *client,
|
|
struct wl_resource *resource,
|
|
uint32_t id, int fd, int size)
|
|
{
|
|
Pool *pool;
|
|
|
|
pool = XLSafeMalloc (sizeof *pool);
|
|
|
|
if (!pool)
|
|
wl_resource_post_no_memory (resource);
|
|
|
|
memset (pool, 0, sizeof *pool);
|
|
|
|
pool->resource = wl_resource_create (client, &wl_shm_pool_interface,
|
|
wl_resource_get_version (resource),
|
|
id);
|
|
|
|
/* There are no references to this pool yet. */
|
|
if (!pool->resource)
|
|
{
|
|
XLFree (pool);
|
|
wl_resource_post_no_memory (resource);
|
|
|
|
return;
|
|
}
|
|
|
|
pool->data = mmap (NULL, size, PROT_READ, MAP_SHARED, fd, 0);
|
|
|
|
if (pool->data == (void *) -1)
|
|
{
|
|
wl_resource_destroy (pool->resource);
|
|
XLFree (pool);
|
|
wl_resource_post_error (resource, WL_SHM_ERROR_INVALID_FD,
|
|
"mmap: %s", strerror (errno));
|
|
|
|
return;
|
|
}
|
|
|
|
wl_resource_set_implementation (pool->resource, &wl_shm_pool_impl,
|
|
pool, HandlePoolResourceDestroy);
|
|
|
|
pool->size = size;
|
|
pool->fd = fd;
|
|
pool->refcount = 1;
|
|
|
|
return;
|
|
}
|
|
|
|
static const struct wl_shm_interface wl_shm_impl =
|
|
{
|
|
.create_pool = CreatePool,
|
|
};
|
|
|
|
static void
|
|
PostFormats (struct wl_resource *resource)
|
|
{
|
|
/* TODO: don't hard-code visuals and be slightly more versatile. */
|
|
|
|
wl_shm_send_format (resource, WL_SHM_FORMAT_XRGB8888);
|
|
wl_shm_send_format (resource, WL_SHM_FORMAT_ARGB8888);
|
|
}
|
|
|
|
static void
|
|
HandleBind (struct wl_client *client, void *data,
|
|
uint32_t version, uint32_t id)
|
|
{
|
|
struct wl_resource *resource;
|
|
|
|
resource = wl_resource_create (client, &wl_shm_interface,
|
|
version, id);
|
|
|
|
if (!resource)
|
|
{
|
|
wl_client_post_no_memory (client);
|
|
return;
|
|
}
|
|
|
|
wl_resource_set_implementation (resource, &wl_shm_impl,
|
|
NULL, HandleResourceDestroy);
|
|
all_shms = XLListPrepend (all_shms, resource);
|
|
|
|
PostFormats (resource);
|
|
}
|
|
|
|
static void
|
|
InitRender (void)
|
|
{
|
|
int major, minor, base, dummy;
|
|
|
|
if (!XRenderQueryExtension (compositor.display,
|
|
&base, &dummy))
|
|
{
|
|
fprintf (stderr, "XRender is not supported by this X server\n");
|
|
exit (1);
|
|
}
|
|
|
|
if (!XRenderQueryVersion (compositor.display,
|
|
&major, &minor)
|
|
|| (!major && minor < 2))
|
|
{
|
|
fprintf (stderr, "XRender is not supported by this X server\n");
|
|
exit (1);
|
|
}
|
|
|
|
compositor.argb_format
|
|
= XRenderFindStandardFormat (compositor.display,
|
|
PictStandardARGB32);
|
|
compositor.xrgb_format
|
|
= XRenderFindStandardFormat (compositor.display,
|
|
PictStandardRGB24);
|
|
|
|
if (!compositor.argb_format)
|
|
{
|
|
fprintf (stderr, "Failed to find standard format PictStandardARGB32\n");
|
|
exit (1);
|
|
}
|
|
|
|
if (!compositor.xrgb_format)
|
|
{
|
|
fprintf (stderr, "Failed to find standard format PictStandardRGB24\n");
|
|
exit (1);
|
|
}
|
|
}
|
|
|
|
void
|
|
XLInitShm (void)
|
|
{
|
|
xcb_shm_query_version_reply_t *reply;
|
|
xcb_shm_query_version_cookie_t cookie;
|
|
|
|
/* This shouldn't be freed. */
|
|
const xcb_query_extension_reply_t *ext;
|
|
|
|
ext = xcb_get_extension_data (compositor.conn, &xcb_shm_id);
|
|
|
|
if (!ext || !ext->present)
|
|
{
|
|
fprintf (stderr, "The MIT-SHM extension is not supported by this X server.\n");
|
|
exit (1);
|
|
}
|
|
|
|
cookie = xcb_shm_query_version (compositor.conn);
|
|
reply = xcb_shm_query_version_reply (compositor.conn,
|
|
cookie, NULL);
|
|
|
|
if (!reply)
|
|
{
|
|
fprintf (stderr, "The MIT-SHM extension on this X server is too old.\n");
|
|
exit (1);
|
|
}
|
|
else if (reply->major_version < 1
|
|
|| (reply->major_version == 1
|
|
&& reply->minor_version < 2))
|
|
{
|
|
fprintf (stderr, "The MIT-SHM extension on this X server is too old"
|
|
" to support POSIX shared memory.\n");
|
|
exit (1);
|
|
}
|
|
|
|
free (reply);
|
|
|
|
InitRender ();
|
|
|
|
global_shm = wl_global_create (compositor.wl_display,
|
|
&wl_shm_interface, 1,
|
|
NULL, HandleBind);
|
|
}
|