Import files

This commit is contained in:
oldosfan 2022-09-12 13:24:50 +00:00
commit 528f7ba858
44 changed files with 36787 additions and 0 deletions

168
12to11.c Normal file
View file

@ -0,0 +1,168 @@
/* Wayland compositor running on top of an X serer.
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 <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "compositor.h"
/* Globals. */
Compositor compositor;
static Visual *
PickVisual (int *depth)
{
int n_visuals;
XVisualInfo vinfo, *visuals;
Visual *selection;
vinfo.screen = DefaultScreen (compositor.display);
vinfo.class = TrueColor;
vinfo.depth = 32;
visuals = XGetVisualInfo (compositor.display, (VisualScreenMask
| VisualClassMask
| VisualDepthMask),
&vinfo, &n_visuals);
if (n_visuals)
{
/* TODO: verify visual format. */
selection = visuals[0].visual;
*depth = visuals[0].depth;
XFree (visuals);
return selection;
}
fprintf (stderr, "A 32-bit TrueColor visual was not found\n");
exit (1);
}
static Colormap
MakeColormap (void)
{
return XCreateColormap (compositor.display,
DefaultRootWindow (compositor.display),
compositor.visual, AllocNone);
}
static void
DetermineServerTime (void)
{
Time server_time;
struct timespec clock_spec, server_spec, diff;
/* Try to determine if the X server time is the same as the
monotonic time. If it is not, certain features such as "active"
frame synchronization will not be available. */
clock_gettime (CLOCK_MONOTONIC, &clock_spec);
server_time = XLGetServerTimeRoundtrip ();
server_spec.tv_sec = server_time / 1000;
server_spec.tv_nsec = ((server_time - server_time / 1000 * 1000)
* 1000000);
diff = TimespecSub (server_spec, clock_spec);
if (TimespecCmp (diff, MakeTimespec (0, 50000000)) <= 0
|| TimespecCmp (diff, MakeTimespec (0, -50000000)) <= 0)
/* Since the difference between the server time and the monotonic
time is less than 50 ms, the server time is the monotonic
time. */
compositor.server_time_monotonic = True;
else
{
compositor.server_time_monotonic = False;
fprintf (stderr, "Warning: the X server time does not seem to"
" be synchronized with the monotonic time. Multiple"
" subsurfaces may be displayed at a reduced maximum"
" frame rate.\n");
}
}
static void
XLMain (int argc, char **argv)
{
Display *dpy;
struct wl_display *wl_display;
const char *socket;
dpy = XOpenDisplay (NULL);
wl_display = wl_display_create ();
if (!dpy || !wl_display)
{
fprintf (stderr, "Display initialization failed\n");
exit (1);
}
socket = wl_display_add_socket_auto (wl_display);
if (!socket)
{
fprintf (stderr, "Unable to add socket to Wayland display\n");
exit (1);
}
compositor.display = dpy;
compositor.conn = XGetXCBConnection (dpy);
compositor.wl_display = wl_display;
compositor.wl_socket = socket;
compositor.wl_event_loop
= wl_display_get_event_loop (wl_display);
compositor.visual = PickVisual (&compositor.n_planes);
compositor.colormap = MakeColormap ();
InitXErrors ();
SubcompositorInit ();
InitSelections ();
XLInitTimers ();
XLInitAtoms ();
XLInitRROutputs ();
XLInitCompositor ();
XLInitSurfaces ();
XLInitShm ();
XLInitXdgWM ();
XLInitXdgSurfaces ();
XLInitXdgToplevels ();
XLInitFrameClock ();
XLInitSubsurfaces ();
XLInitSeats ();
XLInitDataDevice ();
XLInitPopups ();
XLInitDmabuf ();
XLInitXData ();
XLInitXSettings ();
XLInitIconSurfaces ();
/* This has to come after the rest of the initialization. */
DetermineServerTime ();
XLRunCompositor ();
}
int
main (int argc, char **argv)
{
XLMain (argc, argv);
return 0;
}

15
12to11.man Normal file
View file

@ -0,0 +1,15 @@
.TH OCLOCK
.SH NAME
12to11 - Wayland to X protocol translator
.SH SYNOPSIS
.B 12to11
.SH DESCRIPTION
.I 12to11
starts a Wayland compositor on the next available socket;
Wayland programs will then be displayed through the X server.
.SH OPTIONS
None.
.SH "SEE ALSO"
X(1), Xorg(1)
.SH AUTHOR
Various contributors.

94
Imakefile Normal file
View file

@ -0,0 +1,94 @@
#include "libraries.def"
#ifndef HasPosixThreads
#error "Posix threads are required"
#endif
SYS_LIBRARIES = MathLibrary ThreadsLibraries
DEPLIBS = $(DEPXLIB) $(DEPEXTENSIONLIB) $(DEPXRANDRLIB) $(DEPXRENDERLIB) \
$(DEPXFIXESLIB) $(DEPXILIB) $(DEPXKBFILELIB)
ETAGS = etags
LOCAL_LIBRARIES = $(XLIB) $(EXTENSIONLIB) $(XCBLIB) $(XCB) $(XCB_SHM) \
$(XRANDRLIB) $(PIXMAN) $(XRENDERLIB) $(XILIB) $(XKBFILELIB) $(XFIXESLIB) \
$(XCB_DRI3) $(XCB_SHAPE) $(WAYLAND_SERVER)
INCLUDES := $(DRMINCLUDES) $(PIXMANINCLUDES)
SRCS = 12to11.c run.c alloc.c fns.c output.c compositor.c \
xdg-shell.c surface.c region.c shm.c atoms.c subcompositor.c \
positioner.c xdg_wm.c xdg_surface.c xdg_toplevel.c \
frame_clock.c xerror.c ewmh.c timer.c subsurface.c seat.c \
data_device.c xdg_popup.c linux-dmabuf-unstable-v1.c dmabuf.c \
buffer.c select.c xdata.c xsettings.c dnd.c icon_surface.c
OBJS = 12to11.o run.o alloc.o fns.o output.o compositor.o \
xdg-shell.o surface.o region.o shm.o atoms.o subcompositor.o \
positioner.o xdg_wm.o xdg_surface.o xdg_toplevel.o \
frame_clock.o xerror.o ewmh.o timer.o subsurface.o seat.o \
data_device.o xdg_popup.o linux-dmabuf-unstable-v1.o dmabuf.o \
buffer.o select.o xdata.o xsettings.o dnd.o icon_surface.o
OPTIMIZE = -O0
ANALYZE = -fanalyzer
CDEBUGFLAGS := -fno-common -Wall -Warith-conversion -Wdate-time \
-Wdisabled-optimization -Wdouble-promotion -Wduplicated-cond \
-Wextra -Wformat-signedness -Winit-self -Winvalid-pch \
-Wlogical-op -Wmissing-declarations -Wmissing-include-dirs \
-Wmissing-prototypes -Wnested-externs -Wnull-dereference \
-Wold-style-definition -Wopenmp-simd -Wpacked -Wpointer-arith \
-Wstrict-prototypes -Wsuggest-attribute=format \
-Wsuggest-attribute=noreturn -Wsuggest-final-methods \
-Wsuggest-final-types -Wuninitialized -Wunknown-pragmas \
-Wunused-macros -Wvariadic-macros \
-Wvector-operation-performance -Wwrite-strings \
-Warray-bounds=2 -Wattribute-alias=2 -Wformat=2 \
-Wformat-truncation=2 -Wimplicit-fallthrough=5 \
-Wshift-overflow=2 -Wuse-after-free=3 -Wvla-larger-than=4031 \
-Wredundant-decls -Wno-missing-field-initializers \
-Wno-override-init -Wno-sign-compare -Wno-type-limits \
-Wno-unused-parameter -Wno-format-nonliteral -g3 $(OPTIMIZE) \
$(ANALYZE)
short_types.txt: media_types.txt
XCOMM remove all data types starting with application/vnd.
XCOMM no program really uses them in clipboard data, and they
XCOMM waste a lot of space on disk.
sed '/application\/vnd/d' media_types.txt > $@
transfer_atoms.h: short_types.txt mime0.awk mime1.awk mime2.awk mime3.awk \
mime4.awk
awk -f mime0.awk short_types.txt > $@
awk -f mime1.awk short_types.txt >> $@
awk -f mime2.awk short_types.txt >> $@
awk -f mime3.awk short_types.txt >> $@
awk -f mime4.awk short_types.txt >> $@
$(OBJS): transfer_atoms.h
linux-dmabuf-unstable-v1.h: linux-dmabuf-unstable-v1.xml
$(WAYLAND_SCANNER) server-header $< $@
linux-dmabuf-unstable-v1.c: linux-dmabuf-unstable-v1.xml \
linux-dmabuf-unstable-v1.h
$(WAYLAND_SCANNER) private-code $< $@
xdg-shell.h: xdg-shell.xml
$(WAYLAND_SCANNER) server-header $< $@
xdg-shell.c: xdg-shell.xml xdg-shell.h
$(WAYLAND_SCANNER) private-code $< $@
cleandir::
$(RM) linux-dmabuf-unstable-v1.c linux-dmabuf-unstable-v1.h \
xdg-shell.c xdg-shell.h
$(RM) transfer_atoms.h short_types.txt
/* Undefine _BSD_SOURCE and _SVID_SOURCE, since both are deprecated
and are also superseeded by _GNU_SOURCE. */
EXTRA_DEFINES := -D_GNU_SOURCE -U_BSD_SOURCE -U_SVID_SOURCE
ComplexProgramTarget(12to11)

72
README Normal file
View file

@ -0,0 +1,72 @@
This is a tool for running Wayland applications on an X server,
preferably with a compositing manager running.
It is not yet complete. What is not yet implemented includes support
for the primary selection, touchscreens, input methods, device
switching in dmabuf feedback, and the viewporter protocol extension.
There are also problems with output reporting in subsurfaces.
It is not portable to systems other than recent versions of GNU/Linux
running the X.Org server 1.20 or later, and has not been tested on
window (and compositing) managers other than GNOME Shell.
It will not work very well unless the compositing manager supports the
EWMH frame synchronization protocol.
Building and running this tool requires the following X protocol
extensions:
Nonrectangular Window Shape Extension, version 1.1 or later
MIT Shared Memory Extension, version 1.2 or later
X Resize, Rotate and Reflect Extension, version 1.3 or later
(this will soon be 1.4, once support for multiple GPU
systems is fully implemented)
X Synchronization Extension, version 1.0 or later
X Rendering Extension, version 1.2 or later
X Input Extension, version 2.3 or later
Direct Rendering Interface 3, version 1.2 or later
X Fixes Extension, version 1 or later
In addition, it requires Xlib to be built with the XCB transport, and
the XCB bindings for MIT-SHM and DRI3 to be available.
The following Wayland protocols are implemented to a more-or-less
complete degree:
'wl_output', version: 2
'wl_compositor', version: 5
'wl_shm', version: 1
'xdg_wm_base', version: 5
'wl_subcompositor', version: 1
'wl_seat', version: 7
'wl_data_device_manager', version: 3
'zwp_linux_dmabuf_v1', version: 4
With the main caveat being that zwp_linux_dmabuf_v1 has no real
support for multiple-provider setups (help wanted).
Primary selections and window decorations are also not supported, even
though they fit in nicely with X window management.
It would also be nice to have pinch gesture support in wl_pointer.
This directory is organized as follows:
Imakefile - the top level Makefile template
libraries.def - files for libraries that don't provide Imakefiles
*.xml - Wayland protocol definition source
*.c, *.h - C source code
Building the source code is simple, provided that you have the
necessary libwayland-server library, wayland-scanner, pixman, XCB, and
X extension libraries installed:
xmkmf # to generate the Makefile
make # to build the binary
Running the binary should be simple as well:
./12to11
Wayland programs will then run as regular X windows.

106
alloc.c Normal file
View file

@ -0,0 +1,106 @@
/* 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 <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "compositor.h"
void *
XLMalloc (size_t size)
{
void *ptr;
ptr = malloc (size);
if (!ptr)
{
fprintf (stderr, "Allocation of %zu bytes failed\n",
size);
abort ();
}
return ptr;
}
void *
XLSafeMalloc (size_t size)
{
return malloc (size);
}
void *
XLCalloc (size_t nmemb, size_t size)
{
void *ptr;
ptr = calloc (nmemb, size);
if (!ptr)
{
fprintf (stderr, "Allocation of %zu * %zu failed\n",
nmemb, size);
abort ();
}
return ptr;
}
void
XLFree (void *ptr)
{
if (ptr)
free (ptr);
}
char *
XLStrdup (const char *data)
{
char *string;
string = strdup (data);
if (!string)
{
fprintf (stderr, "Allocation of %zu bytes failed\n",
strlen (data));
abort ();
}
return string;
}
void *
XLRealloc (void *ptr, size_t size)
{
if (!ptr)
return XLMalloc (size);
ptr = realloc (ptr, size);
if (!ptr)
{
fprintf (stderr, "Reallocation of %zu bytes failed\n", size);
abort ();
}
return ptr;
}

287
atoms.c Normal file
View file

@ -0,0 +1,287 @@
/* 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 "compositor.h"
#define AtomTableSize 256
typedef struct _AtomTable AtomTable;
/* This is automatically generated by mime4.awk. */
Atom DirectTransferAtoms;
/* Simple hash table for atoms. */
struct _AtomTable
{
/* Atom array indexed by table size. */
Atom *atoms[AtomTableSize];
/* Atom name array indexed by table size. */
char **names[AtomTableSize];
/* Size of each array. */
ptrdiff_t atoms_length[AtomTableSize];
};
/* Array of all atom names. */
static const char *names[] =
{
"_NET_WM_OPAQUE_REGION",
"_XL_BUFFER_RELEASE",
"_NET_WM_SYNC_REQUEST_COUNTER",
"_NET_WM_FRAME_DRAWN",
"WM_DELETE_WINDOW",
"WM_PROTOCOLS",
"_NET_SUPPORTING_WM_CHECK",
"_NET_SUPPORTED",
"_NET_WM_SYNC_REQUEST",
"_MOTIF_WM_HINTS",
"_NET_WM_STATE_MAXIMIZED_VERT",
"_NET_WM_STATE_MAXIMIZED_HORZ",
"_NET_WM_STATE_FOCUSED",
"_NET_WM_STATE_FULLSCREEN",
"_NET_WM_STATE",
"_NET_WM_MOVERESIZE",
"_GTK_FRAME_EXTENTS",
"WM_TRANSIENT_FOR",
"_XL_DMA_BUF_CREATED",
"_GTK_SHOW_WINDOW_MENU",
"_NET_WM_ALLOWED_ACTIONS",
"_NET_WM_ACTION_FULLSCREEN",
"_NET_WM_ACTION_MAXIMIZE_HORZ",
"_NET_WM_ACTION_MAXIMIZE_VERT",
"_NET_WM_ACTION_MINIMIZE",
"INCR",
"CLIPBOARD",
"TARGETS",
"UTF8_STRING",
"_XL_SERVER_TIME_ATOM",
"MULTIPLE",
"TIMESTAMP",
"ATOM_PAIR",
"_NET_WM_NAME",
"WM_NAME",
"MANAGER",
"_XSETTINGS_SETTINGS",
"libinput Scroll Methods Available",
"XdndAware",
"XdndSelection",
"XdndTypeList",
"XdndActionCopy",
"XdndActionMove",
"XdndActionLink",
"XdndActionAsk",
"XdndActionPrivate",
"XdndActionList",
"XdndActionDescription",
"XdndProxy",
"XdndEnter",
"XdndPosition",
"XdndStatus",
"XdndLeave",
"XdndDrop",
"XdndFinished",
"_NET_WM_FRAME_TIMINGS",
"_NET_WM_BYPASS_COMPOSITOR",
"WM_STATE",
"_NET_WM_WINDOW_TYPE",
"_NET_WM_WINDOW_TYPE_MENU",
"_NET_WM_WINDOW_TYPE_DND",
/* These are automatically generated from mime.txt. */
DirectTransferAtomNames
};
Atom _NET_WM_OPAQUE_REGION, _XL_BUFFER_RELEASE, _NET_WM_SYNC_REQUEST_COUNTER,
_NET_WM_FRAME_DRAWN, WM_DELETE_WINDOW, WM_PROTOCOLS,
_NET_SUPPORTING_WM_CHECK, _NET_SUPPORTED, _NET_WM_SYNC_REQUEST,
_MOTIF_WM_HINTS, _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_STATE_MAXIMIZED_HORZ,
_NET_WM_STATE_FOCUSED, _NET_WM_STATE_FULLSCREEN, _NET_WM_STATE,
_NET_WM_MOVERESIZE, _GTK_FRAME_EXTENTS, WM_TRANSIENT_FOR,
_XL_DMA_BUF_CREATED, _GTK_SHOW_WINDOW_MENU, _NET_WM_ALLOWED_ACTIONS,
_NET_WM_ACTION_FULLSCREEN, _NET_WM_ACTION_MAXIMIZE_HORZ,
_NET_WM_ACTION_MAXIMIZE_VERT, _NET_WM_ACTION_MINIMIZE, INCR, CLIPBOARD,
TARGETS, UTF8_STRING, _XL_SERVER_TIME_ATOM, MULTIPLE, TIMESTAMP, ATOM_PAIR,
_NET_WM_NAME, WM_NAME, MANAGER, _XSETTINGS_SETTINGS,
libinput_Scroll_Methods_Available, XdndAware, XdndSelection, XdndTypeList,
XdndActionCopy, XdndActionMove, XdndActionLink, XdndActionAsk,
XdndActionPrivate, XdndActionList, XdndActionDescription, XdndProxy,
XdndEnter, XdndPosition, XdndStatus, XdndLeave, XdndDrop, XdndFinished,
_NET_WM_FRAME_TIMINGS, _NET_WM_BYPASS_COMPOSITOR, WM_STATE,
_NET_WM_WINDOW_TYPE, _NET_WM_WINDOW_TYPE_MENU, _NET_WM_WINDOW_TYPE_DND;
/* Hash table containing atoms. */
static AtomTable atom_table;
static unsigned int
HashAtomString (const char *string)
{
unsigned int i;
i = 3323198485ul;
for (; *string; ++string)
{
i ^= *string;
i *= 0x5bd1e995;
i ^= i >> 15;
}
return i;
}
Atom
InternAtom (const char *name)
{
Atom atom;
unsigned int hash;
ptrdiff_t bucket_length, i;
hash = HashAtomString (name) % AtomTableSize;
bucket_length = atom_table.atoms_length[hash];
for (i = 0; i < bucket_length; ++i)
{
if (!strcmp (atom_table.names[hash][i], name))
return atom_table.atoms[hash][i];
}
atom = XInternAtom (compositor.display, name, False);
atom_table.atoms_length[hash] = ++bucket_length;
atom_table.names[hash]
= XLRealloc (atom_table.names[hash],
bucket_length * sizeof *atom_table.names);
atom_table.atoms[hash]
= XLRealloc (atom_table.atoms[hash],
bucket_length * sizeof *atom_table.atoms);
atom_table.names[hash][bucket_length - 1] = XLStrdup (name);
atom_table.atoms[hash][bucket_length - 1] = atom;
return atom;
}
void
ProvideAtom (const char *name, Atom atom)
{
unsigned int hash;
ptrdiff_t bucket_length, i;
hash = HashAtomString (name) % AtomTableSize;
bucket_length = atom_table.atoms_length[hash];
for (i = 0; i < bucket_length; ++i)
{
if (!strcmp (atom_table.names[hash][i], name))
/* The atom already exists; there is no need to update it. */
return;
}
atom_table.atoms_length[hash] = ++bucket_length;
atom_table.names[hash]
= XLRealloc (atom_table.names[hash],
bucket_length * sizeof *atom_table.names);
atom_table.atoms[hash]
= XLRealloc (atom_table.atoms[hash],
bucket_length * sizeof *atom_table.atoms);
atom_table.names[hash][bucket_length - 1] = XLStrdup (name);
atom_table.atoms[hash][bucket_length - 1] = atom;
}
void
XLInitAtoms (void)
{
Atom atoms[ArrayElements (names)];
if (!XInternAtoms (compositor.display, (char **) names,
ArrayElements (names), False,
atoms))
{
fprintf (stderr, "Failed to intern X atoms\n");
exit (1);
}
_NET_WM_OPAQUE_REGION = atoms[0];
_XL_BUFFER_RELEASE = atoms[1];
_NET_WM_SYNC_REQUEST_COUNTER = atoms[2];
_NET_WM_FRAME_DRAWN = atoms[3];
WM_DELETE_WINDOW = atoms[4];
WM_PROTOCOLS = atoms[5];
_NET_SUPPORTING_WM_CHECK = atoms[6];
_NET_SUPPORTED = atoms[7];
_NET_WM_SYNC_REQUEST = atoms[8];
_MOTIF_WM_HINTS = atoms[9];
_NET_WM_STATE_MAXIMIZED_VERT = atoms[10];
_NET_WM_STATE_MAXIMIZED_HORZ = atoms[11];
_NET_WM_STATE_FOCUSED = atoms[12];
_NET_WM_STATE_FULLSCREEN = atoms[13];
_NET_WM_STATE = atoms[14];
_NET_WM_MOVERESIZE = atoms[15];
_GTK_FRAME_EXTENTS = atoms[16];
WM_TRANSIENT_FOR = atoms[17];
_XL_DMA_BUF_CREATED = atoms[18];
_GTK_SHOW_WINDOW_MENU = atoms[19];
_NET_WM_ALLOWED_ACTIONS = atoms[20];
_NET_WM_ACTION_FULLSCREEN = atoms[21];
_NET_WM_ACTION_MAXIMIZE_HORZ = atoms[22];
_NET_WM_ACTION_MAXIMIZE_VERT = atoms[23];
_NET_WM_ACTION_MINIMIZE = atoms[24];
INCR = atoms[25];
CLIPBOARD = atoms[26];
TARGETS = atoms[27];
UTF8_STRING = atoms[28];
_XL_SERVER_TIME_ATOM = atoms[29];
MULTIPLE = atoms[30];
TIMESTAMP = atoms[31];
ATOM_PAIR = atoms[32];
_NET_WM_NAME = atoms[33];
WM_NAME = atoms[34];
MANAGER = atoms[35];
_XSETTINGS_SETTINGS = atoms[36];
libinput_Scroll_Methods_Available = atoms[37];
XdndAware = atoms[38];
XdndSelection = atoms[39];
XdndTypeList = atoms[40];
XdndActionCopy = atoms[41];
XdndActionMove = atoms[42];
XdndActionLink = atoms[43];
XdndActionAsk = atoms[44];
XdndActionPrivate = atoms[45];
XdndActionList = atoms[46];
XdndActionDescription = atoms[47];
XdndProxy = atoms[48];
XdndEnter = atoms[49];
XdndPosition = atoms[50];
XdndStatus = atoms[51];
XdndLeave = atoms[52];
XdndDrop = atoms[53];
XdndFinished = atoms[54];
_NET_WM_FRAME_TIMINGS = atoms[55];
_NET_WM_BYPASS_COMPOSITOR = atoms[56];
WM_STATE = atoms[57];
_NET_WM_WINDOW_TYPE = atoms[58];
_NET_WM_WINDOW_TYPE_MENU = atoms[59];
_NET_WM_WINDOW_TYPE_DND = atoms[60];
/* This is automatically generated. */
DirectTransferAtomInit (atoms, 61);
}

127
buffer.c Normal file
View file

@ -0,0 +1,127 @@
/* 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 "compositor.h"
typedef struct _DestroyListener DestroyListener;
struct _DestroyListener
{
/* Function to call. */
ExtBufferFunc func;
/* User data. */
void *data;
};
void
XLRetainBuffer (ExtBuffer *buffer)
{
buffer->funcs.retain (buffer);
}
void
XLDereferenceBuffer (ExtBuffer *buffer)
{
buffer->funcs.dereference (buffer);
}
Picture
XLPictureFromBuffer (ExtBuffer *buffer)
{
return buffer->funcs.get_picture (buffer);
}
Pixmap
XLPixmapFromBuffer (ExtBuffer *buffer)
{
return buffer->funcs.get_pixmap (buffer);
}
unsigned int
XLBufferWidth (ExtBuffer *buffer)
{
return buffer->funcs.width (buffer);
}
unsigned int
XLBufferHeight (ExtBuffer *buffer)
{
return buffer->funcs.height (buffer);
}
void
XLReleaseBuffer (ExtBuffer *buffer)
{
buffer->funcs.release (buffer);
}
void *
XLBufferRunOnFree (ExtBuffer *buffer, ExtBufferFunc func,
void *data)
{
DestroyListener *listener;
listener = XLMalloc (sizeof *listener);
listener->func = func;
listener->data = data;
buffer->destroy_listeners
= XLListPrepend (buffer->destroy_listeners,
listener);
return listener;
}
void
XLBufferCancelRunOnFree (ExtBuffer *buffer, void *key)
{
buffer->destroy_listeners
= XLListRemove (buffer->destroy_listeners, key);
XLFree (key);
}
void
XLPrintBuffer (ExtBuffer *buffer)
{
if (buffer->funcs.print_buffer)
buffer->funcs.print_buffer (buffer);
}
void
ExtBufferDestroy (ExtBuffer *buffer)
{
XLList *listener;
DestroyListener *item;
/* Now run every destroy listener connected to this buffer. */
for (listener = buffer->destroy_listeners;
listener; listener = listener->next)
{
item = listener->data;
item->func (buffer, item->data);
}
/* Not very efficient, since the list is followed through twice, but
destroy listener lists should always be small. */
XLListFree (buffer->destroy_listeners, XLFree);
}

83
compositor.c Normal file
View file

@ -0,0 +1,83 @@
/* 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 "compositor.h"
/* List of all resources for our compositor global. */
static XLList *all_compositors;
/* The compositor global. */
static struct wl_global *global_compositor;
static void
CreateSurface (struct wl_client *client,
struct wl_resource *resource,
uint32_t id)
{
XLCreateSurface (client, resource, id);
}
static void
CreateRegion (struct wl_client *client,
struct wl_resource *resource,
uint32_t id)
{
XLCreateRegion (client, resource, id);
}
static void
HandleResourceDestroy (struct wl_resource *resource)
{
all_compositors = XLListRemove (all_compositors, resource);
}
static const struct wl_compositor_interface wl_compositor_impl =
{
.create_surface = CreateSurface,
.create_region = CreateRegion,
};
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_compositor_interface,
version, id);
if (!resource)
{
wl_client_post_no_memory (client);
return;
}
wl_resource_set_implementation (resource, &wl_compositor_impl,
NULL, HandleResourceDestroy);
all_compositors = XLListPrepend (all_compositors, resource);
}
void
XLInitCompositor (void)
{
global_compositor
= wl_global_create (compositor.wl_display,
&wl_compositor_interface,
5, NULL, HandleBind);
}

1005
compositor.h Normal file

File diff suppressed because it is too large Load diff

1587
data_device.c Normal file

File diff suppressed because it is too large Load diff

1393
dmabuf.c Normal file

File diff suppressed because it is too large Load diff

3111
dnd.c Normal file

File diff suppressed because it is too large Load diff

124
ewmh.c Normal file
View file

@ -0,0 +1,124 @@
/* 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 "compositor.h"
/* Array of supported atoms. Free this with XFree. */
static Atom *net_supported_atoms;
/* Number of elements in that array. */
int n_supported_atoms;
static Window
GetWmCheckWindow (void)
{
Window result;
unsigned char *tmp_data;
int rc, actual_format;
unsigned long actual_size, bytes_remaining;
Atom actual_type;
tmp_data = NULL;
rc = XGetWindowProperty (compositor.display,
DefaultRootWindow (compositor.display),
_NET_SUPPORTING_WM_CHECK,
0, 1, False, XA_WINDOW, &actual_type,
&actual_format, &actual_size,
&bytes_remaining, &tmp_data);
if (rc != Success || actual_type != XA_WINDOW
|| actual_format != 32 || actual_size != 1
|| !tmp_data)
{
if (tmp_data)
XFree (tmp_data);
return None;
}
result = *(Window *) tmp_data;
XFree (tmp_data);
return result;
}
static Bool
IsValidWmCheckWindow (Window window)
{
CatchXErrors ();
XSelectInput (compositor.display, window,
SubstructureNotifyMask);
return !UncatchXErrors (NULL);
}
Bool
XLWmSupportsHint (Atom hint)
{
Window wm_check_window;
Bool errors;
unsigned char *tmp_data;
int rc, actual_format, i;
unsigned long actual_size, bytes_remaining;
Atom actual_type;
/* Window manager restarts are not handled here, since the rest of
the code cannot cope with that. */
start_check:
if (net_supported_atoms)
{
for (i = 0; i < n_supported_atoms; ++i)
{
if (net_supported_atoms[i] == hint)
return True;
}
return False;
}
wm_check_window = GetWmCheckWindow ();
if (!IsValidWmCheckWindow (wm_check_window))
return False;
tmp_data = NULL;
CatchXErrors ();
rc = XGetWindowProperty (compositor.display,
DefaultRootWindow (compositor.display),
_NET_SUPPORTED, 0, 4096, False, XA_ATOM,
&actual_type, &actual_format, &actual_size,
&bytes_remaining, &tmp_data);
errors = UncatchXErrors (NULL);
if (rc != Success || actual_type != XA_ATOM || errors)
{
if (tmp_data)
XFree (tmp_data);
return False;
}
else
{
net_supported_atoms = (Atom *) tmp_data;
n_supported_atoms = actual_size;
goto start_check;
}
}

475
fns.c Normal file
View file

@ -0,0 +1,475 @@
/* 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 <sys/mman.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "compositor.h"
struct _RootWindowSelection
{
/* The next and last event selection records in this chain. */
RootWindowSelection *next, *last;
/* The event mask one piece of code has selected for. */
unsigned long event_mask;
};
/* Events that are being selected for on the root window. */
static RootWindowSelection root_window_events;
void
XLListFree (XLList *list, void (*item_func) (void *))
{
XLList *tem, *last;
tem = list;
while (tem)
{
last = tem;
tem = tem->next;
if (item_func)
item_func (last->data);
XLFree (last);
}
}
XLList *
XLListRemove (XLList *list, void *data)
{
XLList *tem, **last;
last = &list;
while (*last)
{
tem = *last;
if (tem->data == data)
{
*last = tem->next;
XLFree (tem);
}
else
last = &tem->next;
}
return list;
}
XLList *
XLListPrepend (XLList *list, void *data)
{
XLList *tem;
tem = XLMalloc (sizeof *tem);
tem->data = data;
tem->next = list;
return tem;
}
/* List of XIDs (not pointers). */
void
XIDListFree (XIDList *list, void (*item_func) (XID))
{
XIDList *tem, *last;
tem = list;
while (tem)
{
last = tem;
tem = tem->next;
if (item_func)
item_func (last->data);
XLFree (last);
}
}
XIDList *
XIDListRemove (XIDList *list, XID resource)
{
XIDList *tem, **last;
last = &list;
while (*last)
{
tem = *last;
if (tem->data == resource)
{
*last = tem->next;
XLFree (tem);
}
else
last = &tem->next;
}
return list;
}
XIDList *
XIDListPrepend (XIDList *list, XID resource)
{
XIDList *tem;
tem = XLMalloc (sizeof *tem);
tem->data = resource;
tem->next = list;
return tem;
}
/* Hash tables between XIDs and arbitrary data. */
XLAssocTable *
XLCreateAssocTable (int size)
{
XLAssocTable *table;
XLAssoc *buckets;
table = XLMalloc (sizeof *table);
buckets = XLCalloc (size, sizeof *buckets);
table->buckets = buckets;
table->size = size;
while (--size >= 0)
{
/* Initialize each bucket with the sentinel node. */
buckets->prev = buckets;
buckets->next = buckets;
buckets++;
}
return table;
}
static void
Insque (XLAssoc *velem, XLAssoc *vprev)
{
XLAssoc *elem, *prev, *next;
elem = velem;
prev = vprev;
next = prev->next;
prev->next = elem;
if (next)
next->prev = elem;
elem->next = next;
elem->prev = prev;
}
static void
Remque (XLAssoc *velem)
{
XLAssoc *elem, *prev, *next;
elem = velem;
next = elem->next;
prev = elem->prev;
if (next)
next->prev = prev;
if (prev)
prev->next = next;
}
void
XLMakeAssoc (XLAssocTable *table, XID x_id, void *data)
{
int hash;
XLAssoc *bucket, *entry, *new_entry;
hash = x_id % table->size;
bucket = &table->buckets[hash];
entry = bucket->next;
if (entry != bucket)
{
/* Bucket isn't empty, start searching. */
for (; entry != bucket; entry = entry->next)
{
if (entry->x_id == x_id)
{
entry->data = data;
return;
}
if (entry->x_id > x_id)
break;
}
}
/* Insert new_entry immediately before entry. */
new_entry = XLMalloc (sizeof *new_entry);
new_entry->x_id = x_id;
new_entry->data = data;
Insque (new_entry, entry->prev);
}
void *
XLLookUpAssoc (XLAssocTable *table, XID x_id)
{
int hash;
XLAssoc *bucket, *entry;
hash = x_id % table->size;
bucket = &table->buckets[hash];
entry = bucket->next;
for (; entry != bucket; entry = entry->next)
{
if (entry->x_id == x_id)
return entry->data;
if (entry->x_id > x_id)
return NULL;
}
return NULL;
}
void
XLDeleteAssoc (XLAssocTable *table, XID x_id)
{
int hash;
XLAssoc *bucket, *entry;
hash = x_id % table->size;
bucket = &table->buckets[hash];
entry = bucket->next;
for (; entry != bucket; entry = entry->next)
{
if (entry->x_id == x_id)
{
Remque (entry);
XLFree (entry);
return;
}
if (entry->x_id > x_id)
return;
}
}
void
XLDestroyAssocTable (XLAssocTable *table)
{
int i;
XLAssoc *bucket, *entry, *entry_next;
for (i = 0; i < table->size; i++)
{
bucket = &table->buckets[i];
for (entry = bucket->next; entry != bucket;
entry = entry_next)
{
entry_next = entry->next;
XLFree (entry);
}
}
XLFree (table->buckets);
XLFree (table);
}
void
XLAssert (Bool condition)
{
if (!condition)
abort ();
}
void
XLScaleRegion (pixman_region32_t *dst, pixman_region32_t *src,
float scale_x, float scale_y)
{
int nrects, i;
pixman_box32_t *src_rects;
pixman_box32_t *dst_rects;
if (scale_x == 1.0f && scale_y == 1.0f)
{
pixman_region32_copy (dst, src);
return;
}
src_rects = pixman_region32_rectangles (src, &nrects);
if (nrects < 128)
dst_rects = alloca (nrects * sizeof *dst_rects);
else
dst_rects = XLMalloc (nrects * sizeof *dst_rects);
for (i = 0; i < nrects; ++i)
{
dst_rects[i].x1 = floor (src_rects[i].x1 * scale_x);
dst_rects[i].x2 = ceil (src_rects[i].x2 * scale_x);
dst_rects[i].y1 = floor (src_rects[i].y1 * scale_y);
dst_rects[i].y2 = ceil (src_rects[i].y2 * scale_y);
}
pixman_region32_fini (dst);
pixman_region32_init_rects (dst, dst_rects, nrects);
if (nrects >= 128)
XLFree (dst_rects);
}
int
XLOpenShm (void)
{
char name[sizeof "SharedBufferXXXXXXXX"];
int fd;
unsigned int i;
i = 0;
while (i <= 0xffffffff)
{
sprintf (name, "SharedBuffer%x", i);
fd = shm_open (name, O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd >= 0)
{
shm_unlink (name);
return fd;
}
if (errno == EEXIST)
++i;
else
{
perror ("shm_open");
exit (1);
}
}
return -1;
}
static Bool
ServerTimePredicate (Display *display, XEvent *event, XPointer arg)
{
return (event->type == PropertyNotify
&& event->xproperty.window == selection_transfer_window
&& event->xproperty.atom == _XL_SERVER_TIME_ATOM);
}
Time
XLGetServerTimeRoundtrip (void)
{
XEvent event;
XChangeProperty (compositor.display, selection_transfer_window,
_XL_SERVER_TIME_ATOM, XA_ATOM, 32, PropModeReplace,
(unsigned char *) &_XL_SERVER_TIME_ATOM, 1);
XIfEvent (compositor.display, &event, ServerTimePredicate, NULL);
return event.xproperty.time;
}
static void
ReselectRootWindowInput (void)
{
unsigned long effective;
RootWindowSelection *record;
effective = NoEventMask;
record = root_window_events.next;
if (!record)
return;
while (record != &root_window_events)
{
effective |= record->event_mask;
record = record->next;
}
XSelectInput (compositor.display,
DefaultRootWindow (compositor.display),
effective);
}
RootWindowSelection *
XLSelectInputFromRootWindow (unsigned long event_mask)
{
RootWindowSelection *selection;
/* This lets different pieces of code select for input from the root
window without clobbering eachothers event masks. */
selection = XLMalloc (sizeof *selection);
/* If the global chain has not yet been initialized, initialize it
now. */
if (!root_window_events.next)
{
root_window_events.next = &root_window_events;
root_window_events.last = &root_window_events;
}
/* Link this onto the chain of events being selected for on the root
window. */
selection->next = root_window_events.next;
selection->last = &root_window_events;
root_window_events.next->last = selection;
root_window_events.next = selection;
/* Set the event mask. */
selection->event_mask = event_mask;
/* Actually select for events. */
ReselectRootWindowInput ();
return selection;
}
void
XLDeselectInputFromRootWindow (RootWindowSelection *key)
{
key->last->next = key->next;
key->next->last = key->last;
XLFree (key);
ReselectRootWindowInput ();
}

772
frame_clock.c Normal file
View file

@ -0,0 +1,772 @@
/* 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 <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "compositor.h"
typedef struct _FrameClockCallback FrameClockCallback;
typedef struct _CursorClockCallback CursorClockCallback;
enum
{
/* 150ms. */
MaxPresentationAge = 150000,
};
/* Major and minor versions of the XSync extension. */
static int xsync_major, xsync_minor;
/* Whether or not the compositor supports frame synchronization. */
static Bool frame_sync_supported;
/* Timer used for cursor animations. */
static Timer *cursor_clock;
/* How many cursors want cursor animations. */
static int cursor_count;
struct _FrameClockCallback
{
/* Function called once a frame is completely written to display and
(ideally, whether or not this actually works depends on various
different factors) enters vblank. */
void (*frame) (FrameClock *, void *);
/* Data that function is called with. */
void *data;
/* Next and last callbacks in this list. */
FrameClockCallback *next, *last;
};
struct _FrameClock
{
/* List of frame clock callbacks. */
FrameClockCallback callbacks;
/* Two sync counters. */
XSyncCounter primary_counter, secondary_counter;
/* The value of the frame currently being drawn in this frame clock,
and the value of the last frame that was marked as complete. */
uint64_t next_frame_id, finished_frame_id;
/* Whether or not we are waiting for a frame to be completely
painted. */
Bool in_frame;
/* A timer used as a fake synchronization source if frame
synchronization is not supported. */
Timer *static_frame_timer;
/* A timer used to end the next frame. */
Timer *end_frame_timer;
/* Whether or not configury is in progress, and whether or not this
is frozen, and whether or not the frame shouldn't actually be
unfrozen until EndFrame. */
Bool need_configure, frozen, frozen_until_end_frame;
/* The wanted configure value. */
uint64_t configure_id;
/* The time the last frame was drawn. */
uint64_t last_frame_time;
/* The presentation time. */
int32_t presentation_time;
/* The refresh interval. */
uint32_t refresh_interval;
/* Whether or not this frame clock should try to predict
presentation times, in order to group frames together. */
Bool predict_refresh;
/* Callback run when the frame is frozen. */
void (*freeze_callback) (void *);
/* Data for that callback. */
void *freeze_callback_data;
};
struct _CursorClockCallback
{
/* Function called every time cursors should animate once. */
void (*frame) (void *, struct timespec);
/* Data for that function. */
void *data;
/* Next and last cursor clock callbacks. */
CursorClockCallback *next, *last;
};
/* List of cursor frame callbacks. */
static CursorClockCallback cursor_callbacks;
static void
SetSyncCounter (XSyncCounter counter, uint64_t value)
{
uint64_t low, high;
XSyncValue sync_value;
low = value & 0xffffffff;
high = value >> 32;
XSyncIntsToValue (&sync_value, low, high);
XSyncSetCounter (compositor.display, counter,
sync_value);
}
static uint64_t
CurrentHighPrecisionTimestamp (void)
{
struct timespec clock;
uint64_t timestamp;
clock_gettime (CLOCK_MONOTONIC, &clock);
if (IntMultiplyWrapv (clock.tv_sec, 1000000, &timestamp)
|| IntAddWrapv (timestamp, clock.tv_nsec / 1000, &timestamp))
/* Overflow. */
return 0;
return timestamp;
}
static Bool
HighPrecisionTimestampToTimespec (uint64_t timestamp,
struct timespec *timespec)
{
uint64_t remainder, seconds;
seconds = timestamp / 1000000;
remainder = timestamp % 1000000;
if (IntAddWrapv (0, seconds, &timespec->tv_sec))
return False;
/* We know that this cannot overflow tv_nsec, which is long int. */
timespec->tv_nsec = remainder * 1000;
return True;
}
/* Forward declaration. */
static void EndFrame (FrameClock *);
static void
HandleEndFrame (Timer *timer, void *data, struct timespec time)
{
FrameClock *clock;
clock = data;
/* Now that the time allotted for the current frame has run out, end
the frame. */
RemoveTimer (timer);
clock->end_frame_timer = NULL;
EndFrame (clock);
}
static void
PostEndFrame (FrameClock *clock)
{
uint64_t target, now;
struct timespec timespec;
XLAssert (clock->end_frame_timer == NULL);
if (!clock->refresh_interval
|| !clock->presentation_time)
return;
/* Calculate the time by which the next frame must be drawn. It is
a multiple of the refresh rate with the vertical blanking
period added. */
target = clock->last_frame_time + clock->presentation_time;
now = CurrentHighPrecisionTimestamp ();
if (!now)
return;
/* If the last time the frame time was obtained was that long ago,
return immediately. */
if (now - clock->last_frame_time >= MaxPresentationAge)
return;
while (target < now)
{
if (IntAddWrapv (target, clock->refresh_interval, &target))
return;
}
/* Convert the high precision timestamp to a timespec. */
if (!HighPrecisionTimestampToTimespec (target, &timespec))
return;
/* Use 3/4ths of the presentation time. Any more and we risk the
counter value change signalling the end of the frame arriving
after the presentation deadline. */
target = target - (clock->presentation_time / 4 * 3);
/* Schedule the timer marking the end of this frame for the target
time. */
clock->end_frame_timer = AddTimerWithBaseTime (HandleEndFrame,
clock,
/* Use no delay; this
timer will only
run once. */
MakeTimespec (0, 0),
timespec);
}
static void
StartFrame (FrameClock *clock, Bool urgent, Bool predict)
{
if (clock->frozen)
return;
if (clock->frozen_until_end_frame)
return;
if (clock->need_configure)
{
clock->next_frame_id = clock->configure_id;
clock->finished_frame_id = 0;
}
clock->in_frame = True;
/* Set the clock to an odd value; if we want the compositor to
redraw this frame immediately (since it is running late), make it
so that value % 4 == 3. Otherwise, make it so that value % 4 ==
1. */
if (urgent)
{
if (clock->next_frame_id % 4 == 2)
clock->next_frame_id += 1;
else
clock->next_frame_id += 3;
}
else
{
if (clock->next_frame_id % 4 == 3)
clock->next_frame_id += 3;
else
clock->next_frame_id += 1;
}
/* If frame synchronization is not supported, setting the sync
counter itself isn't necessary; the values are used as a flag to
tell us whether or not a frame has been completely drawn. */
if (!frame_sync_supported)
return;
SetSyncCounter (clock->secondary_counter,
clock->next_frame_id);
if (clock->predict_refresh && predict)
PostEndFrame (clock);
clock->need_configure = False;
}
static void
EndFrame (FrameClock *clock)
{
if (clock->frozen)
return;
clock->frozen_until_end_frame = False;
if (!clock->in_frame
/* If the end of the frame has already been signalled, this
function should just return instead of increasing the counter
to an odd value. */
|| clock->finished_frame_id == clock->next_frame_id)
return;
if (clock->end_frame_timer)
/* If the frame is ending at a predicted time, don't allow ending
it manually. */
return;
/* Signal to the compositor that the frame is now complete. When
the compositor finishes drawing the frame, a callback will be
received. */
clock->next_frame_id += 1;
clock->finished_frame_id = clock->next_frame_id;
if (!frame_sync_supported)
return;
SetSyncCounter (clock->secondary_counter,
clock->next_frame_id);
}
static void
FreeFrameCallbacks (FrameClock *clock)
{
FrameClockCallback *callback, *last;
callback = clock->callbacks.next;
while (callback != &clock->callbacks)
{
last = callback;
callback = callback->next;
XLFree (last);
}
clock->callbacks.next = &clock->callbacks;
clock->callbacks.last = &clock->callbacks;
}
static void
RunFrameCallbacks (FrameClock *clock)
{
FrameClockCallback *callback;
callback = clock->callbacks.next;
while (callback != &clock->callbacks)
{
callback->frame (clock, callback->data);
callback = callback->next;
}
}
static void
NoteFakeFrame (Timer *timer, void *data, struct timespec time)
{
FrameClock *clock;
clock = data;
if (clock->in_frame
&& (clock->finished_frame_id == clock->next_frame_id))
{
clock->in_frame = False;
RunFrameCallbacks (clock);
}
}
void
XLFrameClockAfterFrame (FrameClock *clock,
void (*frame_func) (FrameClock *, void *),
void *data)
{
FrameClockCallback *callback;
callback = XLCalloc (1, sizeof *callback);
callback->next = clock->callbacks.next;
callback->last = &clock->callbacks;
clock->callbacks.next->last = callback;
clock->callbacks.next = callback;
callback->data = data;
callback->frame = frame_func;
}
void
XLFrameClockStartFrame (FrameClock *clock, Bool urgent)
{
StartFrame (clock, urgent, True);
}
void
XLFrameClockEndFrame (FrameClock *clock)
{
EndFrame (clock);
}
Bool
XLFrameClockFrameInProgress (FrameClock *clock)
{
if (clock->frozen_until_end_frame)
/* Don't consider a frame as being in progress, since the frame
counter has been incremented to freeze the display. */
return False;
return clock->in_frame;
}
/* N.B. that this function is called from popups, where normal
freezing does not work, as the window manager does not
cooperate. */
void
XLFrameClockFreeze (FrameClock *clock)
{
/* Start a frame now, unless one is already in progress, in which
case it suffices to get rid of the timer. */
if (!clock->end_frame_timer)
StartFrame (clock, False, False);
else
{
RemoveTimer (clock->end_frame_timer);
clock->end_frame_timer = NULL;
}
/* Don't unfreeze until the next EndFrame. */
clock->frozen_until_end_frame = True;
clock->frozen = True;
}
void
XLFrameClockHandleFrameEvent (FrameClock *clock, XEvent *event)
{
uint64_t low, high, value;
if (event->xclient.message_type == _NET_WM_FRAME_DRAWN)
{
/* Mask these values against 0xffffffff, since Xlib sign-extends
these 32 bit values to fit into long, which can be 64
bits. */
low = event->xclient.data.l[0] & 0xffffffff;
high = event->xclient.data.l[1] & 0xffffffff;
value = low | (high << 32);
if (value == clock->finished_frame_id
&& clock->in_frame
/* If this means the frame has been completely drawn, then
clear in_frame and run frame callbacks to i.e. draw any
late frame. */
&& (clock->finished_frame_id == clock->next_frame_id))
{
/* Record the time at which the frame was drawn. */
low = event->xclient.data.l[2] & 0xffffffff;
high = event->xclient.data.l[3] & 0xffffffff;
/* Actually compute the time and save it. */
clock->last_frame_time = low | (high << 32);
/* Run any frame callbacks, since drawing has finished. */
clock->in_frame = False;
RunFrameCallbacks (clock);
}
}
if (event->xclient.message_type == _NET_WM_FRAME_TIMINGS)
{
/* Save the presentation time and refresh interval. There is no
need to mask these values, since they are being put into
(u)int32_t. */
clock->presentation_time = event->xclient.data.l[2];
clock->refresh_interval = event->xclient.data.l[3];
if (clock->refresh_interval & (1U << 31))
{
/* This means frame timing information is unavailable. */
clock->presentation_time = 0;
clock->refresh_interval = 0;
}
}
if (event->xclient.message_type == WM_PROTOCOLS
&& event->xclient.data.l[0] == _NET_WM_SYNC_REQUEST
&& event->xclient.data.l[4] == 1)
{
low = event->xclient.data.l[2];
high = event->xclient.data.l[3];
value = low | (high << 32);
/* Ensure that value is even. */
if (value % 2)
value += 1;
/* The frame clock is now frozen, and we will have to wait for a
client to ack_configure and then commit something. */
if (clock->end_frame_timer)
{
/* End the frame now, and clear in_frame early. */
RemoveTimer (clock->end_frame_timer);
clock->end_frame_timer = NULL;
EndFrame (clock);
/* The reason for clearing in_frame is that otherwise a
future Commit after the configuration is acknowledged
will not be able to start a new frame and restart the
frame clock. */
clock->in_frame = False;
}
clock->need_configure = True;
clock->configure_id = value;
clock->frozen = True;
if (clock->freeze_callback)
clock->freeze_callback (clock->freeze_callback_data);
}
}
void
XLFreeFrameClock (FrameClock *clock)
{
FreeFrameCallbacks (clock);
if (frame_sync_supported)
{
XSyncDestroyCounter (compositor.display,
clock->primary_counter);
XSyncDestroyCounter (compositor.display,
clock->secondary_counter);
}
else
RemoveTimer (clock->static_frame_timer);
if (clock->end_frame_timer)
RemoveTimer (clock->end_frame_timer);
XLFree (clock);
}
FrameClock *
XLMakeFrameClockForWindow (Window window)
{
FrameClock *clock;
XSyncValue initial_value;
struct timespec default_refresh_rate;
clock = XLCalloc (1, sizeof *clock);
clock->next_frame_id = 0;
XLOutputGetMinRefresh (&default_refresh_rate);
XSyncIntToValue (&initial_value, 0);
if (frame_sync_supported)
{
clock->primary_counter
= XSyncCreateCounter (compositor.display,
initial_value);
clock->secondary_counter
= XSyncCreateCounter (compositor.display,
initial_value);
}
else
clock->static_frame_timer
= AddTimer (NoteFakeFrame, clock,
default_refresh_rate);
/* Initialize sentinel link. */
clock->callbacks.next = &clock->callbacks;
clock->callbacks.last = &clock->callbacks;
if (frame_sync_supported)
XChangeProperty (compositor.display, window,
_NET_WM_SYNC_REQUEST_COUNTER, XA_CARDINAL, 32,
PropModeReplace,
(unsigned char *) &clock->primary_counter, 2);
if (getenv ("DEBUG_REFRESH_PREDICTION"))
clock->predict_refresh = True;
return clock;
}
void
XLFrameClockUnfreeze (FrameClock *clock)
{
clock->frozen = False;
}
Bool
XLFrameClockNeedConfigure (FrameClock *clock)
{
return clock->need_configure;
}
Bool
XLFrameClockSyncSupported (void)
{
return frame_sync_supported;
}
Bool
XLFrameClockIsFrozen (FrameClock *clock)
{
return clock->frozen;
}
Bool
XLFrameClockCanBatch (FrameClock *clock)
{
/* Hmm... this doesn't seem very accurate. Maybe it would be a
better to test against the target presentation time instead. */
return clock->end_frame_timer != NULL;
}
void
XLFrameClockSetPredictRefresh (FrameClock *clock)
{
/* This sets whether or not the frame clock should try to predict
when the compositing manager will draw a frame to display.
It is useful when multiple subsurfaces are trying to start
subframes on the same toplevel at the same time; in that case,
the subframes will be grouped into a single synchronized frame,
instead of being postponed. */
if (compositor.server_time_monotonic)
clock->predict_refresh = True;
}
void
XLFrameClockDisablePredictRefresh (FrameClock *clock)
{
/* This sets whether or not the frame clock should try to predict
when the compositing manager will draw a frame to display.
It is useful when multiple subsurfaces are trying to start
subframes on the same toplevel at the same time; in that case,
the subframes will be grouped into a single synchronized frame,
instead of being postponed. */
clock->predict_refresh = False;
}
void
XLFrameClockSetFreezeCallback (FrameClock *clock, void (*callback) (void *),
void *data)
{
clock->freeze_callback = callback;
clock->freeze_callback_data = data;
}
/* Cursor animation clock-related functions. */
static void
NoteCursorFrame (Timer *timer, void *data, struct timespec time)
{
CursorClockCallback *callback;
callback = cursor_callbacks.next;
while (callback != &cursor_callbacks)
{
callback->frame (callback->data, time);
callback = callback->next;
}
}
void *
XLAddCursorClockCallback (void (*frame_func) (void *, struct timespec),
void *data)
{
CursorClockCallback *callback;
callback = XLMalloc (sizeof *callback);
callback->next = cursor_callbacks.next;
callback->last = &cursor_callbacks;
cursor_callbacks.next->last = callback;
cursor_callbacks.next = callback;
callback->frame = frame_func;
callback->data = data;
return callback;
}
void
XLStopCursorClockCallback (void *key)
{
CursorClockCallback *callback;
callback = key;
/* First, make the list skip past CALLBACK. */
callback->last->next = callback->next;
callback->next->last = callback->last;
/* Then, free CALLBACK. */
XLFree (callback);
}
void
XLStartCursorClock (void)
{
struct timespec cursor_refresh_rate;
if (cursor_count++)
return;
cursor_refresh_rate.tv_sec = 0;
cursor_refresh_rate.tv_nsec = 60000000;
cursor_clock = AddTimer (NoteCursorFrame, NULL,
cursor_refresh_rate);
}
void
XLStopCursorClock (void)
{
if (--cursor_count)
return;
RemoveTimer (cursor_clock);
cursor_clock = NULL;
}
void
XLInitFrameClock (void)
{
Bool supported;
int xsync_event_base, xsync_error_base;
supported = XSyncQueryExtension (compositor.display,
&xsync_event_base,
&xsync_error_base);
if (supported)
supported = XSyncInitialize (compositor.display,
&xsync_major, &xsync_minor);
if (!supported)
{
fprintf (stderr, "A compatible version of the Xsync extension"
" was not found\n");
exit (1);
}
if (!getenv ("DISABLE_FRAME_SYNCHRONIZATION"))
frame_sync_supported = XLWmSupportsHint (_NET_WM_FRAME_DRAWN);
/* Initialize cursor callbacks. */
cursor_callbacks.next = &cursor_callbacks;
cursor_callbacks.last = &cursor_callbacks;
}

518
icon_surface.c Normal file
View file

@ -0,0 +1,518 @@
/* 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/>. */
/* Generic "icon surface" role. */
#include <stdlib.h>
#include "compositor.h"
#include <X11/extensions/shape.h>
#define IconSurfaceFromRole(role) ((IconSurface *) role)
enum
{
StateLateFrame = 1,
StateIsMapped = (1 << 1),
StateIsReleased = (1 << 2),
};
struct _IconSurface
{
/* The role object itself. */
Role role;
/* The window used by this role. */
Window window;
/* The picture associated with this role. */
Picture picture;
/* The subcompositor associated with this role. */
Subcompositor *subcompositor;
/* The frame clock associated with this role. */
FrameClock *clock;
/* The number of references to this role. */
int refcount;
/* Some state. */
int state;
/* The position of this icon surface relative to the root
window. */
int x, y;
/* The last known bounds of this icon surface. */
int min_x, min_y, max_x, max_y;
};
/* Hash table of all icon surfaces. */
static XLAssocTable *surfaces;
static void
WriteRedirectProperty (IconSurface *icon)
{
unsigned long bypass_compositor;
bypass_compositor = 2;
XChangeProperty (compositor.display, icon->window,
_NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL,
32, PropModeReplace,
(unsigned char *) &bypass_compositor, 1);
}
static void
ReleaseBacking (IconSurface *icon)
{
if (--icon->refcount)
return;
/* Release all allocated resources. */
XRenderFreePicture (compositor.display, icon->picture);
XDestroyWindow (compositor.display, icon->window);
/* And the association. */
XLDeleteAssoc (surfaces, icon->window);
/* There shouldn't be any children of the subcompositor at this
point. */
SubcompositorFree (icon->subcompositor);
/* The frame clock is no longer useful. */
XLFreeFrameClock (icon->clock);
/* And since there are no C level references to the icon surface
anymore, it can be freed. */
XLFree (icon);
}
static void
Teardown (Surface *surface, Role *role)
{
IconSurface *icon;
icon = IconSurfaceFromRole (role);
role->surface = NULL;
/* 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 (icon);
}
static Bool
Setup (Surface *surface, Role *role)
{
IconSurface *icon;
/* Set role->surface here, since this is where the refcounting is
done as well. */
role->surface = surface;
icon = IconSurfaceFromRole (role);
ViewSetSubcompositor (surface->view,
icon->subcompositor);
ViewSetSubcompositor (surface->under,
icon->subcompositor);
/* Make sure the under view ends up beneath surface->view. */
SubcompositorInsert (icon->subcompositor,
surface->under);
SubcompositorInsert (icon->subcompositor,
surface->view);
/* Retain the backing data. */
icon->refcount++;
return True;
}
static void
ReleaseBuffer (Surface *surface, Role *role, ExtBuffer *buffer)
{
/* Icon surfaces are not supposed to change much, so doing an XSync
here is okay. */
XSync (compositor.display, False);
/* Now really release the buffer. */
XLReleaseBuffer (buffer);
}
static void
UpdateOutputs (IconSurface *icon)
{
int x_off, y_off;
if (!icon->role.surface)
return;
x_off = icon->role.surface->current_state.x;
y_off = icon->role.surface->current_state.y;
XLUpdateSurfaceOutputs (icon->role.surface,
icon->x + icon->min_x + x_off,
icon->y + icon->min_y + y_off,
icon->max_x - icon->min_x + 1,
icon->max_y - icon->min_y + 1);
}
static void
NoteBounds (void *data, int min_x, int min_y, int max_x, int max_y)
{
IconSurface *icon;
int x, y;
icon = data;
if (min_x != icon->min_x || min_y != icon->min_y
|| max_x != icon->max_x || max_y != icon->max_y)
{
x = icon->x + icon->role.surface->current_state.x;
y = icon->y + icon->role.surface->current_state.y;
/* If the bounds changed, move the window to the right
position. */
XMoveResizeWindow (compositor.display, icon->window,
x + min_x, y + min_y, max_x - min_x + 1,
max_y - min_y + 1);
/* Update the outputs that this surface is inside. */
UpdateOutputs (icon);
/* Save the new bounds. */
icon->min_x = min_x;
icon->min_y = min_y;
icon->max_x = max_x;
icon->max_y = max_y;
}
}
static void
RunFrameCallbacks (Surface *surface)
{
struct timespec time;
/* Surface can be NULL for various reasons, especially events
arriving after the icon surface is detached. */
if (!surface)
return;
clock_gettime (CLOCK_MONOTONIC, &time);
XLSurfaceRunFrameCallbacks (surface, time);
}
static void
AfterFrame (FrameClock *clock, void *data)
{
IconSurface *icon;
icon = data;
if (icon->state & StateLateFrame)
{
icon->state &= ~StateLateFrame;
/* Since we are running late, make the compositor draw the frame
now. */
XLFrameClockStartFrame (clock, True);
SubcompositorUpdate (icon->subcompositor);
XLFrameClockEndFrame (clock);
return;
}
RunFrameCallbacks (icon->role.surface);
}
static void
MaybeMapWindow (IconSurface *icon)
{
if (icon->state & StateIsMapped)
return;
if (icon->state & StateIsReleased)
return;
XMapRaised (compositor.display, icon->window);
icon->state |= StateIsMapped;
UpdateOutputs (icon);
}
static void
MaybeUnmapWindow (IconSurface *icon)
{
if (!(icon->state & StateIsMapped))
return;
XUnmapWindow (compositor.display, icon->window);
icon->state &= ~StateIsMapped;
if (icon->role.surface)
XLClearOutputs (icon->role.surface);
}
static void
MoveWindowTo (IconSurface *icon, int x, int y)
{
int x_off, y_off;
if (icon->x == x && icon->y == y)
return;
icon->x = x;
icon->y = y;
x_off = icon->role.surface->current_state.x;
y_off = icon->role.surface->current_state.y;
XMoveWindow (compositor.display, icon->window,
icon->x + icon->min_x + x_off,
icon->y + icon->min_y + y_off);
UpdateOutputs (icon);
}
static void
Commit (Surface *surface, Role *role)
{
IconSurface *icon;
icon = IconSurfaceFromRole (role);
if (XLFrameClockFrameInProgress (icon->clock))
{
/* A frame is already in progress; schedule another one for
later. */
icon->state |= StateLateFrame;
}
else
{
/* Start a frame and update the icon surface now. */
XLFrameClockStartFrame (icon->clock, False);
SubcompositorUpdate (icon->subcompositor);
XLFrameClockEndFrame (icon->clock);
}
/* Move the window if any offset was specified. */
if (surface->pending_state.pending & PendingAttachments)
MoveWindowTo (icon, icon->x, icon->y);
/* Map or unmap the window according to whether or not the surface
has an attached buffer. */
if (surface->current_state.buffer)
MaybeMapWindow (icon);
else
MaybeUnmapWindow (icon);
}
static Bool
Subframe (Surface *surface, Role *role)
{
IconSurface *icon;
icon = IconSurfaceFromRole (role);
if (XLFrameClockFrameInProgress (icon->clock))
{
/* A frame is already in progress; schedule another one for
later. */
icon->state |= StateLateFrame;
return False;
}
/* I guess subsurface updates don't count as urgent frames? */
XLFrameClockStartFrame (icon->clock, False);
return True;
}
static void
EndSubframe (Surface *surface, Role *role)
{
IconSurface *icon;
icon = IconSurfaceFromRole (role);
XLFrameClockEndFrame (icon->clock);
}
static Window
GetWindow (Surface *surface, Role *role)
{
/* XLWindowFromSurface is used to obtain a window for input-related
purposes. Icon surfaces cannot be subject to input, so don't
return the backing window. */
return None;
}
IconSurface *
XLGetIconSurface (Surface *surface)
{
IconSurface *role;
XSetWindowAttributes attrs;
XRenderPictureAttributes picture_attrs;
unsigned int flags;
role = XLCalloc (1, sizeof *role);
role->refcount = 1;
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;
/* Make an override-redirect window to use as the icon surface. */
flags = (CWColormap | CWBorderPixel | CWEventMask
| CWOverrideRedirect);
attrs.colormap = compositor.colormap;
attrs.border_pixel = border_pixel;
attrs.event_mask = (ExposureMask | StructureNotifyMask);
attrs.override_redirect = 1;
role->window = XCreateWindow (compositor.display,
DefaultRootWindow (compositor.display),
0, 0, 1, 1, 0, compositor.n_planes,
InputOutput, compositor.visual, flags,
&attrs);
/* Add _NET_WM_SYNC_REQUEST to the list of supported protocols. */
XSetWMProtocols (compositor.display, role->window,
&_NET_WM_SYNC_REQUEST, 1);
/* Set _NET_WM_WINDOW_TYPE to _NET_WM_WINDOW_TYPE_DND. */
XChangeProperty (compositor.display, role->window,
_NET_WM_WINDOW_TYPE, XA_ATOM, 32,
PropModeReplace,
(unsigned char *) &_NET_WM_WINDOW_TYPE_DND, 1);
/* Create a picture associated with the window. */
role->picture = XRenderCreatePicture (compositor.display,
role->window,
/* TODO: get this from the
visual instead. */
compositor.argb_format,
0, &picture_attrs);
/* Create a subcompositor associated with the window. */
role->subcompositor = MakeSubcompositor ();
role->clock = XLMakeFrameClockForWindow (role->window);
/* Set the subcompositor target and some callbacks. */
SubcompositorSetTarget (role->subcompositor, role->picture);
SubcompositorSetBoundsCallback (role->subcompositor,
NoteBounds, role);
/* Clear the input region of the window. */
XShapeCombineRectangles (compositor.display, role->window,
ShapeInput, 0, 0, NULL, 0, ShapeSet,
Unsorted);
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 ();
return role;
}
Bool
XLHandleOneXEventForIconSurfaces (XEvent *event)
{
IconSurface *icon;
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)))
{
icon = XLLookUpAssoc (surfaces, event->xclient.window);
if (icon)
{
XLFrameClockHandleFrameEvent (icon->clock, event);
return True;
}
return False;
}
if (event->type == Expose)
{
icon = XLLookUpAssoc (surfaces, event->xexpose.window);
if (icon)
{
SubcompositorExpose (icon->subcompositor, event);
return True;
}
return False;
}
return False;
}
void
XLMoveIconSurface (IconSurface *surface, int root_x, int root_y)
{
MoveWindowTo (surface, root_x, root_y);
}
void
XLInitIconSurfaces (void)
{
/* This assoc table is rather small, since the amount of icon
surfaces alive at any given time is also low. */
surfaces = XLCreateAssocTable (25);
}
void
XLReleaseIconSurface (IconSurface *icon)
{
/* Unmap the surface and mark it as released, meaning it will not be
mapped again in the future. */
MaybeUnmapWindow (icon);
icon->state |= StateIsReleased;
/* Release the icon surface. */
ReleaseBacking (icon);
}
Bool
XLIsWindowIconSurface (Window window)
{
return XLLookUpAssoc (surfaces, window) != NULL;
}

17
libraries.def Normal file
View file

@ -0,0 +1,17 @@
/* Edit this file if any of these libraries are named differently
on your system. */
XCB = -lxcb
XCB_SHM = -lxcb-shm
XCB_DRI3 = -lxcb-dri3
XCB_SHAPE = -lxcb-shape
WAYLAND_SERVER = -lwayland-server
XCBLIB = -lX11-xcb
PIXMAN = -lpixman-1
DRMINCLUDES = -I$(INCROOT)/drm
PIXMANINCLUDES = -I$(INCROOT)/pixman-1
/* And edit this if wayland-scanner is named something else on your
system. */
WAYLAND_SCANNER = wayland-scanner

View file

@ -0,0 +1,586 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="linux_dmabuf_unstable_v1">
<copyright>
Copyright © 2014, 2015 Collabora, Ltd.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<interface name="zwp_linux_dmabuf_v1" version="4">
<description summary="factory for creating dmabuf-based wl_buffers">
Following the interfaces from:
https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_image_dma_buf_import.txt
https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_image_dma_buf_import_modifiers.txt
and the Linux DRM sub-system's AddFb2 ioctl.
This interface offers ways to create generic dmabuf-based wl_buffers.
Clients can use the get_surface_feedback request to get dmabuf feedback
for a particular surface. If the client wants to retrieve feedback not
tied to a surface, they can use the get_default_feedback request.
The following are required from clients:
- Clients must ensure that either all data in the dma-buf is
coherent for all subsequent read access or that coherency is
correctly handled by the underlying kernel-side dma-buf
implementation.
- Don't make any more attachments after sending the buffer to the
compositor. Making more attachments later increases the risk of
the compositor not being able to use (re-import) an existing
dmabuf-based wl_buffer.
The underlying graphics stack must ensure the following:
- The dmabuf file descriptors relayed to the server will stay valid
for the whole lifetime of the wl_buffer. This means the server may
at any time use those fds to import the dmabuf into any kernel
sub-system that might accept it.
However, when the underlying graphics stack fails to deliver the
promise, because of e.g. a device hot-unplug which raises internal
errors, after the wl_buffer has been successfully created the
compositor must not raise protocol errors to the client when dmabuf
import later fails.
To create a wl_buffer from one or more dmabufs, a client creates a
zwp_linux_dmabuf_params_v1 object with a zwp_linux_dmabuf_v1.create_params
request. All planes required by the intended format are added with
the 'add' request. Finally, a 'create' or 'create_immed' request is
issued, which has the following outcome depending on the import success.
The 'create' request,
- on success, triggers a 'created' event which provides the final
wl_buffer to the client.
- on failure, triggers a 'failed' event to convey that the server
cannot use the dmabufs received from the client.
For the 'create_immed' request,
- on success, the server immediately imports the added dmabufs to
create a wl_buffer. No event is sent from the server in this case.
- on failure, the server can choose to either:
- terminate the client by raising a fatal error.
- mark the wl_buffer as failed, and send a 'failed' event to the
client. If the client uses a failed wl_buffer as an argument to any
request, the behaviour is compositor implementation-defined.
For all DRM formats and unless specified in another protocol extension,
pre-multiplied alpha is used for pixel values.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
</description>
<request name="destroy" type="destructor">
<description summary="unbind the factory">
Objects created through this interface, especially wl_buffers, will
remain valid.
</description>
</request>
<request name="create_params">
<description summary="create a temporary object for buffer parameters">
This temporary object is used to collect multiple dmabuf handles into
a single batch to create a wl_buffer. It can only be used once and
should be destroyed after a 'created' or 'failed' event has been
received.
</description>
<arg name="params_id" type="new_id" interface="zwp_linux_buffer_params_v1"
summary="the new temporary"/>
</request>
<event name="format">
<description summary="supported buffer format">
This event advertises one buffer format that the server supports.
All the supported formats are advertised once when the client
binds to this interface. A roundtrip after binding guarantees
that the client has received all supported formats.
For the definition of the format codes, see the
zwp_linux_buffer_params_v1::create request.
Starting version 4, the format event is deprecated and must not be
sent by compositors. Instead, use get_default_feedback or
get_surface_feedback.
</description>
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
</event>
<event name="modifier" since="3">
<description summary="supported buffer format modifier">
This event advertises the formats that the server supports, along with
the modifiers supported for each format. All the supported modifiers
for all the supported formats are advertised once when the client
binds to this interface. A roundtrip after binding guarantees that
the client has received all supported format-modifier pairs.
For legacy support, DRM_FORMAT_MOD_INVALID (that is, modifier_hi ==
0x00ffffff and modifier_lo == 0xffffffff) is allowed in this event.
It indicates that the server can support the format with an implicit
modifier. When a plane has DRM_FORMAT_MOD_INVALID as its modifier, it
is as if no explicit modifier is specified. The effective modifier
will be derived from the dmabuf.
A compositor that sends valid modifiers and DRM_FORMAT_MOD_INVALID for
a given format supports both explicit modifiers and implicit modifiers.
For the definition of the format and modifier codes, see the
zwp_linux_buffer_params_v1::create and zwp_linux_buffer_params_v1::add
requests.
Starting version 4, the modifier event is deprecated and must not be
sent by compositors. Instead, use get_default_feedback or
get_surface_feedback.
</description>
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
<arg name="modifier_hi" type="uint"
summary="high 32 bits of layout modifier"/>
<arg name="modifier_lo" type="uint"
summary="low 32 bits of layout modifier"/>
</event>
<!-- Version 4 additions -->
<request name="get_default_feedback" since="4">
<description summary="get default feedback">
This request creates a new wp_linux_dmabuf_feedback object not bound
to a particular surface. This object will deliver feedback about dmabuf
parameters to use if the client doesn't support per-surface feedback
(see get_surface_feedback).
</description>
<arg name="id" type="new_id" interface="zwp_linux_dmabuf_feedback_v1"/>
</request>
<request name="get_surface_feedback" since="4">
<description summary="get feedback for a surface">
This request creates a new wp_linux_dmabuf_feedback object for the
specified wl_surface. This object will deliver feedback about dmabuf
parameters to use for buffers attached to this surface.
If the surface is destroyed before the wp_linux_dmabuf_feedback object,
the feedback object becomes inert.
</description>
<arg name="id" type="new_id" interface="zwp_linux_dmabuf_feedback_v1"/>
<arg name="surface" type="object" interface="wl_surface"/>
</request>
</interface>
<interface name="zwp_linux_buffer_params_v1" version="4">
<description summary="parameters for creating a dmabuf-based wl_buffer">
This temporary object is a collection of dmabufs and other
parameters that together form a single logical buffer. The temporary
object may eventually create one wl_buffer unless cancelled by
destroying it before requesting 'create'.
Single-planar formats only require one dmabuf, however
multi-planar formats may require more than one dmabuf. For all
formats, an 'add' request must be called once per plane (even if the
underlying dmabuf fd is identical).
You must use consecutive plane indices ('plane_idx' argument for 'add')
from zero to the number of planes used by the drm_fourcc format code.
All planes required by the format must be given exactly once, but can
be given in any order. Each plane index can be set only once.
</description>
<enum name="error">
<entry name="already_used" value="0"
summary="the dmabuf_batch object has already been used to create a wl_buffer"/>
<entry name="plane_idx" value="1"
summary="plane index out of bounds"/>
<entry name="plane_set" value="2"
summary="the plane index was already set"/>
<entry name="incomplete" value="3"
summary="missing or too many planes to create a buffer"/>
<entry name="invalid_format" value="4"
summary="format not supported"/>
<entry name="invalid_dimensions" value="5"
summary="invalid width or height"/>
<entry name="out_of_bounds" value="6"
summary="offset + stride * height goes out of dmabuf bounds"/>
<entry name="invalid_wl_buffer" value="7"
summary="invalid wl_buffer resulted from importing dmabufs via
the create_immed request on given buffer_params"/>
</enum>
<request name="destroy" type="destructor">
<description summary="delete this object, used or not">
Cleans up the temporary data sent to the server for dmabuf-based
wl_buffer creation.
</description>
</request>
<request name="add">
<description summary="add a dmabuf to the temporary set">
This request adds one dmabuf to the set in this
zwp_linux_buffer_params_v1.
The 64-bit unsigned value combined from modifier_hi and modifier_lo
is the dmabuf layout modifier. DRM AddFB2 ioctl calls this the
fb modifier, which is defined in drm_mode.h of Linux UAPI.
This is an opaque token. Drivers use this token to express tiling,
compression, etc. driver-specific modifications to the base format
defined by the DRM fourcc code.
Starting from version 4, the invalid_format protocol error is sent if
the format + modifier pair was not advertised as supported.
This request raises the PLANE_IDX error if plane_idx is too large.
The error PLANE_SET is raised if attempting to set a plane that
was already set.
</description>
<arg name="fd" type="fd" summary="dmabuf fd"/>
<arg name="plane_idx" type="uint" summary="plane index"/>
<arg name="offset" type="uint" summary="offset in bytes"/>
<arg name="stride" type="uint" summary="stride in bytes"/>
<arg name="modifier_hi" type="uint"
summary="high 32 bits of layout modifier"/>
<arg name="modifier_lo" type="uint"
summary="low 32 bits of layout modifier"/>
</request>
<enum name="flags" bitfield="true">
<entry name="y_invert" value="1" summary="contents are y-inverted"/>
<entry name="interlaced" value="2" summary="content is interlaced"/>
<entry name="bottom_first" value="4" summary="bottom field first"/>
</enum>
<request name="create">
<description summary="create a wl_buffer from the given dmabufs">
This asks for creation of a wl_buffer from the added dmabuf
buffers. The wl_buffer is not created immediately but returned via
the 'created' event if the dmabuf sharing succeeds. The sharing
may fail at runtime for reasons a client cannot predict, in
which case the 'failed' event is triggered.
The 'format' argument is a DRM_FORMAT code, as defined by the
libdrm's drm_fourcc.h. The Linux kernel's DRM sub-system is the
authoritative source on how the format codes should work.
The 'flags' is a bitfield of the flags defined in enum "flags".
'y_invert' means the that the image needs to be y-flipped.
Flag 'interlaced' means that the frame in the buffer is not
progressive as usual, but interlaced. An interlaced buffer as
supported here must always contain both top and bottom fields.
The top field always begins on the first pixel row. The temporal
ordering between the two fields is top field first, unless
'bottom_first' is specified. It is undefined whether 'bottom_first'
is ignored if 'interlaced' is not set.
This protocol does not convey any information about field rate,
duration, or timing, other than the relative ordering between the
two fields in one buffer. A compositor may have to estimate the
intended field rate from the incoming buffer rate. It is undefined
whether the time of receiving wl_surface.commit with a new buffer
attached, applying the wl_surface state, wl_surface.frame callback
trigger, presentation, or any other point in the compositor cycle
is used to measure the frame or field times. There is no support
for detecting missed or late frames/fields/buffers either, and
there is no support whatsoever for cooperating with interlaced
compositor output.
The composited image quality resulting from the use of interlaced
buffers is explicitly undefined. A compositor may use elaborate
hardware features or software to deinterlace and create progressive
output frames from a sequence of interlaced input buffers, or it
may produce substandard image quality. However, compositors that
cannot guarantee reasonable image quality in all cases are recommended
to just reject all interlaced buffers.
Any argument errors, including non-positive width or height,
mismatch between the number of planes and the format, bad
format, bad offset or stride, may be indicated by fatal protocol
errors: INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS,
OUT_OF_BOUNDS.
Dmabuf import errors in the server that are not obvious client
bugs are returned via the 'failed' event as non-fatal. This
allows attempting dmabuf sharing and falling back in the client
if it fails.
This request can be sent only once in the object's lifetime, after
which the only legal request is destroy. This object should be
destroyed after issuing a 'create' request. Attempting to use this
object after issuing 'create' raises ALREADY_USED protocol error.
It is not mandatory to issue 'create'. If a client wants to
cancel the buffer creation, it can just destroy this object.
</description>
<arg name="width" type="int" summary="base plane width in pixels"/>
<arg name="height" type="int" summary="base plane height in pixels"/>
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
<arg name="flags" type="uint" enum="flags" summary="see enum flags"/>
</request>
<event name="created">
<description summary="buffer creation succeeded">
This event indicates that the attempted buffer creation was
successful. It provides the new wl_buffer referencing the dmabuf(s).
Upon receiving this event, the client should destroy the
zlinux_dmabuf_params object.
</description>
<arg name="buffer" type="new_id" interface="wl_buffer"
summary="the newly created wl_buffer"/>
</event>
<event name="failed">
<description summary="buffer creation failed">
This event indicates that the attempted buffer creation has
failed. It usually means that one of the dmabuf constraints
has not been fulfilled.
Upon receiving this event, the client should destroy the
zlinux_buffer_params object.
</description>
</event>
<request name="create_immed" since="2">
<description summary="immediately create a wl_buffer from the given
dmabufs">
This asks for immediate creation of a wl_buffer by importing the
added dmabufs.
In case of import success, no event is sent from the server, and the
wl_buffer is ready to be used by the client.
Upon import failure, either of the following may happen, as seen fit
by the implementation:
- the client is terminated with one of the following fatal protocol
errors:
- INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, OUT_OF_BOUNDS,
in case of argument errors such as mismatch between the number
of planes and the format, bad format, non-positive width or
height, or bad offset or stride.
- INVALID_WL_BUFFER, in case the cause for failure is unknown or
plaform specific.
- the server creates an invalid wl_buffer, marks it as failed and
sends a 'failed' event to the client. The result of using this
invalid wl_buffer as an argument in any request by the client is
defined by the compositor implementation.
This takes the same arguments as a 'create' request, and obeys the
same restrictions.
</description>
<arg name="buffer_id" type="new_id" interface="wl_buffer"
summary="id for the newly created wl_buffer"/>
<arg name="width" type="int" summary="base plane width in pixels"/>
<arg name="height" type="int" summary="base plane height in pixels"/>
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
<arg name="flags" type="uint" enum="flags" summary="see enum flags"/>
</request>
</interface>
<interface name="zwp_linux_dmabuf_feedback_v1" version="4">
<description summary="dmabuf feedback">
This object advertises dmabuf parameters feedback. This includes the
preferred devices and the supported formats/modifiers.
The parameters are sent once when this object is created and whenever they
change. The done event is always sent once after all parameters have been
sent. When a single parameter changes, all parameters are re-sent by the
compositor.
Compositors can re-send the parameters when the current client buffer
allocations are sub-optimal. Compositors should not re-send the
parameters if re-allocating the buffers would not result in a more optimal
configuration. In particular, compositors should avoid sending the exact
same parameters multiple times in a row.
The tranche_target_device and tranche_modifier events are grouped by
tranches of preference. For each tranche, a tranche_target_device, one
tranche_flags and one or more tranche_modifier events are sent, followed
by a tranche_done event finishing the list. The tranches are sent in
descending order of preference. All formats and modifiers in the same
tranche have the same preference.
To send parameters, the compositor sends one main_device event, tranches
(each consisting of one tranche_target_device event, one tranche_flags
event, tranche_modifier events and then a tranche_done event), then one
done event.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the feedback object">
Using this request a client can tell the server that it is not going to
use the wp_linux_dmabuf_feedback object anymore.
</description>
</request>
<event name="done">
<description summary="all feedback has been sent">
This event is sent after all parameters of a wp_linux_dmabuf_feedback
object have been sent.
This allows changes to the wp_linux_dmabuf_feedback parameters to be
seen as atomic, even if they happen via multiple events.
</description>
</event>
<event name="format_table">
<description summary="format and modifier table">
This event provides a file descriptor which can be memory-mapped to
access the format and modifier table.
The table contains a tightly packed array of consecutive format +
modifier pairs. Each pair is 16 bytes wide. It contains a format as a
32-bit unsigned integer, followed by 4 bytes of unused padding, and a
modifier as a 64-bit unsigned integer. The native endianness is used.
The client must map the file descriptor in read-only private mode.
Compositors are not allowed to mutate the table file contents once this
event has been sent. Instead, compositors must create a new, separate
table file and re-send feedback parameters. Compositors are allowed to
store duplicate format + modifier pairs in the table.
</description>
<arg name="fd" type="fd" summary="table file descriptor"/>
<arg name="size" type="uint" summary="table size, in bytes"/>
</event>
<event name="main_device">
<description summary="preferred main device">
This event advertises the main device that the server prefers to use
when direct scan-out to the target device isn't possible. The
advertised main device may be different for each
wp_linux_dmabuf_feedback object, and may change over time.
There is exactly one main device. The compositor must send at least
one preference tranche with tranche_target_device equal to main_device.
Clients need to create buffers that the main device can import and
read from, otherwise creating the dmabuf wl_buffer will fail (see the
wp_linux_buffer_params.create and create_immed requests for details).
The main device will also likely be kept active by the compositor,
so clients can use it instead of waking up another device for power
savings.
In general the device is a DRM node. The DRM node type (primary vs.
render) is unspecified. Clients must not rely on the compositor sending
a particular node type. Clients cannot check two devices for equality
by comparing the dev_t value.
If explicit modifiers are not supported and the client performs buffer
allocations on a different device than the main device, then the client
must force the buffer to have a linear layout.
</description>
<arg name="device" type="array" summary="device dev_t value"/>
</event>
<event name="tranche_done">
<description summary="a preference tranche has been sent">
This event splits tranche_target_device and tranche_modifier events in
preference tranches. It is sent after a set of tranche_target_device
and tranche_modifier events; it represents the end of a tranche. The
next tranche will have a lower preference.
</description>
</event>
<event name="tranche_target_device">
<description summary="target device">
This event advertises the target device that the server prefers to use
for a buffer created given this tranche. The advertised target device
may be different for each preference tranche, and may change over time.
There is exactly one target device per tranche.
The target device may be a scan-out device, for example if the
compositor prefers to directly scan-out a buffer created given this
tranche. The target device may be a rendering device, for example if
the compositor prefers to texture from said buffer.
The client can use this hint to allocate the buffer in a way that makes
it accessible from the target device, ideally directly. The buffer must
still be accessible from the main device, either through direct import
or through a potentially more expensive fallback path. If the buffer
can't be directly imported from the main device then clients must be
prepared for the compositor changing the tranche priority or making
wl_buffer creation fail (see the wp_linux_buffer_params.create and
create_immed requests for details).
If the device is a DRM node, the DRM node type (primary vs. render) is
unspecified. Clients must not rely on the compositor sending a
particular node type. Clients cannot check two devices for equality by
comparing the dev_t value.
This event is tied to a preference tranche, see the tranche_done event.
</description>
<arg name="device" type="array" summary="device dev_t value"/>
</event>
<event name="tranche_formats">
<description summary="supported buffer format modifier">
This event advertises the format + modifier combinations that the
compositor supports.
It carries an array of indices, each referring to a format + modifier
pair in the last received format table (see the format_table event).
Each index is a 16-bit unsigned integer in native endianness.
For legacy support, DRM_FORMAT_MOD_INVALID is an allowed modifier.
It indicates that the server can support the format with an implicit
modifier. When a buffer has DRM_FORMAT_MOD_INVALID as its modifier, it
is as if no explicit modifier is specified. The effective modifier
will be derived from the dmabuf.
A compositor that sends valid modifiers and DRM_FORMAT_MOD_INVALID for
a given format supports both explicit modifiers and implicit modifiers.
Compositors must not send duplicate format + modifier pairs within the
same tranche or across two different tranches with the same target
device and flags.
This event is tied to a preference tranche, see the tranche_done event.
For the definition of the format and modifier codes, see the
wp_linux_buffer_params.create request.
</description>
<arg name="indices" type="array" summary="array of 16-bit indexes"/>
</event>
<enum name="tranche_flags" bitfield="true">
<entry name="scanout" value="1" summary="direct scan-out tranche"/>
</enum>
<event name="tranche_flags">
<description summary="tranche flags">
This event sets tranche-specific flags.
The scanout flag is a hint that direct scan-out may be attempted by the
compositor on the target device if the client appropriately allocates a
buffer. How to allocate a buffer that can be scanned out on the target
device is implementation-defined.
This event is tied to a preference tranche, see the tranche_done event.
</description>
<arg name="flags" type="uint" enum="tranche_flags" summary="tranche flags"/>
</event>
</interface>
</protocol>

2974
media_types.txt Normal file

File diff suppressed because it is too large Load diff

32
mime0.awk Normal file
View file

@ -0,0 +1,32 @@
# Wayland compositor running on top of an X serer.
# 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/>.
BEGIN {
print "/* Automatically generated file. Do not edit! */"
print "#define DirectTransferMappings \\"
}
/ ([a-z]+\/[-+.[:alnum:]]+) / { # Leave 2 spaces before and 1 space after
match ($0, / ([a-z]+\/[-+.[:alnum:]]+) /, array)
printf " { 0, \"%s\"},\\\n", array[1]
}
END {
printf "\n"
}

34
mime1.awk Normal file
View file

@ -0,0 +1,34 @@
# Wayland compositor running on top of an X serer.
# 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/>.
BEGIN {
print "#define DirectTransferInitializer(table, start) \\"
i = 0
}
/ ([a-z]+\/[-+.[:alnum:]]+) / { # Leave 2 spaces before and 1 space after
match ($0, / ([a-z]+\/[-+.[:alnum:]]+) /, array)
name = array[1]
gsub (/[[:punct:]]/, "_", name) # Convert to a valid atom name
printf " table[%d + start].atom = %s;\\\n", i++, name
}
END {
printf "\n"
}

31
mime2.awk Normal file
View file

@ -0,0 +1,31 @@
# Wayland compositor running on top of an X serer.
# 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/>.
BEGIN {
print "#define DirectTransferAtomNames \\"
}
/ ([a-z]+\/[-+.[:alnum:]]+) / { # Leave 2 spaces before and 1 space after
match ($0, / ([a-z]+\/[-+.[:alnum:]]+) /, array)
printf " \"%s\",\\\n", array[1]
}
END {
printf "\n"
}

34
mime3.awk Normal file
View file

@ -0,0 +1,34 @@
# Wayland compositor running on top of an X serer.
# 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/>.
BEGIN {
print "#define DirectTransferAtomInit(list, base) \\"
i = 0
}
/ ([a-z]+\/[-+.[:alnum:]]+) / { # Leave 2 spaces before and 1 space after
match ($0, / ([a-z]+\/[-+.[:alnum:]]+) /, array)
name = array[1]
gsub (/[[:punct:]]/, "_", name) # Convert to a valid atom name
printf " %s = list[base + %d];\\\n", name, i++
}
END {
printf "\n"
}

33
mime4.awk Normal file
View file

@ -0,0 +1,33 @@
# Wayland compositor running on top of an X serer.
# 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/>.
BEGIN {
print "#define DirectTransferAtoms \\"
}
/ ([a-z]+\/[-+.[:alnum:]]+) / { # Leave 2 spaces before and 1 space after
match ($0, / ([a-z]+\/[-+.[:alnum:]]+) /, array)
name = array[1]
gsub (/[[:punct:]]/, "_", name) # Convert to a valid atom name
printf " %s, \\\n", name
}
END {
print " dummy_atom"
}

1107
output.c Normal file

File diff suppressed because it is too large Load diff

809
positioner.c Normal file
View file

@ -0,0 +1,809 @@
/* 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 <stdio.h>
#include "xdg-shell.h"
#include "compositor.h"
typedef enum _AnchorGravity Anchor;
typedef enum _AnchorGravity Gravity;
enum _AnchorGravity
{
AnchorGravityNone,
AnchorGravityTop,
AnchorGravityBottom,
AnchorGravityLeft,
AnchorGravityRight,
AnchorGravityTopLeft,
AnchorGravityBottomLeft,
AnchorGravityTopRight,
AnchorGravityBottomRight,
};
struct _Positioner
{
/* The fields below mean what they do in the xdg_shell protocol
spec. */
int width, height;
int anchor_x, anchor_y;
int anchor_width, anchor_height;
unsigned int anchor, gravity, constraint;
int offset_x, offset_y;
Bool reactive;
int parent_width, parent_height;
uint32_t constraint_adjustment;
/* The wl_resource corresponding to this positioner. */
struct wl_resource *resource;
/* The number of references to this positioner. */
int refcount;
};
static void
Destroy (struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
static void
SetSize (struct wl_client *client, struct wl_resource *resource,
int32_t width, int32_t height)
{
Positioner *positioner;
if (width < 1 || height < 1)
{
wl_resource_post_error (resource, XDG_POSITIONER_ERROR_INVALID_INPUT,
"invalid size %d %d", width, height);
return;
}
positioner = wl_resource_get_user_data (resource);
positioner->width = width;
positioner->height = height;
}
static void
SetAnchorRect (struct wl_client *client, struct wl_resource *resource,
int32_t x, int32_t y, int32_t width, int32_t height)
{
Positioner *positioner;
if (width < 1 || height < 1)
{
wl_resource_post_error (resource, XDG_POSITIONER_ERROR_INVALID_INPUT,
"invalid size %d %d", width, height);
return;
}
positioner = wl_resource_get_user_data (resource);
positioner->anchor_x = x;
positioner->anchor_y = y;
positioner->anchor_width = width;
positioner->anchor_height = height;
}
static void
SetAnchor (struct wl_client *client, struct wl_resource *resource,
uint32_t anchor)
{
Positioner *positioner;
if (anchor > XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT)
{
wl_resource_post_error (resource, XDG_POSITIONER_ERROR_INVALID_INPUT,
"not an anchor");
return;
}
positioner = wl_resource_get_user_data (resource);
positioner->anchor = anchor;
}
static void
SetGravity (struct wl_client *client, struct wl_resource *resource,
uint32_t gravity)
{
Positioner *positioner;
if (gravity > XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT)
{
wl_resource_post_error (resource, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT,
"not a gravity");
return;
}
positioner = wl_resource_get_user_data (resource);
positioner->gravity = gravity;
}
static void
SetConstraintAdjustment (struct wl_client *client, struct wl_resource *resource,
uint32_t constraint_adjustment)
{
Positioner *positioner;
positioner = wl_resource_get_user_data (resource);
positioner->constraint_adjustment = constraint_adjustment;
}
static void
SetOffset (struct wl_client *client, struct wl_resource *resource,
int32_t x, int32_t y)
{
Positioner *positioner;
positioner = wl_resource_get_user_data (resource);
positioner->offset_x = x;
positioner->offset_y = y;
}
static void
SetReactive (struct wl_client *client, struct wl_resource *resource)
{
Positioner *positioner;
positioner = wl_resource_get_user_data (resource);
positioner->reactive = True;
}
static void
SetParentSize (struct wl_client *client, struct wl_resource *resource,
int width, int height)
{
Positioner *positioner;
positioner = wl_resource_get_user_data (resource);
positioner->parent_width = width;
positioner->parent_height = height;
}
static void
SetParentConfigure (struct wl_client *client, struct wl_resource *resource,
uint32_t configure)
{
/* Unused. */
return;
}
static const struct xdg_positioner_interface xdg_positioner_impl =
{
.destroy = Destroy,
.set_size = SetSize,
.set_anchor_rect = SetAnchorRect,
.set_anchor = SetAnchor,
.set_gravity = SetGravity,
.set_constraint_adjustment = SetConstraintAdjustment,
.set_offset = SetOffset,
.set_reactive = SetReactive,
.set_parent_size = SetParentSize,
.set_parent_configure = SetParentConfigure,
};
static void
RetainPositioner (Positioner *positioner)
{
positioner->refcount++;
}
static void
ReleasePositioner (Positioner *positioner)
{
if (--positioner->refcount)
return;
XLFree (positioner);
}
static void
HandleResourceDestroy (struct wl_resource *resource)
{
Positioner *positioner;
positioner = wl_resource_get_user_data (resource);
ReleasePositioner (positioner);
}
static void
CalculatePosition (Positioner *positioner, int *x_out, int *y_out)
{
int x, y, anchor_x, anchor_y, anchor_width, anchor_height;
/* Code mostly copied from weston. I cannot understand
xdg_positioner due to the terrible documentation. */
x = positioner->offset_x;
y = positioner->offset_y;
anchor_x = positioner->anchor_x;
anchor_y = positioner->anchor_y;
anchor_width = positioner->anchor_width;
anchor_height = positioner->anchor_height;
switch (positioner->anchor)
{
case AnchorGravityTop:
case AnchorGravityTopLeft:
case AnchorGravityTopRight:
y += anchor_y;
break;
case AnchorGravityBottom:
case AnchorGravityBottomLeft:
case AnchorGravityBottomRight:
y += anchor_y + anchor_height;
break;
default:
y += anchor_y + anchor_height / 2;
}
switch (positioner->anchor)
{
case AnchorGravityLeft:
case AnchorGravityTopLeft:
case AnchorGravityBottomLeft:
x += anchor_x;
break;
case AnchorGravityRight:
case AnchorGravityTopRight:
case AnchorGravityBottomRight:
x += anchor_x + anchor_width;
break;
default:
x += anchor_x + anchor_width / 2;
}
switch (positioner->gravity)
{
case AnchorGravityTop:
case AnchorGravityTopLeft:
case AnchorGravityTopRight:
y -= positioner->height;
break;
case AnchorGravityBottom:
case AnchorGravityBottomLeft:
case AnchorGravityBottomRight:
y = y;
break;
default:
y -= positioner->height / 2;
}
switch (positioner->gravity)
{
case AnchorGravityLeft:
case AnchorGravityTopLeft:
case AnchorGravityBottomLeft:
x -= positioner->width;
break;
case AnchorGravityRight:
case AnchorGravityTopRight:
case AnchorGravityBottomRight:
break;
default:
x -= positioner->width / 2;
}
if (x_out)
*x_out = x;
if (y_out)
*y_out = y;
}
static int
TrySlideX (Positioner *positioner, int x, int width, int cx, int cwidth)
{
int cx1, x1;
int new_x;
cx1 = cx + cwidth - 1;
x1 = x + width - 1;
/* See if the rect is unconstrained on the X axis. */
if (x >= cx && x1 <= cx1)
return x;
new_x = x;
/* Which of these conditions to use first depends on the gravity.
If the gravity is leftwards, then we try to first keep new_x less
than cx1. Otherwise, we first try to keep new_x bigger than
cx. */
switch (positioner->gravity)
{
case AnchorGravityLeft:
case AnchorGravityTopLeft:
case AnchorGravityBottomLeft:
if (x < cx)
/* If x is less than cx, move it to cx. */
new_x = cx;
else if (x1 > cx1)
/* If x1 extends past cx1, move it back. */
new_x = x - (x1 - cx1);
break;
case AnchorGravityRight:
case AnchorGravityTopRight:
case AnchorGravityBottomRight:
if (x1 > cx1)
/* If x1 extends past cx1, move it back. */
new_x = x - (x1 - cx1);
else if (x < cx)
/* If x is less than cx, move it to cx. */
new_x = cx;
break;
}
return new_x;
}
static int
TrySlideY (Positioner *positioner, int y, int height, int cy, int cheight)
{
int cy1, y1;
int new_y;
cy1 = cy + cheight - 1;
y1 = y + height - 1;
/* See if the rect is unconstrained on the Y axis. */
if (y >= cy && y1 <= cy1)
return y;
new_y = y;
/* Which of these conditions to use first depends on the gravity.
If the gravity is topwards, then we try to first keep new_y less
than cy1. Otherwise, we first try to keep new_y bigger than
cy. */
switch (positioner->gravity)
{
case AnchorGravityTop:
case AnchorGravityTopLeft:
case AnchorGravityTopRight:
if (y < cy)
/* If y is less than cy, move it to cy. */
new_y = cy;
else if (y1 > cy1)
/* If y1 eytends past cy1, move it back. */
new_y = y - (y1 - cy1);
break;
case AnchorGravityBottom:
case AnchorGravityBottomLeft:
case AnchorGravityBottomRight:
if (y1 > cy1)
/* If y1 eytends past cy1, move it back. */
new_y = y - (y1 - cy1);
else if (y < cy)
/* If y is less than cy, move it to cy. */
new_y = cy;
break;
}
return new_y;
}
static int
TryFlipX (Positioner *positioner, int x, int width, int cx, int cwidth,
int offset)
{
int cx1, x1;
int new_x;
Positioner new;
cx1 = cx + cwidth - 1;
x1 = x + width - 1;
/* If the rect is unconstrained, don't flip anything. */
if (x >= cx && x1 <= cx1)
return x;
/* Otherwise, create a copy of the positioner, but with the X
gravity and X anchor flipped. */
new = *positioner;
switch (positioner->gravity)
{
case AnchorGravityLeft:
new.gravity = AnchorGravityRight;
break;
case AnchorGravityTopLeft:
new.gravity = AnchorGravityTopRight;
break;
case AnchorGravityBottomLeft:
new.gravity = AnchorGravityBottomRight;
break;
case AnchorGravityRight:
new.gravity = AnchorGravityLeft;
break;
case AnchorGravityTopRight:
new.gravity = AnchorGravityTopLeft;
break;
case AnchorGravityBottomRight:
new.gravity = AnchorGravityBottomRight;
break;
}
switch (positioner->anchor)
{
case AnchorGravityLeft:
new.anchor = AnchorGravityRight;
break;
case AnchorGravityTopLeft:
new.anchor = AnchorGravityTopRight;
break;
case AnchorGravityBottomLeft:
new.anchor = AnchorGravityBottomRight;
break;
case AnchorGravityRight:
new.anchor = AnchorGravityLeft;
break;
case AnchorGravityTopRight:
new.anchor = AnchorGravityTopLeft;
break;
case AnchorGravityBottomRight:
new.anchor = AnchorGravityBottomRight;
break;
}
/* If neither the gravity nor the anchor changed, punt, since
flipping won't help. */
if (positioner->gravity == new.gravity
&& positioner->anchor == new.anchor)
return x;
/* Otherwise, compute a new position using the new positioner. */
CalculatePosition (&new, &new_x, NULL);
/* Scale that position. */
new_x *= global_scale_factor;
/* If new_x is still constrained, use the previous position. */
if (new_x + offset < cx
|| new_x + offset + width - 1 > cx1)
return x;
/* Return the new X. */
return new_x + offset;
}
static int
TryFlipY (Positioner *positioner, int y, int height, int cy, int cheight,
int offset)
{
int cy1, y1;
int new_y;
Positioner new;
cy1 = cy + cheight - 1;
y1 = y + height - 1;
/* If the rect is unconstrained, don't flip anything. */
if (y >= cy && y1 <= cy1)
return y;
/* Otherwise, create a copy of the positioner, but with the Y
gravity and Y anchor flipped. */
new = *positioner;
switch (positioner->gravity)
{
case AnchorGravityTop:
new.gravity = AnchorGravityBottom;
break;
case AnchorGravityTopLeft:
new.gravity = AnchorGravityBottomLeft;
break;
case AnchorGravityTopRight:
new.gravity = AnchorGravityBottomRight;
break;
case AnchorGravityBottom:
new.gravity = AnchorGravityTop;
break;
case AnchorGravityBottomLeft:
new.gravity = AnchorGravityTopLeft;
break;
case AnchorGravityBottomRight:
new.gravity = AnchorGravityTopRight;
break;
}
switch (positioner->anchor)
{
case AnchorGravityTop:
new.anchor = AnchorGravityBottom;
break;
case AnchorGravityTopLeft:
new.anchor = AnchorGravityBottomLeft;
break;
case AnchorGravityTopRight:
new.anchor = AnchorGravityBottomRight;
break;
case AnchorGravityBottom:
new.anchor = AnchorGravityTop;
break;
case AnchorGravityBottomLeft:
new.anchor = AnchorGravityTopLeft;
break;
case AnchorGravityBottomRight:
new.anchor = AnchorGravityTopRight;
break;
}
/* If neither the gravity nor the anchor changed, punt, since
flipping won't help. */
if (positioner->gravity == new.gravity
&& positioner->anchor == new.anchor)
return y;
/* Otherwise, compute a new position using the new positioner. */
CalculatePosition (&new, NULL, &new_y);
/* Scale that position. */
new_y *= global_scale_factor;
/* If new_y is still constrained, use the previous position. */
if (new_y + offset < cy
|| new_y + offset + height - 1 > cy1)
return y;
/* Return the new Y. */
return new_y + offset;
}
static void
TryResizeX (int x, int width, int cx, int cwidth, int offset,
int *new_x, int *new_width)
{
int x1, cx1, result_width, result_x;
x1 = x + width - 1;
cx1 = cx + cwidth - 1;
if (x >= cx && x1 <= cx1)
/* The popup is not constrained on the X axis. */
return;
/* Otherwise, resize the popup to fit inside cx, cx1.
If new_width ends up less than 1, punt. */
result_x = MAX (cx, x) - offset;
result_width = MIN (cx1, x1) - (*new_x + offset) + 1;
if (result_width <= 0)
return;
*new_x = result_x;
*new_width = result_width;
}
static void
TryResizeY (int y, int height, int cy, int cheight, int offset,
int *new_y, int *new_height)
{
int y1, cy1, result_height, result_y;
y1 = y + height - 1;
cy1 = cy + cheight - 1;
if (y >= cy && y1 <= cy1)
/* The popup is not constrained on the Y axis. */
return;
/* Otherwise, resize the popup to fit inside cy, cy1.
If new_height ends up less than 1, punt. */
result_y = MAX (cy, y) - offset;
result_height = MIN (cy1, y1) - (*new_y + offset) + 1;
if (result_height <= 0)
return;
*new_y = result_y;
*new_height = result_height;
}
static void
GetAdjustmentOffset (Role *parent, int *off_x, int *off_y)
{
int root_x, root_y, parent_gx, parent_gy;
XLXdgRoleGetCurrentGeometry (parent, &parent_gx,
&parent_gy, NULL, NULL);
XLXdgRoleCurrentRootPosition (parent, &root_x, &root_y);
*off_x = root_x + parent_gx * global_scale_factor;
*off_y = root_y + parent_gy * global_scale_factor;
}
static void
ApplyConstraintAdjustment (Positioner *positioner, Role *parent, int x,
int y, int *x_out, int *y_out, int *width_out,
int *height_out)
{
int width, height, cx, cy, cwidth, cheight, off_x, off_y;
width = positioner->width * global_scale_factor;
height = positioner->height * global_scale_factor;
/* Constraint calculations are simplest if we use scaled
coordinates, and then unscale them later. */
x *= global_scale_factor;
y *= global_scale_factor;
if (positioner->constraint_adjustment
== XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE)
/* There is no constraint adjustment. */
goto finish;
/* Compute the current offset. */
GetAdjustmentOffset (parent, &off_x, &off_y);
if (!XLGetOutputRectAt (off_x + x, off_y + y, &cx, &cy,
&cwidth, &cheight))
/* There is no output in which to constrain this popup. */
goto finish;
if (positioner->constraint_adjustment
& XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X)
x = TrySlideX (positioner, x + off_x, width,
cx, cwidth) - off_x;
if (positioner->constraint_adjustment
& XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y)
y = TrySlideY (positioner, y + off_y, height,
cy, cheight) - off_y;
if (positioner->constraint_adjustment
& XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X)
x = TryFlipX (positioner, x + off_x, width,
cx, cwidth, off_x) - off_x;
if (positioner->constraint_adjustment
& XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y)
y = TryFlipY (positioner, y + off_y, height,
cy, cheight, off_y) - off_y;
if (positioner->constraint_adjustment
& XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X)
TryResizeX (x + off_x, width, cx, cwidth,
off_x, &x, &width);
if (positioner->constraint_adjustment
& XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y)
TryResizeY (y + off_y, height, cy, cheight,
off_y, &y, &height);
finish:
*x_out = x / global_scale_factor;
*y_out = y / global_scale_factor;
*width_out = width / global_scale_factor;
*height_out = height / global_scale_factor;
}
void
XLPositionerCalculateGeometry (Positioner *positioner, Role *parent,
int *x_out, int *y_out, int *width_out,
int *height_out)
{
int x, y, width, height;
CalculatePosition (positioner, &x, &y);
ApplyConstraintAdjustment (positioner, parent, x, y,
&x, &y, &width, &height);
*x_out = x;
*y_out = y;
*width_out = width;
*height_out = height;
}
void
XLCreateXdgPositioner (struct wl_client *client, struct wl_resource *resource,
uint32_t id)
{
Positioner *positioner;
positioner = XLSafeMalloc (sizeof *positioner);
if (!positioner)
{
wl_client_post_no_memory (client);
return;
}
memset (positioner, 0, sizeof *positioner);
positioner->resource
= wl_resource_create (client, &xdg_positioner_interface,
wl_resource_get_version (resource),
id);
if (!positioner->resource)
{
wl_resource_post_no_memory (resource);
XLFree (positioner);
return;
}
wl_resource_set_implementation (positioner->resource,
&xdg_positioner_impl,
positioner,
HandleResourceDestroy);
RetainPositioner (positioner);
}
void
XLRetainPositioner (Positioner *positioner)
{
RetainPositioner (positioner);
}
void
XLReleasePositioner (Positioner *positioner)
{
ReleasePositioner (positioner);
}
Bool
XLPositionerIsReactive (Positioner *positioner)
{
return positioner->reactive;
}

105
region.c Normal file
View file

@ -0,0 +1,105 @@
/* 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 <pixman.h>
#include "compositor.h"
static void
DestroyRegion (struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
static void
SubtractRegion (struct wl_client *client, struct wl_resource *resource,
int32_t x, int32_t y, int32_t width, int32_t height)
{
pixman_region32_t *region, operand;
region = wl_resource_get_user_data (resource);
pixman_region32_init_rect (&operand, x, y, width, height);
pixman_region32_subtract (region, region, &operand);
}
static void
AddRegion (struct wl_client *client, struct wl_resource *resource,
int32_t x, int32_t y, int32_t width, int32_t height)
{
pixman_region32_t *region, operand;
region = wl_resource_get_user_data (resource);
pixman_region32_init_rect (&operand, x, y, width, height);
pixman_region32_union (region, region, &operand);
}
static const struct wl_region_interface wl_region_impl =
{
.destroy = DestroyRegion,
.subtract = SubtractRegion,
.add = AddRegion
};
static void
HandleResourceDestroy (struct wl_resource *resource)
{
pixman_region32_t *region;
region = wl_resource_get_user_data (resource);
pixman_region32_fini (region);
XLFree (region);
}
void
XLCreateRegion (struct wl_client *client,
struct wl_resource *resource,
uint32_t id)
{
pixman_region32_t *region;
struct wl_resource *region_resource;
region = XLSafeMalloc (sizeof *region);
if (!region)
{
wl_resource_post_no_memory (resource);
return;
}
region_resource
= wl_resource_create (client, &wl_region_interface,
wl_resource_get_version (resource),
id);
if (!resource)
{
wl_resource_post_no_memory (resource);
XLFree (region);
return;
}
pixman_region32_init (region);
wl_resource_set_implementation (region_resource,
&wl_region_impl,
region,
HandleResourceDestroy);
}

293
run.c Normal file
View file

@ -0,0 +1,293 @@
/* Wayland compositor running on top of an X serer.
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 <sys/param.h>
#include <stdio.h>
#include <poll.h>
#include <alloca.h>
#include "compositor.h"
typedef struct _PollFd PollFd;
struct _PollFd
{
/* The next and last writable fd in this chain. */
PollFd *next, *last;
/* The file descriptor itself. */
int write_fd;
/* Callback run with the fd number and data when the fd becomes
writable or readable. */
void (*poll_callback) (int, void *);
/* Data for the callback. */
void *data;
/* The direction; 1 means write, 0 means read. */
int direction;
};
/* Number of file descriptors for which we are waiting for something
to be written. */
static int num_poll_fd;
/* Linked list of file descriptors and write callbacks. */
static PollFd poll_fds;
WriteFd *
XLAddWriteFd (int fd, void *data, void (*poll_callback) (int, void *))
{
WriteFd *record;
record = XLMalloc (sizeof *record);
record->next = poll_fds.next;
record->last = &poll_fds;
record->write_fd = fd;
record->poll_callback = poll_callback;
record->data = data;
record->direction = 1;
poll_fds.next->last = record;
poll_fds.next = record;
num_poll_fd++;
return record;
}
ReadFd *
XLAddReadFd (int fd, void *data, void (*poll_callback) (int, void *))
{
WriteFd *record;
record = XLMalloc (sizeof *record);
record->next = poll_fds.next;
record->last = &poll_fds;
record->write_fd = fd;
record->poll_callback = poll_callback;
record->data = data;
record->direction = 0;
poll_fds.next->last = record;
poll_fds.next = record;
num_poll_fd++;
return record;
}
void
XLRemoveWriteFd (WriteFd *fd)
{
/* Mark this record as invalid. Records cannot safely change while
the event loop is in progress, so we remove all invalid records
immediately before polling. */
fd->write_fd = -1;
}
void
XLRemoveReadFd (ReadFd *fd)
{
/* Mark this record as invalid. Records cannot safely change while
the event loop is in progress, so we remove all invalid records
immediately before polling. */
fd->write_fd = -1;
}
static void
RemoveWriteFd (WriteFd *fd)
{
fd->next->last = fd->last;
fd->last->next = fd->next;
num_poll_fd--;
XLFree (fd);
}
static void
HandleOneXEvent (XEvent *event)
{
XLHandleOneXEventForDnd (event);
if (XLHandleXEventForXdgSurfaces (event))
return;
if (XLHandleXEventForXdgToplevels (event))
return;
if (XLHandleXEventForXdgPopups (event))
return;
if (XLHandleOneXEventForSeats (event))
return;
if (XLHandleOneXEventForIconSurfaces (event))
return;
if (XLHandleOneXEventForDmabuf (event))
return;
if (XLHandleOneXEventForXData (event))
return;
if (XLHandleOneXEventForOutputs (event))
return;
if (XLHandleOneXEventForXSettings (event))
return;
}
static void
ReadXEvents (void)
{
XEvent event;
while (XPending (compositor.display))
{
XNextEvent (compositor.display, &event);
/* We failed to get event data for a generic event, so there's
no point in continuing. */
if (event.type == GenericEvent
&& !XGetEventData (compositor.display, &event.xcookie))
continue;
if (!HookSelectionEvent (&event))
HandleOneXEvent (&event);
if (event.type == GenericEvent)
XFreeEventData (compositor.display, &event.xcookie);
}
}
static void
RunStep (void)
{
int x_connection, wl_connection, rc, i, j;
struct timespec timeout;
struct pollfd *fds;
PollFd **pollfds, *item, *last;
XFlush (compositor.display);
wl_display_flush_clients (compositor.wl_display);
fds = alloca (sizeof fds * (num_poll_fd + 2));
/* This is used as an optimization to not have to loop over the
entire descriptor list twice. */
pollfds = alloca (sizeof *pollfds * num_poll_fd);
/* Run timers. This, and draining selection transfers, must be done
before setting up poll file descriptors, since timer callbacks
can change the write fd list. */
timeout = TimerCheck ();
/* Drain complete selection transfers. */
FinishTransfers ();
x_connection = ConnectionNumber (compositor.display);
wl_connection = wl_event_loop_get_fd (compositor.wl_event_loop);
fds[0].fd = x_connection;
fds[1].fd = wl_connection;
fds[0].events = POLLIN;
fds[1].events = POLLIN;
fds[0].revents = 0;
fds[1].revents = 0;
/* Copy valid write file descriptors into the pollfd array, while
removing invalid ones. */
item = poll_fds.next;
i = 0;
while (item != &poll_fds)
{
last = item;
item = item->next;
if (last->write_fd == -1)
{
/* Remove this invalid pollfd. */
RemoveWriteFd (last);
continue;
}
/* Otherwise, add the fd and bump i. */
fds[2 + i].fd = last->write_fd;
fds[2 + i].events = POLLOUT;
fds[2 + i].revents = 0;
pollfds[i] = last;
if (!last->direction)
/* See https://www.greenend.org.uk/rjk/tech/poll.html for why
POLLHUP. */
fds[2 + i].events = POLLIN | POLLHUP;
i += 1;
}
/* Handle any events already in the queue, which can happen if
something inside ReadXEvents synced. */
if (XEventsQueued (compositor.display, QueuedAlready))
{
ReadXEvents ();
XFlush (compositor.display);
wl_display_flush_clients (compositor.wl_display);
}
rc = ppoll (fds, 2 + i, &timeout, NULL);
if (rc > 0)
{
if (fds[0].revents & POLLIN)
ReadXEvents ();
if (fds[1].revents & POLLIN)
wl_event_loop_dispatch (compositor.wl_event_loop, -1);
/* Now see how many write fds are set. */
for (j = 0; j < i; ++j)
{
if (fds[2 + j].revents & (POLLOUT | POLLIN | POLLHUP)
/* Check that pollfds[j] is still valid, and wasn't
removed while handling X events. */
&& pollfds[j]->write_fd != -1)
/* Then call the write callback. */
pollfds[j]->poll_callback (pollfds[j]->write_fd,
pollfds[j]->data);
}
}
}
void __attribute__ ((noreturn))
XLRunCompositor (void)
{
/* Set up the sentinel node for file descriptors that are being
polled from. */
poll_fds.next = &poll_fds;
poll_fds.last = &poll_fds;
while (true)
RunStep ();
}

5122
seat.c Normal file

File diff suppressed because it is too large Load diff

1844
select.c Normal file

File diff suppressed because it is too large Load diff

584
shm.c Normal file
View file

@ -0,0 +1,584 @@
/* 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);
}

2433
subcompositor.c Normal file

File diff suppressed because it is too large Load diff

982
subsurface.c Normal file
View file

@ -0,0 +1,982 @@
/* 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 "compositor.h"
/* TODO: handle unmapping nested subsurfaces. */
enum
{
PendingPosition = 1,
};
enum _SurfaceActionType
{
Sentinel,
PlaceAboveOther,
PlaceBelowOther,
};
typedef enum _SurfaceActionType SurfaceActionType;
typedef struct _Subsurface Subsurface;
typedef struct _Substate Substate;
typedef struct _SurfaceAction SurfaceAction;
typedef struct _SurfaceActionClientData SurfaceActionClientData;
#define SubsurfaceFromRole(role) ((Subsurface *) (role))
struct _SurfaceAction
{
/* What this action is. */
SurfaceActionType type;
/* What subsurface this action applies to. */
Subsurface *subsurface;
/* What surface is the "other" surface. */
Surface *other;
/* Surface destroy listener. */
DestroyCallback *destroy_listener;
/* The next and last surface actions in this list. */
SurfaceAction *next, *last;
};
struct _Substate
{
/* The position of the subsurface relative to the parent. */
int x, y;
/* Various flags. */
int flags;
};
struct _Subsurface
{
/* The role object itself. */
Role role;
/* The parent surface. */
Surface *parent;
/* The number of references to this subsurface. */
int refcount;
/* Pending substate. */
Substate pending_substate;
/* Current substate. */
Substate current_substate;
/* Commit callback attached to the parent. */
CommitCallback *commit_callback;
/* Whether or not this is synchronous. */
Bool synchronous;
/* Whether or not a commit is pending. */
Bool pending_commit;
/* The last dimensions and position that were used to update this
surface's outputs. */
int output_x, output_y, output_width, output_height;
};
struct _SurfaceActionClientData
{
/* Any pending subsurface actions. */
SurfaceAction actions;
};
/* The global wl_subcompositor resource. */
struct wl_global *global_subcompositor;
static void
UnlinkSurfaceAction (SurfaceAction *subaction)
{
subaction->last->next = subaction->next;
subaction->next->last = subaction->last;
}
static void
HandleOtherSurfaceDestroyed (void *data)
{
SurfaceAction *action;
action = data;
UnlinkSurfaceAction (action);
XLFree (action);
}
static void
DestroySurfaceAction (SurfaceAction *subaction)
{
XLSurfaceCancelRunOnFree (subaction->destroy_listener);
UnlinkSurfaceAction (subaction);
XLFree (subaction);
}
static Bool
CheckSiblingRelationship (Subsurface *subsurface, Surface *other)
{
Subsurface *other_subsurface;
if (other->role_type != SubsurfaceType
/* The role might've been detached from the other surface. */
|| !other->role)
return False;
other_subsurface = SubsurfaceFromRole (other->role);
if (other_subsurface->parent != subsurface->parent)
return False;
return True;
}
static void
ParentBelow (View *parent, View *below, Surface *surface)
{
ViewInsertBefore (parent, surface->view, below);
ViewInsertBefore (parent, surface->under, surface->view);
}
static void
ParentAbove (View *parent, View *above, Surface *surface)
{
ViewInsertAfter (parent, surface->under, above);
ViewInsertAfter (parent, surface->view, surface->under);
}
static void
ParentStart (View *parent, Surface *surface)
{
ViewInsert (parent, surface->under);
ViewInsert (parent, surface->view);
}
static void
RunOneSurfaceAction (Subsurface *subsurface, SurfaceAction *subaction)
{
View *target;
if (!subsurface->role.surface || !subsurface->parent)
return;
if (subaction->type == PlaceAboveOther)
{
if (subaction->other != subsurface->parent
&& !CheckSiblingRelationship (subsurface, subaction->other))
/* The hierarchy changed in some unacceptable way between the
action being recorded and the commit of the parent.
Ignore. */
return;
/* Determine the target under which to place the view. If
subaction->other is underneath the parent, then this will
actually be subsurface->parent->under. */
target = ViewGetParent (subaction->other->view);
/* After that, unparent the views. */
ViewUnparent (subsurface->role.surface->view);
ViewUnparent (subsurface->role.surface->under);
if (subaction->other == subsurface->parent)
/* Re-insert this view at the beginning of the parent. */
ParentStart (subsurface->parent->view,
subsurface->role.surface);
else
/* Re-insert this view in front of the other surface. */
ParentAbove (target, subaction->other->view,
subsurface->role.surface);
}
else if (subaction->type == PlaceBelowOther)
{
if (subaction->other != subsurface->parent
&& !CheckSiblingRelationship (subsurface, subaction->other))
return;
target = ViewGetParent (subaction->other->view);
ViewUnparent (subsurface->role.surface->view);
ViewUnparent (subsurface->role.surface->under);
if (subaction->other != subsurface->parent)
/* Re-insert this view before the other surface. */
ParentBelow (target, subaction->other->under,
subsurface->role.surface);
else
/* Re-insert this view below the parent surface. */
ParentStart (subsurface->parent->under,
subsurface->role.surface);
}
}
static void
FreeSurfaceActions (SurfaceAction *first)
{
SurfaceAction *action, *last;
action = first->next;
while (action != first)
{
last = action;
action = action->next;
DestroySurfaceAction (last);
}
}
static void
FreeSubsurfaceData (void *data)
{
SurfaceActionClientData *client;
client = data;
FreeSurfaceActions (&client->actions);
}
static SurfaceAction *
AddSurfaceAction (Subsurface *subsurface, Surface *other,
SurfaceActionType type)
{
SurfaceAction *action;
SurfaceActionClientData *client;
action = XLMalloc (sizeof *action);
action->subsurface = subsurface;
action->type = type;
action->other = other;
action->destroy_listener
= XLSurfaceRunOnFree (other, HandleOtherSurfaceDestroyed,
action);
client = XLSurfaceGetClientData (subsurface->parent,
SubsurfaceData,
sizeof *client,
FreeSubsurfaceData);
if (!client->actions.next)
{
/* Client is not yet initialized, so initialize the sentinel
node. */
client->actions.next = &client->actions;
client->actions.last = &client->actions;
client->actions.type = Sentinel;
}
action->next = client->actions.next;
action->last = &client->actions;
client->actions.next->last = action;
client->actions.next = action;
return action;
}
static void
RunSurfaceActions (SurfaceAction *first)
{
SurfaceAction *action, *last;
action = first->last;
while (action != first)
{
last = action;
/* Run the actions backwards so they appear in the right
order. */
action = action->last;
RunOneSurfaceAction (last->subsurface, last);
DestroySurfaceAction (last);
}
}
static void
DestroySubsurface (struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
static void
SetPosition (struct wl_client *client, struct wl_resource *resource,
int32_t x, int32_t y)
{
Subsurface *subsurface;
subsurface = wl_resource_get_user_data (resource);
subsurface->pending_substate.x = x;
subsurface->pending_substate.y = y;
subsurface->pending_substate.flags |= PendingPosition;
}
static void
PlaceAbove (struct wl_client *client, struct wl_resource *resource,
struct wl_resource *surface_resource)
{
Subsurface *subsurface;
Surface *other;
subsurface = wl_resource_get_user_data (resource);
other = wl_resource_get_user_data (surface_resource);
if (other != subsurface->parent
&& !CheckSiblingRelationship (subsurface, other))
{
wl_resource_post_error (resource, WL_SUBSURFACE_ERROR_BAD_SURFACE,
"surface is not a sibling or the parent");
return;
}
AddSurfaceAction (subsurface, other, PlaceAboveOther);
}
static void
PlaceBelow (struct wl_client *client, struct wl_resource *resource,
struct wl_resource *surface_resource)
{
Subsurface *subsurface;
Surface *other;
subsurface = wl_resource_get_user_data (resource);
other = wl_resource_get_user_data (surface_resource);
if (other != subsurface->parent
|| !CheckSiblingRelationship (subsurface, other))
{
wl_resource_post_error (resource, WL_SUBSURFACE_ERROR_BAD_SURFACE,
"surface is not a sibling or the parent");
return;
}
AddSurfaceAction (subsurface, other, PlaceBelowOther);
}
static void
NoteDesyncChild (Surface *surface, Role *role)
{
Subsurface *subsurface;
subsurface = SubsurfaceFromRole (role);
if (!subsurface->parent || !subsurface->parent->role
|| !subsurface->parent->role->funcs.note_desync_child)
return;
subsurface->parent->role->funcs.note_desync_child (subsurface->parent,
subsurface->parent->role);
}
static void
NoteChildSynced (Surface *surface, Role *role)
{
Subsurface *subsurface;
subsurface = SubsurfaceFromRole (role);
if (!subsurface->parent || !subsurface->parent->role
|| !subsurface->parent->role->funcs.note_child_synced)
return;
subsurface->parent->role->funcs.note_child_synced (subsurface->parent,
subsurface->parent->role);
}
static void
SetSync (struct wl_client *client, struct wl_resource *resource)
{
Subsurface *subsurface;
subsurface = wl_resource_get_user_data (resource);
if (subsurface->role.surface
&& !subsurface->synchronous)
NoteChildSynced (subsurface->role.surface,
&subsurface->role);
subsurface->synchronous = True;
}
static void
SetDesync (struct wl_client *client, struct wl_resource *resource)
{
Subsurface *subsurface;
subsurface = wl_resource_get_user_data (resource);
if (subsurface->role.surface
&& subsurface->synchronous)
NoteDesyncChild (subsurface->role.surface,
&subsurface->role);
subsurface->synchronous = False;
if (subsurface->pending_commit
&& subsurface->role.surface)
XLCommitSurface (subsurface->role.surface, False);
subsurface->pending_commit = False;
}
static const struct wl_subsurface_interface wl_subsurface_impl =
{
.destroy = DestroySubsurface,
.set_position = SetPosition,
.place_above = PlaceAbove,
.place_below = PlaceBelow,
.set_sync = SetSync,
.set_desync = SetDesync,
};
static void
DestroyBacking (Subsurface *subsurface)
{
if (--subsurface->refcount)
return;
XLFree (subsurface);
}
static Bool
EarlyCommit (Surface *surface, Role *role)
{
Subsurface *subsurface;
subsurface = SubsurfaceFromRole (role);
/* If the role is synchronous, don't commit until the parent
commits. */
if (subsurface->synchronous)
{
subsurface->pending_commit = True;
return False;
}
return True;
}
static void
MaybeUpdateOutputs (Subsurface *subsurface)
{
int x, y, width, height, base_x, base_y;
if (subsurface->role.surface->output_x == INT_MIN
|| subsurface->role.surface->output_y == INT_MIN)
/* Valid base coordinates are not yet available. */
return;
/* Compute the positions relative to the parent. */
x = subsurface->current_substate.x * global_scale_factor;
y = subsurface->current_substate.y * global_scale_factor;
/* And the base X and Y. */
base_x = subsurface->role.surface->output_x;
base_y = subsurface->role.surface->output_y;
/* Compute the absolute width and height of the surface
contents. */
width = ViewWidth (subsurface->role.surface->view);
height = ViewHeight (subsurface->role.surface->view);
/* If nothing really changed, return. */
if (x == subsurface->output_x
&& y == subsurface->output_y
&& width == subsurface->output_width
&& height == subsurface->output_height)
return;
/* Otherwise, recompute the outputs this subsurface overlaps and
record those values. */
subsurface->output_x = x;
subsurface->output_y = y;
subsurface->output_width = width;
subsurface->output_height = height;
/* Recompute overlaps. */
XLUpdateSurfaceOutputs (subsurface->role.surface,
x + base_x, y + base_y,
width, height);
}
static void
AfterParentCommit (Surface *surface, void *data)
{
Subsurface *subsurface;
subsurface = data;
/* The surface might've been destroyed already. */
if (!subsurface->role.surface)
return;
/* Apply pending state. */
if (subsurface->pending_substate.flags & PendingPosition)
{
subsurface->current_substate.x
= subsurface->pending_substate.x;
subsurface->current_substate.y
= subsurface->pending_substate.y;
/* The X and Y coordinates here are also surface-local and must
be scaled by the global scale factor. */
ViewMove (subsurface->role.surface->view,
subsurface->current_substate.x * global_scale_factor,
subsurface->current_substate.y * global_scale_factor);
}
/* And any cached surface state too. */
if (subsurface->pending_commit)
{
XLCommitSurface (subsurface->role.surface, False);
/* If the size changed, update the outputs this surface is in
the scanout area of. */
MaybeUpdateOutputs (subsurface);
}
subsurface->pending_commit = False;
subsurface->pending_substate.flags = 0;
}
static Bool
Subframe (Surface *surface, Role *role)
{
Subsurface *subsurface;
subsurface = SubsurfaceFromRole (role);
if (!subsurface->parent || !subsurface->parent->role
|| !subsurface->parent->role->funcs.subframe)
return True;
return subsurface->parent->role->funcs.subframe (subsurface->parent,
subsurface->parent->role);
}
static void
EndSubframe (Surface *surface, Role *role)
{
Subsurface *subsurface;
subsurface = SubsurfaceFromRole (role);
if (!subsurface->parent || !subsurface->parent->role
|| !subsurface->parent->role->funcs.end_subframe)
return;
subsurface->parent->role->funcs.end_subframe (subsurface->parent,
subsurface->parent->role);
}
static Window
GetWindow (Surface *surface, Role *role)
{
Subsurface *subsurface;
subsurface = SubsurfaceFromRole (role);
if (!subsurface->parent || !subsurface->parent->role
|| !subsurface->parent->role->funcs.get_window)
return None;
return
subsurface->parent->role->funcs.get_window (subsurface->parent,
subsurface->parent->role);
}
static void
Commit (Surface *surface, Role *role)
{
Subcompositor *subcompositor;
Subsurface *subsurface;
subcompositor = ViewGetSubcompositor (surface->view);
subsurface = SubsurfaceFromRole (role);
if (!subcompositor)
return;
/* If no buffer is attached, unmap the views. */
if (!surface->current_state.buffer)
{
ViewUnmap (surface->under);
ViewUnmap (surface->view);
}
else
/* Once a buffer is attached to the view, it is automatically
mapped. */
ViewMap (surface->under);
if (!subsurface->synchronous)
{
/* If the surface is asynchronous, draw this subframe now.
Otherwise it is synchronous, so we should wait for the
toplevel to end the frame. */
if (Subframe (surface, role))
{
SubcompositorUpdate (subcompositor);
EndSubframe (surface, role);
}
/* If the size changed, update the outputs this surface is in
the scanout area of. */
MaybeUpdateOutputs (subsurface);
}
}
static Bool
Setup (Surface *surface, Role *role)
{
Subsurface *subsurface;
View *parent_view;
surface->role_type = SubsurfaceType;
subsurface = SubsurfaceFromRole (role);
subsurface->refcount++;
subsurface->output_x = INT_MIN;
subsurface->output_y = INT_MIN;
role->surface = surface;
parent_view = subsurface->parent->view;
/* Set the subcompositor here. If the role providing the
subcompositor hasn't been attached to the parent, then when it is
it will call ViewSetSubcompositor on the parent's view. */
ViewSetSubcompositor (surface->under,
ViewGetSubcompositor (parent_view));
ViewInsert (parent_view, surface->under);
ViewSetSubcompositor (surface->view,
ViewGetSubcompositor (parent_view));
ViewInsert (parent_view, surface->view);
/* Now add the subsurface to the parent's list of subsurfaces. */
subsurface->parent->subsurfaces
= XLListPrepend (subsurface->parent->subsurfaces,
surface);
return True;
}
static void
Rescale (Surface *surface, Role *role)
{
Subsurface *subsurface;
subsurface = SubsurfaceFromRole (role);
/* The scale factor changed; move the subsurface to the new correct
position. */
ViewMove (surface->view,
subsurface->current_substate.x * global_scale_factor,
subsurface->current_substate.y * global_scale_factor);
}
static void
Teardown (Surface *surface, Role *role)
{
Subsurface *subsurface;
SurfaceActionClientData *client;
SurfaceAction *action;
Subcompositor *subcompositor;
subsurface = SubsurfaceFromRole (role);
/* If this subsurface is desynchronous, tell the toplevel parent
that it is now gone. */
if (!subsurface->synchronous)
NoteDesyncChild (role->surface, role);
role->surface = NULL;
if (subsurface->parent)
{
subcompositor = ViewGetSubcompositor (surface->view);
ViewUnparent (surface->view);
ViewSetSubcompositor (surface->view, NULL);
ViewUnparent (surface->under);
ViewSetSubcompositor (surface->under, NULL);
client = subsurface->parent->client_data[SubsurfaceData];
if (client)
{
/* Free all subsurface actions involving this
subsurface. */
action = client->actions.next;
while (action != &client->actions)
{
if (action->subsurface == subsurface)
DestroySurfaceAction (action);
}
}
subsurface->parent->subsurfaces
= XLListRemove (subsurface->parent->subsurfaces, surface);
XLSurfaceCancelCommitCallback (subsurface->commit_callback);
/* According to the spec, this removal should take effect
immediately. */
if (subcompositor
&& Subframe (surface, role))
{
SubcompositorUpdate (subcompositor);
EndSubframe (surface, role);
}
}
DestroyBacking (subsurface);
}
static void
ReleaseBuffer (Surface *surface, Role *role, ExtBuffer *buffer)
{
Subsurface *subsurface;
subsurface = SubsurfaceFromRole (role);
if (!subsurface->parent || !subsurface->parent->role)
{
XLReleaseBuffer (buffer);
return;
}
subsurface->parent->role->funcs.release_buffer (subsurface->parent,
subsurface->parent->role,
buffer);
}
static void
HandleSubsurfaceResourceDestroy (struct wl_resource *resource)
{
Subsurface *subsurface;
subsurface = wl_resource_get_user_data (resource);
DestroyBacking (subsurface);
}
static void
GetSubsurface (struct wl_client *client, struct wl_resource *resource,
uint32_t id, struct wl_resource *surface_resource,
struct wl_resource *parent_resource)
{
Surface *surface, *parent;
Subsurface *subsurface;
surface = wl_resource_get_user_data (surface_resource);
parent = wl_resource_get_user_data (parent_resource);
/* If the surface already has a role, don't attach this subsurface.
Likewise if the surface previously held some other role. */
if (surface->role || (surface->role_type != AnythingType
&& surface->role_type != SubsurfaceType))
{
wl_resource_post_error (resource, WL_DISPLAY_ERROR_IMPLEMENTATION,
"trying to attach subsurface to surface with role");
return;
}
subsurface = XLSafeMalloc (sizeof *subsurface);
if (!subsurface)
{
wl_resource_post_no_memory (resource);
return;
}
memset (subsurface, 0, sizeof *subsurface);
subsurface->role.resource
= wl_resource_create (client, &wl_subsurface_interface,
wl_resource_get_version (resource),
id);
if (!subsurface->role.resource)
{
XLFree (subsurface);
wl_resource_post_no_memory (resource);
return;
}
wl_resource_set_implementation (subsurface->role.resource, &wl_subsurface_impl,
subsurface, HandleSubsurfaceResourceDestroy);
/* Now the wl_resource holds a reference to the subsurface. */
subsurface->refcount++;
subsurface->role.funcs.commit = Commit;
subsurface->role.funcs.teardown = Teardown;
subsurface->role.funcs.setup = Setup;
subsurface->role.funcs.release_buffer = ReleaseBuffer;
subsurface->role.funcs.subframe = Subframe;
subsurface->role.funcs.end_subframe = EndSubframe;
subsurface->role.funcs.early_commit = EarlyCommit;
subsurface->role.funcs.get_window = GetWindow;
subsurface->role.funcs.rescale = Rescale;
subsurface->role.funcs.note_child_synced = NoteChildSynced;
subsurface->role.funcs.note_desync_child = NoteDesyncChild;
subsurface->parent = parent;
subsurface->commit_callback
= XLSurfaceRunAtCommit (parent, AfterParentCommit, subsurface);
subsurface->synchronous = True;
if (!XLSurfaceAttachRole (surface, &subsurface->role))
abort ();
}
static void
DestroySubcompositor (struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
static const struct wl_subcompositor_interface wl_subcompositor_impl =
{
.destroy = DestroySubcompositor,
.get_subsurface = GetSubsurface,
};
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_subcompositor_interface,
version, id);
if (!resource)
{
wl_client_post_no_memory (client);
return;
}
wl_resource_set_implementation (resource, &wl_subcompositor_impl,
NULL, NULL);
}
void
XLInitSubsurfaces (void)
{
global_subcompositor
= wl_global_create (compositor.wl_display,
&wl_subcompositor_interface,
1, NULL, HandleBind);
}
void
XLSubsurfaceParentDestroyed (Role *role)
{
Subsurface *subsurface;
subsurface = SubsurfaceFromRole (role);
subsurface->parent = NULL;
/* The callback is freed with the parent. */
subsurface->commit_callback = NULL;
if (subsurface->role.surface)
{
ViewUnparent (subsurface->role.surface->view);
ViewUnparent (subsurface->role.surface->under);
}
}
void
XLSubsurfaceHandleParentCommit (Surface *parent)
{
SurfaceActionClientData *client;
client = parent->client_data[SubsurfaceData];
if (client)
RunSurfaceActions (&client->actions);
}
void
XLUpdateOutputsForChildren (Surface *parent, int base_x, int base_y)
{
XLList *item;
Subsurface *subsurface;
Surface *child;
int output_x, output_y, output_width, output_height;
for (item = parent->subsurfaces; item; item = item->next)
{
child = item->data;
subsurface = SubsurfaceFromRole (child->role);
output_x = (subsurface->current_substate.x
* global_scale_factor);
output_y = (subsurface->current_substate.y
* global_scale_factor);
output_width = ViewWidth (child->view);
output_height = ViewHeight (child->view);
XLUpdateSurfaceOutputs (child,
base_x + output_x,
base_y + output_y,
output_width,
output_height);
/* Record those values in the child. */
subsurface->output_x = output_x;
subsurface->output_y = output_y;
subsurface->output_width = output_width;
subsurface->output_height = output_height;
}
}
void
XLUpdateDesynchronousChildren (Surface *parent, int *n_children)
{
XLList *item;
Subsurface *subsurface;
Surface *child;
for (item = parent->subsurfaces; item; item = item->next)
{
child = item->data;
subsurface = SubsurfaceFromRole (child->role);
if (!subsurface->synchronous)
/* The subsurface is desynchronous, so add it to the number of
desynchronous children. */
*n_children += 1;
/* Update these numbers recursively as well. */
XLUpdateDesynchronousChildren (child, n_children);
}
}

1313
surface.c Normal file

File diff suppressed because it is too large Load diff

4
svn-commit.tmp Normal file
View file

@ -0,0 +1,4 @@
--This line, and those below, will be ignored--
A /home/oldosfan/12to11

268
timer.c Normal file
View file

@ -0,0 +1,268 @@
/* 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/>. */
/* Some of this file was taken from timespec-add.c and timespec-sub.c,
part of gnulib, written by Paul Eggert <eggert@cs.ucla.edu>. */
#include "compositor.h"
/* Linked list of all timers. */
static Timer all_timers;
struct _Timer
{
/* The next and last timers in this list. */
Timer *next, *last;
/* The repeat of this timer. */
struct timespec repeat;
/* The next time this timer should be run. */
struct timespec next_time;
/* The function that should be called when the timer is run. */
void (*function) (Timer *, void *, struct timespec);
/* User data associated with the timer. */
void *timer_data;
};
struct timespec
CurrentTimespec (void)
{
struct timespec timespec;
clock_gettime (CLOCK_MONOTONIC, &timespec);
return timespec;
}
struct timespec
MakeTimespec (time_t s, long int ns)
{
struct timespec r;
r.tv_sec = s;
r.tv_nsec = ns;
return r;
}
int
TimespecCmp (struct timespec a, struct timespec b)
{
return (2 * SafeCmp (a.tv_sec, b.tv_sec)
+ SafeCmp (a.tv_nsec, b.tv_nsec));
}
struct timespec
TimespecAdd (struct timespec a, struct timespec b)
{
time_t rs, bs, bs1;
int ns, nsd, rns;
rs = a.tv_sec;
bs = b.tv_sec;
ns = a.tv_nsec + b.tv_nsec;
nsd = ns - 1000000000;
rns = ns;
if (0 < nsd)
{
rns = nsd;
if (!IntAddWrapv (bs, 1, &bs1))
bs = bs1;
else if (rs < 0)
rs++;
else
goto high_overflow;
}
if (IntAddWrapv (rs, bs, &rs))
{
if (bs < 0)
{
rs = TypeMinimum (time_t);
rns = 0;
}
else
{
high_overflow:
rs = TypeMaximum (time_t);
rns = 1000000000 - 1;
}
}
return MakeTimespec (rs, rns);
}
struct timespec
TimespecSub (struct timespec a, struct timespec b)
{
time_t rs, bs, bs1;
int ns, rns;
rs = a.tv_sec;
bs = b.tv_sec;
ns = a.tv_nsec - b.tv_nsec;
rns = ns;
if (ns < 0)
{
rns = ns + 1000000000;
if (!IntAddWrapv (bs, 1, &bs1))
bs = bs1;
else if (- TypeIsSigned (time_t) < rs)
rs--;
else
goto low_overflow;
}
if (IntSubtractWrapv (rs, bs, &rs))
{
if (0 < bs)
{
low_overflow:
rs = TypeMinimum (time_t);
rns = 0;
}
else
{
rs = TypeMaximum (time_t);
rns = 1000000000 - 1;
}
}
return MakeTimespec (rs, rns);
}
Timer *
AddTimer (void (*function) (Timer *, void *, struct timespec),
void *data, struct timespec delay)
{
Timer *timer;
timer = XLMalloc (sizeof *timer);
timer->function = function;
timer->timer_data = data;
timer->repeat = delay;
timer->next_time = TimespecAdd (CurrentTimespec (),
delay);
/* Chain the timer onto our list of timers. */
timer->next = all_timers.next;
timer->last = &all_timers;
all_timers.next->last = timer;
all_timers.next = timer;
return timer;
}
Timer *
AddTimerWithBaseTime (void (*function) (Timer *, void *, struct timespec),
void *data, struct timespec delay, struct timespec base)
{
Timer *timer;
timer = XLMalloc (sizeof *timer);
timer->function = function;
timer->timer_data = data;
timer->repeat = delay;
timer->next_time = TimespecAdd (base, delay);
/* Chain the timer onto our list of timers. */
timer->next = all_timers.next;
timer->last = &all_timers;
all_timers.next->last = timer;
all_timers.next = timer;
return timer;
}
void
RemoveTimer (Timer *timer)
{
/* Start by removing the timer from the list of timers. This is
only safe inside a timer callback our outside TimerCheck. */
timer->next->last = timer->last;
timer->last->next = timer->next;
/* Then, free the timer. */
XLFree (timer);
}
void
RetimeTimer (Timer *timer)
{
timer->next_time = TimespecAdd (CurrentTimespec (),
timer->repeat);
}
struct timespec
TimerCheck (void)
{
struct timespec now, wait, temp;
Timer *timer, *next;
Bool flag;
now = CurrentTimespec ();
wait = MakeTimespec (TypeMaximum (time_t),
1000000000 - 1);
timer = all_timers.next;
while (timer != &all_timers)
{
/* Move the list forward first, so the timer callback can
safely remove itself from the list. */
next = timer->next;
flag = False;
if (TimespecCmp (timer->next_time, now) <= 0)
{
timer->next_time = TimespecAdd (timer->next_time,
timer->repeat);
flag = True;
}
temp = TimespecSub (timer->next_time, now);
if (TimespecCmp (temp, wait) < 0)
/* Wait is the time to wait until the next timer might
fire. */
wait = temp;
if (flag)
/* Run this function here instead, since it might remove the
timer from the list. */
timer->function (timer, timer->timer_data, now);
timer = next;
}
return wait;
}
void
XLInitTimers (void)
{
all_timers.next = &all_timers;
all_timers.last = &all_timers;
}

1782
xdata.c Normal file

File diff suppressed because it is too large Load diff

1313
xdg-shell.xml Normal file

File diff suppressed because it is too large Load diff

866
xdg_popup.c Normal file
View file

@ -0,0 +1,866 @@
/* 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 "compositor.h"
#include "xdg-shell.h"
#include <X11/extensions/XInput2.h>
#define PopupFromRoleImpl(impl) ((XdgPopup *) (impl))
typedef struct _XdgPopup XdgPopup;
typedef struct _PropMotifWmHints PropMotifWmHints;
enum
{
StateIsMapped = 1,
StateIsGrabbed = (1 << 1),
StatePendingGrab = (1 << 2),
StatePendingPosition = (1 << 3),
StateAckPosition = (1 << 4),
};
struct _PropMotifWmHints
{
unsigned long flags;
unsigned long functions;
unsigned long decorations;
long input_mode;
unsigned long status;
};
struct _XdgPopup
{
/* The parent role implementation. */
XdgRoleImplementation impl;
/* The role associated with this popup. */
Role *role;
/* The parent xdg_surface object. */
Role *parent;
/* The wl_resource associated with this popup. */
struct wl_resource *resource;
/* The number of references to this popup. */
int refcount;
/* Some state associated with this popup. */
int state;
/* Whether or not we are waiting for a reply to a configure
event. */
Bool conf_reply;
/* The serial of the last configure event sent, and the last
position event sent. */
uint32_t conf_serial, position_serial;
/* The associated positioner. */
Positioner *positioner;
/* Any pending seat on which a grab should be asserted. */
Seat *pending_grab_seat;
/* The serial to use for that grab. */
uint32_t pending_grab_serial;
/* The seat that currently holds the grab. */
Seat *grab_holder;
/* The current grab serial. */
uint32_t current_grab_serial;
/* Its destroy callback key. */
void *seat_callback_key, *pending_callback_key;
/* The current position. */
int x, y;
/* The pending coordinates. */
int pending_x, pending_y;
/* The current width and height. */
int width, height;
/* Reconstrain callback associated with the parent. */
void *reconstrain_callback_key;
/* The next and last popups in this list. */
XdgPopup *next, *last;
};
/* List of all current popups. */
XdgPopup live_popups;
/* Forward declarations. */
static void DoGrab (XdgPopup *, Seat *, uint32_t);
static void Dismiss (XdgPopup *, Bool);
static void
DestroyBacking (XdgPopup *popup)
{
void *key;
if (--popup->refcount)
return;
key = popup->reconstrain_callback_key;
if (key)
XLXdgRoleCancelReconstrainCallback (key);
/* Release the parent if it exists. */
if (popup->parent)
XLReleaseXdgRole (popup->parent);
/* Release seat callbacks if they exist. */
if (popup->seat_callback_key)
XLSeatCancelDestroyListener (popup->seat_callback_key);
if (popup->pending_callback_key)
XLSeatCancelDestroyListener (popup->pending_callback_key);
/* Unlink the popup from the list. */
popup->last->next = popup->next;
popup->next->last = popup->last;
/* Release the positioner and free the popup. */
XLReleasePositioner (popup->positioner);
XLFree (popup);
}
static void
HandleResourceDestroy (struct wl_resource *resource)
{
XdgPopup *popup;
popup = wl_resource_get_user_data (resource);
popup->resource = NULL;
DestroyBacking (popup);
}
static void
Attach (Role *role, XdgRoleImplementation *impl)
{
XdgPopup *popup;
XSetWindowAttributes attrs;
PropMotifWmHints hints;
Window window;
popup = PopupFromRoleImpl (impl);
popup->refcount++;
popup->role = role;
window = XLWindowFromXdgRole (role);
/* Make the popup override-redirect. */
attrs.override_redirect = True;
XChangeWindowAttributes (compositor.display, window,
CWOverrideRedirect, &attrs);
/* It turns out that Mutter still draws drop shadows for popups, so
turn them off. */
hints.flags = 0;
hints.decorations = 0;
/* Add _NET_WM_SYNC_REQUEST to the list of supported protocols. */
XSetWMProtocols (compositor.display, XLWindowFromXdgRole (role),
&_NET_WM_SYNC_REQUEST, 1);
XChangeProperty (compositor.display, window,
_MOTIF_WM_HINTS, _MOTIF_WM_HINTS, 32,
PropModeReplace,
(unsigned char *) &hints, 5);
}
static void
Unmap (XdgPopup *popup)
{
popup->state &= ~StateIsMapped;
XUnmapWindow (compositor.display,
XLWindowFromXdgRole (popup->role));
}
static void
RevertGrabTo (XdgPopup *popup, Role *parent_role)
{
XdgPopup *parent;
XdgRoleImplementation *impl;
impl = XLImplementationOfXdgRole (parent_role);
if (!impl || XLTypeOfXdgRole (parent_role) != TypePopup)
return;
parent = PopupFromRoleImpl (impl);
DoGrab (parent, popup->grab_holder,
popup->current_grab_serial);
}
static void
Detach (Role *role, XdgRoleImplementation *impl)
{
XdgPopup *popup;
XSetWindowAttributes attrs;
popup = PopupFromRoleImpl (impl);
/* Detaching the popup means that it will be destroyed soon. Revert
the grab to the parent and unmap it. */
if (popup->state & StateIsGrabbed)
RevertGrabTo (popup, popup->parent);
if (popup->state & StateIsMapped)
Unmap (popup);
popup->role = NULL;
DestroyBacking (popup);
/* Make the window non-override-redirect. */
attrs.override_redirect = False;
XChangeWindowAttributes (compositor.display,
XLWindowFromXdgRole (role),
CWOverrideRedirect, &attrs);
}
static void
SendConfigure (XdgPopup *popup, int x, int y, int width, int height)
{
uint32_t serial;
serial = wl_display_next_serial (compositor.wl_display);
if (width != -1 && height != -1)
{
xdg_popup_send_configure (popup->resource,
x, y, width, height);
popup->state |= StateAckPosition;
}
XLXdgRoleSendConfigure (popup->role, serial);
popup->conf_reply = True;
popup->conf_serial = serial;
popup->position_serial = serial;
}
static void
MoveWindow (XdgPopup *popup)
{
int root_x, root_y, parent_gx, parent_gy;
int geometry_x, geometry_y, x, y;
Window window;
/* No parent was specified. */
if (!popup->parent)
return;
if (!popup->role || !popup->parent)
return;
window = XLWindowFromXdgRole (popup->role);
XLXdgRoleGetCurrentGeometry (popup->parent, &parent_gx,
&parent_gy, NULL, NULL);
XLXdgRoleGetCurrentGeometry (popup->role, &geometry_x,
&geometry_y, NULL, NULL);
XLXdgRoleCurrentRootPosition (popup->parent, &root_x,
&root_y);
parent_gx *= global_scale_factor;
parent_gy *= global_scale_factor;
geometry_x *= global_scale_factor;
geometry_y *= global_scale_factor;
x = popup->x * global_scale_factor;
y = popup->y * global_scale_factor;
XMoveWindow (compositor.display, window,
x + root_x + parent_gx - geometry_x,
y + root_y + parent_gy - geometry_y);
}
static void
Map (XdgPopup *popup)
{
/* We can't guarantee that the toplevel contents will be preserved
at this point. */
SubcompositorGarbage (XLSubcompositorFromXdgRole (popup->role));
/* Update the state. */
popup->state |= StateIsMapped;
/* Move the window to the correct position. */
MoveWindow (popup);
/* And map the window. */
XMapRaised (compositor.display, XLWindowFromXdgRole (popup->role));
/* Do any pending grab if the seat is still there. */
if (popup->state & StatePendingGrab)
{
if (popup->pending_grab_seat)
DoGrab (popup, popup->pending_grab_seat,
popup->pending_grab_serial);
else
Dismiss (popup, False);
/* Now free the callback belonging to the pending grab seat. */
if (popup->pending_callback_key)
XLSeatCancelDestroyListener (popup->pending_callback_key);
popup->pending_grab_seat = NULL;
popup->pending_callback_key = NULL;
popup->state &= ~StatePendingGrab;
}
}
static void
Commit (Role *role, Surface *surface, XdgRoleImplementation *impl)
{
XdgPopup *popup;
popup = PopupFromRoleImpl (impl);
if (popup->state & StatePendingPosition)
{
popup->x = popup->pending_x;
popup->y = popup->pending_y;
MoveWindow (popup);
}
popup->state &= ~StatePendingPosition;
if (!surface->current_state.buffer)
{
/* No buffer was attached, unmap the window. */
if (popup->state & StateIsMapped)
Unmap (popup);
}
else if (!popup->conf_reply)
{
/* Map the window if a reply was received. */
if (!(popup->state & StateIsMapped))
Map (popup);
}
}
static void
AckConfigure (Role *role, XdgRoleImplementation *impl, uint32_t serial)
{
XdgPopup *popup;
popup = PopupFromRoleImpl (impl);
if (serial == popup->conf_serial)
{
popup->conf_reply = False;
popup->conf_serial = 0;
}
if (serial == popup->position_serial
&& popup->state & StateAckPosition)
{
/* Now apply the position of the popup. */
popup->x = popup->pending_x;
popup->y = popup->pending_y;
popup->state &= ~StateAckPosition;
popup->position_serial = 0;
}
}
static void
InternalReposition (XdgPopup *popup)
{
int x, y, width, height;
FrameClock *clock;
/* No parent was specified. */
if (!popup->parent)
return;
if (!popup->role || !popup->parent)
return;
XLPositionerCalculateGeometry (popup->positioner,
popup->parent, &x, &y,
&width, &height);
popup->pending_x = x;
popup->pending_y = y;
SendConfigure (popup, popup->pending_x, popup->pending_y,
width, height);
/* Now, freeze the frame clock, to avoid flicker when the client
commits before ack_configure. */
clock = XLXdgRoleGetFrameClock (popup->role);
XLFrameClockFreeze (clock);
popup->state |= StateAckPosition;
}
static void
HandleGeometryChange (Role *role, XdgRoleImplementation *impl)
{
XdgPopup *popup;
popup = PopupFromRoleImpl (impl);
MoveWindow (popup);
}
static Bool
CheckCanGrab (Role *parent, Seat *seat)
{
XdgRoleImplementationType type;
XdgRoleImplementation *parent_impl;
XdgPopup *popup;
if (!parent->surface)
return False;
parent_impl = XLImplementationOfXdgRole (parent);
if (!parent_impl)
return False;
type = XLTypeOfXdgRole (parent);
if (type == TypeToplevel)
return True;
if (type == TypePopup)
{
popup = PopupFromRoleImpl (parent_impl);
return (popup->state & StateIsGrabbed
&& popup->grab_holder == seat);
}
return False;
}
static void
HandleGrabHolderDestroy (void *data)
{
XdgPopup *popup;
popup = data;
popup->grab_holder = NULL;
popup->seat_callback_key = NULL;
Dismiss (popup, False);
}
static void
SaveGrabHolder (XdgPopup *popup, Seat *seat)
{
if (popup->grab_holder == seat)
return;
if (popup->grab_holder)
{
XLSeatCancelDestroyListener (popup->seat_callback_key);
popup->seat_callback_key = NULL;
popup->grab_holder = NULL;
}
if (seat)
{
popup->grab_holder = seat;
popup->seat_callback_key
= XLSeatRunOnDestroy (seat, HandleGrabHolderDestroy,
popup);
}
}
static void
DoGrab (XdgPopup *popup, Seat *seat, uint32_t serial)
{
if (popup->resource
&& popup->role && popup->role->surface
&& CheckCanGrab (popup->parent, seat)
&& XLSeatExplicitlyGrabSurface (seat,
popup->role->surface,
serial))
{
popup->current_grab_serial = serial;
SaveGrabHolder (popup, seat);
popup->state |= StateIsGrabbed;
}
else
Dismiss (popup, False);
}
static void
Dismiss (XdgPopup *popup, Bool do_parents)
{
Role *role;
XdgRoleImplementation *impl;
XdgPopup *parent;
if (popup->state & StateIsGrabbed)
RevertGrabTo (popup, popup->parent);
if (popup->state & StateIsMapped)
Unmap (popup);
popup->state &= ~StateIsGrabbed;
if (popup->resource)
xdg_popup_send_popup_done (popup->resource);
if (do_parents && popup->parent)
{
role = popup->parent;
impl = XLImplementationOfXdgRole (role);
if (impl && XLTypeOfXdgRole (role) == TypePopup)
{
parent = PopupFromRoleImpl (impl);
Dismiss (parent, True);
}
}
}
static void
HandleSeatDestroy (void *data)
{
XdgPopup *popup;
popup = data;
popup->pending_callback_key = NULL;
popup->pending_grab_seat = NULL;
/* The popup will later be dismissed upon mapping. */
}
static void
RecordGrabPending (XdgPopup *popup, Seat *seat, uint32_t serial)
{
void *key;
if (popup->seat_callback_key || popup->pending_callback_key)
return;
key = XLSeatRunOnDestroy (seat, HandleSeatDestroy, popup);
if (!key)
Dismiss (popup, False);
else
{
popup->pending_callback_key = key;
popup->pending_grab_seat = seat;
popup->pending_grab_serial = serial;
popup->state |= StatePendingGrab;
}
}
static void
Grab (struct wl_client *client, struct wl_resource *resource,
struct wl_resource *seat_resource, uint32_t serial)
{
Seat *seat;
XdgPopup *popup;
seat = wl_resource_get_user_data (seat_resource);
popup = wl_resource_get_user_data (resource);
if (!popup->role || !popup->role->surface)
return;
if (popup->state & StateIsGrabbed)
return;
if (!(popup->state & StateIsMapped))
RecordGrabPending (popup, seat, serial);
else
wl_resource_post_error (resource, XDG_POPUP_ERROR_INVALID_GRAB,
"trying to grab mapped popup");
}
static void
Reposition (struct wl_client *client, struct wl_resource *resource,
struct wl_resource *positioner_resource, uint32_t token)
{
XdgPopup *popup;
popup = wl_resource_get_user_data (resource);
XLReleasePositioner (popup->positioner);
popup->positioner
= wl_resource_get_user_data (positioner_resource);
XLRetainPositioner (popup->positioner);
xdg_popup_send_repositioned (resource, token);
InternalReposition (popup);
}
static void
Destroy (struct wl_client *client, struct wl_resource *resource)
{
XdgPopup *popup;
popup = wl_resource_get_user_data (resource);
if (popup->role)
XLXdgRoleDetachImplementation (popup->role,
&popup->impl);
wl_resource_destroy (resource);
}
static Bool
MaybeDismissPopup (XIDeviceEvent *xev)
{
XdgRoleImplementation *impl;
XdgPopup *popup;
impl = XLLookUpXdgPopup (xev->event);
if (!impl)
return False;
popup = PopupFromRoleImpl (impl);
if (popup->state & StateIsGrabbed)
{
/* If the popup is grabbed and the click is outside the input
region of the popup, this means a click has happened outside
the client, and the popup should be dismissed. */
if (!XLXdgRoleInputRegionContains (popup->role,
lrint (xev->event_x),
lrint (xev->event_y)))
{
Dismiss (popup, True);
return True;
}
}
return False;
}
static Bool
HandleOneGenericEvent (XIEvent *event)
{
switch (event->evtype)
{
case XI_ButtonRelease:
case XI_ButtonPress:
return MaybeDismissPopup ((XIDeviceEvent *) event);
default:
return False;
}
}
static Bool
HandleOneConfigureNotify (XEvent *event)
{
XdgPopup *popup;
XdgRoleImplementation *impl;
impl = XLLookUpXdgPopup (event->xconfigure.window);
if (!impl)
return False;
popup = PopupFromRoleImpl (impl);
XLXdgRoleNoteConfigure (popup->role, event);
return False;
}
static void
NoteSize (Role *role, XdgRoleImplementation *impl,
int width, int height)
{
XdgPopup *popup;
popup = PopupFromRoleImpl (impl);
popup->width = width;
popup->height = height;
}
static void
HandleParentConfigure (void *data, XEvent *xevent)
{
XdgPopup *popup;
popup = data;
if (XLPositionerIsReactive (popup->positioner))
InternalReposition (popup);
}
static void
HandleParentResize (void *data)
{
XdgPopup *popup;
popup = data;
if (XLPositionerIsReactive (popup->positioner))
InternalReposition (popup);
}
static const struct xdg_popup_interface xdg_popup_impl =
{
.destroy = Destroy,
.grab = Grab,
.reposition = Reposition,
};
void
XLGetXdgPopup (struct wl_client *client, struct wl_resource *resource,
uint32_t id, struct wl_resource *parent_resource,
struct wl_resource *positioner)
{
XdgPopup *popup;
Role *role, *parent;
void *key;
popup = XLSafeMalloc (sizeof *popup);
role = wl_resource_get_user_data (resource);
if (!popup)
{
wl_client_post_no_memory (client);
return;
}
memset (popup, 0, sizeof *popup);
popup->resource = wl_resource_create (client, &xdg_popup_interface,
wl_resource_get_version (resource),
id);
if (!popup->resource)
{
wl_resource_post_no_memory (resource);
XLFree (popup);
return;
}
popup->impl.funcs.attach = Attach;
popup->impl.funcs.commit = Commit;
popup->impl.funcs.detach = Detach;
popup->impl.funcs.ack_configure = AckConfigure;
popup->impl.funcs.note_size = NoteSize;
popup->impl.funcs.handle_geometry_change = HandleGeometryChange;
if (parent_resource)
{
parent = wl_resource_get_user_data (parent_resource);
key = XLXdgRoleRunOnReconstrain (parent, HandleParentConfigure,
HandleParentResize, popup);
XLRetainXdgRole (parent);
popup->parent = parent;
popup->reconstrain_callback_key = key;
}
popup->positioner = wl_resource_get_user_data (positioner);
XLRetainPositioner (popup->positioner);
wl_resource_set_implementation (popup->resource, &xdg_popup_impl,
popup, HandleResourceDestroy);
popup->refcount++;
XLXdgRoleAttachImplementation (role, &popup->impl);
/* Link the popup onto the list of all popups. */
popup->last = &live_popups;
popup->next = live_popups.next;
live_popups.next->last = popup;
live_popups.next = popup;
/* Send the initial configure event. */
InternalReposition (popup);
}
Bool
XLHandleXEventForXdgPopups (XEvent *event)
{
if (event->type == GenericEvent
&& event->xgeneric.extension == xi2_opcode)
return HandleOneGenericEvent (event->xcookie.data);
if (event->type == ConfigureNotify)
return HandleOneConfigureNotify (event);
return False;
}
void
XLInitPopups (void)
{
live_popups.next = &live_popups;
live_popups.last = &live_popups;
}
Bool
XLHandleButtonForXdgPopups (Seat *seat, Surface *dispatch)
{
XdgPopup *popup;
Bool rc;
/* This means a button press happened on dispatch, on seat. Loop
through all grabbed popups. Dismiss each one whose client is not
the same as dispatch. */
popup = live_popups.next;
rc = False;
while (popup != &live_popups)
{
if (popup->state & StateIsGrabbed
&& seat == popup->grab_holder
&& (wl_resource_get_client (popup->resource)
!= wl_resource_get_client (dispatch->resource)))
{
Dismiss (popup, True);
rc = True;
}
popup = popup->next;
}
return rc;
}

1769
xdg_surface.c Normal file

File diff suppressed because it is too large Load diff

1867
xdg_toplevel.c Normal file

File diff suppressed because it is too large Load diff

97
xdg_wm.c Normal file
View file

@ -0,0 +1,97 @@
/* 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 "compositor.h"
#include "xdg-shell.h"
/* The xdg_wm_base global. */
static struct wl_global *global_xdg_wm_base;
/* All xdg_wm_base resources. */
static XLList *all_xdg_wm_bases;
static void
CreatePositioner (struct wl_client *client, struct wl_resource *resource,
uint32_t id)
{
XLCreateXdgPositioner (client, resource, id);
}
static void
GetXdgSurface (struct wl_client *client, struct wl_resource *resource,
uint32_t id, struct wl_resource *surface_resource)
{
XLGetXdgSurface (client, resource, id, surface_resource);
}
static void
Pong (struct wl_client *client, struct wl_resource *resource,
uint32_t serial)
{
/* TODO... */
}
static void
Destroy (struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy (resource);
}
static const struct xdg_wm_base_interface xdg_wm_base_impl =
{
.destroy = Destroy,
.create_positioner = CreatePositioner,
.get_xdg_surface = GetXdgSurface,
.pong = Pong,
};
static void
HandleResourceDestroy (struct wl_resource *resource)
{
all_xdg_wm_bases = XLListRemove (all_xdg_wm_bases, resource);
}
static void
HandleBind (struct wl_client *client, void *data,
uint32_t version, uint32_t id)
{
struct wl_resource *resource;
resource = wl_resource_create (client, &xdg_wm_base_interface,
version, id);
if (!resource)
{
wl_client_post_no_memory (client);
return;
}
wl_resource_set_implementation (resource, &xdg_wm_base_impl,
NULL, HandleResourceDestroy);
all_xdg_wm_bases = XLListPrepend (all_xdg_wm_bases, resource);
}
void
XLInitXdgWM (void)
{
global_xdg_wm_base
= wl_global_create (compositor.wl_display,
&xdg_wm_base_interface,
5, NULL, HandleBind);
}

119
xerror.c Normal file
View file

@ -0,0 +1,119 @@
/* 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 <stdio.h>
#include <stdlib.h>
#include "compositor.h"
#include <X11/extensions/XInput.h>
/* X error handling routines. The entry point into this code is
CatchXErrors, which starts catching X errors in the following code,
and UncatchXErrors, which syncs (if necessary) and saves any
received XErrorEvent into a provided buffer.
This code is not reentrant since it doesn't have to take care of
many complicated scenarios that the Emacs code needs. */
/* First request from which errors should be caught. -1 if we are not
currently catching errors. */
static unsigned long first_error_req;
/* XErrorEvent any error that happens while errors are being caught
will be saved in. */
static XErrorEvent error;
/* True if any error was caught. */
static Bool error_caught;
void
CatchXErrors (void)
{
first_error_req = XNextRequest (compositor.display);
error_caught = False;
}
Bool
UncatchXErrors (XErrorEvent *event)
{
/* Try to avoid syncing to obtain errors if we know none could have
been generated, because either no request has been made, or all
requests have been processed. */
if ((LastKnownRequestProcessed (compositor.display)
!= XNextRequest (compositor.display) - 1)
&& (NextRequest (compositor.display)
> first_error_req))
/* If none of those conditions apply, catch errors now. */
XSync (compositor.display, False);
first_error_req = -1;
if (!event)
return error_caught;
if (!error_caught)
return False;
*event = error;
return True;
}
static int
ErrorHandler (Display *display, XErrorEvent *event)
{
char buf[256];
if (first_error_req != -1
&& event->serial >= first_error_req)
{
error = *event;
error_caught = True;
return 0;
}
if (XLHandleErrorForDmabuf (event))
return 0;
if (event->error_code == (xi_first_error + XI_BadDevice))
/* Various XI requests can result in XI_BadDevice errors if the
device has been removed on the X server, but we have not yet
processed the corresponding hierarchy events. */
return 0;
XGetErrorText (display, event->error_code, buf, sizeof buf);
fprintf (stderr, "X protocol error: %s on protocol request %d\n",
buf, event->request_code);
exit (70);
}
void
InitXErrors (void)
{
first_error_req = -1;
XSetErrorHandler (ErrorHandler);
/* Allow debugging by setting an environment variable. */
if (getenv ("SYNCHRONIZE"))
XSynchronize (compositor.display, True);
}

432
xsettings.c Normal file
View file

@ -0,0 +1,432 @@
/* 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 <byteswap.h>
#include <stdio.h>
#include <string.h>
#include "compositor.h"
typedef struct _IntegerValueListener IntegerValueListener;
typedef enum _SettingType SettingType;
enum _SettingType
{
Integer = 0,
String = 1,
RgbColor = 2,
};
struct _IntegerValueListener
{
/* Function called when the value of the setting changes. */
void (*new_value) (int);
/* The last serial at which the value changed; -1 if the value is
not yet known. */
long long last_change_serial;
/* The name of the setting that this listener wants to react to. */
const char *setting_name;
/* The next listener in this chain. */
IntegerValueListener *next;
};
/* The settings manager window. */
static Window xsettings_window;
/* Manager selection atom. */
static Atom xsettings_atom;
/* List of all listeners for integer setting changes. */
IntegerValueListener *integer_listeners;
/* Key for selected input. */
static RootWindowSelection *input_key;
#define PadValue(n, m) ((n + m - 1) & (~(m - 1)))
static void
Swap32 (unsigned char byteorder, uint32_t *cardinal)
{
#ifdef __BIG_ENDIAN__
if (byteorder == MSBFirst)
return;
#else
if (byteorder == LSBFirst)
return;
#endif
*cardinal = bswap_32 (*cardinal);
}
static void
SwapI32 (unsigned char byteorder, int *cardinal)
{
#ifdef __BIG_ENDIAN__
if (byteorder == MSBFirst)
return;
#else
if (byteorder == LSBFirst)
return;
#endif
*cardinal = bswap_32 (*cardinal);
}
static void
Swap16 (unsigned char byteorder, uint16_t *cardinal)
{
#ifdef __BIG_ENDIAN__
if (byteorder == MSBFirst)
return;
#else
if (byteorder == LSBFirst)
return;
#endif
*cardinal = bswap_16 (*cardinal);
}
static void
HandleIntegerValue (char *name, int value, uint32_t last_change_serial)
{
IntegerValueListener *listener;
for (listener = integer_listeners; listener; listener = listener->next)
{
if (!strcmp (listener->setting_name, name)
&& last_change_serial > listener->last_change_serial)
{
listener->last_change_serial = last_change_serial;
listener->new_value (value);
}
}
}
static void
ReadSettingsData (void)
{
unsigned char *prop_data, *read, *name_start, *value_start;
Atom actual_type;
int actual_format;
Status rc;
unsigned long nitems_return, bytes_after;
unsigned char byteorder;
uint32_t serial, n_settings, value_length, last_change_serial;
uint16_t name_length;
int i, value;
uint8_t type;
XRenderColor color;
ptrdiff_t nitems;
char *name_buffer;
prop_data = NULL;
name_buffer = NULL;
/* Now read the actual property data. */
CatchXErrors ();
rc = XGetWindowProperty (compositor.display, xsettings_window,
_XSETTINGS_SETTINGS, 0, LONG_MAX, False,
_XSETTINGS_SETTINGS, &actual_type,
&actual_format, &nitems_return, &bytes_after,
&prop_data);
if (UncatchXErrors (NULL))
{
/* An error occured while reading property data. This means
that the manager window is gone, so begin watching for it
again. */
if (prop_data)
XFree (prop_data);
XLInitXSettings ();
return;
}
if (rc != Success || actual_type != _XSETTINGS_SETTINGS
|| actual_format != 8 || !nitems_return)
{
/* The property is invalid. */
if (prop_data)
XFree (prop_data);
return;
}
read = prop_data;
nitems = nitems_return;
/* Begin reading property data. */
if (nitems < 12)
goto end;
nitems -= 12;
/* CARD8, byte-order. */
byteorder = prop_data[0];
prop_data++;
/* CARD8 + CARD16, padding. */
prop_data += 3;
/* CARD32, serial. */
serial = ((uint32_t *) prop_data)[0];
prop_data += 4;
Swap32 (byteorder, &serial);
/* CARD32, number of settings in the property. */
n_settings = ((uint32_t *) prop_data)[0];
prop_data += 4;
Swap32 (byteorder, &n_settings);
/* Begin reading each entry. */
i = 0;
while (i < n_settings)
{
if (nitems < 4)
goto end;
/* CARD8, settings type. */
type = prop_data[0];
prop_data++;
/* CARD8, padding. */
prop_data++;
/* CARD16, name length. */
name_length = ((uint16_t *) prop_data)[0];
prop_data += 2;
Swap16 (byteorder, &name_length);
if (nitems < PadValue (name_length, 4) +4)
goto end;
nitems -= PadValue (name_length, 4) + 4;
/* NAME_LENGTH + padding, property name. */
name_start = prop_data;
prop_data += PadValue (name_length, 4);
/* CARD32, last-change-serial. */
last_change_serial = ((uint32_t *) prop_data)[0];
prop_data += 4;
switch (type)
{
case String:
if (nitems < 4)
goto end;
nitems -= 4;
/* CARD32, value length. */
value_length = ((uint32_t *) prop_data)[0];
prop_data += 4;
Swap32 (byteorder, &value_length);
if (nitems < PadValue (value_length, 4))
goto end;
nitems -= PadValue (value_length, 4);
/* VALUE_LENGTH + padding, property value. */
value_start = prop_data;
prop_data += PadValue (value_length, 4);
/* Note that string values are not yet handled. */
(void) value_start;
break;
case Integer:
if (nitems < 4)
goto end;
nitems -= 4;
/* INT32, value. */
value = ((int32_t *) prop_data)[0];
prop_data += 4;
SwapI32 (byteorder, &value);
/* Now, write the name to the name buffer, with NULL
termination. */
name_buffer = XLRealloc (name_buffer, name_length + 1);
memcpy (name_buffer, name_start, name_length);
name_buffer[name_length] = '\0';
/* And run any change handlers. */
HandleIntegerValue (name_buffer, value, last_change_serial);
break;
case RgbColor:
if (nitems < 8)
goto end;
nitems -= 8;
/* CARD16, red. */
color.red = ((uint16_t *) prop_data)[0];
prop_data += 2;
Swap16 (byteorder, &color.red);
/* CARD16, green. */
color.green = ((uint16_t *) prop_data)[0];
prop_data += 2;
Swap16 (byteorder, &color.green);
/* CARD16, blue. */
color.blue = ((uint16_t *) prop_data)[0];
prop_data += 2;
Swap16 (byteorder, &color.blue);
/* CARD16, alpha. */
color.alpha = ((uint16_t *) prop_data)[0];
prop_data += 2;
Swap16 (byteorder, &color.alpha);
break;
}
i++;
}
end:
if (read)
XFree (read);
XFree (name_buffer);
}
Bool
XLHandleOneXEventForXSettings (XEvent *event)
{
if (event->type == ClientMessage
&& event->xclient.message_type == MANAGER
&& event->xclient.data.l[1] == xsettings_atom)
{
/* Set the settings manager window, deselect for StructureNotify
on the root window, and read the new settings data. */
if (input_key)
XLDeselectInputFromRootWindow (input_key);
input_key = NULL;
xsettings_window = event->xclient.data.l[2];
CatchXErrors ();
/* Also select for PropertyNotify on the settings window, so we
can get notifications once properties change. */
XSelectInput (compositor.display, xsettings_window,
PropertyChangeMask);
if (UncatchXErrors (NULL))
/* The settings window vanished; select for manager events
again until we obtain the new settings window. */
XLInitXSettings ();
else
/* Begin reading settings data. */
ReadSettingsData ();
return True;
}
else if (event->type == PropertyNotify
&& event->xproperty.window == xsettings_window
&& event->xproperty.atom == _XSETTINGS_SETTINGS)
{
CatchXErrors ();
/* Also select for PropertyNotify on the settings window, so we
can get notifications once properties change. */
XSelectInput (compositor.display, xsettings_window,
PropertyChangeMask);
if (UncatchXErrors (NULL))
/* The settings window vanished; select for manager events
again until we obtain the new settings window. */
XLInitXSettings ();
else
/* Begin reading settings data. */
ReadSettingsData ();
return True;
}
else if (event->type == DestroyNotify
&& event->xdestroywindow.window == xsettings_window)
{
xsettings_window = None;
/* The settings window was destroyed; select for manager events
again until the settings window reappears. */
XLInitXSettings ();
}
return False;
}
void
XLListenToIntegerSetting (const char *name, void (*callback) (int))
{
IntegerValueListener *listener;
listener = XLMalloc (sizeof *listener);
listener->last_change_serial = -1;
listener->new_value = callback;
listener->setting_name = name;
listener->next = integer_listeners;
integer_listeners = listener;
}
void
XLInitXSettings (void)
{
IntegerValueListener *listener;
char buffer[64];
if (xsettings_atom == None)
{
/* Intern the manager selection atom, if it doesn't already
exist. */
sprintf (buffer, "_XSETTINGS_S%d",
DefaultScreen (compositor.display));
xsettings_atom = XInternAtom (compositor.display, buffer,
False);
}
/* Reset the last change serial of all listeners, since the settings
provider window has vanished. */
for (listener = integer_listeners; listener; listener = listener->next)
listener->last_change_serial = -1;
/* Grab the server, and get the value of the manager selection. */
XGrabServer (compositor.display);
xsettings_window = XGetSelectionOwner (compositor.display,
xsettings_atom);
/* If the settings window doesn't exist yet, select for MANAGER
messages on the root window. */
if (!xsettings_window && !input_key)
input_key = XLSelectInputFromRootWindow (StructureNotifyMask);
/* If the settings window exists, then begin reading property
data. */
if (xsettings_window != None)
{
/* Also select for PropertyNotify events. */
XSelectInput (compositor.display, xsettings_window,
PropertyChangeMask);
ReadSettingsData ();
}
/* Finally, ungrab the X server. */
XUngrabServer (compositor.display);
}