forked from 12to11/12to11
Import files
This commit is contained in:
commit
528f7ba858
44 changed files with 36787 additions and 0 deletions
168
12to11.c
Normal file
168
12to11.c
Normal 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
15
12to11.man
Normal 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
94
Imakefile
Normal 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
72
README
Normal 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
106
alloc.c
Normal 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
287
atoms.c
Normal 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
127
buffer.c
Normal 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
83
compositor.c
Normal 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
1005
compositor.h
Normal file
File diff suppressed because it is too large
Load diff
1587
data_device.c
Normal file
1587
data_device.c
Normal file
File diff suppressed because it is too large
Load diff
124
ewmh.c
Normal file
124
ewmh.c
Normal 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
475
fns.c
Normal 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
772
frame_clock.c
Normal 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, ×tamp)
|
||||
|| IntAddWrapv (timestamp, clock.tv_nsec / 1000, ×tamp))
|
||||
/* 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, ×pec->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, ×pec))
|
||||
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
518
icon_surface.c
Normal 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
17
libraries.def
Normal 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
|
586
linux-dmabuf-unstable-v1.xml
Normal file
586
linux-dmabuf-unstable-v1.xml
Normal 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
2974
media_types.txt
Normal file
File diff suppressed because it is too large
Load diff
32
mime0.awk
Normal file
32
mime0.awk
Normal 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
34
mime1.awk
Normal 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
31
mime2.awk
Normal 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
34
mime3.awk
Normal 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
33
mime4.awk
Normal 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"
|
||||
}
|
809
positioner.c
Normal file
809
positioner.c
Normal 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
105
region.c
Normal 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
293
run.c
Normal 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 ();
|
||||
}
|
584
shm.c
Normal file
584
shm.c
Normal 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
2433
subcompositor.c
Normal file
File diff suppressed because it is too large
Load diff
982
subsurface.c
Normal file
982
subsurface.c
Normal 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);
|
||||
}
|
||||
}
|
4
svn-commit.tmp
Normal file
4
svn-commit.tmp
Normal file
|
@ -0,0 +1,4 @@
|
|||
|
||||
--This line, and those below, will be ignored--
|
||||
|
||||
A /home/oldosfan/12to11
|
268
timer.c
Normal file
268
timer.c
Normal 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, ×pec);
|
||||
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;
|
||||
}
|
1313
xdg-shell.xml
Normal file
1313
xdg-shell.xml
Normal file
File diff suppressed because it is too large
Load diff
866
xdg_popup.c
Normal file
866
xdg_popup.c
Normal 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
1769
xdg_surface.c
Normal file
File diff suppressed because it is too large
Load diff
1867
xdg_toplevel.c
Normal file
1867
xdg_toplevel.c
Normal file
File diff suppressed because it is too large
Load diff
97
xdg_wm.c
Normal file
97
xdg_wm.c
Normal 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
119
xerror.c
Normal 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
432
xsettings.c
Normal 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);
|
||||
}
|
Loading…
Add table
Reference in a new issue