commit 528f7ba8588481457990bb67fe0f05270bffd4c8 Author: oldosfan Date: Mon Sep 12 13:24:50 2022 +0000 Import files diff --git a/12to11.c b/12to11.c new file mode 100644 index 0000000..e88ca82 --- /dev/null +++ b/12to11.c @@ -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 . */ + +#include +#include + +#include + +#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; +} diff --git a/12to11.man b/12to11.man new file mode 100644 index 0000000..e97638c --- /dev/null +++ b/12to11.man @@ -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. diff --git a/Imakefile b/Imakefile new file mode 100644 index 0000000..3a4395f --- /dev/null +++ b/Imakefile @@ -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) diff --git a/README b/README new file mode 100644 index 0000000..113fd34 --- /dev/null +++ b/README @@ -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. diff --git a/alloc.c b/alloc.c new file mode 100644 index 0000000..8b89538 --- /dev/null +++ b/alloc.c @@ -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 . */ + +#include +#include +#include +#include + +#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; +} diff --git a/atoms.c b/atoms.c new file mode 100644 index 0000000..30ade15 --- /dev/null +++ b/atoms.c @@ -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 . */ + +#include +#include +#include + +#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); +} diff --git a/buffer.c b/buffer.c new file mode 100644 index 0000000..2392ea7 --- /dev/null +++ b/buffer.c @@ -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 . */ + +#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); +} diff --git a/compositor.c b/compositor.c new file mode 100644 index 0000000..6765be9 --- /dev/null +++ b/compositor.c @@ -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 . */ + +#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); +} diff --git a/compositor.h b/compositor.h new file mode 100644 index 0000000..b1779b7 --- /dev/null +++ b/compositor.h @@ -0,0 +1,1005 @@ +/* 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 . */ + +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include "transfer_atoms.h" + +typedef struct _Compositor Compositor; + +struct _Compositor +{ + /* The X display for this "compositor" instance. */ + Display *display; + + /* The XCB connection. */ + xcb_connection_t *conn; + + /* The Wayland display used to communicate with clients. */ + struct wl_display *wl_display; + + /* Its event loop object. */ + struct wl_event_loop *wl_event_loop; + + /* The name of the socket used to communicate with clients. */ + const char *wl_socket; + + /* XRandr event and error base, and versions. */ + int rr_event_base, rr_error_base, rr_major, rr_minor; + + /* The visual used for all windows. */ + Visual *visual; + + /* The colormap. */ + Colormap colormap; + + /* The picture format used for ARGB formats. */ + XRenderPictFormat *argb_format; + + /* The picture format used for XRGB formats. */ + XRenderPictFormat *xrgb_format; + + /* The depth of that visual. */ + int n_planes; + + /* Whether the server time is monotonic. */ + Bool server_time_monotonic; +}; + +/* Forward declarations from seat.c. */ + +typedef struct _Seat Seat; + +/* Defined in 12to11.c. */ + +extern Compositor compositor; + +/* Defined in run.c. */ + +typedef struct _PollFd WriteFd; +typedef struct _PollFd ReadFd; + +extern void XLRunCompositor (void); +extern WriteFd *XLAddWriteFd (int, void *, void (*) (int, void *)); +extern ReadFd *XLAddReadFd (int, void *, void (*) (int, void *)); +extern void XLRemoveWriteFd (WriteFd *); +extern void XLRemoveReadFd (ReadFd *); + +/* Defined in alloc.c. */ + +extern void *XLMalloc (size_t); +extern void *XLRealloc (void *, size_t); +extern void *XLSafeMalloc (size_t); +extern void *XLCalloc (size_t, size_t); +extern char *XLStrdup (const char *); +extern void XLFree (void *); + +/* Defined in fns.c. */ + +/* Generic singly linked list structure. */ + +typedef struct _XLList XLList; +typedef struct _XIDList XIDList; +typedef struct _XLAssoc XLAssoc; +typedef struct _XLAssocTable XLAssocTable; +typedef struct _RootWindowSelection RootWindowSelection; + +struct _XLList +{ + XLList *next; + void *data; +}; + +/* The same, but for X server resources. */ + +struct _XIDList +{ + XIDList *next; + XID data; +}; + +extern void XLListFree (XLList *, void (*) (void *)); +extern XLList *XLListRemove (XLList *, void *); +extern XLList *XLListPrepend (XLList *, void *); + +extern void XIDListFree (XIDList *, void (*) (XID)); +extern XIDList *XIDListRemove (XIDList *, XID); +extern XIDList *XIDListPrepend (XIDList *, XID); + +struct _XLAssoc +{ + /* Next object in this bucket. */ + XLAssoc *next; + + /* Last object in this bucket. */ + XLAssoc *prev; + + /* XID of the object. */ + XID x_id; + + /* Untyped data. */ + void *data; +}; + +/* Map between XID and untyped data. Implemented the same way as the + old XAssocTable. */ + +struct _XLAssocTable +{ + /* Pointer to first pucket in bucket array. */ + XLAssoc *buckets; + + /* Table size (number of buckets). */ + int size; +}; + +extern XLAssocTable *XLCreateAssocTable (int); +extern void XLMakeAssoc (XLAssocTable *, XID, void *); +extern void *XLLookUpAssoc (XLAssocTable *, XID); +extern void XLDeleteAssoc (XLAssocTable *, XID); +extern void XLDestroyAssocTable (XLAssocTable *); + +extern void XLAssert (Bool); +extern int XLOpenShm (void); + +extern void XLScaleRegion (pixman_region32_t *, pixman_region32_t *, + float, float); +extern Time XLGetServerTimeRoundtrip (void); + +extern RootWindowSelection *XLSelectInputFromRootWindow (unsigned long); +extern void XLDeselectInputFromRootWindow (RootWindowSelection *); + +/* Defined in compositor.c. */ + +extern void XLInitCompositor (void); + +/* Defined in region.c. */ + +extern void XLCreateRegion (struct wl_client *, + struct wl_resource *, + uint32_t); + +/* Defined in buffer.c. */ + +typedef struct _ExtBuffer ExtBuffer; +typedef struct _ExtBufferFuncs ExtBufferFuncs; +typedef void (*ExtBufferFunc) (ExtBuffer *, void *); + +struct _ExtBufferFuncs +{ + void (*retain) (ExtBuffer *); + void (*dereference) (ExtBuffer *); + Picture (*get_picture) (ExtBuffer *); + Pixmap (*get_pixmap) (ExtBuffer *); + unsigned int (*width) (ExtBuffer *); + unsigned int (*height) (ExtBuffer *); + void (*release) (ExtBuffer *); + void (*print_buffer) (ExtBuffer *); +}; + +struct _ExtBuffer +{ + /* Functions for this buffer. */ + ExtBufferFuncs funcs; + + /* List of destroy listeners. */ + XLList *destroy_listeners; +}; + +extern void XLRetainBuffer (ExtBuffer *); +extern void XLDereferenceBuffer (ExtBuffer *); +extern Picture XLPictureFromBuffer (ExtBuffer *); +extern Pixmap XLPixmapFromBuffer (ExtBuffer *); +extern unsigned int XLBufferWidth (ExtBuffer *); +extern unsigned int XLBufferHeight (ExtBuffer *); +extern void XLReleaseBuffer (ExtBuffer *); +extern void *XLBufferRunOnFree (ExtBuffer *, ExtBufferFunc, + void *); +extern void XLBufferCancelRunOnFree (ExtBuffer *, void *); +extern void XLPrintBuffer (ExtBuffer *); + +extern void ExtBufferDestroy (ExtBuffer *); + +/* Defined in shm.c. */ + +extern void XLInitShm (void); + +/* Defined in subcompositor.c. */ + +typedef struct _View View; +typedef struct _List List; +typedef struct _Subcompositor Subcompositor; + +extern void SubcompositorInit (void); + +extern Subcompositor *MakeSubcompositor (void); +extern View *MakeView (void); + +extern void SubcompositorSetTarget (Subcompositor *, Picture); +extern void SubcompositorInsert (Subcompositor *, View *); +extern void SubcompositorInsertBefore (Subcompositor *, View *, View *); +extern void SubcompositorInsertAfter (Subcompositor *, View *, View *); + +extern void SubcompositorSetOpaqueCallback (Subcompositor *, + void (*) (Subcompositor *, + void *, + pixman_region32_t *), + void *); +extern void SubcompositorSetInputCallback (Subcompositor *, + void (*) (Subcompositor *, + void *, + pixman_region32_t *), + void *); +extern void SubcompositorSetBoundsCallback (Subcompositor *, + void (*) (void *, int, int, + int, int), + void *); +extern void SubcompositorBounds (Subcompositor *, int *, int *, int *, int *); +extern void SubcompositorSetProjectiveTransform (Subcompositor *, int, int); + +extern void SubcompositorUpdate (Subcompositor *); +extern void SubcompositorExpose (Subcompositor *, XEvent *); +extern void SubcompositorGarbage (Subcompositor *); +extern View *SubcompositorLookupView (Subcompositor *, int, int, int *, int *); +extern Bool SubcompositorIsEmpty (Subcompositor *); + +extern int SubcompositorWidth (Subcompositor *); +extern int SubcompositorHeight (Subcompositor *); + +extern void SubcompositorFree (Subcompositor *); + +extern void SubcompositorFreeze (Subcompositor *); +extern void SubcompositorUnfreeze (Subcompositor *); + +extern void ViewSetSubcompositor (View *, Subcompositor *); + +extern void ViewInsert (View *, View *); +extern void ViewInsertAfter (View *, View *, View *); +extern void ViewInsertBefore (View *, View *, View *); +extern void ViewInsertStart (View *, View *); + +extern void ViewUnparent (View *); +extern View *ViewGetParent (View *); +extern void ViewAttachBuffer (View *, ExtBuffer *); +extern void ViewMove (View *, int, int); +extern void ViewDetach (View *); +extern void ViewMap (View *); +extern void ViewUnmap (View *); + +extern void ViewSetData (View *, void *); +extern void *ViewGetData (View *); + +extern void ViewTranslate (View *, int, int, int *, int *); + +extern void ViewFree (View *); + +extern void ViewDamage (View *, pixman_region32_t *); +extern void ViewSetOpaque (View *, pixman_region32_t *); +extern void ViewSetInput (View *, pixman_region32_t *); +extern int ViewWidth (View *); +extern int ViewHeight (View *); +extern void ViewSetScale (View *, int); + +extern Subcompositor *ViewGetSubcompositor (View *); + +/* Defined in surface.c. */ + +typedef struct _State State; +typedef struct _FrameCallback FrameCallback; +typedef enum _RoleType RoleType; + +enum _RoleType + { + AnythingType, + SubsurfaceType, + XdgType, + CursorType, + DndIconType, + }; + +enum + { + PendingNone = 0, + PendingOpaqueRegion = 1, + PendingInputRegion = (1 << 2), + PendingDamage = (1 << 3), + PendingSurfaceDamage = (1 << 4), + PendingBuffer = (1 << 5), + PendingFrameCallbacks = (1 << 6), + PendingBufferScale = (1 << 7), + PendingAttachments = (1 << 8), + }; + +struct _FrameCallback +{ + /* The next and last callbacks. */ + FrameCallback *next, *last; + + /* The wl_resource containing this callback. */ + struct wl_resource *resource; +}; + +struct _State +{ + /* Accumulated damage. */ + pixman_region32_t damage; + + /* Opaque region. */ + pixman_region32_t opaque; + + /* Input region. */ + pixman_region32_t input; + + /* Surface damage. */ + pixman_region32_t surface; + + /* The currently attached buffer. */ + ExtBuffer *buffer; + + /* What part of this state has been changed. */ + int pending; + + /* The scale of this buffer. */ + int buffer_scale; + + /* List of frame callbacks. */ + FrameCallback frame_callbacks; + + /* Attachment position. */ + int x, y; +}; + +typedef enum _ClientDataType ClientDataType; +typedef struct _Surface Surface; +typedef struct _Role Role; +typedef struct _RoleFuncs RoleFuncs; +typedef struct _CommitCallback CommitCallback; +typedef struct _UnmapCallback UnmapCallback; +typedef struct _DestroyCallback DestroyCallback; + +enum _ClientDataType + { + SubsurfaceData, + MaxClientData, + }; + +struct _DestroyCallback +{ + /* The next and last destroy callbacks in this list. */ + DestroyCallback *next, *last; + + /* Function called when the surface is destroyed. */ + void (*destroy_func) (void *data); + + /* Data for the surface. */ + void *data; +}; + +struct _UnmapCallback +{ + /* The next and last callbacks in this list. */ + UnmapCallback *next, *last; + + /* Function called when the surface is unmapped. */ + void (*unmap) (void *data); + + /* Data for the surface. */ + void *data; +}; + +struct _CommitCallback +{ + /* Function called when the surface is committed, but before any + role commit function. */ + void (*commit) (Surface *, void *); + + /* Data that callback is called with. */ + void *data; + + /* The next and last commit callbacks in this list. */ + CommitCallback *next, *last; +}; + +struct _Surface +{ + /* The view associated with this surface. */ + View *view; + + /* The view used to store subsurfaces below this surface. + No buffer is ever attached; this is purely a container. */ + View *under; + + /* The resource corresponding to this surface. */ + struct wl_resource *resource; + + /* The role corresponding to this surface. */ + Role *role; + + /* The kind of role allowed for this surface. */ + RoleType role_type; + + /* The state that's pending a commit. */ + State pending_state; + + /* The state that's currently in use. */ + State current_state; + + /* Any state cached for application after early_commit returns + False. */ + State cached_state; + + /* List of subsurfaces. */ + XLList *subsurfaces; + + /* Array of "client data". */ + void *client_data[MaxClientData]; + + /* List of functions for freeing "client data". */ + void (*free_client_data[MaxClientData]) (void *); + + /* List of commit callbacks. */ + CommitCallback commit_callbacks; + + /* List of destroy callbacks. */ + DestroyCallback destroy_callbacks; + + /* List of unmap callbacks. */ + UnmapCallback unmap_callbacks; + + /* The outputs this surface is known to be on. */ + RROutput *outputs; + + /* The number of outputs this surface is known to be on. */ + int n_outputs; + + /* Bounds inside which the surface output need not be + recomputed. */ + pixman_region32_t output_region; + + /* The next and last surfaces in this list. */ + Surface *next, *last; + + /* The key for the window scalling factor callback. */ + void *scale_callback_key; + + /* X, Y of the last coordinates that were used to update this + surface's entered outputs. */ + int output_x, output_y; +}; + +struct _RoleFuncs +{ + /* These are mandatory. */ + Bool (*setup) (Surface *, Role *); + void (*teardown) (Surface *, Role *); + void (*commit) (Surface *, Role *); + void (*release_buffer) (Surface *, Role *, ExtBuffer *); + + /* These are optional. */ + Bool (*early_commit) (Surface *, Role *); + Bool (*subframe) (Surface *, Role *); + void (*end_subframe) (Surface *, Role *); + Window (*get_window) (Surface *, Role *); + void (*get_resize_dimensions) (Surface *, Role *, int *, int *); + void (*post_resize) (Surface *, Role *, int, int, int, int); + void (*move_by) (Surface *, Role *, int, int); + void (*rescale) (Surface *, Role *); + void (*note_desync_child) (Surface *, Role *); + void (*note_child_synced) (Surface *, Role *); +}; + +struct _Role +{ + /* Various callbacks for the role function. */ + RoleFuncs funcs; + + /* The struct wl_resource backing this role. */ + struct wl_resource *resource; + + /* Surface attached to this role. */ + Surface *surface; +}; + +extern Surface all_surfaces; + +extern void XLCreateSurface (struct wl_client *, + struct wl_resource *, + uint32_t); +extern void XLCommitSurface (Surface *, Bool); +extern void XLInitSurfaces (void); +extern Bool XLSurfaceAttachRole (Surface *, Role *); +extern void XLSurfaceReleaseRole (Surface *, Role *); + +extern void XLDefaultCommit (Surface *); + +extern void XLStateAttachBuffer (State *, ExtBuffer *); +extern void XLStateDetachBuffer (State *); +extern void XLSurfaceRunFrameCallbacks (Surface *, struct timespec); +extern CommitCallback *XLSurfaceRunAtCommit (Surface *, + void (*) (Surface *, void *), + void *); +extern void XLSurfaceCancelCommitCallback (CommitCallback *); +extern UnmapCallback *XLSurfaceRunAtUnmap (Surface *, void (*) (void *), + void *); +extern void XLSurfaceCancelUnmapCallback (UnmapCallback *); +extern DestroyCallback *XLSurfaceRunOnFree (Surface *, void (*) (void *), + void *); +extern void XLSurfaceCancelRunOnFree (DestroyCallback *); +extern void *XLSurfaceGetClientData (Surface *, ClientDataType, + size_t, void (*) (void *)); +extern Bool XLSurfaceGetResizeDimensions (Surface *, int *, int *); +extern void XLSurfacePostResize (Surface *, int, int, int, int); +extern void XLSurfaceMoveBy (Surface *, int, int); +extern Window XLWindowFromSurface (Surface *); +extern void XLUpdateSurfaceOutputs (Surface *, int, int, int, int); + +/* Defined in output.c. */ + +extern int global_scale_factor; + +extern void XLInitRROutputs (void); +extern Bool XLHandleOneXEventForOutputs (XEvent *); +extern void XLOutputGetMinRefresh (struct timespec *); +extern Bool XLGetOutputRectAt (int, int, int *, int *, int *, int *); +extern void *XLAddScaleChangeCallback (void *, void (*) (void *, int)); +extern void XLRemoveScaleChangeCallback (void *); +extern void XLClearOutputs (Surface *); + +/* Defined in atoms.c. */ + +extern 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; + +/* This is automatically generated by mime4.awk. */ +extern Atom DirectTransferAtoms; + +extern Atom InternAtom (const char *); +extern void ProvideAtom (const char *, Atom); + +extern void XLInitAtoms (void); + +/* Defined in xdg_wm.c. */ + +extern void XLInitXdgWM (void); + +/* Defined in frame_clock.c. */ + +typedef struct _FrameClock FrameClock; + +extern FrameClock *XLMakeFrameClockForWindow (Window); +extern Bool XLFrameClockFrameInProgress (FrameClock *); +extern void XLFrameClockAfterFrame (FrameClock *, void (*) (FrameClock *, + void *), + void *); +extern void XLFrameClockHandleFrameEvent (FrameClock *, XEvent *); +extern void XLFrameClockStartFrame (FrameClock *, Bool); +extern void XLFrameClockEndFrame (FrameClock *); +extern Bool XLFrameClockIsFrozen (FrameClock *); +extern Bool XLFrameClockCanBatch (FrameClock *); +extern Bool XLFrameClockNeedConfigure (FrameClock *); +extern void XLFrameClockFreeze (FrameClock *); +extern void XLFrameClockUnfreeze (FrameClock *); +extern void XLFreeFrameClock (FrameClock *); +extern Bool XLFrameClockSyncSupported (void); +extern Bool XLFrameClockCanRunFrame (FrameClock *); +extern void XLFrameClockSetPredictRefresh (FrameClock *); +extern void XLFrameClockDisablePredictRefresh (FrameClock *); +extern void XLFrameClockSetFreezeCallback (FrameClock *, void (*) (void *), + void *); +extern void *XLAddCursorClockCallback (void (*) (void *, struct timespec), + void *); +extern void XLStopCursorClockCallback (void *); +extern void XLStartCursorClock (void); +extern void XLStopCursorClock (void); +extern void XLInitFrameClock (void); + +/* Defined in xdg_surface.c. */ + +typedef struct _XdgRoleImplementation XdgRoleImplementation; +typedef struct _XdgRoleImplementationFuncs XdgRoleImplementationFuncs; +typedef enum _XdgRoleImplementationType XdgRoleImplementationType; + +enum _XdgRoleImplementationType + { + TypeUnknown, + TypeToplevel, + TypePopup, + }; + +struct _XdgRoleImplementationFuncs +{ + void (*attach) (Role *, XdgRoleImplementation *); + void (*commit) (Role *, Surface *, XdgRoleImplementation *); + void (*detach) (Role *, XdgRoleImplementation *); + void (*ack_configure) (Role *, XdgRoleImplementation *, uint32_t); + void (*note_size) (Role *, XdgRoleImplementation *, int, int); + void (*note_window_pre_resize) (Role *, XdgRoleImplementation *, int, int); + void (*note_window_resized) (Role *, XdgRoleImplementation *, int, int); + void (*handle_geometry_change) (Role *, XdgRoleImplementation *); + void (*post_resize) (Role *, XdgRoleImplementation *, int, int, int, int); + void (*commit_inside_frame) (Role *, XdgRoleImplementation *); +}; + +struct _XdgRoleImplementation +{ + /* Various functions implementing the logic behind this role. */ + XdgRoleImplementationFuncs funcs; +}; + +extern unsigned long border_pixel; +extern int shape_base; + +extern Bool XLHandleXEventForXdgSurfaces (XEvent *); + +extern void XLInitXdgSurfaces (void); +extern void XLGetXdgSurface (struct wl_client *, struct wl_resource *, + uint32_t, struct wl_resource *); +extern void XLXdgRoleAttachImplementation (Role *, XdgRoleImplementation *); +extern void XLXdgRoleDetachImplementation (Role *, XdgRoleImplementation *); +extern void XLXdgRoleSendConfigure (Role *, uint32_t); +extern void XLXdgRoleCalcNewWindowSize (Role *, int, int, int *, int *); +extern int XLXdgRoleGetWidth (Role *); +extern int XLXdgRoleGetHeight (Role *); +extern void XLXdgRoleSetBoundsSize (Role *, int, int); +extern void XLXdgRoleGetCurrentGeometry (Role *, int *, int *, int *, int *); +extern void XLXdgRoleNoteConfigure (Role *, XEvent *); +extern void XLRetainXdgRole (Role *); +extern void XLReleaseXdgRole (Role *); +extern void XLXdgRoleCurrentRootPosition (Role *, int *, int *); +extern Bool XLXdgRoleInputRegionContains (Role *, int, int); +extern void XLXdgRoleResizeForMap (Role *); +extern void *XLXdgRoleRunOnReconstrain (Role *, void (*) (void *, XEvent *), + void (*) (void *), void *); +extern void XLXdgRoleCancelReconstrainCallback (void *); +extern void XLXdgRoleReconstrain (Role *, XEvent *); +extern void XLXdgRoleMoveBy (Role *, int, int); + +extern Window XLWindowFromXdgRole (Role *); +extern Subcompositor *XLSubcompositorFromXdgRole (Role *); +extern XdgRoleImplementationType XLTypeOfXdgRole (Role *); +extern XdgRoleImplementation *XLImplementationOfXdgRole (Role *); +extern FrameClock *XLXdgRoleGetFrameClock (Role *); +extern XdgRoleImplementation *XLLookUpXdgToplevel (Window); +extern XdgRoleImplementation *XLLookUpXdgPopup (Window); + +/* Defined in positioner.c. */ + +typedef struct _Positioner Positioner; + +extern void XLCreateXdgPositioner (struct wl_client *, struct wl_resource *, + uint32_t); +extern void XLPositionerCalculateGeometry (Positioner *, Role *, int *, int *, + int *, int *); +extern void XLRetainPositioner (Positioner *); +extern void XLReleasePositioner (Positioner *); +extern Bool XLPositionerIsReactive (Positioner *); + +/* Defined in xdg_toplevel.c. */ + +extern void XLGetXdgToplevel (struct wl_client *, struct wl_resource *, + uint32_t); +extern Bool XLHandleXEventForXdgToplevels (XEvent *); +extern Bool XLIsXdgToplevel (Window); +extern void XLInitXdgToplevels (void); + +/* Defined in xdg_popup.c. */ + +extern void XLGetXdgPopup (struct wl_client *, struct wl_resource *, + uint32_t, struct wl_resource *, + struct wl_resource *); +extern Bool XLHandleXEventForXdgPopups (XEvent *); +extern Bool XLHandleButtonForXdgPopups (Seat *, Surface *); +extern void XLInitPopups (void); + +/* Defined in xerror.c. */ + +extern void InitXErrors (void); +extern void CatchXErrors (void); +extern Bool UncatchXErrors (XErrorEvent *); + +/* Defined in ewmh.c. */ + +extern Bool XLWmSupportsHint (Atom); + +/* Defined in timer.c. */ + +typedef struct _Timer Timer; + +extern Timer *AddTimer (void (*) (Timer *, void *, + struct timespec), + void *, struct timespec); +extern Timer *AddTimerWithBaseTime (void (*) (Timer *, void *, + struct timespec), + void *, struct timespec, + struct timespec); + +extern void RemoveTimer (Timer *); +extern void RetimeTimer (Timer *); +extern struct timespec TimerCheck (void); + +extern struct timespec CurrentTimespec (void); +extern struct timespec MakeTimespec (time_t, long int); +extern int TimespecCmp (struct timespec, struct timespec); +extern struct timespec TimespecAdd (struct timespec, struct timespec); +extern struct timespec TimespecSub (struct timespec, struct timespec); + +extern void XLInitTimers (void); + +/* Defined in subsurface.c. */ + +extern void XLSubsurfaceParentDestroyed (Role *); +extern void XLSubsurfaceHandleParentCommit (Surface *); +extern void XLInitSubsurfaces (void); +extern void XLUpdateOutputsForChildren (Surface *, int, int); +extern void XLUpdateDesynchronousChildren (Surface *, int *); + +/* Defined in data_device.c. */ + +typedef struct _DataDevice DataDevice; +typedef struct _DataSource DataSource; +typedef struct _CreateOfferFuncs CreateOfferFuncs; +typedef struct _DndOfferFuncs DndOfferFuncs; + +typedef struct wl_resource *(*CreateOfferFunc) (struct wl_client *, Time); +typedef void (*SendDataFunc) (struct wl_resource *, Time); + +struct _CreateOfferFuncs +{ + CreateOfferFunc create_offer; + SendDataFunc send_offers; +}; + +struct _DndOfferFuncs +{ + struct wl_resource *(*create) (struct wl_client *, int); + void (*send_offers) (struct wl_resource *); +}; + +extern void XLInitDataDevice (void); +extern void XLRetainDataDevice (DataDevice *); +extern void XLReleaseDataDevice (DataDevice *); +extern void XLDataDeviceClearSeat (DataDevice *); +extern void XLDataDeviceHandleFocusChange (DataDevice *); +extern void XLSetForeignSelection (Time, CreateOfferFuncs); +extern void XLClearForeignSelection (Time); +extern int XLDataSourceTargetCount (DataSource *); +extern void XLDataSourceGetTargets (DataSource *, Atom *); +extern struct wl_resource *XLResourceFromDataSource (DataSource *); +extern Bool XLDataSourceHasAtomTarget (DataSource *, Atom); +extern Bool XLDataSourceHasTarget (DataSource *, const char *); +extern void XLDataSourceAttachDragDevice (DataSource *, DataDevice *); +extern void *XLDataSourceAddDestroyCallback (DataSource *, void (*) (void *), + void *); +extern void XLDataSourceCancelDestroyCallback (void *); +extern void XLDataSourceSendDropPerformed (DataSource *); +extern void XLDataSourceSendDropCancelled (DataSource *); +extern Bool XLDataSourceCanDrop (DataSource *); +extern XLList *XLDataSourceGetMimeTypeList (DataSource *); +extern void XLDataSourceUpdateDeviceActions (DataSource *); +extern uint32_t XLDataSourceGetSupportedActions (DataSource *); +extern void XLDataDeviceMakeOffers (Seat *, DndOfferFuncs, Surface *, + int, int); +extern void XLDataDeviceSendEnter (Seat *, Surface *, double, double, + DataSource *); +extern void XLDataDeviceSendMotion (Seat *, Surface *, double, double, Time); +extern void XLDataDeviceSendLeave (Seat *, Surface *, DataSource *); +extern void XLDataDeviceSendDrop (Seat *, Surface *); + +/* Defined in seat.c. */ + +extern int xi2_opcode; +extern int xi_first_event; +extern int xi_first_error; + +extern XLList *live_seats; + +extern Bool XLHandleOneXEventForSeats (XEvent *); +extern Window XLGetGEWindowForSeats (XEvent *); +extern void XLDispatchGEForSeats (XEvent *, Surface *, + Subcompositor *); +extern void XLSelectStandardEvents (Window); +extern void XLInitSeats (void); +extern Bool XLResizeToplevel (Seat *, Surface *, uint32_t, uint32_t); +extern void XLMoveToplevel (Seat *, Surface *, uint32_t); +extern Bool XLSeatExplicitlyGrabSurface (Seat *, Surface *, uint32_t); + +extern void *XLSeatRunAfterResize (Seat *, void (*) (void *, void *), + void *); +extern void XLSeatCancelResizeCallback (void *); +extern void *XLSeatRunOnDestroy (Seat *, void (*) (void *), void *); +extern void XLSeatCancelDestroyListener (void *); +extern DataDevice *XLSeatGetDataDevice (Seat *); +extern void XLSeatSetDataDevice (Seat *, DataDevice *); +extern Bool XLSeatIsInert (Seat *); +extern Bool XLSeatIsClientFocused (Seat *, struct wl_client *); +extern void XLSeatShowWindowMenu (Seat *, Surface *, int, int); +extern Time XLSeatGetLastUserTime (Seat *); +extern void XLSeatBeginDrag (Seat *, DataSource *, Surface *, + Surface *, uint32_t); +extern DataSource *XLSeatGetDragDataSource (Seat *); +extern void *XLSeatAddModifierCallback (Seat *, void (*) (unsigned int, void *), + void *); +extern void XLSeatRemoveModifierCallback (void *); +extern unsigned int XLSeatGetEffectiveModifiers (Seat *); + +extern Cursor InitDefaultCursor (void); + +/* Defined in dmabuf.c. */ + +extern void XLInitDmabuf (void); +extern Bool XLHandleErrorForDmabuf (XErrorEvent *); +extern Bool XLHandleOneXEventForDmabuf (XEvent *); + +/* Defined in select.c. */ + +typedef struct _ReadTransfer ReadTransfer; +typedef struct _WriteTransfer WriteTransfer; + +typedef enum _ReadStatus ReadStatus; + +typedef ReadStatus (*GetDataFunc) (WriteTransfer *, unsigned char *, + ptrdiff_t, ptrdiff_t *); + +enum _ReadStatus + { + ReadOk, + EndOfFile, + NeedBiggerBuffer, + }; + +extern Window selection_transfer_window; + +extern void FinishTransfers (void); +extern long SelectionQuantum (void); +extern Bool HookSelectionEvent (XEvent *); +extern void InitSelections (void); +extern ReadTransfer *ConvertSelectionFuncs (Atom, Atom, Time, void *, + void (*) (ReadTransfer *, Atom, + int), + void (*) (ReadTransfer *, Atom, + int, ptrdiff_t), + Bool (*) (ReadTransfer *, Bool)); +extern void *GetTransferData (ReadTransfer *); +extern Time GetTransferTime (ReadTransfer *); + +extern unsigned char *ReadChunk (ReadTransfer *, int, ptrdiff_t *, + ptrdiff_t *); +extern void SkipChunk (ReadTransfer *); +extern void CompleteDelayedTransfer (ReadTransfer *); + +extern Bool OwnSelection (Time, Atom, GetDataFunc (*) (WriteTransfer *, + Atom, Atom *), + Atom *, int); +extern void DisownSelection (Atom); + +extern void SetWriteTransferData (WriteTransfer *, void *); +extern void *GetWriteTransferData (WriteTransfer *); +extern void StartReading (WriteTransfer *); + +/* Defined in xdata.c. */ + +extern Bool XLHandleOneXEventForXData (XEvent *); +extern void XLNoteSourceDestroyed (DataSource *); +extern Bool XLNoteLocalSelection (Seat *, DataSource *); +extern void XLReceiveDataFromSelection (Time, Atom, Atom, int); +extern Bool XLOwnDragSelection (Time, DataSource *); + +extern void XLInitXData (void); + +/* Defined in xsettings.c. */ + +extern void XLInitXSettings (void); +extern Bool XLHandleOneXEventForXSettings (XEvent *); +extern void XLListenToIntegerSetting (const char *, void (*) (int)); + +/* Defined in dnd.c. */ + +extern void XLDndWriteAwarenessProperty (Window); +extern Bool XLDndFilterClientMessage (Surface *, XEvent *); + +extern void XLHandleOneXEventForDnd (XEvent *); + +extern void XLDoDragLeave (Seat *); +extern void XLDoDragMotion (Seat *, double, double); +extern void XLDoDragFinish (Seat *); +extern Bool XLDoDragDrop (Seat *); + +/* Defined in icon_surface.c. */ + +typedef struct _IconSurface IconSurface; + +extern IconSurface *XLGetIconSurface (Surface *); +extern Bool XLHandleOneXEventForIconSurfaces (XEvent *); +extern void XLMoveIconSurface (IconSurface *, int, int); +extern void XLInitIconSurfaces (void); +extern void XLReleaseIconSurface (IconSurface *); +extern Bool XLIsWindowIconSurface (Window); + +/* Utility functions that don't belong in a specific file. */ + +#define ArrayElements(arr) (sizeof (arr) / sizeof (arr)[0]) + +#define BoxStartX(box) (MIN ((box).x1, (box).x2)) +#define BoxEndX(box) (MAX ((box).x1, (box).x2) - 1) +#define BoxStartY(box) (MIN ((box).y1, (box).y2)) +#define BoxEndY(box) (MAX ((box).y1, (box).y2) - 1) + +#define BoxWidth(box) (BoxEndX (box) - BoxStartX (box) + 1) +#define BoxHeight(box) (BoxEndY (box) - BoxStartY (box) + 1) + +#define SafeCmp(n1, n2) (((n1) > (n2)) - ((n1) < (n2))) + +/* Taken from intprops.h in gnulib. */ + +/* True if the real type T is signed. */ +#define TypeIsSigned(t) (! ((t) 0 < (t) -1)) + +/* The width in bits of the integer type or expression T. + Do not evaluate T. T must not be a bit-field expression. + Padding bits are not supported; this is checked at compile-time below. */ +#define TypeWidth(t) (sizeof (t) * CHAR_BIT) + +/* The maximum and minimum values for the integer type T. */ +#define TypeMinimum(t) ((t) ~ TypeMaximum (t)) +#define TypeMaximum(t) \ + ((t) (! TypeIsSigned (t) \ + ? (t) -1 \ + : ((((t) 1 << (TypeWidth (t) - 2)) - 1) * 2 + 1))) + +#define IntAddWrapv(a, b, r) __builtin_add_overflow (a, b, r) +#define IntSubtractWrapv(a, b, r) __builtin_sub_overflow (a, b, r) +#define IntMultiplyWrapv(a, b, r) __builtin_mul_overflow (a, b, r) + +#define ConfigureWidth(event) ((event)->xconfigure.width / global_scale_factor) +#define ConfigureHeight(event) ((event)->xconfigure.height / global_scale_factor) + diff --git a/data_device.c b/data_device.c new file mode 100644 index 0000000..ceaa13b --- /dev/null +++ b/data_device.c @@ -0,0 +1,1587 @@ +/* Wayland compositor running on top of an X server. + +Copyright (C) 2022 to various contributors. + +This file is part of 12to11. + +12to11 is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +12to11 is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with 12to11. If not, see . */ + +#include +#include + +#include "compositor.h" + +typedef struct _DataSource DataSource; +typedef struct _DataDeviceReference DataDeviceReference; +typedef struct _DataOffer DataOffer; +typedef struct _DataDestroyCallback DataDestroyCallback; + +enum + { + IsDragAndDrop = 1, + IsMimeTypeAccepted = (1 << 2), + IsActionSent = (1 << 3), + IsFinished = (1 << 4), + }; + +enum + { + ActionsSet = 1, + ActionsSent = (1 << 2), + TypeAccepted = (1 << 3), + Version3Supported = (1 << 4), + }; + +struct _DataDestroyCallback +{ + /* The next and last destroy callbacks in this list. */ + DataDestroyCallback *next, *last; + + /* Function called when the surface is destroyed. */ + void (*destroy_func) (void *data); + + /* Data for the surface. */ + void *data; +}; + +struct _DataOffer +{ + /* The next data offer object in this list. */ + DataOffer *next, *last; + + /* The DataSource corresponding to this data offer. */ + DataSource *source; + + /* Some state and flags. */ + int state; + + /* The last action sent. -1 if invalid. */ + int last_action; + + /* The struct wl_resource of this data offer. */ + struct wl_resource *resource; + + /* The serial of the data device entry in response to which this + object was created. */ + uint32_t dnd_serial; +}; + +struct _DataDeviceReference +{ + /* The next and last data device references. */ + DataDeviceReference *next, *last; + + /* The associated data device. */ + DataDevice *device; + + /* The associated struct wl_resource. */ + struct wl_resource *resource; +}; + +struct _DataSource +{ + /* List of const char *, which are the MIME types offered by this + data source. */ + XLList *mime_types; + + /* List of atoms corresponding to those MIME types, in the same + order. */ + XIDList *atom_types; + + /* Number of corresponding MIME types. */ + int n_mime_types; + + /* The resource associated with this data source. */ + struct wl_resource *resource; + + /* List of data offers associated with this data source. */ + DataOffer offers; + + /* Some flags associated with this data source. */ + int state; + + /* Drag-and-drop actions supported by this data source. */ + uint32_t actions; + + /* The data device from which this data source is being dragged. */ + DataDevice *drag_device; + + /* The destroy callback associated with that data device. */ + DataDestroyCallback *drag_device_callback; + + /* List of destroy callbacks. */ + DataDestroyCallback destroy_callbacks; +}; + +struct _DataDevice +{ + /* The associated seat. */ + Seat *seat; + + /* The number of references to this data device. */ + int refcount; + + /* Linked list of references to this data device. */ + DataDeviceReference references; + + /* The drag and drop operation state. supported_actions is the mask + consisting of actions supported by the target. */ + uint32_t supported_actions; + + /* This is the mask containing actions preferred by the target. */ + uint32_t preferred_action; + + /* This is the "serial" of the last enter event. */ + uint32_t dnd_serial; + + /* List of destroy callbacks. */ + DataDestroyCallback destroy_callbacks; +}; + +/* The data device manager global. */ +static struct wl_global *global_data_device_manager; + +/* The current selection. */ +static DataSource *current_selection_data; + +/* A sentinel value that means a foreign selection is in use. */ +static DataSource foreign_selection_key; + +/* Functions to call to obtain wl_data_offer resources and send + associated data for foreign selections. */ +static CreateOfferFuncs foreign_selection_functions; + +/* Time the foreign selection was changed. */ +static Time foreign_selection_time; + +/* When it changed. */ +static uint32_t last_selection_change_serial; + +/* Generic destroy callback implementation. */ + + +static DataDestroyCallback * +AddDestroyCallbackAfter (DataDestroyCallback *start, + void (*destroy_func) (void *), + void *data) +{ + DataDestroyCallback *callback; + + callback = XLMalloc (sizeof *callback); + callback->last = start; + callback->next = start->next; + + start->next->last = callback; + start->next = callback; + + callback->destroy_func = destroy_func; + callback->data = data; + + return callback; +} + +static void +FreeDestroyCallbacks (DataDestroyCallback *start) +{ + DataDestroyCallback *next, *last; + + next = start->next; + + while (next != start) + { + last = next; + next = last->next; + + last->destroy_func (last->data); + XLFree (last); + } +} + +static void +CancelDestroyCallback (DataDestroyCallback *start) +{ + start->next->last = start->last; + start->last->next = start->next; + + XLFree (start); +} + +/* Data offer implementation. */ + +static void +FreeDataOffer (DataOffer *offer) +{ + /* Mark this offer as invalid by setting the resource's user_data to + NULL. */ + if (offer->resource) + wl_resource_set_user_data (offer->resource, NULL); + + /* Unlink the offer. */ + offer->last->next = offer->next; + offer->next->last = offer->last; + + /* Free the offer. */ + XLFree (offer); +} + +static void +Accept (struct wl_client *client, struct wl_resource *resource, + uint32_t serial, const char *mime_type) +{ + DataOffer *offer; + + offer = wl_resource_get_user_data (resource); + + if (!offer) + return; + + wl_data_source_send_target (offer->source->resource, + mime_type); + + if (mime_type) + { + offer->state |= IsMimeTypeAccepted; + offer->source->state |= TypeAccepted; + } + else + { + offer->state &= ~IsMimeTypeAccepted; + offer->source->state &= ~TypeAccepted; + } +} + +static void +Receive (struct wl_client *client, struct wl_resource *resource, + const char *mime_type, int fd) +{ + DataOffer *offer; + + offer = wl_resource_get_user_data (resource); + + if (!offer) + { +#ifdef DEBUG + fprintf (stderr, "wl_client@%p is trying to receive from an outdated" + " wl_data_offer@%u\n", client, wl_resource_get_id (resource)); +#endif + close (fd); + return; + } + + if (offer->state & IsFinished) + { + wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_FINISH, + "trying to receive from finished offer"); + return; + } + +#ifdef DEBUG + fprintf (stderr, "wl_client@%p is now receiving from wl_data_offer@%u\n", + client, wl_resource_get_id (resource)); +#endif + + wl_data_source_send_send (offer->source->resource, mime_type, fd); + close (fd); +} + +static void +DestroyDataOffer (struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static void +Finish (struct wl_client *client, struct wl_resource *resource) +{ + DataOffer *offer; + + offer = wl_resource_get_user_data (resource); + + if (!offer) + /* The data source was destroyed... */ + return; + + if (!(offer->state & IsDragAndDrop)) + { + wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_FINISH, + "trying to finish non-drag-and-drop data offer"); + return; + } + + /* The serial or drag device is not tested here, since CancelDrag + detaches the attached drag-and-drop data device before finish is + called in response to a drop. */ + + if (!(offer->state & IsMimeTypeAccepted)) + { + wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_FINISH, + "trying to finish drag and drop offer with nothing" + " accepted"); + return; + } + + if (!(offer->state & IsActionSent)) + { + wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_FINISH, + "trying to finish drag and drop offer with no action" + " sent"); + return; + } + + if (offer->state & IsFinished) + { + wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_FINISH, + "trying to finish drag and drop offer which was" + " already finished"); + return; + } + + offer->state |= IsFinished; + + if (wl_resource_get_version (offer->source->resource) < 3) + return; + + if (offer->source->state & Version3Supported + && (!(offer->source->state & ActionsSent) + || !(offer->source->state & TypeAccepted))) + { + /* The drag and drop operation is no longer eligible for + successful completion. Cancel it and return. */ + wl_data_source_send_cancelled (offer->source->resource); + return; + } + + wl_data_source_send_dnd_finished (offer->source->resource); +} + +static void +UpdateDeviceActions (DataDevice *device, DataSource *source) +{ + uint32_t action, intersection; + unsigned int modifiers; + DataOffer *offer; + + modifiers = XLSeatGetEffectiveModifiers (device->seat); + + /* How actions are resolved. First, the preferred action is matched + against the actions supported by the source, and used if it is + supported. If it is not supported by the data source, then the + following actions are tried in order: copy, move, ask, none. If + none of those are supported, then no action is used. The + preferred action is ignored if the Shift key is held down, in + which case Move is also preferred to copy. */ + + if (!(modifiers & ShiftMask) + && device->preferred_action & source->actions) + action = device->preferred_action; + else + { + intersection = (device->supported_actions + & source->actions); + + /* Now, try the following actions in order, preferring move to + copy if Shift is held down. */ + + if (modifiers & ShiftMask + && intersection & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + action = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + else if (intersection & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) + action = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + else if (intersection & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + action = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + else if (intersection & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) + action = WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; + else + action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + } + + /* Send the action to all attached data offers. */ + offer = source->offers.next; + while (offer != &source->offers) + { + if (!(offer->state & IsDragAndDrop)) + goto next; + + if (offer->dnd_serial < device->dnd_serial) + goto next; + + if (offer->last_action != action) + wl_data_offer_send_action (offer->resource, action); + + offer->last_action = action; + offer->state |= IsActionSent; + + next: + offer = offer->next; + } + + /* Set flags on the source indicating that an action has been set, + unless action is 0, in which case clear it. */ + if (action) + source->state |= ActionsSent; + else + source->state &= ~ActionsSent; + + /* Send the new action to the data source. */ + if (wl_resource_get_version (source->resource) >= 3) + wl_data_source_send_action (source->resource, action); +} + +static void +DataOfferSetActions (struct wl_client *client, struct wl_resource *resource, + uint32_t dnd_actions, uint32_t preferred_action) +{ + DataOffer *offer; + + offer = wl_resource_get_user_data (resource); + + if (!offer) + /* The data source was destroyed... */ + return; + + if (!(offer->state & IsDragAndDrop)) + { + wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_FINISH, + "trying to finish non-drag-and-drop data offer"); + return; + } + + if (!offer->source->drag_device) + /* The data device has been destroyed. */ + return; + + if (offer->dnd_serial < offer->source->drag_device->dnd_serial) + /* The data offer is out of data and effectively inert. */ + return; + + if (dnd_actions & ~(WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY + | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE + | WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)) + { + wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_ACTION, + "invalid action or action mask among: %u", + dnd_actions); + return; + } + + if (preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK + && preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE + && preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY + && preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE) + { + wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_ACTION, + "invalid action not in enum: %u", + preferred_action); + return; + } + + offer->source->drag_device->supported_actions = dnd_actions; + offer->source->drag_device->preferred_action = preferred_action; + UpdateDeviceActions (offer->source->drag_device, offer->source); +} + +static const struct wl_data_offer_interface wl_data_offer_impl = + { + .accept = Accept, + .receive = Receive, + .destroy = DestroyDataOffer, + .finish = Finish, + .set_actions = DataOfferSetActions, + }; + +static void +HandleOfferResourceDestroy (struct wl_resource *resource) +{ + DataOffer *offer; + + offer = wl_resource_get_user_data (resource); + + if (!offer) + /* The offer was made inert. */ + return; + + offer->resource = NULL; + FreeDataOffer (offer); +} + +static struct wl_resource * +AddDataOffer (struct wl_client *client, DataSource *source) +{ + DataOffer *offer; + struct wl_resource *resource; + + resource = wl_resource_create (client, &wl_data_offer_interface, + /* 0 means to allocate a new resource + ID server-side. */ + 3, 0); + + if (!resource) + return NULL; + + offer = XLCalloc (1, sizeof *offer); + offer->next = source->offers.next; + offer->last = &source->offers; + + source->offers.next->last = offer; + source->offers.next = offer; + + offer->resource = resource; + offer->source = source; + + wl_resource_set_implementation (resource, &wl_data_offer_impl, + offer, HandleOfferResourceDestroy); + + return resource; +} + + +/* Data device and source implementations. */ + +/* Forward declaration. */ + +static void SendDataOffers (void); + +static void +HandleDragDeviceDestroyed (void *data) +{ + DataSource *data_source; + + data_source = data; + data_source->drag_device = NULL; + data_source->drag_device_callback = NULL; + + if (wl_resource_get_version (data_source->resource) >= 3) + wl_data_source_send_cancelled (data_source->resource); +} + +static void +HandleSourceResourceDestroy (struct wl_resource *resource) +{ + DataSource *data_source; + DataOffer *offer, *last; + + data_source = wl_resource_get_user_data (resource); + + /* If data_source is currently the selection, remove it. */ + if (data_source == current_selection_data) + { + current_selection_data = NULL; + + /* Send the updated data to clients. */ + SendDataOffers (); + } + + /* Tell the X selection code that this data source has been + destroyed. */ + XLNoteSourceDestroyed (data_source); + + XLListFree (data_source->mime_types, XLFree); + XIDListFree (data_source->atom_types, NULL); + + /* Make inert and release all data offers on this data source. */ + + offer = data_source->offers.next; + + while (offer != &data_source->offers) + { + last = offer; + offer = offer->next; + + FreeDataOffer (last); + } + + /* Free the destroy callback for the data device. */ + if (data_source->drag_device_callback) + CancelDestroyCallback (data_source->drag_device_callback); + + /* Run all destroy callbacks for this data source. */ + FreeDestroyCallbacks (&data_source->destroy_callbacks); + + XLFree (data_source); +} + +static const char * +FindAtom (DataSource *source, Atom atom) +{ + XIDList *tem; + XLList *tem1; + + /* Find the MIME type corresponding to an atom in source. + Return NULL if the atom is not there. + + source->mime_types must be the same length as + source->atom_types. */ + + tem = source->atom_types; + tem1 = source->mime_types; + + for (; tem; tem = tem->next, tem1 = tem1->next) + { + if (tem->data == atom) + return tem1->data; + } + + return NULL; +} + +static void +Offer (struct wl_client *client, struct wl_resource *resource, + const char *mime_type) +{ + Atom atom; + DataSource *data_source; + DataOffer *offer; + + data_source = wl_resource_get_user_data (resource); + + /* It is more efficient to record both atoms and strings in the data + source, since its contents will be offered to X and Wayland + clients. */ + atom = InternAtom (mime_type); + + /* If the type was already offered, simply return. */ + if (FindAtom (data_source, atom)) + return; + + /* Otherwise, link the atom and the mime type onto the list + simultaneously. */ +#ifdef DEBUG + fprintf (stderr, "Offering: %s (X atom: %lu) from wl_data_source@%u\n", + mime_type, atom, wl_resource_get_id (resource)); +#endif + data_source->atom_types = XIDListPrepend (data_source->atom_types, atom); + data_source->mime_types = XLListPrepend (data_source->mime_types, + XLStrdup (mime_type)); + data_source->n_mime_types++; + + /* Send the new MIME type to any attached offers. */ + + offer = data_source->offers.next; + + while (offer != &data_source->offers) + { + wl_data_offer_send_offer (offer->resource, mime_type); + offer = offer->next; + } +} + +static void +SetActions (struct wl_client *client, struct wl_resource *resource, + uint32_t actions) +{ + DataSource *source; + + source = wl_resource_get_user_data (resource); + + if (source->state & ActionsSet) + { + wl_resource_post_error (resource, WL_DATA_SOURCE_ERROR_INVALID_SOURCE, + "actions already set on this offer or it has" + " already been used."); + return; + } + + if (actions & ~(WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY + | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE + | WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK)) + { + wl_resource_post_error (resource, WL_DATA_SOURCE_ERROR_INVALID_SOURCE, + "unknown actions specified (mask value %u)", + actions); + return; + } + + source->state |= ActionsSet; + source->actions = actions; +} + +static void +DestroySource (struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static const struct wl_data_source_interface wl_data_source_impl = + { + .offer = Offer, + .set_actions = SetActions, + .destroy = DestroySource, + }; + +static void +CreateDataSource (struct wl_client *client, struct wl_resource *resource, + uint32_t id) +{ + DataSource *source; + + source = XLSafeMalloc (sizeof *source); + + if (!source) + { + wl_resource_post_no_memory (resource); + return; + } + + memset (source, 0, sizeof *source); + source->resource = wl_resource_create (client, &wl_data_source_interface, + wl_resource_get_version (resource), + id); + + if (!source->resource) + { + wl_resource_post_no_memory (resource); + XLFree (source); + return; + } + + source->offers.next = &source->offers; + source->offers.last = &source->offers; + source->destroy_callbacks.next = &source->destroy_callbacks; + source->destroy_callbacks.last = &source->destroy_callbacks; + + wl_resource_set_implementation (source->resource, &wl_data_source_impl, + source, HandleSourceResourceDestroy); +} + +static void +UpdateSingleReferenceWithForeignOffer (struct wl_client *client, + DataDeviceReference *reference) +{ + struct wl_resource *resource; + Time time; + + time = foreign_selection_time; + resource = foreign_selection_functions.create_offer (client, time); + + if (!resource) + return; + + /* Make the data offer known to the client. */ + wl_data_device_send_data_offer (reference->resource, resource); + + /* Tell the foreign selection provider to send supported resources + to the client. */ + foreign_selection_functions.send_offers (resource, time); + + /* Finally, tell the client that the offer is a selection. */ + wl_data_device_send_selection (reference->resource, resource); +} + +static void +UpdateForSingleReference (DataDeviceReference *device) +{ + struct wl_resource *resource; + struct wl_client *client; + XLList *type; + + if (!current_selection_data) + { + wl_data_device_send_selection (device->resource, + NULL); + return; + } + + client = wl_resource_get_client (device->resource); + + if (current_selection_data == &foreign_selection_key) + { + /* This means a foreign selection is in use. */ + UpdateSingleReferenceWithForeignOffer (client, device); + + return; + } + + resource = AddDataOffer (client, current_selection_data); + + if (!resource) + return; + + /* First, introduce the data offer to the client. */ + wl_data_device_send_data_offer (device->resource, resource); + + /* Send all the offered MIME types. */ + + type = current_selection_data->mime_types; + + for (; type; type = type->next) + wl_data_offer_send_offer (resource, type->data); + + /* Finally, tell the client it is a selection. */ + wl_data_device_send_selection (device->resource, resource); +} + +static void +SendDataOffersForDevice (DataDevice *device) +{ + DataDeviceReference *reference; + struct wl_client *client; + + reference = device->references.next; + + while (reference != &device->references) + { + client = wl_resource_get_client (reference->resource); + + if (XLSeatIsClientFocused (device->seat, client)) + UpdateForSingleReference (reference); + + reference = reference->next; + } +} + +static void +SendDataOffers (void) +{ + XLList *seat; + DataDevice *device; + + for (seat = live_seats; seat; seat = seat->next) + { + device = XLSeatGetDataDevice (seat->data); + + if (device) + SendDataOffersForDevice (device); + } +} + +static void +DestroyReference (DataDeviceReference *reference) +{ + reference->next->last = reference->last; + reference->last->next = reference->next; + + XLFree (reference); +} + +static void +ReleaseReferences (DataDevice *device) +{ + DataDeviceReference *reference; + + reference = device->references.next; + + while (reference != &device->references) + { + reference->device = NULL; + reference = reference->next; + } +} + +static void +DestroyBacking (DataDevice *device) +{ + if (--device->refcount) + return; + + ReleaseReferences (device); + FreeDestroyCallbacks (&device->destroy_callbacks); + XLFree (device); +} + +static DataDevice * +GetDataDeviceInternal (Seat *seat) +{ + DataDevice *device; + + device = XLSeatGetDataDevice (seat); + + if (!device) + { + device = XLCalloc (1, sizeof *device); + device->seat = seat; + device->references.next = &device->references; + device->references.last = &device->references; + device->destroy_callbacks.next = &device->destroy_callbacks; + device->destroy_callbacks.last = &device->destroy_callbacks; + + XLSeatSetDataDevice (seat, device); + } + + return device; +} + +static DataDeviceReference * +AddReferenceTo (DataDevice *device, struct wl_resource *resource) +{ + DataDeviceReference *reference; + + reference = XLCalloc (1, sizeof *reference); + reference->next = device->references.next; + reference->last = &device->references; + reference->resource = resource; + + device->references.next->last = reference; + device->references.next = reference; + + reference->device = device; + + return reference; +} + +static void +StartDrag (struct wl_client *client, struct wl_resource *resource, + struct wl_resource *source_resource, + struct wl_resource *origin_resource, + struct wl_resource *icon_resource, uint32_t serial) +{ + DataDeviceReference *device; + Surface *icon, *origin; + DataSource *source; + + device = wl_resource_get_user_data (resource); + + if (!device->device || !device->device->seat) + /* This device is inert, since the seat has been deleted. */ + return; + + if (icon_resource) + icon = wl_resource_get_user_data (icon_resource); + else + icon = NULL; + + origin = wl_resource_get_user_data (origin_resource); + source = wl_resource_get_user_data (source_resource); + + if (source == current_selection_data) + { + /* The specification doesn't say anything about this! */ + + wl_resource_post_error (source_resource, + WL_DATA_SOURCE_ERROR_INVALID_SOURCE, + "trying to drag the selection"); + return; + } + + if (source->drag_device) + { + /* The specification doesn't say anything about this! */ + + wl_resource_post_error (source_resource, + WL_DATA_SOURCE_ERROR_INVALID_SOURCE, + "trying to drag a data source that is" + " already being dragged"); + return; + } + + /* If the icon surface isn't the right type, throw an error. */ + if (icon && icon->role_type != AnythingType + && icon->role_type != DndIconType) + { + wl_resource_post_error (resource, WL_DATA_DEVICE_ERROR_ROLE, + "the given surface already has/had" + " another role"); + return; + } + + /* Now make it impossible to set this source as the selection. */ + source->state |= ActionsSet; + + XLSeatBeginDrag (device->device->seat, source, origin, + icon, serial); +} + +static void +SetSelection (struct wl_client *client, struct wl_resource *resource, + struct wl_resource *source_resource, uint32_t serial) +{ + DataSource *source; + DataDeviceReference *device; + +#ifdef DEBUG + if (source_resource) + fprintf (stderr, "wl_client@%p is setting the selection to " + "wl_data_source@%u, at %u\n", + client, wl_resource_get_id (source_resource), serial); +#endif + + if (serial < last_selection_change_serial) + { +#ifdef DEBUG + fprintf (stderr, "wl_client@%p could not set the selection, " + "because the selection changed. (%u < %u)\n", + client, serial, last_selection_change_serial); +#endif + return; + } + + device = wl_resource_get_user_data (resource); + + if (!device->device || !device->device->seat) + /* This device is inert, since the seat has been deleted. */ + return; + + /* Set the last selection change serial. This enables us to avoid + race conditions caused by multiple clients simultaneously setting + the clipboard according to different events, by keeping track of + which event is newer. */ + last_selection_change_serial = serial; + + if (source_resource) + source = wl_resource_get_user_data (source_resource); + else + source = NULL; + + /* If the data source is destined for drag and drop, report an + error. */ + if (source && source->state & ActionsSet) + { + wl_resource_post_error (resource, WL_DATA_SOURCE_ERROR_INVALID_SOURCE, + "trying to set dnd source as the selection"); + return; + } + + /* Try to own the X selection. If it fails, refrain from changing + the current selection data. */ + if (!XLNoteLocalSelection (device->device->seat, source)) + return; + + if (current_selection_data && current_selection_data != source) + { + /* If the current selection data is already set, cancel it. */ + if (current_selection_data != &foreign_selection_key) + wl_data_source_send_cancelled (current_selection_data->resource); + } + + if (current_selection_data != source) + { + current_selection_data = source; + + /* Create data offer objects for the new selection data and send + it to clients. */ + SendDataOffers (); + } +} + +static void +Release (struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static const struct wl_data_device_interface wl_data_device_impl = + { + .start_drag = StartDrag, + .set_selection = SetSelection, + .release = Release, + }; + +static void +HandleDeviceResourceDestroy (struct wl_resource *resource) +{ + DataDeviceReference *reference; + + reference = wl_resource_get_user_data (resource); + DestroyReference (reference); +} + +static void +GetDataDevice (struct wl_client *client, struct wl_resource *resource, + uint32_t id, struct wl_resource *seat_resource) +{ + struct wl_resource *device_resource; + Seat *seat; + DataDevice *device; + DataDeviceReference *reference; + + device_resource = wl_resource_create (client, &wl_data_device_interface, + wl_resource_get_version (resource), + id); + + if (!device_resource) + { + wl_resource_post_no_memory (resource); + return; + } + + seat = wl_resource_get_user_data (seat_resource); + device = GetDataDeviceInternal (seat); + reference = AddReferenceTo (device, device_resource); + + wl_resource_set_implementation (device_resource, &wl_data_device_impl, + reference, HandleDeviceResourceDestroy); +} + +static struct wl_data_device_manager_interface wl_data_device_manager_impl = + { + .create_data_source = CreateDataSource, + .get_data_device = GetDataDevice, + }; + +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_data_device_manager_interface, + version, id); + + if (!resource) + { + wl_client_post_no_memory (client); + return; + } + + wl_resource_set_implementation (resource, + &wl_data_device_manager_impl, + NULL, NULL); +} + +void +XLInitDataDevice (void) +{ + global_data_device_manager + = wl_global_create (compositor.wl_display, + &wl_data_device_manager_interface, + 3, NULL, HandleBind); +} + +void +XLRetainDataDevice (DataDevice *device) +{ + device->refcount++; +} + +void +XLReleaseDataDevice (DataDevice *device) +{ + DestroyBacking (device); +} + +void +XLDataDeviceClearSeat (DataDevice *device) +{ + device->seat = NULL; +} + +void +XLDataDeviceHandleFocusChange (DataDevice *device) +{ + SendDataOffersForDevice (device); +} + +void +XLSetForeignSelection (Time time, CreateOfferFuncs functions) +{ + uint32_t serial; + + if (time < foreign_selection_time) + return; + + serial = wl_display_next_serial (compositor.wl_display); + + /* Use this serial to prevent clients from changing the selection + again until the next event is sent. */ + last_selection_change_serial = serial; + + foreign_selection_time = time; + foreign_selection_functions = functions; + + if (current_selection_data + && current_selection_data != &foreign_selection_key) + wl_data_source_send_cancelled (current_selection_data->resource); + + /* Use a special value of current_selection_data to mean that + foreign selections are in use. */ + current_selection_data = &foreign_selection_key; + + /* Send new data offers to current clients. */ + SendDataOffers (); +} + +void +XLClearForeignSelection (Time time) +{ + if (time < foreign_selection_time) + return; + + if (current_selection_data == &foreign_selection_key) + { + current_selection_data = NULL; + + SendDataOffers (); + } + + foreign_selection_time = time; +} + +int +XLDataSourceTargetCount (DataSource *source) +{ + return source->n_mime_types; +} + +void +XLDataSourceGetTargets (DataSource *source, Atom *targets) +{ + int i; + XIDList *list; + + list = source->atom_types; + + for (i = 0; i < source->n_mime_types; ++i) + { + targets[i] = list->data; + list = list->next; + } +} + +struct wl_resource * +XLResourceFromDataSource (DataSource *source) +{ + return source->resource; +} + +Bool +XLDataSourceHasAtomTarget (DataSource *source, Atom target) +{ + XIDList *list; + + for (list = source->atom_types; list; list = list->next) + { + if (list->data == target) + return True; + } + + return False; +} + +Bool +XLDataSourceHasTarget (DataSource *source, const char *mime_type) +{ + XLList *list; + + for (list = source->mime_types; list; list = list->next) + { + if (!strcmp (list->data, mime_type)) + return True; + } + + return False; +} + +void +XLDataDeviceMakeOffers (Seat *seat, DndOfferFuncs funcs, Surface *surface, + int x, int y) +{ + DataDevice *device; + DataDeviceReference *reference; + struct wl_resource *resource; + struct wl_client *client; + int version; + uint32_t serial; + + device = XLSeatGetDataDevice (seat); + client = wl_resource_get_client (surface->resource); + reference = device->references.next; + serial = wl_display_next_serial (compositor.wl_display); + + while (reference != &device->references) + { + /* If this reference to the data device belongs to the client, + continue. */ + if (wl_resource_get_client (reference->resource) + == wl_resource_get_client (surface->resource)) + { + version = wl_resource_get_version (reference->resource); + + /* Create the offer. */ + resource = funcs.create (client, version); + + if (resource) + { + /* Actually send the data offer to the client. */ + wl_data_device_send_data_offer (reference->resource, + resource); + + /* And data offers. */ + funcs.send_offers (resource); + + /* And send the entry event. */ + wl_data_device_send_enter (reference->resource, + serial, surface->resource, + wl_fixed_from_double (x), + wl_fixed_from_double (y), + resource); + } + } + + reference = reference->next; + } +} + +void +XLDataDeviceSendEnter (Seat *seat, Surface *surface, double x, double y, + DataSource *source) +{ + DataDevice *device; + DataDeviceReference *reference; + uint32_t serial; + struct wl_resource *resource; + struct wl_client *client; + DataOffer *offer; + XLList *type; + + device = XLSeatGetDataDevice (seat); + + if (!device) + /* No data device has been created for this seat yet. */ + return; + + reference = device->references.next; + serial = wl_display_next_serial (compositor.wl_display); + client = wl_resource_get_client (surface->resource); + device->dnd_serial = serial; + + /* Clear the selected actions. */ + device->supported_actions = 0; + device->preferred_action = 0; + + /* And some flags. */ + if (source) + source->state = 0; + + while (reference != &device->references) + { + /* If this reference to the data device belongs to the client, + continue. */ + if (wl_resource_get_client (reference->resource) == client) + { + if (source) + { + /* First, create a data offer corresponding to the data + source if it exists. */ + resource = AddDataOffer (client, source); + offer = wl_resource_get_user_data (resource); + offer->dnd_serial = serial; + offer->last_action = -1; + + /* Mark the offer as a drag-and-drop offer. */ + offer->state |= IsDragAndDrop; + + /* Introduce the data offer to the client. */ + wl_data_device_send_data_offer (reference->resource, resource); + + /* Send all the offered data types to the client. */ + type = source->mime_types; + + for (; type; type = type->next) + wl_data_offer_send_offer (resource, type->data); + + /* Send the source actions. */ + wl_data_offer_send_source_actions (resource, source->actions); + + /* If the data device supports version 3 or later, set + the flag. */ + if (wl_resource_get_version (resource) >= 3) + source->state |= Version3Supported; + } + + wl_data_device_send_enter (reference->resource, + serial, surface->resource, + wl_fixed_from_double (x), + wl_fixed_from_double (y), + source ? resource : NULL); + + } + + reference = reference->next; + } +} + +void +XLDataDeviceSendMotion (Seat *seat, Surface *surface, + double x, double y, Time time) +{ + DataDevice *device; + DataDeviceReference *reference; + + device = XLSeatGetDataDevice (seat); + + if (!device) + /* No data device has been created for this seat yet. */ + return; + + reference = device->references.next; + + while (reference != &device->references) + { + /* If this reference to the data device belongs to the client, + continue. */ + if (wl_resource_get_client (reference->resource) + == wl_resource_get_client (surface->resource)) + wl_data_device_send_motion (reference->resource, time, + wl_fixed_from_double (x), + wl_fixed_from_double (y)); + + reference = reference->next; + } +} + +void +XLDataDeviceSendLeave (Seat *seat, Surface *surface, DataSource *source) +{ + DataDevice *device; + DataDeviceReference *reference; + + device = XLSeatGetDataDevice (seat); + + if (!device) + /* No data device has been created for this seat yet. */ + return; + + reference = device->references.next; + + /* This serial doesn't actually mean anything. It's only used to + invalidate previous data offers. */ + device->dnd_serial = wl_display_next_serial (compositor.wl_display); + + while (reference != &device->references) + { + /* If this reference to the data device belongs to the client, + continue. */ + if (wl_resource_get_client (reference->resource) + == wl_resource_get_client (surface->resource)) + wl_data_device_send_leave (reference->resource); + + reference = reference->next; + } + + if (source) + { + /* Clear the selected actions. */ + device->supported_actions = 0; + device->preferred_action = 0; + + /* Since the pointer has left the source, clear several + flags. */ + source->state = 0; + + /* Send the new effective action to the source. */ + if (wl_resource_get_version (source->resource) >= 3) + wl_data_source_send_action (source->resource, + WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE); + + /* Also tell the source that there is no longer any + target accepted. */ + wl_data_source_send_target (source->resource, NULL); + } +} + +void +XLDataDeviceSendDrop (Seat *seat, Surface *surface) +{ + DataDevice *device; + DataDeviceReference *reference; + + device = XLSeatGetDataDevice (seat); + + if (!device) + /* No data device has been created for this seat yet. */ + return; + + reference = device->references.next; + + while (reference != &device->references) + { + /* If this reference to the data device belongs to the client, + continue. */ + if (wl_resource_get_client (reference->resource) + == wl_resource_get_client (surface->resource)) + wl_data_device_send_drop (reference->resource); + + reference = reference->next; + } +} + + + +void +XLDataSourceAttachDragDevice (DataSource *source, DataDevice *device) +{ + if (source->drag_device) + { + CancelDestroyCallback (source->drag_device_callback); + source->drag_device_callback = NULL; + } + + source->drag_device = device; + + if (device) + source->drag_device_callback + = AddDestroyCallbackAfter (&device->destroy_callbacks, + HandleDragDeviceDestroyed, + source); +} + +void * +XLDataSourceAddDestroyCallback (DataSource *source, + void (*destroy_func) (void *), + void *data) +{ + return AddDestroyCallbackAfter (&source->destroy_callbacks, + destroy_func, data); +} + +void +XLDataSourceCancelDestroyCallback (void *key) +{ + CancelDestroyCallback (key); +} + +void +XLDataSourceSendDropPerformed (DataSource *source) +{ + if (wl_resource_get_version (source->resource) >= 3) + wl_data_source_send_dnd_drop_performed (source->resource); +} + +void +XLDataSourceSendDropCancelled (DataSource *source) +{ + if (wl_resource_get_version (source->resource) >= 3) + wl_data_source_send_cancelled (source->resource); +} + +Bool +XLDataSourceCanDrop (DataSource *source) +{ + /* If version 3 is supported, require that an action has been sent + and a data type has been accepted. Otherwise, always do the + drop. */ + if (source->state & Version3Supported) + return (source->state & ActionsSent + && source->state & TypeAccepted); + + return True; +} + +uint32_t +XLDataSourceGetSupportedActions (DataSource *source) +{ + return source->actions; +} + +XLList * +XLDataSourceGetMimeTypeList (DataSource *source) +{ + return source->mime_types; +} + +void +XLDataSourceUpdateDeviceActions (DataSource *drag_source) +{ + UpdateDeviceActions (drag_source->drag_device, drag_source); +} diff --git a/dmabuf.c b/dmabuf.c new file mode 100644 index 0000000..3921cb0 --- /dev/null +++ b/dmabuf.c @@ -0,0 +1,1393 @@ +/* Wayland compositor running on top of an X server. + +Copyright (C) 2022 to various contributors. + +This file is part of 12to11. + +12to11 is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +12to11 is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with 12to11. If not, see . */ + +#include +#include + +#include +#include +#include + +#include + +#include "compositor.h" + +#include "linux-dmabuf-unstable-v1.h" + +#include + +#include +#include + +typedef struct _DrmFormatInfo DrmFormatInfo; +typedef struct _BufferParams BufferParams; +typedef struct _Buffer Buffer; +typedef struct _TemporarySetEntry TemporarySetEntry; +typedef struct _FormatModifierPair FormatModifierPair; + +enum + { + IsUsed = 1, + }; + +struct _DrmFormatInfo +{ + /* The DRM format code. */ + uint32_t format_code; + + /* The X Windows depth. */ + int depth; + + /* The X Windows green, red, blue, and alpha masks. */ + int red, green, blue, alpha; + + /* The number of bits per pixel. */ + int bits_per_pixel; + + /* PictFormat associated with this format, or NULL if none were + found. */ + XRenderPictFormat *format; + + /* List of supported screen modifiers. */ + uint64_t *supported_modifiers; + + /* Number of supported screen modifiers. */ + int n_supported_modifiers; +}; + +struct _TemporarySetEntry +{ + /* These fields mean the same as they do in the args to + zwp_linux_buffer_params_v1_add. */ + + int fd; + unsigned int plane_idx, offset, stride; + uint32_t modifier_hi, modifier_lo; +}; + +struct _BufferParams +{ + /* Entries for each plane. DRI3 only supports up to 4 planes. */ + TemporarySetEntry entries[4]; + + /* Some flags. */ + int flags; + + /* The struct wl_resource associated with this object. */ + struct wl_resource *resource; + + /* Possible link to a global list of buffer params pending + release. */ + BufferParams *next, *last; + + /* The XID of the buffer that will be created. */ + Pixmap pixmap; + + /* The width and height of the buffer that will be created. */ + int width, height; + + /* The buffer ID that will be used to create the buffer. */ + uint32_t buffer_id; + + /* The DRM format. */ + uint32_t drm_format; +}; + +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 wl_resource corresponding to this buffer. */ + struct wl_resource *resource; + + /* List of "destroy listeners" connected to this buffer. */ + XLList *destroy_listeners; + + /* The number of references to this buffer. */ + int refcount; +}; + +struct _FormatModifierPair +{ + /* See the documentation of + zwp_linux_dmabuf_feedback_v1::format_table for more details. */ + uint32_t format; + uint32_t padding; + uint64_t modifier; +}; + +/* The wl_global associated with linux-dmabuf-unstable-v1. */ +static struct wl_global *global_dmabuf; + +/* List of BufferParams pending success. */ +static BufferParams pending_success; + +/* The id of the next round trip event. */ +static uint64_t next_roundtrip_id; + +/* A window used to receive round trip events. */ +static Window round_trip_window; + +/* List of all supported DRM formats. */ +static DrmFormatInfo all_formats[] = + { + { + .format_code = DRM_FORMAT_ARGB8888, + .depth = 32, + .red = 0xff0000, + .green = 0xff00, + .blue = 0xff, + .alpha = 0xff000000, + .bits_per_pixel = 32, + }, + { + .format_code = DRM_FORMAT_XRGB8888, + .depth = 24, + .red = 0xff0000, + .green = 0xff00, + .blue = 0xff, + .alpha = 0, + .bits_per_pixel = 32, + }, + { + .format_code = DRM_FORMAT_XBGR8888, + .depth = 24, + .blue = 0xff0000, + .green = 0xff00, + .red = 0xff, + .alpha = 0, + .bits_per_pixel = 32, + }, + { + .format_code = DRM_FORMAT_ABGR8888, + .depth = 32, + .blue = 0xff0000, + .green = 0xff00, + .red = 0xff, + .alpha = 0xff000000, + .bits_per_pixel = 32, + }, + { + .format_code = DRM_FORMAT_BGRA8888, + .depth = 32, + .blue = 0xff000000, + .green = 0xff0000, + .red = 0xff00, + .alpha = 0xff, + .bits_per_pixel = 32, + }, + }; + +/* The opcode of the DRI3 extension. */ +static int dri3_opcode; + +/* File descriptor for the format table. */ +static int format_table_fd; + +/* Size of the format table. */ +static ssize_t format_table_size; + +/* Device node of the DRM device. TODO: make this + output-specific. */ +static dev_t drm_device_node; + +static void +CloseFdsEarly (BufferParams *params) +{ + int i; + + for (i = 0; i < ArrayElements (params->entries); ++i) + { + if (params->entries[i].fd != -1) + close (params->entries[i].fd); + } +} + +static void +ReleaseBufferParams (BufferParams *params) +{ + /* Also close any fds that were set if this object was not yet + used. */ + if (!(params->flags & IsUsed)) + CloseFdsEarly (params); + + /* If params is linked into the list of buffers pending success, + remove it. */ + + if (params->last) + { + params->next->last = params->last; + params->last->next = params->next; + } + + XLFree (params); +} + +static void +HandleParamsResourceDestroy (struct wl_resource *resource) +{ + BufferParams *params; + + params = wl_resource_get_user_data (resource); + + /* First, clear params->resource. */ + params->resource = NULL; + + if (params->next) + return; + + /* Next, destroy the params now, unless we are waiting for a buffer + to be created, in which case it is necessary to free the buffer + should the creation succeed. */ + + ReleaseBufferParams (params); +} + +static void +DestroyBufferParams (struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static int +ExistingModifier (BufferParams *params, uint32_t *current_hi, + uint32_t *current_lo) +{ + int i, count; + + for (i = 0, count = 0; i < ArrayElements (params->entries); ++i) + { + if (params->entries[i].fd != -1) + { + *current_hi = params->entries[i].modifier_hi; + *current_lo = params->entries[i].modifier_lo; + + count++; + } + } + + return count; +} + +static void +Add (struct wl_client *client, struct wl_resource *resource, int32_t fd, + uint32_t plane_idx, uint32_t offset, uint32_t stride, uint32_t modifier_hi, + uint32_t modifier_lo) +{ + BufferParams *params; + uint32_t current_hi, current_lo; + + params = wl_resource_get_user_data (resource); + + if (params->flags & IsUsed) + { + wl_resource_post_error (resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, + "the given params resource has already been used"); + close (fd); + + return; + } + + if (plane_idx >= ArrayElements (params->entries)) + { + wl_resource_post_error (resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX, + "maximum number of planes exceeded"); + close (fd); + + return; + } + + if (params->entries[plane_idx].fd != -1) + { + wl_resource_post_error (resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET, + "the plane has already been added in the temporary set"); + close (fd); + + return; + } + + if (ExistingModifier (params, ¤t_hi, ¤t_lo) + && (current_hi != modifier_hi || current_lo != modifier_lo)) + { + wl_resource_post_error (resource, ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT, + "modifier does not match other planes in the temporary set"); + close (fd); + + return; + } + + params->entries[plane_idx].fd = fd; + params->entries[plane_idx].plane_idx = plane_idx; + params->entries[plane_idx].offset = offset; + params->entries[plane_idx].stride = stride; + params->entries[plane_idx].modifier_hi = modifier_hi; + params->entries[plane_idx].modifier_lo = modifier_lo; +} + +static void +ForceRoundTrip (void) +{ + uint64_t id; + XEvent event; + + /* Send an event with a monotonically increasing identifier to + ourselves. + + Once the last event is received, create the actual buffers for + each buffer resource for which error handlers have not run. */ + + id = next_roundtrip_id++; + + memset (&event, 0, sizeof event); + + event.xclient.type = ClientMessage; + event.xclient.window = round_trip_window; + event.xclient.message_type = _XL_DMA_BUF_CREATED; + event.xclient.format = 32; + + event.xclient.data.l[0] = id >> 31 >> 1; + event.xclient.data.l[1] = id & 0xffffffff; + + XSendEvent (compositor.display, round_trip_window, + False, NoEventMask, &event); +} + +static int +DepthForFormat (uint32_t drm_format, int *bits_per_pixel) +{ + int i; + + for (i = 0; i < ArrayElements (all_formats); ++i) + { + if (all_formats[i].format_code == drm_format + && all_formats[i].format) + { + *bits_per_pixel = all_formats[i].bits_per_pixel; + + return all_formats[i].depth; + } + } + + return -1; +} + +Bool +XLHandleErrorForDmabuf (XErrorEvent *error) +{ + BufferParams *params, *next; + + if (error->request_code == dri3_opcode + && error->minor_code == xDRI3BuffersFromPixmap) + { + /* Something chouldn't be created. Find what failed and unlink + it. */ + + next = pending_success.next; + + while (next != &pending_success) + { + params = next; + next = next->next; + + if (params->pixmap == error->resourceid) + { + /* Unlink params. */ + params->next->last = params->last; + params->last->next = params->next; + + params->next = NULL; + params->last = NULL; + + /* Tell the client that buffer creation failed. It will + then delete the object. */ + zwp_linux_buffer_params_v1_send_failed (params->resource); + + break; + } + } + + return True; + } + + return False; +} + +static XRenderPictFormat * +PictFormatForFormat (uint32_t drm_format) +{ + int i; + + for (i = 0; i < ArrayElements (all_formats); ++i) + { + if (all_formats[i].format_code == drm_format + && all_formats[i].format) + return all_formats[i].format; + } + + /* This shouldn't happen, since the format was already verified in + Create. */ + abort (); +} + +static void +DestroyBacking (Buffer *buffer) +{ + if (--buffer->refcount) + return; + + XRenderFreePicture (compositor.display, buffer->picture); + XFreePixmap (compositor.display, buffer->pixmap); + + ExtBufferDestroy (&buffer->buffer); + XLFree (buffer); +} + +static void +DestroyBuffer (struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static struct wl_buffer_interface zwp_linux_dmabuf_v1_buffer_impl = + { + .destroy = DestroyBuffer, + }; + +static void +HandleBufferResourceDestroy (struct wl_resource *resource) +{ + Buffer *buffer; + + buffer = wl_resource_get_user_data (resource); + buffer->resource = NULL; + + DestroyBacking (buffer); +} + +static void +RetainBufferFunc (ExtBuffer *buffer) +{ + Buffer *dmabuf_buffer; + + dmabuf_buffer = (Buffer *) buffer; + dmabuf_buffer->refcount++; +} + +static void +DereferenceBufferFunc (ExtBuffer *buffer) +{ + Buffer *dmabuf_buffer; + + dmabuf_buffer = (Buffer *) buffer; + DestroyBacking (dmabuf_buffer); +} + +static Picture +GetPictureFunc (ExtBuffer *buffer) +{ + Buffer *dmabuf_buffer; + + dmabuf_buffer = (Buffer *) buffer; + return dmabuf_buffer->picture; +} + +static Pixmap +GetPixmapFunc (ExtBuffer *buffer) +{ + Buffer *dmabuf_buffer; + + dmabuf_buffer = (Buffer *) buffer; + return dmabuf_buffer->pixmap; +} + +static unsigned int +WidthFunc (ExtBuffer *buffer) +{ + Buffer *dmabuf_buffer; + + dmabuf_buffer = (Buffer *) buffer; + return dmabuf_buffer->width; +} + +static unsigned int +HeightFunc (ExtBuffer *buffer) +{ + Buffer *dmabuf_buffer; + + dmabuf_buffer = (Buffer *) buffer; + return dmabuf_buffer->height; +} + +static void +ReleaseBufferFunc (ExtBuffer *buffer) +{ + if (((Buffer *) buffer)->resource) + wl_buffer_send_release (((Buffer *) buffer)->resource); +} + +static Buffer * +CreateBufferFor (BufferParams *params, uint32_t id) +{ + Buffer *buffer; + Picture picture; + struct wl_client *client; + XRenderPictureAttributes picture_attrs; + + buffer = XLSafeMalloc (sizeof *buffer); + client = wl_resource_get_client (params->resource); + + if (!buffer) + { + XFreePixmap (compositor.display, params->pixmap); + zwp_linux_buffer_params_v1_send_failed (params->resource); + + return NULL; + } + + memset (buffer, 0, sizeof *buffer); + buffer->resource + = wl_resource_create (client, &wl_buffer_interface, 1, id); + + if (!buffer->resource) + { + XFreePixmap (compositor.display, params->pixmap); + XLFree (buffer); + zwp_linux_buffer_params_v1_send_failed (params->resource); + + return NULL; + } + + picture = XRenderCreatePicture (compositor.display, params->pixmap, + PictFormatForFormat (params->drm_format), + 0, &picture_attrs); + buffer->pixmap = params->pixmap; + buffer->picture = picture; + buffer->width = params->width; + buffer->height = params->height; + buffer->destroy_listeners = NULL; + + /* 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->refcount++; + + wl_resource_set_implementation (buffer->resource, + &zwp_linux_dmabuf_v1_buffer_impl, + buffer, HandleBufferResourceDestroy); + + return buffer; +} + +static void +FinishBufferCreation (void) +{ + BufferParams *params, *next; + Buffer *buffer; + + next = pending_success.next; + + while (next != &pending_success) + { + params = next; + next = next->next; + + if (params->resource) + { + buffer = CreateBufferFor (params, 0); + + /* Send the buffer to the client, unless creation + failed. */ + if (buffer) + zwp_linux_buffer_params_v1_send_created (params->resource, + buffer->resource); + + /* Unlink params, since it's no longer pending creation. */ + params->next->last = params->last; + params->last->next = params->next; + + params->next = NULL; + params->last = NULL; + } + else + { + /* The resource is no longer present, so just delete it. */ + XFreePixmap (compositor.display, params->pixmap); + ReleaseBufferParams (params); + } + } +} + +Bool +XLHandleOneXEventForDmabuf (XEvent *event) +{ + uint64_t id, low, high; + + if (event->type == ClientMessage + && event->xclient.message_type == _XL_DMA_BUF_CREATED) + { + /* Values are masked against 0xffffffff, as Xlib sign-extends + those longs. */ + high = event->xclient.data.l[0] & 0xffffffff; + low = event->xclient.data.l[1] & 0xffffffff; + id = low | (high << 32); + + /* Ignore the message if the id is too old. */ + if (id < next_roundtrip_id) + { + /* Otherwise, it means buffer creation was successful. + Complete all pending buffer creation. */ + + FinishBufferCreation (); + } + + return True; + } + + return False; +} + +#define CreateHeader \ + BufferParams *params; \ + int num_buffers, i, depth, bpp; \ + uint32_t mod_high, mod_low; \ + int32_t *allfds; \ + \ + params = wl_resource_get_user_data (resource); \ + \ + if (params->flags & IsUsed) \ + { \ + wl_resource_post_error (resource, \ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED, \ + "the given params resource has already been used"); \ + return; \ + } \ + \ + /* Find the depth and bpp corresponding to the format. */ \ + depth = DepthForFormat (format, &bpp); \ + \ + /* Now mark the params resource as inert. */ \ + params->flags |= IsUsed; \ + \ + /* Retrieve how many buffers are attached to the temporary set, and \ + any set modifiers. */ \ + num_buffers = ExistingModifier (params, &mod_high, &mod_low); \ + \ + if (!num_buffers) \ + { \ + wl_resource_post_error (resource, \ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, \ + "no fds were attached to this resource's temporary set"); \ + goto inert_error; \ + } \ + \ + if (params->entries[0].fd == -1) \ + { \ + wl_resource_post_error (resource, \ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, \ + "no fd attached for plane 0 in the temporary set"); \ + goto inert_error; \ + } \ + \ + if ((params->entries[3].fd >= 0 || params->entries[2].fd >= 0) \ + && (params->entries[2].fd == -1 || params->entries[1].fd == -1)) \ + { \ + wl_resource_post_error (resource, \ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE, \ + "gap in planes attached to temporary set"); \ + goto inert_error; \ + } \ + \ + if (width < 0 || height < 0 || width > 65535 || height > 65535) \ + { \ + wl_resource_post_error (resource, \ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS, \ + "size out of bounds for X server"); \ + goto inert_error; \ + } \ + \ + if (depth == -1) \ + { \ + if (wl_resource_get_version (resource) >= 4) \ + wl_resource_post_error (resource, \ + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT, \ + "invalid format specified for version 4 resource"); \ + else \ + zwp_linux_buffer_params_v1_send_failed (resource); \ + \ + goto inert_error; \ + } \ + \ + /* TODO: handle flags. */ \ + \ + /* Copy the file descriptors into a buffer. At this point, we know \ + there are no gaps in params->entries. */ \ + allfds = alloca (sizeof *allfds * num_buffers); \ + \ + for (i = 0; i < num_buffers; ++i) \ + allfds[i] = params->entries[i].fd; \ + \ + /* Make the request, and then link the buffer params object onto the \ + list of pending buffers. We don't know if the creation will be \ + rejected by the X server, so we first arrange to catch all errors \ + from DRI3PixmapFromBuffer(s), and send the create event the next \ + time we know that a roundtrip has happened without any errors \ + being raised. */ \ + \ + params->width = width; \ + params->height = height; \ + params->drm_format = format; \ + \ + params->pixmap = xcb_generate_id (compositor.conn) + + +#define CreateFooter \ + inert_error: \ + /* We also have to close each added fd here, since XCB hasn't done \ + that for us. */ \ + \ + CloseFdsEarly (params) + +static void +Create (struct wl_client *client, struct wl_resource *resource, int32_t width, + int32_t height, uint32_t format, uint32_t flags) +{ + CreateHeader; + + params->next = pending_success.next; + params->last = &pending_success; + pending_success.next->last = params; + pending_success.next = params; + + /* Now, create the buffers. XCB will close the file descriptor for + us once the output buffer is flushed. */ + xcb_dri3_pixmap_from_buffers (compositor.conn, params->pixmap, + DefaultRootWindow (compositor.display), + num_buffers, params->width, params->height, + params->entries[0].offset, + params->entries[0].stride, + params->entries[1].offset, + params->entries[1].stride, + params->entries[2].offset, + params->entries[2].stride, + params->entries[3].offset, + params->entries[3].stride, depth, bpp, + (uint64_t) mod_high << 31 | mod_low, allfds); + + /* And force a roundtrip event. */ + ForceRoundTrip (); + + return; + + CreateFooter; +} + +static void +CreateImmed (struct wl_client *client, struct wl_resource *resource, uint32_t id, + int32_t width, int32_t height, uint32_t format, uint32_t flags) +{ + xcb_void_cookie_t check_cookie; + xcb_generic_error_t *error; + + CreateHeader; + + /* Instead of creating buffers asynchronously, check for failures + immediately. */ + + check_cookie + = xcb_dri3_pixmap_from_buffers_checked (compositor.conn, params->pixmap, + DefaultRootWindow (compositor.display), + num_buffers, params->width, params->height, + params->entries[0].offset, + params->entries[0].stride, + params->entries[1].offset, + params->entries[1].stride, + params->entries[2].offset, + params->entries[2].stride, + params->entries[3].offset, + params->entries[3].stride, depth, bpp, + (uint64_t) mod_high << 31 | mod_low, allfds); + error = xcb_request_check (compositor.conn, check_cookie); + + /* A platform-specific error occured creating this buffer. + It is easiest to implement this by sending INVALID_WL_BUFFER. */ + if (error) + { + wl_resource_post_error (resource, + ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER, + "platform specific error: response code %u, error code %u," + " serial %u, xid %u, minor %u, major %u", error->response_type, + error->error_code, error->sequence, error->resource_id, + error->minor_code, error->major_code); + free (error); + } + else + /* Otherwise, the fds were successfully imported into the X + server. */ + CreateBufferFor (params, id); + + return; + + CreateFooter; +} + +static struct zwp_linux_buffer_params_v1_interface zwp_linux_buffer_params_v1_impl = + { + .destroy = DestroyBufferParams, + .add = Add, + .create = Create, + .create_immed = CreateImmed, + }; + +static void +CreateParams (struct wl_client *client, struct wl_resource *resource, + uint32_t id) +{ + BufferParams *params; + int i; + + params = XLSafeMalloc (sizeof *params); + + if (!params) + { + wl_resource_post_no_memory (resource); + return; + } + + memset (params, 0, sizeof *params); + params->resource + = wl_resource_create (client, &zwp_linux_buffer_params_v1_interface, + wl_resource_get_version (resource), id); + + if (!params->resource) + { + XLFree (params); + wl_resource_post_no_memory (resource); + return; + } + + wl_resource_set_implementation (params->resource, + &zwp_linux_buffer_params_v1_impl, + params, HandleParamsResourceDestroy); + + /* Initialize all fds to -1. */ + for (i = 0; i < ArrayElements (params->entries); ++i) + params->entries[i].fd = -1; +} + +static void +Destroy (struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static struct zwp_linux_dmabuf_feedback_v1_interface zld_feedback_v1_impl = + { + .destroy = Destroy, + }; + +/* TODO: dynamically switch tranche for surface feedbacks based on the + crtc of the provider the surface is in. */ + +static void +MakeFeedback (struct wl_client *client, struct wl_resource *resource, + uint32_t id) +{ + struct wl_resource *feedback_resource; + struct wl_array main_device_array, format_array; + int i; + ptrdiff_t format_array_size; + uint16_t *format_array_data; + + feedback_resource = wl_resource_create (client, + &zwp_linux_dmabuf_feedback_v1_interface, + wl_resource_get_version (resource), id); + + if (!resource) + { + wl_resource_post_no_memory (resource); + return; + } + + wl_resource_set_implementation (feedback_resource, + &zld_feedback_v1_impl, + NULL, NULL); + + /* Now, send the relevant information. This should eventually be + dynamically updated, but we don't support that yet. */ + + /* First, send the format table. */ + + zwp_linux_dmabuf_feedback_v1_send_format_table (feedback_resource, + format_table_fd, + format_table_size); + + /* Next, send the main device. */ + + main_device_array.size = sizeof drm_device_node; + main_device_array.data = &drm_device_node; + + main_device_array.alloc = main_device_array.size; + zwp_linux_dmabuf_feedback_v1_send_main_device (feedback_resource, + &main_device_array); + + /* Then, send the first tranche. Right now, the only tranche + contains the formats supported by the default provider. */ + + zwp_linux_dmabuf_feedback_v1_send_tranche_target_device (feedback_resource, + &main_device_array); + + /* Populate the formats array with the contents of the format + table, and send it to the client. */ + + format_array_size = format_table_size / sizeof (FormatModifierPair); + format_array.size = format_array_size * sizeof (uint16_t); + format_array.data = format_array_data = alloca (format_array.size); + + /* This must be reset too. */ + format_array.alloc = format_array.size; + + /* Simply announce every format to the client. */ + for (i = 0; i < format_array_size; ++i) + format_array_data[i] = i; + + zwp_linux_dmabuf_feedback_v1_send_tranche_formats (feedback_resource, + &format_array); + + /* Send flags. We don't currently support direct scanout, so send + nothing. */ + + zwp_linux_dmabuf_feedback_v1_send_tranche_flags (feedback_resource, 0); + + /* Mark the end of the tranche. */ + + zwp_linux_dmabuf_feedback_v1_send_tranche_done (feedback_resource); +} + +static void +GetDefaultFeedback (struct wl_client *client, struct wl_resource *resource, + uint32_t id) +{ + MakeFeedback (client, resource, id); +} + +static void +GetSurfaceFeedback (struct wl_client *client, struct wl_resource *resource, + uint32_t id, struct wl_resource *surface_resource) +{ + MakeFeedback (client, resource, id); +} + +static struct zwp_linux_dmabuf_v1_interface zwp_linux_dmabuf_v1_impl = + { + .create_params = CreateParams, + .destroy = Destroy, + .get_default_feedback = GetDefaultFeedback, + .get_surface_feedback = GetSurfaceFeedback, + }; + +static DrmFormatInfo * +FindFormatMatching (XRenderPictFormat *format) +{ + unsigned long alpha, red, green, blue; + int i; + + if (format->type != PictTypeDirect) + /* No DRM formats are colormapped. */ + return NULL; + + alpha = format->direct.alphaMask << format->direct.alpha; + red = format->direct.redMask << format->direct.red; + green = format->direct.greenMask << format->direct.green; + blue = format->direct.blueMask << format->direct.blue; + + for (i = 0; i < ArrayElements (all_formats); ++i) + { + if (all_formats[i].depth == format->depth + && all_formats[i].red == red + && all_formats[i].green == green + && all_formats[i].blue == blue + && all_formats[i].alpha == alpha) + return &all_formats[i]; + } + + return NULL; +} + +static void +FindSupportedModifiers (void) +{ + Window root_window; + xcb_dri3_get_supported_modifiers_cookie_t *cookies; + xcb_dri3_get_supported_modifiers_reply_t *reply; + int i, length; + uint64_t *mods; + + cookies = alloca (sizeof *cookies * ArrayElements (all_formats)); + root_window = DefaultRootWindow (compositor.display); + + for (i = 0; i < ArrayElements (all_formats); ++i) + { + if (all_formats[i].format) + cookies[i] + = xcb_dri3_get_supported_modifiers (compositor.conn, + root_window, all_formats[i].depth, + all_formats[i].bits_per_pixel); + } + + for (i = 0; i < ArrayElements (all_formats); ++i) + { + if (!all_formats[i].format) + continue; + + reply = xcb_dri3_get_supported_modifiers_reply (compositor.conn, + cookies[i], NULL); + + if (!reply) + continue; + + mods + = xcb_dri3_get_supported_modifiers_screen_modifiers (reply); + length + = xcb_dri3_get_supported_modifiers_screen_modifiers_length (reply); + + all_formats[i].supported_modifiers = XLMalloc (sizeof *mods * length); + all_formats[i].n_supported_modifiers = length; + + memcpy (all_formats[i].supported_modifiers, mods, + sizeof *mods * length); + free (reply); + } +} + +static Bool +FindSupportedFormats (void) +{ + int count; + XRenderPictFormat *format; + DrmFormatInfo *info; + Bool supported; + + count = 0; + supported = False; + + do + { + format = XRenderFindFormat (compositor.display, 0, + NULL, count); + count++; + + if (!format) + break; + + info = FindFormatMatching (format); + + if (info && !info->format) + info->format = format; + + if (info) + supported = True; + } + while (format); + + return supported; +} + +#define ModifierHigh(mod) ((uint64_t) (mod) >> 31 >> 1) +#define ModifierLow(mod) ((uint64_t) (mod) & 0xffffffff) +#define Mod(format, i) ((format)->supported_modifiers[i]) + +static void +SendModifiers (struct wl_resource *resource, DrmFormatInfo *format) +{ + int i; + + for (i = 0; i < format->n_supported_modifiers; ++i) + zwp_linux_dmabuf_v1_send_modifier (resource, format->format_code, + ModifierHigh (Mod (format, i)), + ModifierLow (Mod (format, i))); +} + +static void +SendSupportedFormats (struct wl_resource *resource) +{ + int i; + uint64_t invalid; + + invalid = DRM_FORMAT_MOD_INVALID; + + for (i = 0; i < ArrayElements (all_formats); ++i) + { + /* We consider all formats for which picture formats exist as + supported. Unfortunately, it seems that the formats DRI3 is + willing to support slightly differs, so trying to create + buffers for some of these formats may fail later. */ + if (all_formats[i].format) + { + if (wl_resource_get_version (resource) < 3) + /* Send a legacy format message. */ + zwp_linux_dmabuf_v1_send_format (resource, + all_formats[i].format_code); + else + { + zwp_linux_dmabuf_v1_send_modifier (resource, + all_formats[i].format_code, + ModifierHigh (invalid), + ModifierLow (invalid)); + + SendModifiers (resource, &all_formats[i]); + } + } + } +} + +static void +HandleBind (struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + struct wl_resource *resource; + + resource = wl_resource_create (client, &zwp_linux_dmabuf_v1_interface, + version, id); + + if (!resource) + { + wl_client_post_no_memory (client); + return; + } + + wl_resource_set_implementation (resource, &zwp_linux_dmabuf_v1_impl, + NULL, NULL); + + SendSupportedFormats (resource); +} + +static Bool +InitDrmDevice (void) +{ + xcb_dri3_open_cookie_t cookie; + xcb_dri3_open_reply_t *reply; + int *fds, fd; + struct stat dev_stat; + + /* TODO: if this ever calls exec, set FD_CLOEXEC. TODO TODO + implement multiple providers. */ + cookie = xcb_dri3_open (compositor.conn, + DefaultRootWindow (compositor.display), + None); + reply = xcb_dri3_open_reply (compositor.conn, cookie, NULL); + + if (!reply) + return False; + + fds = xcb_dri3_open_reply_fds (compositor.conn, reply); + + if (!fds) + { + free (reply); + return False; + } + + fd = fds[0]; + + if (fstat (fd, &dev_stat) != 0) + { + close (fd); + free (reply); + + return False; + } + + if (dev_stat.st_rdev) + drm_device_node = dev_stat.st_rdev; + else + { + close (fd); + free (reply); + + return False; + } + + close (fd); + free (reply); + + return True; +} + +static ssize_t +WriteFormatTable (void) +{ + int fd, i, m; + ssize_t written; + FormatModifierPair pair; + + /* Before writing the format table, make sure the DRM device node + can be obtained. */ + if (!InitDrmDevice ()) + { + fprintf (stderr, "Failed to get direct rendering device node. " + "Hardware acceleration will probably be unavailable.\n"); + return -1; + } + + fd = XLOpenShm (); + + if (fd < 0) + { + fprintf (stderr, "Failed to allocate format table fd. " + "Hardware acceleration will probably be unavailable.\n"); + return 1; + } + + written = 0; + + /* Write each format-modifier pair. */ + for (i = 0; i < ArrayElements (all_formats); ++i) + { + if (!all_formats[i].format) + continue; + + /* First, send the default implicit modifier pair. */ + pair.format = all_formats[i].format_code; + pair.padding = 0; + pair.modifier = DRM_FORMAT_MOD_INVALID; + + if (write (fd, &pair, sizeof pair) != sizeof pair) + /* Writing the modifier pair failed. Punt. */ + goto cancel; + + /* Now tell the caller how much was written. */ + written += sizeof pair; + + /* Next, write all the modifier pairs. */ + for (m = 0; m < all_formats[i].n_supported_modifiers; ++m) + { + pair.modifier = all_formats[i].supported_modifiers[m]; + + if (write (fd, &pair, sizeof pair) != sizeof pair) + /* Writing this pair failed, punt. */ + goto cancel; + + written += sizeof pair; + } + } + + format_table_fd = fd; + return written; + + cancel: + close (fd); + return -1; +} + +static void +ReallyInitDmabuf (void) +{ + XSetWindowAttributes attrs; + size_t size; + + if (!FindSupportedFormats ()) + { + fprintf (stderr, "No supported picture formats were found." + " Hardware acceleration will not be available.\n"); + return; + } + + /* Now look up which modifiers are supported for what formats. */ + FindSupportedModifiers (); + + /* And try to create the format table. */ + size = WriteFormatTable (); + + /* Create an unmapped, InputOnly window, that is used to receive + roundtrip events. */ + attrs.override_redirect = True; + round_trip_window = XCreateWindow (compositor.display, + DefaultRootWindow (compositor.display), + -1, -1, 1, 1, 0, CopyFromParent, InputOnly, + CopyFromParent, CWOverrideRedirect, &attrs); + + global_dmabuf = wl_global_create (compositor.wl_display, + &zwp_linux_dmabuf_v1_interface, + /* If writing the format table + failed, don't announce support + for version 4. */ + size >= 0 ? 4 : 3, NULL, HandleBind); + + /* If the format table was successfully created, set its size. */ + format_table_size = size; + + /* Initialize the sentinel node for buffer creation. */ + pending_success.next = &pending_success; + pending_success.last = &pending_success; +} + +void +XLInitDmabuf (void) +{ + xcb_dri3_query_version_cookie_t cookie; + xcb_dri3_query_version_reply_t *reply; + const xcb_query_extension_reply_t *ext; + + ext = xcb_get_extension_data (compositor.conn, &xcb_dri3_id); + reply = NULL; + + if (ext && ext->present) + { + cookie = xcb_dri3_query_version (compositor.conn, 1, 2); + reply = xcb_dri3_query_version_reply (compositor.conn, cookie, + NULL); + + if (!reply) + goto error; + + if (reply->major_version < 1 + || (reply->major_version == 1 + && reply->minor_version < 2)) + goto error; + + dri3_opcode = ext->major_opcode; + ReallyInitDmabuf (); + } + else + error: + fprintf (stderr, "Warning: the X server does not support a new enough version of" + " the DRI3 extension.\nHardware acceleration will not be available.\n"); + + if (reply) + free (reply); +} diff --git a/dnd.c b/dnd.c new file mode 100644 index 0000000..5628303 --- /dev/null +++ b/dnd.c @@ -0,0 +1,3111 @@ +/* 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 . */ + +#include +#include +#include + +#include "compositor.h" + +#include + +/* This module implements the Xdnd protocol. + + Drags between Wayland clients are implemented in seat.c and + data_device.c instead. */ + +/* TODO: Handle XdndActionAsk and allow using keyboard modifiers to + change the selected action. Then, update README. */ + +enum + { + XdndProtocolVersion = 5, + }; + +typedef struct _DndState DndState; +typedef struct _DragState DragState; +typedef struct _WindowCache WindowCache; +typedef struct _WindowCacheEntry WindowCacheEntry; +typedef struct _WindowCacheEntryHeader WindowCacheEntryHeader; + +enum + { + IsMapped = 1, + IsDestroyed = (1 << 2), + IsToplevel = (1 << 3), + IsNotToplevel = (1 << 4), + IsPropertyRead = (1 << 5), + IsShapeDirtied = (1 << 6), + }; + +struct _DndState +{ + /* The source window. */ + Window source_window; + + /* The target window. */ + Window target_window; + + /* The protocol version in use. */ + int proto; + + /* The seat that is being used. */ + Seat *seat; + + /* The key for the seat destruction callback. */ + void *seat_callback; + + /* Array of selection targets, which are MIME types in the Xdnd + protocol, making our interaction with Wayland clients very + convenient. */ + char **targets; + + /* Number of targets in that array. */ + int ntargets; + + /* Monotonically increasing counter. */ + unsigned int serial; + + /* Whether or not non-default values should be used to respond to + drag-and-drop events. */ + Bool respond; + + /* The struct wl_resource (s) associated with this drag and drop + operation. */ + XLList *resources; + + /* The surface associated with this drag and drop session. */ + Surface *surface; + + /* The destroy callback associated with that surface. */ + DestroyCallback *callback; + + /* The source action mask. */ + uint32_t source_actions; + + /* The supported action and preferred action. */ + uint32_t supported_actions, preferred_action; + + /* The chosen DND action. */ + uint32_t used_action; + + /* Whether or not something was accepted. */ + Bool accepted; + + /* Whether or not the transfer finished. */ + Bool finished; + + /* Whether or not the drop has already happened. */ + Bool dropped; + + /* The timestamp to use for accessing selection data. */ + Time timestamp; + + /* The toplevel or child surface the pointer is currently + inside. */ + Surface *child; + + /* The unmap callback for that child. */ + UnmapCallback *unmap_callback; + + /* The version of the XDND protocol being used. */ + int version; +}; + +enum + { + TypeListSet = 1, + MoreThanThreeTargets = (1 << 2), + WaitingForStatus = (1 << 3), + PendingPosition = (1 << 4), + PendingDrop = (1 << 5), + WillAcceptDrop = (1 << 6), + NeedMouseRect = (1 << 7), + SelectionFailed = (1 << 8), + SelectionSet = (1 << 9), + ActionListSet = (1 << 10), + }; + +struct _DragState +{ + /* The seat performing the drag. */ + Seat *seat; + + /* The seat destroy callback. */ + void *seat_key; + + /* The seat modifier callback. */ + void *mods_key; + + /* The window cache. */ + WindowCache *window_cache; + + /* The last coordinates the pointer was seen at. */ + int last_root_x, last_root_y; + + /* The last toplevel window the pointer entered, and the actual + window client messages will be sent to. */ + Window toplevel, target; + + /* The first 3 targets. */ + Atom first_targets[3]; + + /* The protocol version of the target. */ + int version; + + /* Some flags. */ + int flags; + + /* Rectangle within which further position events should not be + sent. */ + XRectangle mouse_rect; + + /* The time at which ownership of the selection was obtained. */ + Time timestamp; + + /* The selected action. */ + Atom action; + + /* The modifiers currently held down. */ + unsigned int modifiers; +}; + +struct _WindowCache +{ + /* The association table between windows and entries. */ + XLAssocTable *entries; + + /* The root window. */ + WindowCacheEntry *root_window; +}; + +struct _WindowCacheEntryHeader +{ + /* The next and last window cache entries. Not set on the root + window. */ + WindowCacheEntry *next, *last; +}; + +struct _WindowCacheEntry +{ + /* The next and last window cache entries. Not set on the root + window. */ + WindowCacheEntry *next, *last; + + /* The XID of the window. */ + Window window; + + /* The XID of the parent. */ + Window parent; + + /* Linked list of children. The first node is a sentinel node that + is really a WindowCacheEntryHeader. */ + WindowCacheEntry *children; + + /* The bounds of the window relative to its parents. */ + int x, y, width, height; + + /* Some flags. The protocol version is flags >> 16 & 0xff; 0 means + XDND is not supported. */ + int flags; + + /* The XDND proxy window. Usually None. */ + Window dnd_proxy; + + /* The region describing its shape. */ + pixman_region32_t shape; + + /* The window cache. */ + WindowCache *cache; + + /* The old event mask. Not set on the root window. */ + unsigned long old_event_mask; + + /* The key for input selection, if this is the root window. */ + RootWindowSelection *input_key; +}; + +/* The global drop state. */ +static DndState dnd_state; + +/* The global drag state. */ +static DragState drag_state; + +/* The DataSource to which XdndFinish events will be set. */ +static DataSource *finish_source; + +/* The version of any XdndFinish event received. */ +static int finish_version; + +/* The action selected at the time of receiving the XdndFinish + event. */ +static Atom finish_action; + +/* The destroy callback for that data source. */ +static void *finish_source_key; + +/* The timeout for that data source. */ +static Timer *finish_timeout; + +/* Forward declaration. */ + +static void FinishDndEntry (void); + +static Seat * +AssignSeat (void) +{ + /* Since the XDND protocol doesn't provide any way to determine the + seat a drag-and-drop operation is originating from, simply return + the first seat to be created. */ + + if (live_seats) + return live_seats->data; + + return NULL; +} + +static void +HandleSeatDestroy (void *data) +{ + dnd_state.seat = NULL; + dnd_state.seat_callback = NULL; + + /* Since the seat has been destroyed, finish the drag and drop + operation. */ + FinishDndEntry (); +} + +static uint32_t +TranslateAction (Atom action) +{ + if (action == XdndActionCopy) + return WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + + if (action == XdndActionMove) + return WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + + if (action == XdndActionAsk) + return WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; + + /* Wayland doesn't have an equivalent to XdndActionPrivate, so fall + back to copy. */ + if (action == XdndActionPrivate) + return WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + + /* Otherwise, return None. */ + return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; +} + +/* Forward declarations. */ + +static void SendStatus (void); +static void RespondToDndDrop (void); + +static void +Accept (struct wl_client *client, struct wl_resource *resource, + uint32_t serial, const char *mime_type) +{ + uint32_t sc; + + sc = (intptr_t) wl_resource_get_user_data (resource); + + if (sc < dnd_state.serial + || dnd_state.source_window == None) + /* This data offer is out of date. */ + return; + + if (wl_resource_get_version (resource) <= 2) + /* In version 2 and below, this doesn't affect anything. */ + return; + + if (!mime_type) + { + if (dnd_state.accepted) + /* The accepted state changed. */ + SendStatus (); + + dnd_state.accepted = False; + } + else + { + if (!dnd_state.accepted) + /* The accepted state changed. */ + SendStatus (); + + dnd_state.accepted = True; + } +} + +static void +Receive (struct wl_client *client, struct wl_resource *resource, + const char *mime_type, int fd) +{ + uint32_t serial; + + serial = (intptr_t) wl_resource_get_user_data (resource); + + if (serial < dnd_state.serial + || dnd_state.source_window == None) + { + /* This data offer is out of date. */ + close (fd); + return; + } + + XLReceiveDataFromSelection (dnd_state.timestamp, XdndSelection, + InternAtom (mime_type), fd); +} + +static void +Destroy (struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static void +Finish (struct wl_client *client, struct wl_resource *resource) +{ + uint32_t serial; + + serial = (intptr_t) wl_resource_get_user_data (resource); + + if (serial < dnd_state.serial + || !dnd_state.used_action + || !dnd_state.accepted + || dnd_state.finished) + { + wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_FINISH, + "finish called at inopportune moment"); + return; + } + + dnd_state.finished = True; + + /* If XdndDrop was received, send the XdndFinished message. */ + if (dnd_state.dropped) + RespondToDndDrop (); +} + +static Atom +ConvertAction (uint32_t action) +{ + if (action == WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) + return XdndActionCopy; + + if (action == WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + return XdndActionMove; + + if (action == WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) + return XdndActionAsk; + + return None; +} + +static void +SendStatus (void) +{ + XEvent event; + + if (dnd_state.dropped) + return; + + memset (&event, 0, sizeof event); + event.xclient.type = ClientMessage; + event.xclient.window = dnd_state.source_window; + event.xclient.message_type = XdndStatus; + event.xclient.format = 32; + + event.xclient.data.l[0] = dnd_state.target_window; + + if (dnd_state.respond) + { + if ((dnd_state.used_action + != WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE) + && dnd_state.accepted) + event.xclient.data.l[1] = 1; + + if (dnd_state.version >= 3) + event.xclient.data.l[4] = ConvertAction (dnd_state.used_action); + else + /* The version of the data device manager protocol spoken by + the client doesn't support actions. Use XdndActionPrivate. */ + event.xclient.data.l[4] = XdndActionPrivate; + } + + CatchXErrors (); + XSendEvent (compositor.display, dnd_state.source_window, + False, NoEventMask, &event); + UncatchXErrors (NULL); +} + +static void +UpdateUsedAction (void) +{ + uint32_t intersection, old; + XLList *list; + + old = dnd_state.used_action; + + /* First, see if the preferred action is supported. If it is, + simply use it. */ + if (dnd_state.source_actions & dnd_state.preferred_action) + dnd_state.used_action = dnd_state.preferred_action; + else + { + intersection = (dnd_state.supported_actions + & dnd_state.source_actions); + + /* Now, try the following actions in order. */ + if (intersection & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) + dnd_state.used_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + else if (intersection & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + dnd_state.used_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE; + else if (intersection & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) + dnd_state.used_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK; + else + dnd_state.used_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + } + + /* Send the updated action to clients if it changed. */ + if (old != dnd_state.used_action) + { + for (list = dnd_state.resources; list; list = list->next) + { + if (wl_resource_get_version (list->data) >= 3) + wl_data_offer_send_action (list->data, + dnd_state.used_action); + } + } + + /* Send an XdndStatus if the action changed. */ + SendStatus (); +} + +static void +SetActions (struct wl_client *client, struct wl_resource *resource, + uint32_t dnd_actions, uint32_t preferred_action) +{ + uint32_t serial; + + serial = (intptr_t) wl_resource_get_user_data (resource); + + if (serial < dnd_state.serial + || !dnd_state.source_window) + /* This data offer is out of date. */ + return; + + if (dnd_actions & ~(WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY + | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE + | WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) + || (preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK + && preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE + && preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY + && preferred_action != WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE)) + wl_resource_post_error (resource, WL_DATA_OFFER_ERROR_INVALID_ACTION, + "invalid action or action mask among: %u %u", + dnd_actions, preferred_action); + + /* Otherwise, update the DND state with the supported action. */ + dnd_state.supported_actions = dnd_actions; + dnd_state.preferred_action = preferred_action; + + /* And send the updated state. */ + UpdateUsedAction (); +} + +static const struct wl_data_offer_interface wl_data_offer_impl = + { + .accept = Accept, + .receive = Receive, + .destroy = Destroy, + .finish = Finish, + .set_actions = SetActions, + }; + +static void +HandleResourceDestroy (struct wl_resource *resource) +{ + uint32_t serial; + + serial = (intptr_t) wl_resource_get_user_data (resource); + + if (serial >= dnd_state.serial + && dnd_state.source_window != None) + { + /* Send XdndFinish if it hasn't already been sent. Since the + resource has been destroyed without previously completing, + signal an error if its version is 3 or later. */ + + if (wl_resource_get_version (resource) >= 3) + dnd_state.accepted = False; + + if (dnd_state.dropped) + RespondToDndDrop (); + + /* Remove the resource from the resource list. */ + dnd_state.resources = XLListRemove (dnd_state.resources, + resource); + + /* If there are no more resources, finish the drag and drop + operation. Note that this might've already been done by + RespondToDndDrop, but it is safe to call FinishDndEntry + twice. */ + if (!dnd_state.resources) + FinishDndEntry (); + } +} + +static struct wl_resource * +CreateOffer (struct wl_client *client, int version) +{ + struct wl_resource *resource; + + resource = wl_resource_create (client, &wl_data_offer_interface, + version, 0); + + if (!resource) + return NULL; + + wl_resource_set_implementation (resource, &wl_data_offer_impl, + (void *) (intptr_t) dnd_state.serial, + HandleResourceDestroy); + dnd_state.resources = XLListPrepend (dnd_state.resources, + resource); + + /* If version <= 2, then the drag-and-drop operation should always + be accepted, no matter whether or not accept is called. */ + if (version <= 2) + dnd_state.accepted = True; + + if (!dnd_state.version || dnd_state.version > version) + dnd_state.version = version; + + return resource; +} + +static void +SendOffers (struct wl_resource *resource) +{ + int i; + + for (i = 0; i < dnd_state.ntargets; ++i) + { + if (dnd_state.targets[i]) + wl_data_offer_send_offer (resource, + dnd_state.targets[i]); + } +} + +static void +FinishDndEntry (void) +{ + int i; + + if (dnd_state.seat && dnd_state.resources + /* Don't send leave if a drop already happened. */ + && !dnd_state.dropped) + XLDataDeviceSendLeave (dnd_state.seat, dnd_state.surface, + NULL); + + dnd_state.source_window = None; + dnd_state.target_window = None; + dnd_state.surface = NULL; + dnd_state.proto = 0; + + if (dnd_state.callback) + XLSurfaceCancelRunOnFree (dnd_state.callback); + dnd_state.callback = NULL; + + if (dnd_state.seat) + XLSeatCancelDestroyListener (dnd_state.seat_callback); + dnd_state.seat = NULL; + dnd_state.seat_callback = NULL; + + if (dnd_state.child) + XLSurfaceCancelUnmapCallback (dnd_state.unmap_callback); + dnd_state.child = NULL; + dnd_state.unmap_callback = NULL; + + for (i = 0; i < dnd_state.ntargets; ++i) + { + if (dnd_state.targets[i]) + XFree (dnd_state.targets[i]); + } + + XLFree (dnd_state.targets); + dnd_state.ntargets = 0; + dnd_state.targets = NULL; + dnd_state.source_actions = 0; + dnd_state.supported_actions = 0; + dnd_state.preferred_action = 0; + dnd_state.used_action = 0; + dnd_state.version = 0; + dnd_state.accepted = False; + dnd_state.finished = False; + dnd_state.dropped = False; + dnd_state.timestamp = CurrentTime; + + /* The resources are not destroyed, since the client will do that + later. */ + XLListFree (dnd_state.resources, NULL); + dnd_state.resources = NULL; +} + +static void +RespondToDndDrop (void) +{ + XEvent event; + + memset (&event, 0, sizeof event); + event.xclient.type = ClientMessage; + event.xclient.window = dnd_state.source_window; + event.xclient.message_type = XdndFinished; + event.xclient.format = 32; + + event.xclient.data.l[0] = dnd_state.target_window; + + if (dnd_state.proto >= 5 + && dnd_state.used_action && dnd_state.accepted + && dnd_state.seat && dnd_state.respond) + { + /* This determines whether or not the drag and drop operation + was accepted. */ + event.xclient.data.l[1] = 1; + + if (dnd_state.version >= 3) + /* And this specifies the action that was really taken. */ + event.xclient.data.l[2] = ConvertAction (dnd_state.used_action); + else + /* The version of the data device manager protocol spoken by + the client doesn't support actions. Use XdndActionPrivate. */ + event.xclient.data.l[2] = XdndActionPrivate; + } + + CatchXErrors (); + XSendEvent (compositor.display, dnd_state.source_window, + False, NoEventMask, &event); + UncatchXErrors (NULL); + + /* Now that XdndFinished has been sent, the drag and drop operation + is complete. */ + FinishDndEntry (); +} + +static void +HandleSurfaceDestroy (void *data) +{ + dnd_state.surface = NULL; + dnd_state.callback = NULL; +} + +static void +HandleDndEntry (Surface *target, Window source, Atom *targets, + int ntargets, int proto) +{ + int i; + char **names; + + if (dnd_state.source_window) + { + fprintf (stderr, "XdndEnter received while a drag-and-drop operation" + " is in progress; overriding current drag-and-drop operation\n"); + FinishDndEntry (); + } + + dnd_state.proto = proto; + dnd_state.source_window = source; + dnd_state.surface = target; + dnd_state.callback = XLSurfaceRunOnFree (dnd_state.surface, + HandleSurfaceDestroy, NULL); + + /* Retrieve the atoms inside the targets list. */ + names = XLCalloc (ntargets, sizeof *names); + + XGetAtomNames (compositor.display, targets, + ntargets, names); + + /* Enter the names of the targets into the atom table so that they + can be interned without roundtrips in the future. */ + for (i = 0; i < ntargets; ++i) + { + if (names[i]) + ProvideAtom (names[i], targets[i]); + } + + /* Find a seat to use for this drag-and-drop operation. */ + dnd_state.seat = AssignSeat (); + + /* If a seat was found, listen for its destruction. After the + initiating seat is destroyed (or if none was found), we reply to + all future drag-and-drop messages with dummy values. */ + if (dnd_state.seat) + dnd_state.seat_callback = XLSeatRunOnDestroy (dnd_state.seat, + HandleSeatDestroy, + NULL); + + /* Initialize available data types from the atom names. */ + dnd_state.targets = names; + dnd_state.ntargets = ntargets; + + /* Initialize other drag-and-drop state. */ + dnd_state.respond = False; + + /* There shouldn't be any leftovers from the last session. */ + XLAssert (dnd_state.resources == NULL); + + /* Initialize the target window. */ + dnd_state.target_window = XLWindowFromSurface (target); + + /* Increase the state counter to make all out-of-date data offers + invalid. */ + dnd_state.serial++; +} + +static Atom * +ReadXdndTypeList (Window window, int *nitems_return) +{ + Atom actual_type; + int rc, actual_format; + unsigned long nitems, bytes_remaining; + unsigned char *tmp_data; + + tmp_data = NULL; + + CatchXErrors (); + rc = XGetWindowProperty (compositor.display, window, + XdndTypeList, 0, LONG_MAX, + False, XA_ATOM, &actual_type, + &actual_format, &nitems, + &bytes_remaining, &tmp_data); + if (UncatchXErrors (NULL) || rc != Success || actual_format != 32 + || !tmp_data || actual_type != XA_ATOM || nitems < 1) + { + if (tmp_data) + XFree (tmp_data); + + return NULL; + } + + *nitems_return = nitems; + return (Atom *) tmp_data; +} + +static Bool +HandleXdndEnterEvent (Surface *surface, XEvent *event) +{ + Atom *targets; + Atom builtin[3]; + int ntargets, proto; + + if (event->xclient.data.l[1] & 1) + /* There are more than 3 targets; retrieve them from the + XdndTypeList property. */ + targets = ReadXdndTypeList (event->xclient.data.l[0], + &ntargets); + else + { + /* Otherwise, the first three properties contain the selection + targets. */ + targets = builtin; + ntargets = 0; + + if (event->xclient.data.l[2]) + builtin[ntargets++] = event->xclient.data.l[2]; + + if (event->xclient.data.l[3]) + builtin[ntargets++] = event->xclient.data.l[3]; + + if (event->xclient.data.l[4]) + builtin[ntargets++] = event->xclient.data.l[4]; + } + + if (!targets) + /* For some reason we failed to retrieve XdndTypeList. Ignore the + XdndEnter event. */ + return True; + + proto = MIN (event->xclient.data.l[1] >> 24, + XdndProtocolVersion); + + HandleDndEntry (surface, event->xclient.data.l[0], + targets, ntargets, proto); + + if (event->xclient.data.l[1] & 1) + /* Now, free the type list, which was allocated by Xlib. */ + XFree (targets); + + return True; +} + +static void +HandleChildUnmap (void *data) +{ + /* The child was unmapped. */ + + if (dnd_state.seat) + XLDataDeviceSendLeave (dnd_state.seat, dnd_state.child, + NULL); + XLSurfaceCancelUnmapCallback (dnd_state.unmap_callback); + + dnd_state.child = NULL; + dnd_state.unmap_callback = NULL; + + /* Free our record of the data offers introduced at entry time; it + is assumed that the client will delete them too. */ + XLListFree (dnd_state.resources, NULL); + dnd_state.resources = NULL; +} + +static Bool +HandleMotion (Surface *toplevel, int x, int y, uint32_t action, + int *x_out, int *y_out) +{ + Subcompositor *subcompositor; + View *view; + int x_off, y_off; + Surface *child; + DndOfferFuncs funcs; + XLList *tem; + + subcompositor = ViewGetSubcompositor (toplevel->view); + + /* Find the view underneath the subcompositor. */ + view = SubcompositorLookupView (subcompositor, x, y, + &x_off, &y_off); + child = ViewGetData (view); + + /* Compute the surface-relative coordinates and scale them. */ + *x_out = (x - x_off) / global_scale_factor; + *y_out = (y - y_off) / global_scale_factor; + + if (dnd_state.child == child) + /* If nothing changed, don't do anything. */ + return False; + + /* If the pointer was previously in a different surface, leave + it. */ + if (dnd_state.child) + { + XLDataDeviceSendLeave (dnd_state.seat, dnd_state.child, + NULL); + XLSurfaceCancelUnmapCallback (dnd_state.unmap_callback); + + dnd_state.child = NULL; + dnd_state.unmap_callback = NULL; + + /* Free our record of the data offers introduced at entry time; + it is assumed that the client will delete them too. */ + XLListFree (dnd_state.resources, NULL); + dnd_state.resources = NULL; + dnd_state.used_action = 0; + dnd_state.preferred_action = 0; + dnd_state.supported_actions = 0; + dnd_state.accepted = False; + + if (dnd_state.version <= 2) + dnd_state.accepted = True; + } + + /* Now, enter the new surface. */ + if (child) + { + dnd_state.child = child; + dnd_state.unmap_callback + = XLSurfaceRunAtUnmap (child, HandleChildUnmap, NULL); + funcs.create = CreateOffer; + funcs.send_offers = SendOffers; + + /* Create the offers and send data to the clients. */ + XLDataDeviceMakeOffers (dnd_state.seat, funcs, child, *x_out, + *y_out); + /* Send source actions to each resource created. */ + for (tem = dnd_state.resources; tem; tem = tem->next) + { + if (wl_resource_get_version (tem->data) >= 3) + wl_data_offer_send_source_actions (tem->data, action); + } + + /* Now compute whether or not we should respond with actual + values. */ + dnd_state.respond = (dnd_state.resources != NULL); + } + + return child != NULL; +} + +static uint32_t +ReadDndActionList (Window window) +{ + uint32_t mask; + Atom actual_type, *atoms; + int rc, actual_format; + unsigned long nitems, bytes_remaining, i; + unsigned char *tmp_data; + + mask = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + tmp_data = NULL; + + CatchXErrors (); + rc = XGetWindowProperty (compositor.display, window, + XdndActionList, 0, LONG_MAX, + False, XA_ATOM, &actual_type, + &actual_format, &nitems, + &bytes_remaining, &tmp_data); + if (UncatchXErrors (NULL) || rc != Success || actual_format != 32 + || !tmp_data || actual_type != XA_ATOM || nitems < 1) + { + if (tmp_data) + XFree (tmp_data); + + return WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + } + + atoms = (Atom *) tmp_data; + + for (i = 0; i < nitems; ++i) + mask |= TranslateAction (atoms[i]); + + return mask; +} + +static Bool +HandleXdndPositionEvent (Surface *surface, XEvent *event) +{ + int root_x, root_y, x, y; + Window child; + XLList *tem; + uint32_t action; + Bool sent_actions; + + /* TODO: handle subsurfaces. */ + + if (event->xclient.data.l[0] != dnd_state.source_window) + /* The message is coming from the wrong window, or drag and drop + has not yet been set up. */ + return True; + + if (surface != dnd_state.surface) + /* This message is being delivered to the wrong surface. */ + return True; + + /* Extract the root X and root Y from the event. */ + root_x = event->xclient.data.l[2] >> 16; + root_y = event->xclient.data.l[2] & 0xffff; + + /* Translate the coordinates to the surface's window. */ + XTranslateCoordinates (compositor.display, + DefaultRootWindow (compositor.display), + XLWindowFromSurface (surface), + root_x, root_y, &x, &y, &child); + + /* Compute the action. TODO: handle multiple actions. */ + action = TranslateAction (event->xclient.data.l[4]); + + /* Handle mouse motion. */ + sent_actions = HandleMotion (surface, x, y, action, &x, &y); + + if (action == WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) + { + if (!(action & dnd_state.source_actions)) + /* Fetch the list of available actions, and give that to the + client along with the regular action list, if XdndActionAsk + is being specified for the first time. */ + action |= ReadDndActionList (dnd_state.source_window); + else + /* Otherwise, preserve the action list that was already + read. */ + action |= dnd_state.source_actions; + } + /* Send actions from all data offers. */ + if (dnd_state.resources && !sent_actions) + { + /* If action is different from the current source action, send + the new source action to the client. */ + + if (action != dnd_state.source_actions) + { + /* Send source actions to each resource created. */ + for (tem = dnd_state.resources; tem; tem = tem->next) + { + if (wl_resource_get_version (tem->data) >= 3) + wl_data_offer_send_source_actions (tem->data, action); + } + + /* Update the chosen action. */ + UpdateUsedAction (); + } + } + + dnd_state.source_actions = action; + dnd_state.timestamp = event->xclient.data.l[3]; + + if (dnd_state.seat && dnd_state.child) + XLDataDeviceSendMotion (dnd_state.seat, surface, + /* l[3] is the timestamp of the + movement. */ + x, y, event->xclient.data.l[3]); + + /* Send an XdndStatus event in response. */ + SendStatus (); + + return True; +} + +static Bool +HandleXdndLeaveEvent (Surface *surface, XEvent *event) +{ + if (event->xclient.data.l[0] != dnd_state.source_window) + /* The message is coming from the wrong window, or drag and drop + has not yet been set up. */ + return True; + + if (surface != dnd_state.surface) + /* This message is being delivered to the wrong surface. */ + return True; + + FinishDndEntry (); + + return True; +} + +static Bool +HandleXdndDropEvent (Surface *surface, XEvent *event) +{ + if (event->xclient.data.l[0] != dnd_state.source_window) + /* The message is coming from the wrong window, or drag and drop + has not yet been set up. */ + return True; + + if (surface != dnd_state.surface) + /* This message is being delivered to the wrong surface. */ + return True; + + dnd_state.timestamp = event->xclient.data.l[2]; + + XLDataDeviceSendDrop (dnd_state.seat, surface); + + /* If finish has already been called, send XdndFinish to the source, + and complete the transfer. */ + if (dnd_state.finished + /* Also respond (but with default values) if the transfer cannot + continue because the seat has been destroyed. */ + || !dnd_state.respond + || !dnd_state.seat + /* Also respond if the resource version is less than 3. */ + || dnd_state.version <= 2) + RespondToDndDrop (); + + /* Set dnd_state.dropped. */ + dnd_state.dropped = True; + + return True; +} + +void +XLDndWriteAwarenessProperty (Window window) +{ + unsigned long version; + + version = XdndProtocolVersion; + XChangeProperty (compositor.display, window, + XdndAware, XA_ATOM, 32, PropModeReplace, + (unsigned char *) &version, 1); +} + +/* Keep in mind that the given surface should be a toplevel surface + with a subcompositor attached. */ + +Bool +XLDndFilterClientMessage (Surface *surface, XEvent *event) +{ + if (event->xclient.message_type == XdndEnter) + return HandleXdndEnterEvent (surface, event); + else if (event->xclient.message_type == XdndPosition) + return HandleXdndPositionEvent (surface, event); + else if (event->xclient.message_type == XdndLeave) + return HandleXdndLeaveEvent (surface, event); + else if (event->xclient.message_type == XdndDrop) + return HandleXdndDropEvent (surface, event); + + return False; +} + +/* Window cache management. This allows us to avoid looking up the + window shape each time we encounter a window. */ + + +static void +AddAfter (WindowCacheEntry *entry, WindowCacheEntry *after) +{ + entry->next = after->next; + entry->last = after; + after->next->last = entry; + after->next = entry; +} + +/* Forward declaration. */ +static void AddChildren (WindowCacheEntry *, xcb_query_tree_reply_t *); + +static void +InitRegionWithRects (pixman_region32_t *region, + xcb_shape_get_rectangles_reply_t *rects) +{ + pixman_box32_t *boxes; + xcb_rectangle_t *rectangles; + int nrects, i; + + nrects = xcb_shape_get_rectangles_rectangles_length (rects); + + if (nrects > 64) + boxes = XLMalloc (sizeof *boxes * nrects); + else + boxes = alloca (sizeof *boxes * nrects); + + rectangles = xcb_shape_get_rectangles_rectangles (rects); + + for (i = 0; i < nrects; ++i) + { + /* Convert the X rectangles to pixman boxes. Pixman (X server) + boxes have x2, y2, set to a value one pixel larger than the + actual maximum pixels set, which is why we do not subtract 1 + from rect->x + rect->width. */ + + boxes[i].x1 = rectangles[i].x; + boxes[i].y1 = rectangles[i].y; + boxes[i].x2 = rectangles[i].x + rectangles[i].width; + boxes[i].y2 = rectangles[i].y + rectangles[i].height; + } + + /* Initialize the region with those boxes. */ + pixman_region32_init_rects (region, boxes, nrects); + + if (nrects > 64) + XLFree (boxes); +} + +static void +IntersectRegionWith (pixman_region32_t *region, + xcb_shape_get_rectangles_reply_t *rects) +{ + pixman_box32_t *boxes; + xcb_rectangle_t *rectangles; + int nrects, i; + pixman_region32_t temp; + + nrects = xcb_shape_get_rectangles_rectangles_length (rects); + + if (nrects > 64) + boxes = XLMalloc (sizeof *boxes * nrects); + else + boxes = alloca (sizeof *boxes * nrects); + + rectangles = xcb_shape_get_rectangles_rectangles (rects); + + for (i = 0; i < nrects; ++i) + { + /* Convert the X rectangles to pixman boxes. Pixman (X server) + boxes have x2, y2, set to a value one pixel larger than the + actual maximum pixels set, which is why we do not subtract 1 + from rect->x + rect->width. */ + + boxes[i].x1 = rectangles[i].x; + boxes[i].y1 = rectangles[i].y; + boxes[i].x2 = rectangles[i].x + rectangles[i].width; + boxes[i].y2 = rectangles[i].y + rectangles[i].height; + } + + /* Initialize the temporary region with those boxes. */ + pixman_region32_init_rects (&temp, boxes, nrects); + + if (nrects > 64) + XLFree (boxes); + + /* Intersect the other region with this one. */ + pixman_region32_intersect (region, region, &temp); + + /* Free the temporary region. */ + pixman_region32_fini (&temp); +} + +static void +AddChild (WindowCacheEntry *parent, Window window, + xcb_get_geometry_reply_t *geometry, + xcb_query_tree_reply_t *children, + xcb_get_window_attributes_reply_t *attributes, + xcb_shape_get_rectangles_reply_t *bounding, + xcb_shape_get_rectangles_reply_t *input) +{ + WindowCacheEntry *entry; + unsigned long mask; + + entry = XLCalloc (1, sizeof *entry); + + entry->window = window; + entry->parent = parent->window; + entry->x = geometry->x; + entry->y = geometry->y; + entry->width = geometry->width; + entry->height = geometry->height; + entry->children = XLMalloc (sizeof (WindowCacheEntryHeader)); + entry->children->next = entry->children; + entry->children->last = entry->children; + + InitRegionWithRects (&entry->shape, bounding); + IntersectRegionWith (&entry->shape, input); + + entry->cache = parent->cache; + entry->old_event_mask = attributes->your_event_mask; + + if (attributes->map_state != XCB_MAP_STATE_UNMAPPED) + entry->flags |= IsMapped; + + mask = (entry->old_event_mask + | SubstructureNotifyMask + | PropertyChangeMask); + + /* Select for SubstructureNotifyMask, so hierarchy events can be + received for it and its children. X errors should be caught + around here. In addition, we also ask for PropertyNotifyMask, so + that IsToplevel/IsNotToplevel can be cleared correctly in + response to changes of the WM_STATE property. */ + XSelectInput (compositor.display, window, mask); + + /* Select for ShapeNotify events as well. This allows us to update + the shapes of each toplevel window along the way. */ + xcb_shape_select_input (compositor.conn, window, 1); + + /* Insert the child in front of the window list. */ + AddAfter (entry, parent->children); + + /* Add this child to the assoc table. */ + XLMakeAssoc (parent->cache->entries, window, + entry); + + /* Add this child's children. */ + AddChildren (entry, children); +} + +static void +AddChildren (WindowCacheEntry *entry, xcb_query_tree_reply_t *reply) +{ + xcb_window_t *windows; + int n_children, i; + xcb_get_geometry_cookie_t *geometries; + xcb_query_tree_cookie_t *children; + xcb_get_window_attributes_cookie_t *attributes; + xcb_shape_get_rectangles_cookie_t *boundings; + xcb_shape_get_rectangles_cookie_t *inputs; + xcb_get_geometry_reply_t *geometry; + xcb_query_tree_reply_t *tree; + xcb_get_window_attributes_reply_t *attribute; + xcb_shape_get_rectangles_reply_t *bounding; + xcb_shape_get_rectangles_reply_t *input; + xcb_generic_error_t *error, *error1, *error2, *error3, *error4; + xcb_get_geometry_reply_t **all_geometries; + xcb_query_tree_reply_t **all_trees; + xcb_get_window_attributes_reply_t **all_attributes; + xcb_shape_get_rectangles_reply_t **all_boundings; + xcb_shape_get_rectangles_reply_t **all_inputs; + + error = NULL; + error1 = NULL; + error2 = NULL; + error3 = NULL; + error4 = NULL; + + windows = xcb_query_tree_children (reply); + n_children = xcb_query_tree_children_length (reply); + + /* First, issue all the requests for necessary information. */ + geometries = XLMalloc (sizeof *geometries * n_children); + children = XLMalloc (sizeof *children * n_children); + attributes = XLMalloc (sizeof *attributes * n_children); + boundings = XLMalloc (sizeof *boundings * n_children); + inputs = XLMalloc (sizeof *inputs * n_children); + all_geometries = XLCalloc (n_children, sizeof *all_geometries); + all_trees = XLCalloc (n_children, sizeof *all_trees); + all_attributes = XLCalloc (n_children, sizeof *all_attributes); + all_boundings = XLCalloc (n_children, sizeof *all_boundings); + all_inputs = XLCalloc (n_children, sizeof *all_inputs); + + for (i = 0; i < n_children; ++i) + { + geometries[i] = xcb_get_geometry (compositor.conn, + windows[i]); + children[i] = xcb_query_tree (compositor.conn, + windows[i]); + attributes[i] = xcb_get_window_attributes (compositor.conn, + windows[i]); + boundings[i] = xcb_shape_get_rectangles (compositor.conn, + windows[i], + XCB_SHAPE_SK_BOUNDING); + inputs[i] = xcb_shape_get_rectangles (compositor.conn, + windows[i], + XCB_SHAPE_SK_INPUT); + } + + /* Next, retrieve selection replies. */ + for (i = 0; i < n_children; ++i) + { + geometry = xcb_get_geometry_reply (compositor.conn, + geometries[i], + &error); + tree = xcb_query_tree_reply (compositor.conn, + children[i], + &error1); + attribute = xcb_get_window_attributes_reply (compositor.conn, + attributes[i], + &error2); + bounding = xcb_shape_get_rectangles_reply (compositor.conn, + boundings[i], + &error3); + input = xcb_shape_get_rectangles_reply (compositor.conn, + inputs[i], + &error4); + + if (error || error1 || error2 || error3 || error4 + || !geometry || !tree || !attribute || !bounding || !input) + { + if (error) + free (error); + + if (error1) + free (error1); + + if (error2) + free (error2); + + if (error3) + free (error3); + + if (error4) + free (error4); + + if (geometry) + free (geometry); + + if (tree) + free (tree); + + if (attribute) + free (attribute); + + if (bounding) + free (bounding); + + if (input) + free (input); + + /* If an error occured, don't save the window. */ + continue; + } + + /* Save the geometry and tree replies. */ + all_geometries[i] = geometry; + all_trees[i] = tree; + all_attributes[i] = attribute; + all_boundings[i] = bounding; + all_inputs[i] = input; + } + + /* And prepend all of the windows for which we got valid + replies. */ + for (i = 0; i < n_children; ++i) + { + if (!all_geometries[i]) + continue; + + AddChild (entry, windows[i], all_geometries[i], + all_trees[i], all_attributes[i], + all_boundings[i], all_inputs[i]); + + free (all_geometries[i]); + free (all_trees[i]); + free (all_attributes[i]); + free (all_boundings[i]); + free (all_inputs[i]); + } + + /* Free all the allocated temporary data. */ + XLFree (geometries); + XLFree (children); + XLFree (attributes); + XLFree (boundings); + XLFree (inputs); + XLFree (all_geometries); + XLFree (all_trees); + XLFree (all_attributes); + XLFree (all_boundings); + XLFree (all_inputs); +} + +static void +MakeRootWindowEntry (WindowCache *cache) +{ + WindowCacheEntry *entry; + xcb_get_geometry_cookie_t geometry_cookie; + xcb_query_tree_cookie_t tree_cookie; + Window root; + xcb_get_geometry_reply_t *geometry; + xcb_query_tree_reply_t *tree; + + root = DefaultRootWindow (compositor.display); + + entry = XLCalloc (1, sizeof *entry); + entry->window = DefaultRootWindow (compositor.display); + entry->parent = None; + + entry->children = XLMalloc (sizeof (WindowCacheEntryHeader)); + entry->children->next = entry->children; + entry->children->last = entry->children; + + /* Obtain the geometry of the root window, and its children. */ + geometry_cookie = xcb_get_geometry (compositor.conn, root); + tree_cookie = xcb_query_tree (compositor.conn, root); + + /* Get the replies from those requests. */ + geometry = xcb_get_geometry_reply (compositor.conn, geometry_cookie, + NULL); + tree = xcb_query_tree_reply (compositor.conn, tree_cookie, NULL); + + if (!geometry || !tree) + { + /* This should not happen in principle. */ + fprintf (stderr, "failed to obtain window geometry or tree" + " of root window"); + abort (); + } + + entry->x = geometry->x; + entry->y = geometry->y; + entry->width = geometry->width; + entry->height = geometry->height; + entry->flags |= IsMapped; + + /* The root window shouldn't have an input shape. */ + pixman_region32_init_rect (&entry->shape, entry->x, + entry->y, entry->width, + entry->height); + + /* Select for SubstructureNotifyMask on the root window. */ + entry->input_key + = XLSelectInputFromRootWindow (SubstructureNotifyMask); + + /* Attach the entry to the cache. */ + entry->cache = cache; + cache->root_window = entry; + XLMakeAssoc (cache->entries, root, entry); + + /* Add children to this window cache. */ + CatchXErrors (); + AddChildren (entry, tree); + UncatchXErrors (NULL); + + free (geometry); + free (tree); +} + +static WindowCache * +AllocWindowCache (void) +{ + WindowCache *cache; + + cache = XLMalloc (sizeof *cache); + cache->entries = XLCreateAssocTable (2048); + MakeRootWindowEntry (cache); + + return cache; +} + +static void +FreeWindowCacheEntry (WindowCacheEntry *entry) +{ + WindowCacheEntry *next, *last; + + /* First, free all the children of the entry. */ + next = entry->children->next; + while (next != entry->children) + { + last = next; + next = next->next; + + FreeWindowCacheEntry (last); + } + + /* Remove the association. */ + XLDeleteAssoc (entry->cache->entries, + entry->window); + + /* Free the sentinel node. */ + XLFree (entry->children); + + if (entry->last) + { + /* Unlink the entry, unless it is the root window. */ + entry->last->next = entry->next; + entry->next->last = entry->last; + + if (!(entry->flags & IsDestroyed)) + { + /* Revert back to the old event mask. */ + XSelectInput (compositor.display, entry->window, + entry->old_event_mask); + + /* Also stop selecting for ShapeNotify events. */ + xcb_shape_select_input (compositor.conn, + entry->window, 0); + } + } + else + /* This is the root window; stop selecting for + SubstructureNotifyMask. */ + XLDeselectInputFromRootWindow (entry->input_key); + + /* Free the region. */ + pixman_region32_fini (&entry->shape); + + /* Free the entry itself. */ + XLFree (entry); +} + +static void +FreeWindowCache (WindowCache *cache) +{ + /* This prevents BadWindow errors from trying to destroy a deleted + entry. */ + CatchXErrors (); + + /* Free the root window. */ + FreeWindowCacheEntry (cache->root_window); + + UncatchXErrors (NULL); + + /* And the assoc table. */ + XLDestroyAssocTable (cache->entries); + + /* Free the cache. */ + XLFree (cache); +} + +static void +UnlinkWindowCacheEntry (WindowCacheEntry *entry) +{ + entry->last->next = entry->next; + entry->next->last = entry->last; +} + +static void +HandleCirculateNotify (WindowCache *cache, XEvent *event) +{ + WindowCacheEntry *parent, *window; + + if (event->xcirculate.event == event->xcirculate.window) + /* This is the result of StructureNotifyMask, and the parent + window cannot be accessed through the event. */ + return; + + parent = XLLookUpAssoc (cache->entries, event->xcirculate.event); + + if (!parent) + return; + + window = XLLookUpAssoc (cache->entries, event->xcirculate.window); + + if (window) + return; + + + XLAssert (window->parent == event->xcirculate.event); + + /* If the window has been recirculated to the top, relink it + immediately after the list. Otherwise, it has been recirculated + to the bottom, so place it before the first element of the + list. */ + + UnlinkWindowCacheEntry (window); + + if (event->xcirculate.place == PlaceOnTop) + AddAfter (window->next, parent->children); + else + AddAfter (window->next, parent->children->last); +} + +static void +HandleConfigureNotify (WindowCache *cache, XEvent *event) +{ + WindowCacheEntry *window, *parent, *next; + + if (event->xconfigure.event == event->xconfigure.window) + /* This is the result of StructureNotifyMask, and the parent + window cannot be accessed through the event. */ + return; + + window = XLLookUpAssoc (cache->entries, event->xconfigure.window); + parent = XLLookUpAssoc (cache->entries, event->xconfigure.event); + + /* Reinitialize the contents of the window with the new + information. */ + if (event->xconfigure.x != window->x + || event->xconfigure.y != window->y + || event->xconfigure.width != window->width + || event->xconfigure.height != window->height) + { + window->x = event->xconfigure.x; + window->y = event->xconfigure.y; + window->width = event->xconfigure.width; + window->height = event->xconfigure.height; + + /* If the window is unshaped, then the ConfigureNotify could've + changed the actual shape of the window. Mark the shape as + dirty. */ + pixman_region32_clear (&window->shape); + window->flags |= IsShapeDirtied; + } + + if (!parent) + /* This is the root window or something like it. */ + return; + + /* Move the window to the right place in the stacking order. If + event->xconfigure.above is None, this window is at the bottom. + If it is anywhere else, move it there. */ + if (event->xconfigure.above == None) + { + if (window->last == parent->children) + /* This window is already at the bottom... */ + return; + + /* Unlink the window and relink it at the end of the parent. */ + UnlinkWindowCacheEntry (window); + + /* Move the child to the end of the window list. */ + AddAfter (window, parent->children->last); + } + else if (window->next == parent->children + || window->next->window != event->xconfigure.above) + { + /* Find the item corresponding to the sibling. */ + next = parent->children->next; + + while (next != parent->children) + { + if (next->window == event->xconfigure.above) + { + /* Move the item on top of next by placing it before + next. */ + UnlinkWindowCacheEntry (window); + AddAfter (window, next->last); + break; + } + + next = next->next; + } + + /* This shouldn't be reached if no entry was found. I don't + know what to do in this case. */ + } +} + +static void +HandleCreateNotify (WindowCache *cache, XEvent *event) +{ + WindowCacheEntry *parent; + xcb_get_geometry_cookie_t geometry_cookie; + xcb_query_tree_cookie_t tree_cookie; + xcb_get_window_attributes_cookie_t attributes_cookie; + xcb_shape_get_rectangles_cookie_t bounding_cookie; + xcb_shape_get_rectangles_cookie_t input_cookie; + xcb_get_geometry_reply_t *geometry; + xcb_query_tree_reply_t *tree; + xcb_get_window_attributes_reply_t *attributes; + xcb_shape_get_rectangles_reply_t *bounding; + xcb_shape_get_rectangles_reply_t *input; + xcb_generic_error_t *error, *error1, *error2, *error3, *error4; + + error = NULL; + error1 = NULL; + error2 = NULL; + error3 = NULL; + error4 = NULL; + + parent = XLLookUpAssoc (cache->entries, event->xcreatewindow.parent); + + if (!parent) + return; + + /* If the window already exists (this can happen if AddWindow adds + children before we get the CreateNotify event), just return. */ + if (XLLookUpAssoc (cache->entries, event->xcreatewindow.window)) + return; + + /* Add the window in front of the parent. */ + geometry_cookie = xcb_get_geometry (compositor.conn, + event->xcreatewindow.window); + tree_cookie = xcb_query_tree (compositor.conn, + event->xcreatewindow.window); + attributes_cookie = xcb_get_window_attributes (compositor.conn, + event->xcreatewindow.window); + bounding_cookie = xcb_shape_get_rectangles (compositor.conn, + event->xcreatewindow.window, + XCB_SHAPE_SK_BOUNDING); + input_cookie = xcb_shape_get_rectangles (compositor.conn, + event->xcreatewindow.window, + XCB_SHAPE_SK_INPUT); + + /* Ask for replies from the X server. */ + geometry = xcb_get_geometry_reply (compositor.conn, geometry_cookie, + &error); + tree = xcb_query_tree_reply (compositor.conn, tree_cookie, &error1); + attributes = xcb_get_window_attributes_reply (compositor.conn, + attributes_cookie, + &error2); + bounding = xcb_shape_get_rectangles_reply (compositor.conn, + bounding_cookie, + &error3); + input = xcb_shape_get_rectangles_reply (compositor.conn, + input_cookie, &error4); + + if (error || error1 || error2 || error3 || error4 + || !geometry || !tree || !attributes || !bounding || !input) + { + if (error) + free (error); + + if (error1) + free (error1); + + if (error2) + free (error2); + + if (error3) + free (error3); + + if (error4) + free (error4); + + if (geometry) + free (geometry); + + if (tree) + free (tree); + + if (attributes) + free (attributes); + + if (bounding) + free (bounding); + + if (input) + free (input); + + return; + } + + /* Now, really add the window. */ + CatchXErrors (); + AddChild (parent, event->xcreatewindow.window, geometry, + tree, attributes, bounding, input); + UncatchXErrors (NULL); + + /* And free the reply data. */ + free (geometry); + free (tree); + free (attributes); + free (bounding); + free (input); +} + +static void +HandleMapNotify (WindowCache *cache, XEvent *event) +{ + WindowCacheEntry *window; + + if (event->xmap.event == event->xmap.window) + /* This is the result of StructureNotifyMask, and the parent + window cannot be accessed through the event. */ + return; + + window = XLLookUpAssoc (cache->entries, event->xmap.window); + + if (!window) + return; + + window->flags |= IsMapped; +} + +static void +HandleReparentNotify (WindowCache *cache, XEvent *event) +{ + WindowCacheEntry *parent, *window; + + if (event->xreparent.event == event->xreparent.window) + /* This came from StructureNotifyMask... */ + return; + + parent = XLLookUpAssoc (cache->entries, event->xreparent.parent); + + if (!parent) + return; + + window = XLLookUpAssoc (cache->entries, event->xreparent.window); + + if (window) + return; + + /* First, unlink window. */ + UnlinkWindowCacheEntry (window); + + /* Next, change its parent. */ + window->parent = event->xreparent.parent; + + /* Link it onto the new parent. */ + AddAfter (window, parent->last); +} + +static void +HandleUnmapNotify (WindowCache *cache, XEvent *event) +{ + WindowCacheEntry *window; + + if (event->xunmap.event == event->xunmap.window) + /* This is the result of StructureNotifyMask, and the parent + window cannot be accessed through the event. */ + return; + + window = XLLookUpAssoc (cache->entries, event->xunmap.window); + + if (!window) + return; + + window->flags &= ~IsMapped; +} + +static void +HandleDestroyNotify (WindowCache *cache, XEvent *event) +{ + WindowCacheEntry *window; + + window = XLLookUpAssoc (cache->entries, event->xdestroywindow.window); + + if (!window) + return; + + /* This tells FreeWindowCacheEntry to not bother restoring the old + event mask. */ + window->flags |= IsDestroyed; + FreeWindowCacheEntry (window); +} + +static void +HandlePropertyNotify (WindowCache *cache, XEvent *event) +{ + WindowCacheEntry *window; + + if (event->xproperty.atom != WM_STATE) + return; + + window = XLLookUpAssoc (cache->entries, event->xproperty.window); + + if (!window) + return; + + /* WM_STATE has changed. Clear both IsToplevel and IsNotToplevel; + don't set either of those flags based on event->xproperty.state, + since it's not okay to read the property here. */ + + window->flags &= ~(IsToplevel | IsNotToplevel); +} + +static void +EnsureShape (WindowCacheEntry *entry, Bool force) +{ + xcb_shape_get_rectangles_reply_t *bounding; + xcb_shape_get_rectangles_reply_t *input; + xcb_shape_get_rectangles_cookie_t bounding_cookie; + xcb_shape_get_rectangles_cookie_t input_cookie; + xcb_generic_error_t *error, *error1; + + error = NULL; + error1 = NULL; + + if (!force && !(entry->flags & IsShapeDirtied)) + /* The shape is not dirty. */ + return; + + /* Reinitialize the window shape. */ + bounding_cookie = xcb_shape_get_rectangles (compositor.conn, + entry->window, + XCB_SHAPE_SK_BOUNDING); + input_cookie = xcb_shape_get_rectangles (compositor.conn, + entry->window, + XCB_SHAPE_SK_INPUT); + + /* Ask for replies from the X server. */ + bounding = xcb_shape_get_rectangles_reply (compositor.conn, + bounding_cookie, + &error); + input = xcb_shape_get_rectangles_reply (compositor.conn, + input_cookie, &error1); + + if (error || error1 || !bounding || !input) + { + if (error) + free (error); + + if (error1) + free (error1); + + if (bounding) + free (bounding); + + if (input) + free (input); + + /* An error occured; the window has probably been destroyed, in + which case a DestroyNotify event will arrive shortly. */ + return; + } + + /* Clear the region. */ + pixman_region32_fini (&entry->shape); + + /* Repopulate window->shape with the new shape. */ + InitRegionWithRects (&entry->shape, bounding); + IntersectRegionWith (&entry->shape, input); + + /* Free the replies from the X server. */ + free (bounding); + free (input); + + /* Clear the shape dirtied flag. */ + entry->flags &= ~IsShapeDirtied; +} + +static void +HandleShapeNotify (WindowCache *cache, XEvent *event) +{ + WindowCacheEntry *window; + + /* event->xany.window is the same as ((XShapeEvent *) + event)->window, so we don't have to include the shape extension + header. */ + + window = XLLookUpAssoc (cache->entries, event->xany.window); + + if (!window) + return; + + /* Obtain the new shape from the X server. */ + EnsureShape (window, True); +} + +static void +ProcessEventForWindowCache (WindowCache *cache, XEvent *event) +{ + switch (event->type) + { + case CirculateNotify: + HandleCirculateNotify (cache, event); + break; + + case ConfigureNotify: + HandleConfigureNotify (cache, event); + break; + + case CreateNotify: + HandleCreateNotify (cache, event); + break; + + case DestroyNotify: + HandleDestroyNotify (cache, event); + break; + + case MapNotify: + HandleMapNotify (cache, event); + break; + + case ReparentNotify: + HandleReparentNotify (cache, event); + break; + + case UnmapNotify: + HandleUnmapNotify (cache, event); + break; + + case PropertyNotify: + HandlePropertyNotify (cache, event); + break; + } + + if (event->type == shape_base + XCB_SHAPE_NOTIFY) + HandleShapeNotify (cache, event); +} + +static Bool +IsToplevelWindow (WindowCacheEntry *entry) +{ + unsigned long actual_size; + unsigned long bytes_remaining; + int rc, actual_format; + Atom actual_type; + unsigned char *tmp_data; + + if (entry->flags & IsNotToplevel) + /* We know this isn't a toplevel window. */ + return False; + + if (entry->flags & IsToplevel) + /* We know this is a toplevel window. */ + return True; + + /* We have not yet determined whether or not this is a toplevel + window. Read the WM_STATE property to find out. */ + tmp_data = NULL; + + CatchXErrors (); + rc = XGetWindowProperty (compositor.display, entry->window, WM_STATE, + 0, 2, False, WM_STATE, &actual_type, + &actual_format, &actual_size, &bytes_remaining, + &tmp_data); + if (UncatchXErrors (NULL) || rc != Success + || actual_type != WM_STATE || actual_format != 32 + || bytes_remaining) + { + /* This means the window is not a toplevel. */ + entry->flags |= IsNotToplevel; + + if (tmp_data) + XFree (tmp_data); + return False; + } + + entry->flags |= IsToplevel; + if (tmp_data) + XFree (tmp_data); + return True; +} + +static Window +FindToplevelWindow1 (WindowCacheEntry *entry, int x, int y) +{ + WindowCacheEntry *child; + pixman_box32_t temp; + + child = entry->children->next; + + while (child != entry->children) + { + if (XLIsWindowIconSurface (child->window) + || !(child->flags & IsMapped)) + goto next; + + /* If the shape is dirtied, fetch the new shape. */ + EnsureShape (child, False); + + /* Check if X and Y are contained by the child and its input + region. */ + if (x >= child->x && x < child->x + child->width + && y >= child->y && y < child->y + child->height + && pixman_region32_contains_point (&child->shape, x - child->x, + y - child->y, &temp)) + { + /* If this child is already a toplevel, return it. */ + if (IsToplevelWindow (child)) + return child->window; + + /* Otherwise, keep looking. */ + return FindToplevelWindow1 (child, x - child->x, + y - child->y); + } + + next: + child = child->next; + } + + /* No toplevel window was found. */ + return None; +} + +static Window +FindToplevelWindow (WindowCache *cache, int root_x, int root_y) +{ + /* Find a mapped toplevel window. */ + return FindToplevelWindow1 (cache->root_window, root_x, root_y); +} + +/* Drag-and-drop between Wayland and X. */ + + +/* Forward declaration. */ +static void SendLeave (void); + +static void +FinishDrag (void) +{ + if (drag_state.seat) + XLSeatCancelDestroyListener (drag_state.seat_key); + + if (drag_state.mods_key) + XLSeatRemoveModifierCallback (drag_state.mods_key); + + drag_state.mods_key = NULL; + + /* Leave any surface that we entered. */ + SendLeave (); + + drag_state.seat = NULL; + drag_state.seat_key = NULL; + + if (drag_state.window_cache) + { + FreeWindowCache (drag_state.window_cache); + drag_state.window_cache = NULL; + } + + /* Delete the XdndTypeList property. */ + XDeleteProperty (compositor.display, selection_transfer_window, + XdndTypeList); + + /* Delete the XdndActionList property. */ + XDeleteProperty (compositor.display, selection_transfer_window, + XdndActionList); + + /* Clear flags. */ + drag_state.flags = 0; + + /* Clear the toplevel and target. */ + drag_state.toplevel = 0; + drag_state.target = 0; + + /* Disown XdndSelection. */ + DisownSelection (XdndSelection); +} + +static void +HandleDragSeatDestroy (void *data) +{ + drag_state.seat = NULL; + drag_state.seat_key = NULL; + + FinishDrag (); +} + +static void +ReadProtocolProperties (Window window, int *version_return, + Window *proxy_return) +{ + WindowCacheEntry *entry; + xcb_get_property_cookie_t xdnd_proto_cookie; + xcb_get_property_cookie_t xdnd_proxy_cookie; + xcb_generic_error_t *error, *error1; + xcb_get_property_reply_t *proto, *proxy; + uint32_t *values; + + error = NULL; + error1 = NULL; + + /* Get the window entry corresponding to window in the window + cache. */ + entry = XLLookUpAssoc (drag_state.window_cache->entries, window); + + if (!entry) + /* The entry is not in the window cache... */ + return; + + if (entry->flags & IsPropertyRead) + { + /* The version and proxy window were already obtained. */ + + *version_return = (entry->flags >> 16) & 0xff; + *proxy_return = entry->dnd_proxy; + return; + } + + xdnd_proto_cookie = xcb_get_property (compositor.conn, 0, + window, XdndAware, + XCB_ATOM_ATOM, 0, 1); + xdnd_proxy_cookie = xcb_get_property (compositor.conn, 0, + window, XdndProxy, + XCB_ATOM_WINDOW, 0, 1); + + /* Ask for replies from the X server. */ + proto = xcb_get_property_reply (compositor.conn, xdnd_proto_cookie, + &error); + proxy = xcb_get_property_reply (compositor.conn, xdnd_proxy_cookie, + &error1); + + /* If any errors occured, bail out, while freeing any data + allocated. */ + if (error || error1 || !proto || !proxy) + { + if (error) + free (error); + + if (error1) + free (error1); + + if (proto) + free (proto); + + if (proxy) + free (proxy); + + /* Store some default values before returning. */ + *proxy_return = None; + *version_return = 0; + return; + } + + /* Otherwise, the properties were read. Determine if they are + valid. */ + if (proto->format == 32 && proto->type == XCB_ATOM_ATOM + && xcb_get_property_value_length (proto) == 4) + { + /* Save the protocol version into the window flags. Truncate + values above 255. */ + values = xcb_get_property_value (proto); + entry->flags |= (values[0] & 0xff) << 16; + + /* Return the version to the caller. */ + *version_return = values[0]; + } + else + *version_return = 0; + + free (proto); + + if (proxy->format == 32 && proxy->type == XCB_ATOM_WINDOW + && xcb_get_property_value_length (proxy) == 4) + { + /* Save the proxy window ID into the window cache entry. */ + values = xcb_get_property_value (proxy); + entry->dnd_proxy = values[0]; + + /* Return the proxy to the caller. */ + *proxy_return = values[0]; + } + else + *proxy_return = None; + + free (proxy); + + /* Mark properties as having been read. */ + entry->flags |= IsPropertyRead; +} + +static void +WriteTypeList (void) +{ + DataSource *source; + Atom *targets; + int n_targets; + + source = XLSeatGetDragDataSource (drag_state.seat); + + /* If no data source was specified, then functions for handling + external DND should not be called at all. */ + XLAssert (source != NULL); + + n_targets = XLDataSourceTargetCount (source); + targets = XLMalloc (sizeof *targets * n_targets); + XLDataSourceGetTargets (source, targets); + + if (n_targets) + drag_state.first_targets[0] = targets[0]; + else + drag_state.first_targets[0] = None; + + if (n_targets > 1) + drag_state.first_targets[1] = targets[1]; + else + drag_state.first_targets[1] = None; + + if (n_targets > 2) + drag_state.first_targets[2] = targets[2]; + else + drag_state.first_targets[2] = None; + + if (n_targets > 3) + { + /* There are more than 3 targets. Write the type list. */ + XChangeProperty (compositor.display, selection_transfer_window, + XdndTypeList, XA_ATOM, 32, PropModeReplace, + (unsigned char *) targets, n_targets); + drag_state.flags |= MoreThanThreeTargets; + } + + /* Free the targets. */ + XLFree (targets); + + /* Set the type setup flag. */ + drag_state.flags |= TypeListSet; +} + +static const char * +GetAskActionName (Atom action) +{ + if (action == XdndActionCopy) + return "Copy"; + + if (action == XdndActionMove) + return "Move"; + + if (action == XdndActionLink) + return "Link"; + + if (action == XdndActionAsk) + return "Ask"; + + abort (); +} + +static void +WriteActionList (void) +{ + DataSource *source; + uint32_t action_mask; + Atom actions[2]; + int nactions; + ptrdiff_t i, end, fill; + char *ask_actions; + const char *name; + XTextProperty prop; + + drag_state.flags |= ActionListSet; + + source = XLSeatGetDragDataSource (drag_state.seat); + action_mask = XLDataSourceGetSupportedActions (source); + + if (action_mask & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) + { + /* Write XdndActionList. */ + + nactions = 0; + + if (action_mask & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) + actions[nactions++] = XdndActionCopy; + + if (action_mask & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + actions[nactions++] = XdndActionMove; + + XChangeProperty (compositor.display, selection_transfer_window, + XdndActionList, XA_ATOM, 32, PropModeReplace, + (unsigned char *) actions, nactions); + + /* Write XdndActionDescription. This is a list of strings, + terminated by NULL, describing the drag and drop actions. + + These strings are not actually used by any program, so it is + OK to not translate. */ + + ask_actions = NULL; + end = 0; + + for (i = 0; i < nactions; ++i) + { + fill = end; + name = GetAskActionName (actions[i]); + end += strlen (name) + 1; + + ask_actions = XLRealloc (ask_actions, end); + strncpy (ask_actions + fill, name, end - fill); + } + + prop.value = (unsigned char *) ask_actions; + prop.encoding = XA_STRING; + prop.format = 8; + prop.nitems = end; + + XSetTextProperty (compositor.display, selection_transfer_window, + &prop, XdndActionDescription); + XLFree (ask_actions); + } +} + +static void +SendEnter (void) +{ + XEvent message; + + if (drag_state.toplevel == None + || drag_state.version < 3) + return; + + if (!(drag_state.flags & TypeListSet)) + /* Set up the drag and drop type list now. */ + WriteTypeList (); + + if (!(drag_state.flags & ActionListSet)) + /* Set up the drag and drop action list now. */ + WriteActionList (); + + message.xclient.type = ClientMessage; + message.xclient.message_type = XdndEnter; + message.xclient.format = 32; + message.xclient.window = drag_state.toplevel; + message.xclient.data.l[0] = selection_transfer_window; + message.xclient.data.l[1] = MIN (XdndProtocolVersion, + drag_state.version) << 24; + + if (drag_state.flags & MoreThanThreeTargets) + message.xclient.data.l[1] |= 1; + + message.xclient.data.l[2] = drag_state.first_targets[0]; + message.xclient.data.l[3] = drag_state.first_targets[1]; + message.xclient.data.l[4] = drag_state.first_targets[2]; + + CatchXErrors (); + XSendEvent (compositor.display, drag_state.target, + False, NoEventMask, &message); + UncatchXErrors (NULL); +} + +static Atom +ConvertActionsLoosely (uint32_t actions) +{ + /* Use XdndActionAsk if ask was specified. */ + if (actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK) + return XdndActionAsk; + + if (drag_state.modifiers & ShiftMask + && actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + /* Shift is pressed; default to XdndActionMove. */ + return XdndActionMove; + + if (actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) + return XdndActionCopy; + + if (actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + return XdndActionMove; + + return XdndActionPrivate; +} + +static void +SendPosition (short root_x, short root_y) +{ + XEvent message; + DataSource *source; + uint32_t action_mask; + + if (!drag_state.seat || drag_state.version < 3) + return; + + /* If we are waiting for an XdndStatus event, wait for it to arrive + before sending the position. */ + if (drag_state.flags & WaitingForStatus) + { + if (!(drag_state.flags & PendingDrop)) + /* If the drop already happened, don't bother sending another + position event. */ + drag_state.flags |= PendingPosition; + + return; + } + + drag_state.flags &= ~PendingPosition; + + /* If this rectangle is within the mouse rectangle, do nothing. */ + + if (drag_state.flags & NeedMouseRect + && root_x >= drag_state.mouse_rect.x + && root_y >= drag_state.mouse_rect.y + && root_x < (drag_state.mouse_rect.x + + drag_state.mouse_rect.width) + && root_y < (drag_state.mouse_rect.y + + drag_state.mouse_rect.height)) + return; + + /* Otherwise, send the XdndPosition event now. */ + message.xclient.type = ClientMessage; + message.xclient.message_type = XdndPosition; + message.xclient.format = 32; + message.xclient.window = drag_state.toplevel; + message.xclient.data.l[0] = selection_transfer_window; + message.xclient.data.l[1] = 0; + message.xclient.data.l[2] = (root_x << 16) | root_y; + message.xclient.data.l[3] = 0; + message.xclient.data.l[4] = 0; + + if (MIN (XdndProtocolVersion, drag_state.version) >= 3) + message.xclient.data.l[3] = drag_state.timestamp; + + if (MIN (XdndProtocolVersion, drag_state.version) >= 4) + { + source = (finish_source + /* Use the finish source if it is available. + drag_state.seat's source will be NULL by the time + this is called in response to a delayed drop. */ + ? finish_source + : XLSeatGetDragDataSource (drag_state.seat)); + action_mask = XLDataSourceGetSupportedActions (source); + + /* A word about how converting actions between + wl_data_device_manager and XDND aware programs works. + + When dragging between two Wayland clients, version 3 sources + can specify a mask of supported actions, which the compositor + then compares with the supported actions announced by the + drop target to determine a single selected action. + + The compositor is also supposed to change the selected action + based on information such as the state of the keyboard + modifiers. + + Version 2 sources, on the other hand, do not support any + specific drop actions. Instead, wl_data_offer_accept conveys + whether or not the source accepts a MIME type provided by the + target. + + No matter what version of the wl_data_device protocol is + spoken by the data source, it is difficult to convert between + Wayland actions and XDND actions. With version 3 sources, we + default to looking through the supported actions in the + following order: + + - WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY (XdndActionCopy) + - WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE (XdndActionMove) + - (anything else) (XdndActionPrivate) + + or the following order, if Shift is pressed: + + - WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE (XdndActionMove) + - WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY (XdndActionCopy) + - (anything else) (XdndActionPrivate) + + and returning the action selected by the client (as described + in the XdndStatus event sent by it in response). + + With version 2 sources, we always specify XdndActionPrivate, + and call accept with the first MIME type specified. */ + message.xclient.data.l[4] = ConvertActionsLoosely (action_mask); + } + + CatchXErrors (); + XSendEvent (compositor.display, drag_state.target, + False, NoEventMask, &message); + UncatchXErrors (NULL); + + /* Now wait for an XdndStatus to be sent in reply. */ + drag_state.flags |= WaitingForStatus; +} + +static void +SendLeave (void) +{ + XEvent message; + + if (drag_state.toplevel == None + || drag_state.version < 3) + return; + + message.xclient.type = ClientMessage; + message.xclient.message_type = XdndLeave; + message.xclient.format = 32; + + /* Events have their window field set to drag_state.toplevel, + regardless of whether or not a proxy was specified. */ + + message.xclient.window = drag_state.toplevel; + + /* selection_transfer_window is used, since it is the owner of + XdndSelection. */ + + message.xclient.data.l[0] = selection_transfer_window; + message.xclient.data.l[1] = 0; + message.xclient.data.l[2] = 0; + message.xclient.data.l[3] = 0; + message.xclient.data.l[4] = 0; + + CatchXErrors (); + XSendEvent (compositor.display, drag_state.target, + False, NoEventMask, &message); + UncatchXErrors (NULL); +} + +static const char * +PickMimeType (DataSource *source) +{ + XLList *list; + + list = XLDataSourceGetMimeTypeList (source); + + if (!list) + return NULL; + + return list->data; +} + +static void +ReportStateToSource (void) +{ + DataSource *source; + struct wl_resource *resource; + uint32_t action; + + source = XLSeatGetDragDataSource (drag_state.seat); + + if (!source) + return; + + resource = XLResourceFromDataSource (source); + + /* If no data type was accepted, report that to the source. */ + if (!(drag_state.flags & WillAcceptDrop)) + wl_data_source_send_target (resource, NULL); + else + wl_data_source_send_target (resource, + PickMimeType (source)); + + /* If the source is new enough, report the selected action to the + source. */ + if (wl_resource_get_version (resource) >= 3) + { + action = TranslateAction (drag_state.action); + wl_data_source_send_action (resource, action); + } +} + +/* Forward declaration. */ +static void SendDrop (void); + +static void +HandleXdndStatus (XEvent *event) +{ + unsigned long flags, rect, rect1; + + if (event->xclient.data.l[0] != drag_state.toplevel) + /* This event is for a window other than the toplevel. */ + return; + + /* Clear the waiting for status flag. */ + drag_state.flags &= ~WaitingForStatus; + + /* Determine whether or not the target will accept the drop. */ + flags = event->xclient.data.l[1]; + + if (flags & 1) + drag_state.flags |= WillAcceptDrop; + else + drag_state.flags &= ~WillAcceptDrop; + + /* Determine if the target wants a mouse rectangle. */ + rect = event->xclient.data.l[2]; + rect1 = event->xclient.data.l[3]; + + if (flags & 2 || !rect1) + drag_state.flags &= ~NeedMouseRect; + else + { + drag_state.flags |= NeedMouseRect; + drag_state.mouse_rect.x = (rect & 0xffff0000) >> 16; + drag_state.mouse_rect.y = (rect & 0xffff); + drag_state.mouse_rect.width = (rect1 & 0xffff0000) >> 16; + drag_state.mouse_rect.height = (rect1 & 0xffff); + } + + /* Set the client's selected action. */ + drag_state.action = event->xclient.data.l[4]; + + ReportStateToSource (); + + /* Send any pending XdndPosition event. */ + if (drag_state.flags & PendingPosition) + SendPosition (drag_state.last_root_x, + drag_state.last_root_y); + + if (!(drag_state.flags & WaitingForStatus) + && drag_state.flags & PendingDrop) + { + /* Send any pending XdndDrop event. */ + drag_state.flags &= ~PendingDrop; + + if (!(drag_state.flags & WillAcceptDrop) + || drag_state.action == None) + { + /* The status changed and is no longer eligible for + dropping. Cancel. */ + SendLeave (); + + /* Also tell the data source that this was cancelled. */ + XLDataSourceSendDropCancelled (finish_source); + } + else + /* Otherwise, send the drop. */ + SendDrop (); + } +} + +static void +HandleXdndFinished (XEvent *event) +{ + struct wl_resource *resource; + Atom new_action; + + if (!finish_source) + return; + + /* Send either cancel or performed to the source depending on + whether or not the target accepted the drop. */ + if (finish_version < 5 || event->xclient.data.l[0] & 1) + { + /* The drop was successful. If the action changed, send it to + the data source, followed by finished. */ + + resource = XLResourceFromDataSource (finish_source); + + if (wl_resource_get_version (resource) >= 3 + && finish_version >= 5) + { + new_action = event->xclient.data.l[2]; + + if (new_action != finish_action) + wl_data_source_send_action (resource, + TranslateAction (new_action)); + } + + if (wl_resource_get_version (resource) >= 3) + wl_data_source_send_dnd_finished (resource); + } + else + /* Send the drop cancelled event. */ + XLDataSourceSendDropCancelled (finish_source); + + finish_source = NULL; + XLDataSourceCancelDestroyCallback (finish_source_key); + finish_source_key = NULL; + + RemoveTimer (finish_timeout); + finish_timeout = NULL; + + /* Either way, finish dragging. */ + FinishDrag (); +} + +static void +HandleDataSourceDestroy (void *data) +{ + finish_source = NULL; + finish_source_key = NULL; + + if (finish_timeout) + RemoveTimer (finish_timeout); + FinishDrag (); +} + +static void +HandleTimerExpired (Timer *timer, void *data, struct timespec time) +{ + RemoveTimer (timer); + + if (finish_source) + { + /* Send cancelled to the data source. */ + XLDataSourceSendDropCancelled (finish_source); + + finish_source = NULL; + XLDataSourceCancelDestroyCallback (finish_source_key); + finish_source_key = NULL; + + FinishDrag (); + } +} + +static void +SendDrop (void) +{ + XEvent message; + + if (drag_state.toplevel == None + || drag_state.version < 3) + return; + + message.xclient.type = ClientMessage; + message.xclient.message_type = XdndDrop; + message.xclient.format = 32; + message.xclient.window = drag_state.toplevel; + message.xclient.data.l[0] = selection_transfer_window; + message.xclient.data.l[1] = 0; + message.xclient.data.l[2] = drag_state.timestamp; + message.xclient.data.l[3] = 0; + message.xclient.data.l[4] = 0; + + /* First, send the event to the client. */ + + CatchXErrors (); + XSendEvent (compositor.display, drag_state.target, + False, NoEventMask, &message); + UncatchXErrors (NULL); + + /* Tell the source to start waiting for finish. */ + XLDataSourceSendDropPerformed (finish_source); +} + +static void +ProcessClientMessage (XEvent *event) +{ + if (event->xclient.message_type == XdndStatus) + HandleXdndStatus (event); + else if (event->xclient.message_type == XdndFinished) + HandleXdndFinished (event); +} + +static void +HandleModifiersChanged (unsigned int effective, void *data) +{ + drag_state.modifiers = effective; + + /* Report the new action to the client. */ + SendPosition (drag_state.last_root_x, drag_state.last_root_y); +} + +void +XLHandleOneXEventForDnd (XEvent *event) +{ + if (drag_state.window_cache) + ProcessEventForWindowCache (drag_state.window_cache, + event); + + if (drag_state.seat && event->type == ClientMessage) + ProcessClientMessage (event); +} + +void +XLDoDragLeave (Seat *seat) +{ + if (seat == drag_state.seat && drag_state.toplevel) + { + SendLeave (); + + drag_state.toplevel = None; + drag_state.target = None; + drag_state.version = 0; + drag_state.action = None; + + /* Clear flags that are specific to each toplevel. */ + drag_state.flags &= ~WillAcceptDrop; + drag_state.flags &= ~NeedMouseRect; + drag_state.flags &= ~PendingPosition; + drag_state.flags &= ~PendingDrop; + drag_state.flags &= ~WaitingForStatus; + + /* Report the changed state to the source. */ + ReportStateToSource (); + } +} + +void +XLDoDragMotion (Seat *seat, double root_x, double root_y) +{ + Window toplevel, proxy, self; + int version, proxy_version; + + if (finish_source || drag_state.flags & PendingDrop) + /* A finish is pending. */ + return; + + if (drag_state.seat && drag_state.seat != seat) + /* The XDND protocol doesn't support MPX, so only allow one seat + to drag out of Wayland at once. */ + return; + + if (!drag_state.seat) + { + drag_state.seat = seat; + drag_state.seat_key + = XLSeatRunOnDestroy (seat, HandleDragSeatDestroy, NULL); + drag_state.modifiers + = XLSeatGetEffectiveModifiers (seat); + drag_state.mods_key + = XLSeatAddModifierCallback (seat, HandleModifiersChanged, + NULL); + + drag_state.last_root_x = INT_MIN; + drag_state.last_root_y = INT_MIN; + } + + if (drag_state.flags & SelectionFailed) + /* We do not have ownership over XdndSelection. */ + return; + + if (root_x == drag_state.last_root_x + && root_y == drag_state.last_root_y) + /* Ignore subpixel movement. */ + return; + + drag_state.last_root_x = root_x; + drag_state.last_root_y = root_y; + + /* Try to own XdndSelection with the last user time. */ + if (!(drag_state.flags & SelectionSet)) + { + drag_state.timestamp = XLSeatGetLastUserTime (seat); + + if (!XLOwnDragSelection (drag_state.timestamp, + XLSeatGetDragDataSource (seat))) + { + /* We could not obtain ownership over XdndSelection. */ + drag_state.flags |= SelectionFailed; + return; + } + else + drag_state.flags |= SelectionSet; + } + + /* Also initialize the window cache. */ + if (!drag_state.window_cache) + drag_state.window_cache = AllocWindowCache (); + + toplevel = FindToplevelWindow (drag_state.window_cache, + root_x, root_y); + + if (XLIsXdgToplevel (toplevel)) + /* If this one of our own surfaces, ignore it. */ + toplevel = None; + + if (toplevel && toplevel != drag_state.toplevel) + { + /* Try to determine whether or not the given toplevel supports + XDND, and whether or not a proxy is set. */ + ReadProtocolProperties (toplevel, &version, &proxy); + + if (proxy != None) + { + /* A proxy is set. Read properties off the proxy. */ + ReadProtocolProperties (proxy, &proxy_version, &self); + + /* Check the proxy to make sure its XdndProxy property + points to itself. If it does not, the proxy property is + left over from a crash. */ + if (self != proxy) + proxy = None; + else + /* Otherwise, set the version to the value of XdndAware on + the proxy window. */ + version = proxy_version; + } + } + + /* Now, toplevel is the toplevel itself, version is the version of + the target, and the target is proxy, if set, or toplevel, if + not. Send XdndLeave to any previous target. */ + if (toplevel != drag_state.toplevel) + { + SendLeave (); + + drag_state.toplevel = None; + drag_state.target = None; + drag_state.version = 0; + drag_state.action = None; + + /* Clear flags that are specific to each toplevel. */ + drag_state.flags &= ~WillAcceptDrop; + drag_state.flags &= ~NeedMouseRect; + drag_state.flags &= ~PendingPosition; + drag_state.flags &= ~PendingDrop; + drag_state.flags &= ~WaitingForStatus; + + /* Report the changed state to the source. */ + ReportStateToSource (); + + /* Set the toplevel and target accordingly. */ + if (toplevel) + { + drag_state.toplevel = toplevel; + drag_state.target = (proxy != None + ? proxy : toplevel); + drag_state.version = version; + + /* Then, send XdndEnter followed by XdndPosition, and wait + for an XdndStatus event. */ + SendEnter (); + } + } + + /* Send the position to any attached toplevel, then wait for + XdndStatus. */ + SendPosition (root_x, root_y); +} + +void +XLDoDragFinish (Seat *seat) +{ + if (seat == drag_state.seat) + { + /* If nothing was dropped, finish the drag now. */ + if (!finish_source) + FinishDrag (); + } +} + +static void +StartFinishTimeout (void) +{ + /* Wait for the XdndFinish event to arrive, or a timeout to + expire. */ + finish_source = XLSeatGetDragDataSource (drag_state.seat); + finish_source_key = XLDataSourceAddDestroyCallback (finish_source, + HandleDataSourceDestroy, + NULL); + finish_version = drag_state.version; + finish_action = drag_state.action; + + /* Use a 5 second timeout like we do for all other selection-related + stuff. */ + finish_timeout = AddTimer (HandleTimerExpired, NULL, MakeTimespec (5, 0)); +} + +Bool +XLDoDragDrop (Seat *seat) +{ + if (seat != drag_state.seat) + return False; + + if (drag_state.version < 3) + return False; + + if (!(drag_state.flags & WaitingForStatus)) + { + /* If no status event is pending, and no action was specified or + no type has been specified, return False. */ + + if (!(drag_state.flags & WillAcceptDrop) + || drag_state.action == None) + return False; + + /* Start the finish timeout. */ + StartFinishTimeout (); + + /* Send the drop now. */ + SendDrop (); + return True; + } + else + { + /* Set PendingDrop. Then, return True, so the code in seat.c does + not clobber the data in drag_state. */ + drag_state.flags |= PendingDrop; + + /* Start the finish timeout. */ + StartFinishTimeout (); + return True; + } + + return False; +} diff --git a/ewmh.c b/ewmh.c new file mode 100644 index 0000000..2cc3e1a --- /dev/null +++ b/ewmh.c @@ -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 . */ + +#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; + } +} diff --git a/fns.c b/fns.c new file mode 100644 index 0000000..a61bb09 --- /dev/null +++ b/fns.c @@ -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 . */ + +#include +#include +#include +#include + +#include +#include + +#include + +#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 (); +} diff --git a/frame_clock.c b/frame_clock.c new file mode 100644 index 0000000..b55653f --- /dev/null +++ b/frame_clock.c @@ -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 . */ + +#include +#include +#include + +#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; +} diff --git a/icon_surface.c b/icon_surface.c new file mode 100644 index 0000000..22899ac --- /dev/null +++ b/icon_surface.c @@ -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 . */ + +/* Generic "icon surface" role. */ + +#include + +#include "compositor.h" + +#include + +#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; +} diff --git a/libraries.def b/libraries.def new file mode 100644 index 0000000..b36b8a2 --- /dev/null +++ b/libraries.def @@ -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 diff --git a/linux-dmabuf-unstable-v1.xml b/linux-dmabuf-unstable-v1.xml new file mode 100644 index 0000000..018f876 --- /dev/null +++ b/linux-dmabuf-unstable-v1.xml @@ -0,0 +1,586 @@ + + + + + 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. + + + + + 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. + + + + + Objects created through this interface, especially wl_buffers, will + remain valid. + + + + + + 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. + + + + + + + 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. + + + + + + + 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. + + + + + + + + + + + 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). + + + + + + + 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. + + + + + + + + + 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. + + + + + + + + + + + + + + + + Cleans up the temporary data sent to the server for dmabuf-based + wl_buffer creation. + + + + + + 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. + + + + + + + + + + + + + + + + + + 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. + + + + + + + + + + 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. + + + + + + + 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. + + + + + + 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. + + + + + + + + + + + + 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. + + + + + Using this request a client can tell the server that it is not going to + use the wp_linux_dmabuf_feedback object anymore. + + + + + + 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. + + + + + + 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. + + + + + + + + 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. + + + + + + + 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. + + + + + + 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. + + + + + + + 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. + + + + + + + + + + + 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. + + + + + + diff --git a/media_types.txt b/media_types.txt new file mode 100644 index 0000000..1eb6a4e --- /dev/null +++ b/media_types.txt @@ -0,0 +1,2974 @@ +This file is not part of 12to11 and is not subject to its terms of +distribution. It is only used to generate transfer_atoms.h. + +The generation takes place by first preprocessing this file into +short_types.txt, by deleting all of the vendor data types. + +Then, it is run through several awk scripts to generate +transfer_atoms.h, which is used in xdata.c and atoms.c to map data +types between X and Wayland programs. + + Internet Assigned Numbers Authority + + Media Types + + Last Updated + 2022-09-05 + + Registration Procedure(s) + + Expert Review for Vendor and Personal Trees + + Expert(s) + + Alexey Melnikov, Murray Kucherawy (backup) + + Reference + [RFC6838][RFC4855] + + Note + + Per Section 3.1 of [RFC6838], Standards Tree requests made through IETF + documents will be reviewed and approved by the IESG, while requests made by + other recognized standards organizations will be reviewed by the Designated + Expert in accordance with the Specification Required policy. IANA will verify + that this organization is recognized as a standards organization by the + IESG. + + Note + + [RFC2046] specifies that Media Types (formerly known as MIME types) and Media + Subtypes will be assigned and listed by the IANA. + + Procedures for registering Media Types can be found in [RFC6838], [RFC4289], + and [RFC6657]. Additional procedures for registering media types for transfer via + Real-time Transport Protocol (RTP) can be found in [RFC4855]. + + The following is the list of Directories of Content Types and Subtypes. If you wish to + register a Media Type with the IANA, please see the following for the online application: + + [Application for registration of Media Types] + + Other Media Type Parameters: [IANA registry media-types-parameters] + Media Type Sub-Parameters: [IANA registry media-type-sub-parameters] + Provisional Standard Media Type Registry: [IANA registry provisional-standard-media-types] + + Note + + Per Section 12.5.1 of [RFC9110], use of the "q" parameter name to control + content negotiation would interfere with any media type parameter having the same name. + Hence, the media type registry disallows parameters named "q". + + + Available Formats + [IMG] + XML [IMG] + HTML [IMG] + Plain text + + Registries included below + + * application + * audio + * font + * example + * image + * message + * model + * multipart + * text + * video + +application + + Available Formats + [IMG] + CSV + + Name Template Reference +1d-interleaved-parityfec application/1d-interleaved-parityfec [RFC6015] +3gpdash-qoe-report+xml application/3gpdash-qoe-report+xml [_3GPP][Ozgur_Oyman] +3gppHal+json application/3gppHal+json [_3GPP][Ulrich_Wiehe] +3gppHalForms+json application/3gppHalForms+json [_3GPP][Ulrich_Wiehe] +3gpp-ims+xml application/3gpp-ims+xml [_3GPP][John_M_Meredith] +A2L application/A2L [ASAM][Thomas_Thomsen] +ace+cbor application/ace+cbor [RFC9200] +ace+json application/ace+json [RFC-ietf-ace-mqtt-tls-profile-17] +activemessage application/activemessage [Ehud_Shapiro] +activity+json application/activity+json [W3C][Benjamin_Goering] +aif+cbor application/aif+cbor [RFC9237] +aif+json application/aif+json [RFC9237] +alto-cdni+json application/alto-cdni+json [RFC9241] +alto-cdnifilter+json application/alto-cdnifilter+json [RFC9241] +alto-costmap+json application/alto-costmap+json [RFC7285] +alto-costmapfilter+json application/alto-costmapfilter+json [RFC7285] +alto-directory+json application/alto-directory+json [RFC7285] +alto-endpointprop+json application/alto-endpointprop+json [RFC7285] +alto-endpointpropparams+json application/alto-endpointpropparams+json [RFC7285] +alto-endpointcost+json application/alto-endpointcost+json [RFC7285] +alto-endpointcostparams+json application/alto-endpointcostparams+json [RFC7285] +alto-error+json application/alto-error+json [RFC7285] +alto-networkmapfilter+json application/alto-networkmapfilter+json [RFC7285] +alto-networkmap+json application/alto-networkmap+json [RFC7285] +alto-propmap+json application/alto-propmap+json [RFC9240] +alto-propmapparams+json application/alto-propmapparams+json [RFC9240] +alto-updatestreamcontrol+json application/alto-updatestreamcontrol+json [RFC8895] +alto-updatestreamparams+json application/alto-updatestreamparams+json [RFC8895] +AML application/AML [ASAM][Thomas_Thomsen] +andrew-inset application/andrew-inset [Nathaniel_Borenstein] +applefile application/applefile [Patrik_Faltstrom] +at+jwt application/at+jwt [RFC9068] +ATF application/ATF [ASAM][Thomas_Thomsen] +ATFX application/ATFX [ASAM][Thomas_Thomsen] +atom+xml application/atom+xml [RFC4287][RFC5023] +atomcat+xml application/atomcat+xml [RFC5023] +atomdeleted+xml application/atomdeleted+xml [RFC6721] +atomicmail application/atomicmail [Nathaniel_Borenstein] +atomsvc+xml application/atomsvc+xml [RFC5023] +atsc-dwd+xml application/atsc-dwd+xml [ATSC] +atsc-dynamic-event-message application/atsc-dynamic-event-message [ATSC] +atsc-held+xml application/atsc-held+xml [ATSC] +atsc-rdt+json application/atsc-rdt+json [ATSC] +atsc-rsat+xml application/atsc-rsat+xml [ATSC] +ATXML application/ATXML [ASAM][Thomas_Thomsen] +auth-policy+xml application/auth-policy+xml [RFC4745] +automationml-aml+xml application/automationml-aml+xml [AutomationML_e.V.] +automationml-amlx+zip application/automationml-amlx+zip [AutomationML_e.V.] +bacnet-xdd+zip application/bacnet-xdd+zip [ASHRAE][Dave_Robin] +batch-SMTP application/batch-SMTP [RFC2442] +beep+xml application/beep+xml [RFC3080] +calendar+json application/calendar+json [RFC7265] +calendar+xml application/calendar+xml [RFC6321] +call-completion application/call-completion [RFC6910] +CALS-1840 application/CALS-1840 [RFC1895] +captive+json application/captive+json [RFC8908] +cbor application/cbor [RFC8949] +cbor-seq application/cbor-seq [RFC8742] +cccex application/cccex [_3GPP] +ccmp+xml application/ccmp+xml [RFC6503] +ccxml+xml application/ccxml+xml [RFC4267] +cda+xml application/cda+xml [HL7][Marc_Duteau] +CDFX+XML application/CDFX+XML [ASAM][Thomas_Thomsen] +cdmi-capability application/cdmi-capability [RFC6208] +cdmi-container application/cdmi-container [RFC6208] +cdmi-domain application/cdmi-domain [RFC6208] +cdmi-object application/cdmi-object [RFC6208] +cdmi-queue application/cdmi-queue [RFC6208] +cdni application/cdni [RFC7736] +CEA application/CEA [ASAM][Thomas_Thomsen] +cea-2018+xml application/cea-2018+xml [Gottfried_Zimmermann] +cellml+xml application/cellml+xml [RFC4708] +cfw application/cfw [RFC6230] +city+json application/city+json [OGC][Scott_Simmons] +clr application/clr [IMS_Global][Andy_Miller] +clue_info+xml application/clue_info+xml [RFC8846] +clue+xml application/clue+xml [RFC8847] +cms application/cms [RFC7193] +cnrp+xml application/cnrp+xml [RFC3367] +coap-group+json application/coap-group+json [RFC7390] +coap-payload application/coap-payload [RFC8075] +commonground application/commonground [David_Glazer] +concise-problem-details+cbor application/concise-problem-details+cbor [RFC-ietf-core-problem-details-08, Section 6.3] +conference-info+xml application/conference-info+xml [RFC4575] +cpl+xml application/cpl+xml [RFC3880] +cose application/cose [RFC9052] +cose-key application/cose-key [RFC9052] +cose-key-set application/cose-key-set [RFC9052] +csrattrs application/csrattrs [RFC7030] +csta+xml application/csta+xml [Ecma_International_Helpdesk] +CSTAdata+xml application/CSTAdata+xml [Ecma_International_Helpdesk] +csvm+json application/csvm+json [W3C][Ivan_Herman] +cwl application/cwl [CWL_Project][Michael_R._Crusoe] +cwl+json application/cwl+json [CWL_Project][Michael_R._Crusoe] +cwt application/cwt [RFC8392] +cybercash application/cybercash [Donald_E._Eastlake_3rd] +dash+xml application/dash+xml [ISO-IEC_JTC1][Thomas_Stockhammer] +dash-patch+xml application/dash-patch+xml [ISO-IEC_JTC1] +dashdelta application/dashdelta [David_Furbeck] +davmount+xml application/davmount+xml [RFC4709] +dca-rft application/dca-rft [Larry_Campbell] +DCD application/DCD [ASAM][Thomas_Thomsen] +dec-dx application/dec-dx [Larry_Campbell] +dialog-info+xml application/dialog-info+xml [RFC4235] +dicom application/dicom [RFC3240] +dicom+json application/dicom+json [DICOM_Standards_Committee][David_Clunie] +dicom+xml application/dicom+xml [DICOM_Standards_Committee][David_Clunie] +DII application/DII [ASAM][Thomas_Thomsen] +DIT application/DIT [ASAM][Thomas_Thomsen] +dns application/dns [RFC4027] +dns+json application/dns+json [RFC8427] +dns-message application/dns-message [RFC8484] +dots+cbor application/dots+cbor [RFC9132] +dskpp+xml application/dskpp+xml [RFC6063] +dssc+der application/dssc+der [RFC5698] +dssc+xml application/dssc+xml [RFC5698] +dvcs application/dvcs [RFC3029] +ecmascript (OBSOLETED in favor of text/javascript) application/ecmascript [RFC4329][RFC9239] +EDI-consent application/EDI-consent [RFC1767] +EDIFACT application/EDIFACT [RFC1767] +EDI-X12 application/EDI-X12 [RFC1767] +efi application/efi [UEFI_Forum][Samer_El-Haj-Mahmoud] +elm+json application/elm+json [HL7][Bryn_Rhodes] +elm+xml application/elm+xml [HL7][Bryn_Rhodes] +EmergencyCallData.cap+xml application/EmergencyCallData.cap+xml [RFC8876] +EmergencyCallData.Comment+xml application/EmergencyCallData.Comment+xml [RFC7852] +EmergencyCallData.Control+xml application/EmergencyCallData.Control+xml [RFC8147] +EmergencyCallData.DeviceInfo+xml application/EmergencyCallData.DeviceInfo+xml [RFC7852] +EmergencyCallData.eCall.MSD application/EmergencyCallData.eCall.MSD [RFC8147] +EmergencyCallData.ProviderInfo+xml application/EmergencyCallData.ProviderInfo+xml [RFC7852] +EmergencyCallData.ServiceInfo+xml application/EmergencyCallData.ServiceInfo+xml [RFC7852] +EmergencyCallData.SubscriberInfo+xml application/EmergencyCallData.SubscriberInfo+xml [RFC7852] +EmergencyCallData.VEDS+xml application/EmergencyCallData.VEDS+xml [RFC8148][RFC Errata 6500] +emma+xml application/emma+xml [W3C][http://www.w3.org/TR/2007/CR-emma-20071211/#media-type-registration][ISO-IEC_JTC1] +emotionml+xml application/emotionml+xml [W3C][Kazuyuki_Ashimura] +encaprtp application/encaprtp [RFC6849] +epp+xml application/epp+xml [RFC5730] +epub+zip application/epub+zip [W3C][EPUB_3_WG] +eshop application/eshop [Steve_Katz] +example application/example [RFC4735] +exi application/exi [W3C][http://www.w3.org/TR/2009/CR-exi-20091208/#mediaTypeRegistration] +expect-ct-report+json application/expect-ct-report+json [RFC9163] +express application/express [ISO-TC_184-SC_4][Dana_Tripp] +fastinfoset application/fastinfoset [ITU-T_ASN.1_Rapporteur][ISO-IEC_JTC1_SC6_ASN.1_Rapporteur] +fastsoap application/fastsoap [ITU-T_ASN.1_Rapporteur][ISO-IEC_JTC1_SC6_ASN.1_Rapporteur] +fdf application/fdf [ISO-TC_171-SC_2][Betsy_Fanning] +fdt+xml application/fdt+xml [RFC6726] +fhir+json application/fhir+json [HL7][Grahame_Grieve] +fhir+xml application/fhir+xml [HL7][Grahame_Grieve] +fits application/fits [RFC4047] +flexfec application/flexfec [RFC8627] +font-sfnt - DEPRECATED in favor of font/sfnt application/font-sfnt [Levantovsky][ISO-IEC_JTC1][RFC8081] +font-tdpfr application/font-tdpfr [RFC3073] +font-woff - DEPRECATED in favor of font/woff application/font-woff [W3C][RFC8081] +framework-attributes+xml application/framework-attributes+xml [RFC6230] +geo+json application/geo+json [RFC7946] +geo+json-seq application/geo+json-seq [RFC8142] +geopackage+sqlite3 application/geopackage+sqlite3 [OGC][Scott_Simmons] +geoxacml+xml application/geoxacml+xml [OGC][Scott_Simmons] +gltf-buffer application/gltf-buffer [Khronos][Saurabh_Bhatia] +gml+xml application/gml+xml [OGC][Clemens_Portele] +gzip application/gzip [RFC6713] +H224 application/H224 [RFC4573] +held+xml application/held+xml [RFC5985] +hl7v2+xml application/hl7v2+xml [HL7][Marc_Duteau] +http application/http [RFC9112] +hyperstudio application/hyperstudio [Michael_Domino] +ibe-key-request+xml application/ibe-key-request+xml [RFC5408] +ibe-pkg-reply+xml application/ibe-pkg-reply+xml [RFC5408] +ibe-pp-data application/ibe-pp-data [RFC5408] +iges application/iges [Curtis_Parks] +im-iscomposing+xml application/im-iscomposing+xml [RFC3994] +index application/index [RFC2652] +index.cmd application/index.cmd [RFC2652] +index.obj application/index.obj [RFC2652] +index.response application/index.response [RFC2652] +index.vnd application/index.vnd [RFC2652] +inkml+xml application/inkml+xml [Kazuyuki_Ashimura] +IOTP application/IOTP [RFC2935] +ipfix application/ipfix [RFC5655] +ipp application/ipp [RFC8010] +ISUP application/ISUP [RFC3204] +its+xml application/its+xml [W3C][ITS-IG-W3C] +javascript (OBSOLETED in favor of text/javascript) application/javascript [RFC4329][RFC9239] +jf2feed+json application/jf2feed+json [W3C][Ivan_Herman] +jose application/jose [RFC7515] +jose+json application/jose+json [RFC7515] +jrd+json application/jrd+json [RFC7033] +jscalendar+json application/jscalendar+json [RFC8984] +json application/json [RFC8259] +json-patch+json application/json-patch+json [RFC6902] +json-seq application/json-seq [RFC7464] +jwk+json application/jwk+json [RFC7517] +jwk-set+json application/jwk-set+json [RFC7517] +jwt application/jwt [RFC7519] +kpml-request+xml application/kpml-request+xml [RFC4730] +kpml-response+xml application/kpml-response+xml [RFC4730] +ld+json application/ld+json [W3C][Ivan_Herman] +lgr+xml application/lgr+xml [RFC7940] +link-format application/link-format [RFC6690] +linkset application/linkset [RFC9264] +linkset+json application/linkset+json [RFC9264] +load-control+xml application/load-control+xml [RFC7200] +lost+xml application/lost+xml [RFC5222] +lostsync+xml application/lostsync+xml [RFC6739] +lpf+zip application/lpf+zip [W3C][Ivan_Herman] +LXF application/LXF [ASAM][Thomas_Thomsen] +mac-binhex40 application/mac-binhex40 [Patrik_Faltstrom] +macwriteii application/macwriteii [Paul_Lindner] +mads+xml application/mads+xml [RFC6207] +manifest+json application/manifest+json [W3C][Marcos_Caceres] +marc application/marc [RFC2220] +marcxml+xml application/marcxml+xml [RFC6207] +mathematica application/mathematica [Wolfram] +mathml+xml application/mathml+xml [W3C][http://www.w3.org/TR/MathML3/appendixb.html] +mathml-content+xml application/mathml-content+xml [W3C][http://www.w3.org/TR/MathML3/appendixb.html] +mathml-presentation+xml application/mathml-presentation+xml [W3C][http://www.w3.org/TR/MathML3/appendixb.html] +mbms-associated-procedure-description+xml application/mbms-associated-procedure-description+xml [_3GPP] +mbms-deregister+xml application/mbms-deregister+xml [_3GPP] +mbms-envelope+xml application/mbms-envelope+xml [_3GPP] +mbms-msk-response+xml application/mbms-msk-response+xml [_3GPP] +mbms-msk+xml application/mbms-msk+xml [_3GPP] +mbms-protection-description+xml application/mbms-protection-description+xml [_3GPP] +mbms-reception-report+xml application/mbms-reception-report+xml [_3GPP] +mbms-register-response+xml application/mbms-register-response+xml [_3GPP] +mbms-register+xml application/mbms-register+xml [_3GPP] +mbms-schedule+xml application/mbms-schedule+xml [_3GPP][Eric_Turcotte] +mbms-user-service-description+xml application/mbms-user-service-description+xml [_3GPP] +mbox application/mbox [RFC4155] +media_control+xml application/media_control+xml [RFC5168] +media-policy-dataset+xml application/media-policy-dataset+xml [RFC6796] +mediaservercontrol+xml application/mediaservercontrol+xml [RFC5022] +merge-patch+json application/merge-patch+json [RFC7396] +metalink4+xml application/metalink4+xml [RFC5854] +mets+xml application/mets+xml [RFC6207] +MF4 application/MF4 [ASAM][Thomas_Thomsen] +mikey application/mikey [RFC3830] +mipc application/mipc [NCGIS][Bryan_Blank] +missing-blocks+cbor-seq application/missing-blocks+cbor-seq [RFC9177] +mmt-aei+xml application/mmt-aei+xml [ATSC] +mmt-usd+xml application/mmt-usd+xml [ATSC] +mods+xml application/mods+xml [RFC6207] +moss-keys application/moss-keys [RFC1848] +moss-signature application/moss-signature [RFC1848] +mosskey-data application/mosskey-data [RFC1848] +mosskey-request application/mosskey-request [RFC1848] +mp21 application/mp21 [RFC6381][David_Singer] +mp4 application/mp4 [RFC4337][RFC6381] +mpeg4-generic application/mpeg4-generic [RFC3640] +mpeg4-iod application/mpeg4-iod [RFC4337] +mpeg4-iod-xmt application/mpeg4-iod-xmt [RFC4337] +mrb-consumer+xml application/mrb-consumer+xml [RFC6917] +mrb-publish+xml application/mrb-publish+xml [RFC6917] +msc-ivr+xml application/msc-ivr+xml [RFC6231] +msc-mixer+xml application/msc-mixer+xml [RFC6505] +msword application/msword [Paul_Lindner] +mud+json application/mud+json [RFC8520] +multipart-core application/multipart-core [RFC8710] +mxf application/mxf [RFC4539] +n-quads application/n-quads [W3C][Eric_Prudhommeaux] +n-triples application/n-triples [W3C][Eric_Prudhommeaux] +nasdata application/nasdata [RFC4707] +news-checkgroups application/news-checkgroups [RFC5537] +news-groupinfo application/news-groupinfo [RFC5537] +news-transmission application/news-transmission [RFC5537] +nlsml+xml application/nlsml+xml [RFC6787] +node application/node [Node.js_TSC] +nss application/nss [Michael_Hammer] +oauth-authz-req+jwt application/oauth-authz-req+jwt [RFC9101] +oblivious-dns-message application/oblivious-dns-message [RFC9230] +ocsp-request application/ocsp-request [RFC6960] +ocsp-response application/ocsp-response [RFC6960] +octet-stream application/octet-stream [RFC2045][RFC2046] +ODA application/ODA [RFC1494] +odm+xml application/odm+xml [CDISC][Sam_Hume] +ODX application/ODX [ASAM][Thomas_Thomsen] +oebps-package+xml application/oebps-package+xml [W3C][EPUB_3_WG] +ogg application/ogg [RFC5334][RFC7845] +opc-nodeset+xml application/opc-nodeset+xml [OPC_Foundation] +oscore application/oscore [RFC8613] +oxps application/oxps [Ecma_International_Helpdesk] +p21 application/p21 [ISO-TC_184-SC_4][Dana_Tripp] +p21+zip application/p21+zip [ISO-TC_184-SC_4][Dana_Tripp] +p2p-overlay+xml application/p2p-overlay+xml [RFC6940] +parityfec application/parityfec [RFC3009] +passport application/passport [RFC8225] +patch-ops-error+xml application/patch-ops-error+xml [RFC5261] +pdf application/pdf [RFC8118] +PDX application/PDX [ASAM][Thomas_Thomsen] +pem-certificate-chain application/pem-certificate-chain [RFC8555] +pgp-encrypted application/pgp-encrypted [RFC3156] +pgp-keys application/pgp-keys [RFC3156] +pgp-signature application/pgp-signature [RFC3156] +pidf-diff+xml application/pidf-diff+xml [RFC5262] +pidf+xml application/pidf+xml [RFC3863] +pkcs10 application/pkcs10 [RFC5967] +pkcs7-mime application/pkcs7-mime [RFC8551][RFC7114] +pkcs7-signature application/pkcs7-signature [RFC8551] +pkcs8 application/pkcs8 [RFC5958] +pkcs8-encrypted application/pkcs8-encrypted [RFC8351] +pkcs12 application/pkcs12 [IETF] +pkix-attr-cert application/pkix-attr-cert [RFC5877] +pkix-cert application/pkix-cert [RFC2585] +pkix-crl application/pkix-crl [RFC2585] +pkix-pkipath application/pkix-pkipath [RFC6066] +pkixcmp application/pkixcmp [RFC2510] +pls+xml application/pls+xml [RFC4267] +poc-settings+xml application/poc-settings+xml [RFC4354] +postscript application/postscript [RFC2045][RFC2046] +ppsp-tracker+json application/ppsp-tracker+json [RFC7846] +problem+json application/problem+json [RFC7807] +problem+xml application/problem+xml [RFC7807] +provenance+xml application/provenance+xml [W3C][Ivan_Herman] +prs.alvestrand.titrax-sheet application/prs.alvestrand.titrax-sheet [Harald_T._Alvestrand] +prs.cww application/prs.cww [Khemchart_Rungchavalnont] +prs.cyn application/prs.cyn [Cynthia_Revström] +prs.hpub+zip application/prs.hpub+zip [Giulio_Zambon] +prs.nprend application/prs.nprend [Jay_Doggett] +prs.plucker application/prs.plucker [Bill_Janssen] +prs.rdf-xml-crypt application/prs.rdf-xml-crypt [Toby_Inkster] +prs.xsf+xml application/prs.xsf+xml [Maik_Stührenberg] +pskc+xml application/pskc+xml [RFC6030] +pvd+json application/pvd+json [RFC8801] +rdf+xml application/rdf+xml [RFC3870] +route-apd+xml application/route-apd+xml [ATSC] +route-s-tsid+xml application/route-s-tsid+xml [ATSC] +route-usd+xml application/route-usd+xml [ATSC] +QSIG application/QSIG [RFC3204] +raptorfec application/raptorfec [RFC6682] +rdap+json application/rdap+json [RFC9083] +reginfo+xml application/reginfo+xml [RFC3680] +relax-ng-compact-syntax application/relax-ng-compact-syntax [http://www.jtc1sc34.org/repository/0661.pdf] +remote-printing application/remote-printing [RFC1486][Marshall_Rose] +reputon+json application/reputon+json [RFC7071] +resource-lists-diff+xml application/resource-lists-diff+xml [RFC5362] +resource-lists+xml application/resource-lists+xml [RFC4826] +rfc+xml application/rfc+xml [RFC7991] +riscos application/riscos [Nick_Smith] +rlmi+xml application/rlmi+xml [RFC4662] +rls-services+xml application/rls-services+xml [RFC4826] +rpki-ghostbusters application/rpki-ghostbusters [RFC6493] +rpki-manifest application/rpki-manifest [RFC6481] +rpki-publication application/rpki-publication [RFC8181] +rpki-roa application/rpki-roa [RFC6481] +rpki-updown application/rpki-updown [RFC6492] +rtf application/rtf [Paul_Lindner] +rtploopback application/rtploopback [RFC6849] +rtx application/rtx [RFC4588] +samlassertion+xml application/samlassertion+xml [OASIS_Security_Services_Technical_Committee_SSTC] +samlmetadata+xml application/samlmetadata+xml [OASIS_Security_Services_Technical_Committee_SSTC] +sarif-external-properties+json application/sarif-external-properties+json [OASIS][David_Keaton][Michael_C._Fanning] +sarif+json application/sarif+json [OASIS][Michael_C._Fanning][Laurence_J._Golding] +sbe application/sbe [FIX_Trading_Community][Donald_L._Mendelson] +sbml+xml application/sbml+xml [RFC3823] +scaip+xml application/scaip+xml [SIS][Oskar_Jonsson] +scim+json application/scim+json [RFC7644] +scvp-cv-request application/scvp-cv-request [RFC5055] +scvp-cv-response application/scvp-cv-response [RFC5055] +scvp-vp-request application/scvp-vp-request [RFC5055] +scvp-vp-response application/scvp-vp-response [RFC5055] +sdp application/sdp [RFC8866] +secevent+jwt application/secevent+jwt [RFC8417] +senml-etch+cbor application/senml-etch+cbor [RFC8790] +senml-etch+json application/senml-etch+json [RFC8790] +senml-exi application/senml-exi [RFC8428] +senml+cbor application/senml+cbor [RFC8428] +senml+json application/senml+json [RFC8428] +senml+xml application/senml+xml [RFC8428] +sensml-exi application/sensml-exi [RFC8428] +sensml+cbor application/sensml+cbor [RFC8428] +sensml+json application/sensml+json [RFC8428] +sensml+xml application/sensml+xml [RFC8428] +sep-exi application/sep-exi [Robby_Simpson][ZigBee] +sep+xml application/sep+xml [Robby_Simpson][ZigBee] +session-info application/session-info [_3GPP][Frederic_Firmin] +set-payment application/set-payment [Brian_Korver] +set-payment-initiation application/set-payment-initiation [Brian_Korver] +set-registration application/set-registration [Brian_Korver] +set-registration-initiation application/set-registration-initiation [Brian_Korver] +SGML application/SGML [RFC1874] +sgml-open-catalog application/sgml-open-catalog [Paul_Grosso] +shf+xml application/shf+xml [RFC4194] +sieve application/sieve [RFC5228] +simple-filter+xml application/simple-filter+xml [RFC4661] +simple-message-summary application/simple-message-summary [RFC3842] +simpleSymbolContainer application/simpleSymbolContainer [_3GPP] +sipc application/sipc [NCGIS][Bryan_Blank] +slate application/slate [Terry_Crowley] +smil (OBSOLETED in favor of application/smil+xml) application/smil [RFC4536] +smil+xml application/smil+xml [RFC4536] +smpte336m application/smpte336m [RFC6597] +soap+fastinfoset application/soap+fastinfoset [ITU-T_ASN.1_Rapporteur][ISO-IEC_JTC1_SC6_ASN.1_Rapporteur] +soap+xml application/soap+xml [RFC3902] +sparql-query application/sparql-query [W3C][http://www.w3.org/TR/2007/CR-rdf-sparql-query-20070614/#mediaType] +spdx+json application/spdx+json [Linux_Foundation][Rose_Judge] +sparql-results+xml application/sparql-results+xml [W3C][http://www.w3.org/TR/2007/CR-rdf-sparql-XMLres-20070925/#mime] +spirits-event+xml application/spirits-event+xml [RFC3910] +sql application/sql [RFC6922] +srgs application/srgs [RFC4267] +srgs+xml application/srgs+xml [RFC4267] +sru+xml application/sru+xml [RFC6207] +ssml+xml application/ssml+xml [RFC4267] +stix+json application/stix+json [OASIS][Chet_Ensign] +swid+cbor application/swid+cbor [RFC-ietf-sacm-coswid-22] +swid+xml application/swid+xml [ISO-IEC_JTC1][David_Waltermire][Ron_Brill] +tamp-apex-update application/tamp-apex-update [RFC5934] +tamp-apex-update-confirm application/tamp-apex-update-confirm [RFC5934] +tamp-community-update application/tamp-community-update [RFC5934] +tamp-community-update-confirm application/tamp-community-update-confirm [RFC5934] +tamp-error application/tamp-error [RFC5934] +tamp-sequence-adjust application/tamp-sequence-adjust [RFC5934] +tamp-sequence-adjust-confirm application/tamp-sequence-adjust-confirm [RFC5934] +tamp-status-query application/tamp-status-query [RFC5934] +tamp-status-response application/tamp-status-response [RFC5934] +tamp-update application/tamp-update [RFC5934] +tamp-update-confirm application/tamp-update-confirm [RFC5934] +taxii+json application/taxii+json [OASIS][Chet_Ensign] +td+json application/td+json [W3C][Matthias_Kovatsch] +tei+xml application/tei+xml [RFC6129] +TETRA_ISI application/TETRA_ISI [ETSI][Miguel_Angel_Reina_Ortega] +thraud+xml application/thraud+xml [RFC5941] +timestamp-query application/timestamp-query [RFC3161] +timestamp-reply application/timestamp-reply [RFC3161] +timestamped-data application/timestamped-data [RFC5955] +tlsrpt+gzip application/tlsrpt+gzip [RFC8460] +tlsrpt+json application/tlsrpt+json [RFC8460] +tm+json application/tm+json [W3C][Sebastian_Kaebisch] +tnauthlist application/tnauthlist [RFC8226] +token-introspection+jwt application/token-introspection+jwt [RFC-oauth-jwt-introspection-response-12] +trickle-ice-sdpfrag application/trickle-ice-sdpfrag [RFC8840] +trig application/trig [W3C][W3C_RDF_Working_Group] +ttml+xml application/ttml+xml [W3C][W3C_Timed_Text_Working_Group] +tve-trigger application/tve-trigger [Linda_Welsh] +tzif application/tzif [RFC8536] +tzif-leap application/tzif-leap [RFC8536] +ulpfec application/ulpfec [RFC5109] +urc-grpsheet+xml application/urc-grpsheet+xml [Gottfried_Zimmermann][ISO-IEC_JTC1] +urc-ressheet+xml application/urc-ressheet+xml [Gottfried_Zimmermann][ISO-IEC_JTC1] +urc-targetdesc+xml application/urc-targetdesc+xml [Gottfried_Zimmermann][ISO-IEC_JTC1] +urc-uisocketdesc+xml application/urc-uisocketdesc+xml [Gottfried_Zimmermann] +vcard+json application/vcard+json [RFC7095] +vcard+xml application/vcard+xml [RFC6351] +vemmi application/vemmi [RFC2122] +vnd.1000minds.decision-model+xml application/vnd.1000minds.decision-model+xml [Franz_Ombler] +vnd.3gpp.5gnas application/vnd.3gpp.5gnas [_3GPP][Jones_Lu_Yunjie] +vnd.3gpp.access-transfer-events+xml application/vnd.3gpp.access-transfer-events+xml [Frederic_Firmin] +vnd.3gpp.bsf+xml application/vnd.3gpp.bsf+xml [John_M_Meredith] +vnd.3gpp.GMOP+xml application/vnd.3gpp.GMOP+xml [Frederic_Firmin] +vnd.3gpp.gtpc application/vnd.3gpp.gtpc [_3GPP][Yang_Yong] +vnd.3gpp.interworking-data application/vnd.3gpp.interworking-data [Frederic_Firmin] +vnd.3gpp.lpp application/vnd.3gpp.lpp [_3GPP][Jones_Lu_Yunjie] +vnd.3gpp.mc-signalling-ear application/vnd.3gpp.mc-signalling-ear [Tim_Woodward] +vnd.3gpp.mcdata-affiliation-command+xml application/vnd.3gpp.mcdata-affiliation-command+xml [Frederic_Firmin] +vnd.3gpp.mcdata-info+xml application/vnd.3gpp.mcdata-info+xml [Frederic_Firmin] +vnd.3gpp.mcdata-msgstore-ctrl-request+xml application/vnd.3gpp.mcdata-msgstore-ctrl-request+xml [Kiran_Kapale] +vnd.3gpp.mcdata-payload application/vnd.3gpp.mcdata-payload [Frederic_Firmin] +vnd.3gpp.mcdata-regroup+xml application/vnd.3gpp.mcdata-regroup+xml [Kiran_Kapale] +vnd.3gpp.mcdata-service-config+xml application/vnd.3gpp.mcdata-service-config+xml [Frederic_Firmin] +vnd.3gpp.mcdata-signalling application/vnd.3gpp.mcdata-signalling [Frederic_Firmin] +vnd.3gpp.mcdata-ue-config+xml application/vnd.3gpp.mcdata-ue-config+xml [Frederic_Firmin] +vnd.3gpp.mcdata-user-profile+xml application/vnd.3gpp.mcdata-user-profile+xml [Frederic_Firmin] +vnd.3gpp.mcptt-affiliation-command+xml application/vnd.3gpp.mcptt-affiliation-command+xml [Frederic_Firmin] +vnd.3gpp.mcptt-floor-request+xml application/vnd.3gpp.mcptt-floor-request+xml [Frederic_Firmin] +vnd.3gpp.mcptt-info+xml application/vnd.3gpp.mcptt-info+xml [Frederic_Firmin] +vnd.3gpp.mcptt-location-info+xml application/vnd.3gpp.mcptt-location-info+xml [Frederic_Firmin] +vnd.3gpp.mcptt-mbms-usage-info+xml application/vnd.3gpp.mcptt-mbms-usage-info+xml [Frederic_Firmin] +vnd.3gpp.mcptt-service-config+xml application/vnd.3gpp.mcptt-service-config+xml [Frederic_Firmin] +vnd.3gpp.mcptt-signed+xml application/vnd.3gpp.mcptt-signed+xml [Frederic_Firmin] +vnd.3gpp.mcptt-ue-config+xml application/vnd.3gpp.mcptt-ue-config+xml [Frederic_Firmin] +vnd.3gpp.mcptt-ue-init-config+xml application/vnd.3gpp.mcptt-ue-init-config+xml [Frederic_Firmin] +vnd.3gpp.mcptt-user-profile+xml application/vnd.3gpp.mcptt-user-profile+xml [Frederic_Firmin] +vnd.3gpp.mcvideo-affiliation-command+xml application/vnd.3gpp.mcvideo-affiliation-command+xml [Frederic_Firmin] +vnd.3gpp.mcvideo-affiliation-info+xml (OBSOLETED in favor of application/vnd.3gpp.mcvideo-affiliation-info+xml [Frederic_Firmin] +application/vnd.3gpp.mcvideo-info+xml) +vnd.3gpp.mcvideo-info+xml application/vnd.3gpp.mcvideo-info+xml [Frederic_Firmin] +vnd.3gpp.mcvideo-location-info+xml application/vnd.3gpp.mcvideo-location-info+xml [Frederic_Firmin] +vnd.3gpp.mcvideo-mbms-usage-info+xml application/vnd.3gpp.mcvideo-mbms-usage-info+xml [Frederic_Firmin] +vnd.3gpp.mcvideo-service-config+xml application/vnd.3gpp.mcvideo-service-config+xml [Frederic_Firmin] +vnd.3gpp.mcvideo-transmission-request+xml application/vnd.3gpp.mcvideo-transmission-request+xml [Frederic_Firmin] +vnd.3gpp.mcvideo-ue-config+xml application/vnd.3gpp.mcvideo-ue-config+xml [Frederic_Firmin] +vnd.3gpp.mcvideo-user-profile+xml application/vnd.3gpp.mcvideo-user-profile+xml [Frederic_Firmin] +vnd.3gpp.mid-call+xml application/vnd.3gpp.mid-call+xml [Frederic_Firmin] +vnd.3gpp.ngap application/vnd.3gpp.ngap [_3GPP][Yang_Yong] +vnd.3gpp.pfcp application/vnd.3gpp.pfcp [_3GPP][Bruno_Landais] +vnd.3gpp.pic-bw-large application/vnd.3gpp.pic-bw-large [John_M_Meredith] +vnd.3gpp.pic-bw-small application/vnd.3gpp.pic-bw-small [John_M_Meredith] +vnd.3gpp.pic-bw-var application/vnd.3gpp.pic-bw-var [John_M_Meredith] +vnd.3gpp-prose-pc3a+xml application/vnd.3gpp-prose-pc3a+xml [Haorui_Yang] +vnd.3gpp-prose-pc3ach+xml application/vnd.3gpp-prose-pc3ach+xml [Haorui_Yang] +vnd.3gpp-prose-pc3ch+xml application/vnd.3gpp-prose-pc3ch+xml [Frederic_Firmin] +vnd.3gpp-prose-pc8+xml application/vnd.3gpp-prose-pc8+xml [Haorui_Yang] +vnd.3gpp-prose+xml application/vnd.3gpp-prose+xml [Frederic_Firmin] +vnd.3gpp.s1ap application/vnd.3gpp.s1ap [_3GPP][Yang_Yong] +vnd.3gpp.sms application/vnd.3gpp.sms [John_M_Meredith] +vnd.3gpp.sms+xml application/vnd.3gpp.sms+xml [Frederic_Firmin] +vnd.3gpp.srvcc-ext+xml application/vnd.3gpp.srvcc-ext+xml [Frederic_Firmin] +vnd.3gpp.SRVCC-info+xml application/vnd.3gpp.SRVCC-info+xml [Frederic_Firmin] +vnd.3gpp.state-and-event-info+xml application/vnd.3gpp.state-and-event-info+xml [Frederic_Firmin] +vnd.3gpp.ussd+xml application/vnd.3gpp.ussd+xml [Frederic_Firmin] +vnd.3gpp-v2x-local-service-information application/vnd.3gpp-v2x-local-service-information [Frederic_Firmin] +vnd.3gpp2.bcmcsinfo+xml application/vnd.3gpp2.bcmcsinfo+xml [AC_Mahendran] +vnd.3gpp2.sms application/vnd.3gpp2.sms [AC_Mahendran] +vnd.3gpp2.tcap application/vnd.3gpp2.tcap [AC_Mahendran] +vnd.3lightssoftware.imagescal application/vnd.3lightssoftware.imagescal [Gus_Asadi] +vnd.3M.Post-it-Notes application/vnd.3M.Post-it-Notes [Michael_OBrien] +vnd.accpac.simply.aso application/vnd.accpac.simply.aso [Steve_Leow] +vnd.accpac.simply.imp application/vnd.accpac.simply.imp [Steve_Leow] +vnd.acucobol application/vnd.acucobol [Dovid_Lubin] +vnd.acucorp application/vnd.acucorp [Dovid_Lubin] +vnd.adobe.flash.movie application/vnd.adobe.flash.movie [Henrik_Andersson] +vnd.adobe.formscentral.fcdt application/vnd.adobe.formscentral.fcdt [Chris_Solc] +vnd.adobe.fxp application/vnd.adobe.fxp [Steven_Heintz] +vnd.adobe.partial-upload application/vnd.adobe.partial-upload [Tapani_Otala] +vnd.adobe.xdp+xml application/vnd.adobe.xdp+xml [John_Brinkman] +vnd.aether.imp application/vnd.aether.imp [Jay_Moskowitz] +vnd.afpc.afplinedata application/vnd.afpc.afplinedata [Jörg_Palmer] +vnd.afpc.afplinedata-pagedef application/vnd.afpc.afplinedata-pagedef [Jörg_Palmer] +vnd.afpc.cmoca-cmresource application/vnd.afpc.cmoca-cmresource [Jörg_Palmer] +vnd.afpc.foca-charset application/vnd.afpc.foca-charset [Jörg_Palmer] +vnd.afpc.foca-codedfont application/vnd.afpc.foca-codedfont [Jörg_Palmer] +vnd.afpc.foca-codepage application/vnd.afpc.foca-codepage [Jörg_Palmer] +vnd.afpc.modca application/vnd.afpc.modca [Jörg_Palmer] +vnd.afpc.modca-cmtable application/vnd.afpc.modca-cmtable [Jörg_Palmer] +vnd.afpc.modca-formdef application/vnd.afpc.modca-formdef [Jörg_Palmer] +vnd.afpc.modca-mediummap application/vnd.afpc.modca-mediummap [Jörg_Palmer] +vnd.afpc.modca-objectcontainer application/vnd.afpc.modca-objectcontainer [Jörg_Palmer] +vnd.afpc.modca-overlay application/vnd.afpc.modca-overlay [Jörg_Palmer] +vnd.afpc.modca-pagesegment application/vnd.afpc.modca-pagesegment [Jörg_Palmer] +vnd.age application/vnd.age [Filippo_Valsorda] +vnd.ah-barcode application/vnd.ah-barcode [Katsuhiko_Ichinose] +vnd.ahead.space application/vnd.ahead.space [Tor_Kristensen] +vnd.airzip.filesecure.azf application/vnd.airzip.filesecure.azf [Daniel_Mould][Gary_Clueit] +vnd.airzip.filesecure.azs application/vnd.airzip.filesecure.azs [Daniel_Mould][Gary_Clueit] +vnd.amadeus+json application/vnd.amadeus+json [Patrick_Brosse] +vnd.amazon.mobi8-ebook application/vnd.amazon.mobi8-ebook [Kim_Scarborough] +vnd.americandynamics.acc application/vnd.americandynamics.acc [Gary_Sands] +vnd.amiga.ami application/vnd.amiga.ami [Kevin_Blumberg] +vnd.amundsen.maze+xml application/vnd.amundsen.maze+xml [Mike_Amundsen] +vnd.android.ota application/vnd.android.ota [Greg_Kaiser] +vnd.anki application/vnd.anki [Kerrick_Staley] +vnd.anser-web-certificate-issue-initiation application/vnd.anser-web-certificate-issue-initiation [Hiroyoshi_Mori] +vnd.antix.game-component application/vnd.antix.game-component [Daniel_Shelton] +vnd.apache.arrow.file application/vnd.apache.arrow.file [Apache_Arrow_Project] +vnd.apache.arrow.stream application/vnd.apache.arrow.stream [Apache_Arrow_Project] +vnd.apache.thrift.binary application/vnd.apache.thrift.binary [Roger_Meier] +vnd.apache.thrift.compact application/vnd.apache.thrift.compact [Roger_Meier] +vnd.apache.thrift.json application/vnd.apache.thrift.json [Roger_Meier] +vnd.apexlang application/vnd.apexlang [Fawad_Shaikh] +vnd.api+json application/vnd.api+json [Steve_Klabnik] +vnd.aplextor.warrp+json application/vnd.aplextor.warrp+json [Oleg_Uryutin] +vnd.apothekende.reservation+json application/vnd.apothekende.reservation+json [Adrian_Föder] +vnd.apple.installer+xml application/vnd.apple.installer+xml [Peter_Bierman] +vnd.apple.keynote application/vnd.apple.keynote [Manichandra_Sajjanapu] +vnd.apple.mpegurl application/vnd.apple.mpegurl [RFC8216] +vnd.apple.numbers application/vnd.apple.numbers [Manichandra_Sajjanapu] +vnd.apple.pages application/vnd.apple.pages [Manichandra_Sajjanapu] +vnd.arastra.swi (OBSOLETED in favor of application/vnd.arastra.swi [Bill_Fenner] +application/vnd.aristanetworks.swi) +vnd.aristanetworks.swi application/vnd.aristanetworks.swi [Bill_Fenner] +vnd.artisan+json application/vnd.artisan+json [Brad_Turner] +vnd.artsquare application/vnd.artsquare [Christopher_Smith] +vnd.astraea-software.iota application/vnd.astraea-software.iota [Christopher_Snazell] +vnd.audiograph application/vnd.audiograph [Horia_Cristian_Slusanschi] +vnd.autopackage application/vnd.autopackage [Mike_Hearn] +vnd.avalon+json application/vnd.avalon+json [Ben_Hinman] +vnd.avistar+xml application/vnd.avistar+xml [Vladimir_Vysotsky] +vnd.balsamiq.bmml+xml application/vnd.balsamiq.bmml+xml [Giacomo_Guilizzoni] +vnd.banana-accounting application/vnd.banana-accounting [José_Del_Romano] +vnd.bbf.usp.error application/vnd.bbf.usp.error [Broadband_Forum] +vnd.bbf.usp.msg application/vnd.bbf.usp.msg [Broadband_Forum] +vnd.bbf.usp.msg+json application/vnd.bbf.usp.msg+json [Broadband_Forum] +vnd.balsamiq.bmpr application/vnd.balsamiq.bmpr [Giacomo_Guilizzoni] +vnd.bekitzur-stech+json application/vnd.bekitzur-stech+json [Jegulsky] +vnd.belightsoft.lhzd+zip application/vnd.belightsoft.lhzd+zip [Dmytro_Yunchyk] +vnd.belightsoft.lhzl+zip application/vnd.belightsoft.lhzl+zip [Dmytro_Yunchyk] +vnd.bint.med-content application/vnd.bint.med-content [Heinz-Peter_Schütz] +vnd.biopax.rdf+xml application/vnd.biopax.rdf+xml [Pathway_Commons] +vnd.blink-idb-value-wrapper application/vnd.blink-idb-value-wrapper [Victor_Costan] +vnd.blueice.multipass application/vnd.blueice.multipass [Thomas_Holmstrom] +vnd.bluetooth.ep.oob application/vnd.bluetooth.ep.oob [Mike_Foley] +vnd.bluetooth.le.oob application/vnd.bluetooth.le.oob [Mark_Powell] +vnd.bmi application/vnd.bmi [Tadashi_Gotoh] +vnd.bpf application/vnd.bpf [NCGIS][Bryan_Blank] +vnd.bpf3 application/vnd.bpf3 [NCGIS][Bryan_Blank] +vnd.businessobjects application/vnd.businessobjects [Philippe_Imoucha] +vnd.byu.uapi+json application/vnd.byu.uapi+json [Brent_Moore] +vnd.cab-jscript application/vnd.cab-jscript [Joerg_Falkenberg] +vnd.canon-cpdl application/vnd.canon-cpdl [Shin_Muto] +vnd.canon-lips application/vnd.canon-lips [Shin_Muto] +vnd.capasystems-pg+json application/vnd.capasystems-pg+json [Yüksel_Aydemir] +vnd.cendio.thinlinc.clientconf application/vnd.cendio.thinlinc.clientconf [Peter_Astrand] +vnd.century-systems.tcp_stream application/vnd.century-systems.tcp_stream [Shuji_Fujii] +vnd.chemdraw+xml application/vnd.chemdraw+xml [Glenn_Howes] +vnd.chess-pgn application/vnd.chess-pgn [Kim_Scarborough] +vnd.chipnuts.karaoke-mmd application/vnd.chipnuts.karaoke-mmd [Chunyun_Xiong] +vnd.ciedi application/vnd.ciedi [Hidekazu_Enjo] +vnd.cinderella application/vnd.cinderella [Ulrich_Kortenkamp] +vnd.cirpack.isdn-ext application/vnd.cirpack.isdn-ext [Pascal_Mayeux] +vnd.citationstyles.style+xml application/vnd.citationstyles.style+xml [Rintze_M._Zelle] +vnd.claymore application/vnd.claymore [Ray_Simpson] +vnd.cloanto.rp9 application/vnd.cloanto.rp9 [Mike_Labatt] +vnd.clonk.c4group application/vnd.clonk.c4group [Guenther_Brammer] +vnd.cluetrust.cartomobile-config application/vnd.cluetrust.cartomobile-config [Gaige_Paulsen] +vnd.cluetrust.cartomobile-config-pkg application/vnd.cluetrust.cartomobile-config-pkg [Gaige_Paulsen] +vnd.cncf.helm.chart.content.v1.tar+gzip application/vnd.cncf.helm.chart.content.v1.tar+gzip [Andrew_Block] +vnd.coffeescript application/vnd.coffeescript [Devyn_Collier_Johnson] +vnd.collabio.xodocuments.document application/vnd.collabio.xodocuments.document [Alexey_Meandrov] +vnd.collabio.xodocuments.document-template application/vnd.collabio.xodocuments.document-template [Alexey_Meandrov] +vnd.collabio.xodocuments.presentation application/vnd.collabio.xodocuments.presentation [Alexey_Meandrov] +vnd.collabio.xodocuments.presentation-template application/vnd.collabio.xodocuments.presentation-template [Alexey_Meandrov] +vnd.collabio.xodocuments.spreadsheet application/vnd.collabio.xodocuments.spreadsheet [Alexey_Meandrov] +vnd.collabio.xodocuments.spreadsheet-template application/vnd.collabio.xodocuments.spreadsheet-template [Alexey_Meandrov] +vnd.collection.doc+json application/vnd.collection.doc+json [Irakli_Nadareishvili] +vnd.collection+json application/vnd.collection+json [Mike_Amundsen] +vnd.collection.next+json application/vnd.collection.next+json [Ioseb_Dzmanashvili] +vnd.comicbook-rar application/vnd.comicbook-rar [Kim_Scarborough] +vnd.comicbook+zip application/vnd.comicbook+zip [Kim_Scarborough] +vnd.commerce-battelle application/vnd.commerce-battelle [David_Applebaum] +vnd.commonspace application/vnd.commonspace [Ravinder_Chandhok] +vnd.coreos.ignition+json application/vnd.coreos.ignition+json [Alex_Crawford] +vnd.cosmocaller application/vnd.cosmocaller [Steve_Dellutri] +vnd.contact.cmsg application/vnd.contact.cmsg [Frank_Patz] +vnd.crick.clicker application/vnd.crick.clicker [Andrew_Burt] +vnd.crick.clicker.keyboard application/vnd.crick.clicker.keyboard [Andrew_Burt] +vnd.crick.clicker.palette application/vnd.crick.clicker.palette [Andrew_Burt] +vnd.crick.clicker.template application/vnd.crick.clicker.template [Andrew_Burt] +vnd.crick.clicker.wordbank application/vnd.crick.clicker.wordbank [Andrew_Burt] +vnd.criticaltools.wbs+xml application/vnd.criticaltools.wbs+xml [Jim_Spiller] +vnd.cryptii.pipe+json application/vnd.cryptii.pipe+json [Fränz_Friederes] +vnd.crypto-shade-file application/vnd.crypto-shade-file [Connor_Horman] +vnd.cryptomator.encrypted application/vnd.cryptomator.encrypted [Sebastian_Stenzel] +vnd.cryptomator.vault application/vnd.cryptomator.vault [Sebastian_Stenzel] +vnd.ctc-posml application/vnd.ctc-posml [Bayard_Kohlhepp] +vnd.ctct.ws+xml application/vnd.ctct.ws+xml [Jim_Ancona] +vnd.cups-pdf application/vnd.cups-pdf [Michael_Sweet] +vnd.cups-postscript application/vnd.cups-postscript [Michael_Sweet] +vnd.cups-ppd application/vnd.cups-ppd [Michael_Sweet] +vnd.cups-raster application/vnd.cups-raster [Michael_Sweet] +vnd.cups-raw application/vnd.cups-raw [Michael_Sweet] +vnd.curl application/vnd.curl [Robert_Byrnes] +vnd.cyan.dean.root+xml application/vnd.cyan.dean.root+xml [Matt_Kern] +vnd.cybank application/vnd.cybank [Nor_Helmee] +vnd.cyclonedx+json application/vnd.cyclonedx+json [Patrick_Dwyer] +vnd.cyclonedx+xml application/vnd.cyclonedx+xml [Patrick_Dwyer] +vnd.d2l.coursepackage1p0+zip application/vnd.d2l.coursepackage1p0+zip [Viktor_Haag] +vnd.d3m-dataset application/vnd.d3m-dataset [Mi_Tar] +vnd.d3m-problem application/vnd.d3m-problem [Mi_Tar] +vnd.dart application/vnd.dart [Anders_Sandholm] +vnd.data-vision.rdz application/vnd.data-vision.rdz [James_Fields] +vnd.datalog application/vnd.datalog [Simon_Johnston] +vnd.datapackage+json application/vnd.datapackage+json [Paul_Walsh] +vnd.dataresource+json application/vnd.dataresource+json [Paul_Walsh] +vnd.dbf application/vnd.dbf [Mi_Tar] +vnd.debian.binary-package application/vnd.debian.binary-package [Debian_Policy_mailing_list] +vnd.dece.data application/vnd.dece.data [Michael_A_Dolan] +vnd.dece.ttml+xml application/vnd.dece.ttml+xml [Michael_A_Dolan] +vnd.dece.unspecified application/vnd.dece.unspecified [Michael_A_Dolan] +vnd.dece.zip application/vnd.dece.zip [Michael_A_Dolan] +vnd.denovo.fcselayout-link application/vnd.denovo.fcselayout-link [Michael_Dixon] +vnd.desmume.movie application/vnd.desmume.movie [Henrik_Andersson] +vnd.dir-bi.plate-dl-nosuffix application/vnd.dir-bi.plate-dl-nosuffix [Yamanaka] +vnd.dm.delegation+xml application/vnd.dm.delegation+xml [Axel_Ferrazzini] +vnd.dna application/vnd.dna [Meredith_Searcy] +vnd.document+json application/vnd.document+json [Tom_Christie] +vnd.dolby.mobile.1 application/vnd.dolby.mobile.1 [Steve_Hattersley] +vnd.dolby.mobile.2 application/vnd.dolby.mobile.2 [Steve_Hattersley] +vnd.doremir.scorecloud-binary-document application/vnd.doremir.scorecloud-binary-document [Erik_Ronström] +vnd.dpgraph application/vnd.dpgraph [David_Parker] +vnd.dreamfactory application/vnd.dreamfactory [William_C._Appleton] +vnd.drive+json application/vnd.drive+json [Keith_Kester] +vnd.dtg.local application/vnd.dtg.local [Ali_Teffahi] +vnd.dtg.local.flash application/vnd.dtg.local.flash [Ali_Teffahi] +vnd.dtg.local.html application/vnd.dtg.local.html [Ali_Teffahi] +vnd.dvb.ait application/vnd.dvb.ait [Peter_Siebert][Michael_Lagally] +vnd.dvb.dvbisl+xml application/vnd.dvb.dvbisl+xml [Emily_DUBS] +vnd.dvb.dvbj application/vnd.dvb.dvbj [Peter_Siebert][Michael_Lagally] +vnd.dvb.esgcontainer application/vnd.dvb.esgcontainer [Joerg_Heuer] +vnd.dvb.ipdcdftnotifaccess application/vnd.dvb.ipdcdftnotifaccess [Roy_Yue] +vnd.dvb.ipdcesgaccess application/vnd.dvb.ipdcesgaccess [Joerg_Heuer] +vnd.dvb.ipdcesgaccess2 application/vnd.dvb.ipdcesgaccess2 [Jerome_Marcon] +vnd.dvb.ipdcesgpdd application/vnd.dvb.ipdcesgpdd [Jerome_Marcon] +vnd.dvb.ipdcroaming application/vnd.dvb.ipdcroaming [Yiling_Xu] +vnd.dvb.iptv.alfec-base application/vnd.dvb.iptv.alfec-base [Jean-Baptiste_Henry] +vnd.dvb.iptv.alfec-enhancement application/vnd.dvb.iptv.alfec-enhancement [Jean-Baptiste_Henry] +vnd.dvb.notif-aggregate-root+xml application/vnd.dvb.notif-aggregate-root+xml [Roy_Yue] +vnd.dvb.notif-container+xml application/vnd.dvb.notif-container+xml [Roy_Yue] +vnd.dvb.notif-generic+xml application/vnd.dvb.notif-generic+xml [Roy_Yue] +vnd.dvb.notif-ia-msglist+xml application/vnd.dvb.notif-ia-msglist+xml [Roy_Yue] +vnd.dvb.notif-ia-registration-request+xml application/vnd.dvb.notif-ia-registration-request+xml [Roy_Yue] +vnd.dvb.notif-ia-registration-response+xml application/vnd.dvb.notif-ia-registration-response+xml [Roy_Yue] +vnd.dvb.notif-init+xml application/vnd.dvb.notif-init+xml [Roy_Yue] +vnd.dvb.pfr application/vnd.dvb.pfr [Peter_Siebert][Michael_Lagally] +vnd.dvb.service application/vnd.dvb.service [Peter_Siebert][Michael_Lagally] +vnd.dxr application/vnd.dxr [Michael_Duffy] +vnd.dynageo application/vnd.dynageo [Roland_Mechling] +vnd.dzr application/vnd.dzr [Carl_Anderson] +vnd.easykaraoke.cdgdownload application/vnd.easykaraoke.cdgdownload [Iain_Downs] +vnd.ecip.rlp application/vnd.ecip.rlp [Wei_Tang] +vnd.ecdis-update application/vnd.ecdis-update [Gert_Buettgenbach] +vnd.eclipse.ditto+json application/vnd.eclipse.ditto+json [Eclipse_Ditto_developers] +vnd.ecowin.chart application/vnd.ecowin.chart [Thomas_Olsson] +vnd.ecowin.filerequest application/vnd.ecowin.filerequest [Thomas_Olsson] +vnd.ecowin.fileupdate application/vnd.ecowin.fileupdate [Thomas_Olsson] +vnd.ecowin.series application/vnd.ecowin.series [Thomas_Olsson] +vnd.ecowin.seriesrequest application/vnd.ecowin.seriesrequest [Thomas_Olsson] +vnd.ecowin.seriesupdate application/vnd.ecowin.seriesupdate [Thomas_Olsson] +vnd.efi.img application/vnd.efi.img [UEFI_Forum][Fu_Siyuan] +vnd.efi.iso application/vnd.efi.iso [UEFI_Forum][Fu_Siyuan] +vnd.emclient.accessrequest+xml application/vnd.emclient.accessrequest+xml [Filip_Navara] +vnd.enliven application/vnd.enliven [Paul_Santinelli_Jr.] +vnd.enphase.envoy application/vnd.enphase.envoy [Chris_Eich] +vnd.eprints.data+xml application/vnd.eprints.data+xml [Tim_Brody] +vnd.epson.esf application/vnd.epson.esf [Shoji_Hoshina] +vnd.epson.msf application/vnd.epson.msf [Shoji_Hoshina] +vnd.epson.quickanime application/vnd.epson.quickanime [Yu_Gu] +vnd.epson.salt application/vnd.epson.salt [Yasuhito_Nagatomo] +vnd.epson.ssf application/vnd.epson.ssf [Shoji_Hoshina] +vnd.ericsson.quickcall application/vnd.ericsson.quickcall [Paul_Tidwell] +vnd.espass-espass+zip application/vnd.espass-espass+zip [Marcus_Ligi_Büschleb] +vnd.eszigno3+xml application/vnd.eszigno3+xml [Szilveszter_Tóth] +vnd.etsi.aoc+xml application/vnd.etsi.aoc+xml [Shicheng_Hu] +vnd.etsi.asic-s+zip application/vnd.etsi.asic-s+zip [Miguel_Angel_Reina_Ortega] +vnd.etsi.asic-e+zip application/vnd.etsi.asic-e+zip [Miguel_Angel_Reina_Ortega] +vnd.etsi.cug+xml application/vnd.etsi.cug+xml [Shicheng_Hu] +vnd.etsi.iptvcommand+xml application/vnd.etsi.iptvcommand+xml [Shicheng_Hu] +vnd.etsi.iptvdiscovery+xml application/vnd.etsi.iptvdiscovery+xml [Shicheng_Hu] +vnd.etsi.iptvprofile+xml application/vnd.etsi.iptvprofile+xml [Shicheng_Hu] +vnd.etsi.iptvsad-bc+xml application/vnd.etsi.iptvsad-bc+xml [Shicheng_Hu] +vnd.etsi.iptvsad-cod+xml application/vnd.etsi.iptvsad-cod+xml [Shicheng_Hu] +vnd.etsi.iptvsad-npvr+xml application/vnd.etsi.iptvsad-npvr+xml [Shicheng_Hu] +vnd.etsi.iptvservice+xml application/vnd.etsi.iptvservice+xml [Miguel_Angel_Reina_Ortega] +vnd.etsi.iptvsync+xml application/vnd.etsi.iptvsync+xml [Miguel_Angel_Reina_Ortega] +vnd.etsi.iptvueprofile+xml application/vnd.etsi.iptvueprofile+xml [Shicheng_Hu] +vnd.etsi.mcid+xml application/vnd.etsi.mcid+xml [Shicheng_Hu] +vnd.etsi.mheg5 application/vnd.etsi.mheg5 [Miguel_Angel_Reina_Ortega][Ian_Medland] +vnd.etsi.overload-control-policy-dataset+xml application/vnd.etsi.overload-control-policy-dataset+xml [Miguel_Angel_Reina_Ortega] +vnd.etsi.pstn+xml application/vnd.etsi.pstn+xml [Jiwan_Han][Thomas_Belling] +vnd.etsi.sci+xml application/vnd.etsi.sci+xml [Shicheng_Hu] +vnd.etsi.simservs+xml application/vnd.etsi.simservs+xml [Shicheng_Hu] +vnd.etsi.timestamp-token application/vnd.etsi.timestamp-token [Miguel_Angel_Reina_Ortega] +vnd.etsi.tsl+xml application/vnd.etsi.tsl+xml [Shicheng_Hu] +vnd.etsi.tsl.der application/vnd.etsi.tsl.der [Shicheng_Hu] +vnd.eu.kasparian.car+json application/vnd.eu.kasparian.car+json [Hervé_Kasparian] +vnd.eudora.data application/vnd.eudora.data [Pete_Resnick] +vnd.evolv.ecig.profile application/vnd.evolv.ecig.profile [James_Bellinger] +vnd.evolv.ecig.settings application/vnd.evolv.ecig.settings [James_Bellinger] +vnd.evolv.ecig.theme application/vnd.evolv.ecig.theme [James_Bellinger] +vnd.exstream-empower+zip application/vnd.exstream-empower+zip [Bill_Kidwell] +vnd.exstream-package application/vnd.exstream-package [Bill_Kidwell] +vnd.ezpix-album application/vnd.ezpix-album [ElectronicZombieCorp] +vnd.ezpix-package application/vnd.ezpix-package [ElectronicZombieCorp] +vnd.f-secure.mobile application/vnd.f-secure.mobile [Samu_Sarivaara] +vnd.fastcopy-disk-image application/vnd.fastcopy-disk-image [Thomas_Huth] +vnd.familysearch.gedcom+zip application/vnd.familysearch.gedcom+zip [Gordon_Clarke] +vnd.fdsn.mseed application/vnd.fdsn.mseed [Chad_Trabant] +vnd.fdsn.seed application/vnd.fdsn.seed [Chad_Trabant] +vnd.ffsns application/vnd.ffsns [Holstage] +vnd.ficlab.flb+zip application/vnd.ficlab.flb+zip [Steve_Gilberd] +vnd.filmit.zfc application/vnd.filmit.zfc [Harms_Moeller] +vnd.fints application/vnd.fints [Ingo_Hammann] +vnd.firemonkeys.cloudcell application/vnd.firemonkeys.cloudcell [Alex_Dubov] +vnd.FloGraphIt application/vnd.FloGraphIt [Dick_Floersch] +vnd.fluxtime.clip application/vnd.fluxtime.clip [Marc_Winter] +vnd.font-fontforge-sfd application/vnd.font-fontforge-sfd [George_Williams] +vnd.framemaker application/vnd.framemaker [Mike_Wexler] +vnd.frogans.fnc (OBSOLETE) application/vnd.frogans.fnc [OP3FT][Alexis_Tamas] +vnd.frogans.ltf (OBSOLETE) application/vnd.frogans.ltf [OP3FT][Alexis_Tamas] +vnd.fsc.weblaunch application/vnd.fsc.weblaunch [Derek_Smith] +vnd.fujifilm.fb.docuworks application/vnd.fujifilm.fb.docuworks [Kazuya_Iimura] +vnd.fujifilm.fb.docuworks.binder application/vnd.fujifilm.fb.docuworks.binder [Kazuya_Iimura] +vnd.fujifilm.fb.docuworks.container application/vnd.fujifilm.fb.docuworks.container [Kazuya_Iimura] +vnd.fujifilm.fb.jfi+xml application/vnd.fujifilm.fb.jfi+xml [Keitaro_Ishida] +vnd.fujitsu.oasys application/vnd.fujitsu.oasys [Nobukazu_Togashi] +vnd.fujitsu.oasys2 application/vnd.fujitsu.oasys2 [Nobukazu_Togashi] +vnd.fujitsu.oasys3 application/vnd.fujitsu.oasys3 [Seiji_Okudaira] +vnd.fujitsu.oasysgp application/vnd.fujitsu.oasysgp [Masahiko_Sugimoto] +vnd.fujitsu.oasysprs application/vnd.fujitsu.oasysprs [Masumi_Ogita] +vnd.fujixerox.ART4 application/vnd.fujixerox.ART4 [Fumio_Tanabe] +vnd.fujixerox.ART-EX application/vnd.fujixerox.ART-EX [Fumio_Tanabe] +vnd.fujixerox.ddd application/vnd.fujixerox.ddd [Masanori_Onda] +vnd.fujixerox.docuworks application/vnd.fujixerox.docuworks [Takatomo_Wakibayashi] +vnd.fujixerox.docuworks.binder application/vnd.fujixerox.docuworks.binder [Takashi_Matsumoto] +vnd.fujixerox.docuworks.container application/vnd.fujixerox.docuworks.container [Kiyoshi_Tashiro] +vnd.fujixerox.HBPL application/vnd.fujixerox.HBPL [Fumio_Tanabe] +vnd.fut-misnet application/vnd.fut-misnet [Jann_Pruulman] +vnd.futoin+cbor application/vnd.futoin+cbor [Andrey_Galkin] +vnd.futoin+json application/vnd.futoin+json [Andrey_Galkin] +vnd.fuzzysheet application/vnd.fuzzysheet [Simon_Birtwistle] +vnd.genomatix.tuxedo application/vnd.genomatix.tuxedo [Torben_Frey] +vnd.genozip application/vnd.genozip [Divon_Lan] +vnd.gentics.grd+json application/vnd.gentics.grd+json [Philipp_Gortan] +vnd.geo+json (OBSOLETED by [RFC7946] in favor of application/geo+json) application/vnd.geo+json [Sean_Gillies] +vnd.geocube+xml (OBSOLETED by request) application/vnd.geocube+xml [Francois_Pirsch] +vnd.geogebra.file application/vnd.geogebra.file [GeoGebra][Yves_Kreis] +vnd.geogebra.slides application/vnd.geogebra.slides [GeoGebra][Michael_Borcherds][Markus_Hohenwarter] +vnd.geogebra.tool application/vnd.geogebra.tool [GeoGebra][Yves_Kreis] +vnd.geometry-explorer application/vnd.geometry-explorer [Michael_Hvidsten] +vnd.geonext application/vnd.geonext [Matthias_Ehmann] +vnd.geoplan application/vnd.geoplan [Christian_Mercat] +vnd.geospace application/vnd.geospace [Christian_Mercat] +vnd.gerber application/vnd.gerber [Thomas_Weyn] +vnd.globalplatform.card-content-mgt application/vnd.globalplatform.card-content-mgt [Gil_Bernabeu] +vnd.globalplatform.card-content-mgt-response application/vnd.globalplatform.card-content-mgt-response [Gil_Bernabeu] +vnd.gmx - DEPRECATED application/vnd.gmx [Christian_V._Sciberras] +vnd.gnu.taler.exchange+json application/vnd.gnu.taler.exchange+json [Christian_Grothoff] +vnd.gnu.taler.merchant+json application/vnd.gnu.taler.merchant+json [Christian_Grothoff] +vnd.google-earth.kml+xml application/vnd.google-earth.kml+xml [Michael_Ashbridge] +vnd.google-earth.kmz application/vnd.google-earth.kmz [Michael_Ashbridge] +vnd.gov.sk.e-form+xml application/vnd.gov.sk.e-form+xml [Peter_Biro][Stefan_Szilva] +vnd.gov.sk.e-form+zip application/vnd.gov.sk.e-form+zip [Peter_Biro][Stefan_Szilva] +vnd.gov.sk.xmldatacontainer+xml application/vnd.gov.sk.xmldatacontainer+xml [Peter_Biro][Stefan_Szilva] +vnd.grafeq application/vnd.grafeq [Jeff_Tupper] +vnd.gridmp application/vnd.gridmp [Jeff_Lawson] +vnd.groove-account application/vnd.groove-account [Todd_Joseph] +vnd.groove-help application/vnd.groove-help [Todd_Joseph] +vnd.groove-identity-message application/vnd.groove-identity-message [Todd_Joseph] +vnd.groove-injector application/vnd.groove-injector [Todd_Joseph] +vnd.groove-tool-message application/vnd.groove-tool-message [Todd_Joseph] +vnd.groove-tool-template application/vnd.groove-tool-template [Todd_Joseph] +vnd.groove-vcard application/vnd.groove-vcard [Todd_Joseph] +vnd.hal+json application/vnd.hal+json [Mike_Kelly] +vnd.hal+xml application/vnd.hal+xml [Mike_Kelly] +vnd.HandHeld-Entertainment+xml application/vnd.HandHeld-Entertainment+xml [Eric_Hamilton] +vnd.hbci application/vnd.hbci [Ingo_Hammann] +vnd.hc+json application/vnd.hc+json [Jan_Schütze] +vnd.hcl-bireports application/vnd.hcl-bireports [Doug_R._Serres] +vnd.hdt application/vnd.hdt [Javier_D._Fernández] +vnd.heroku+json application/vnd.heroku+json [Wesley_Beary] +vnd.hhe.lesson-player application/vnd.hhe.lesson-player [Randy_Jones] +vnd.hp-HPGL application/vnd.hp-HPGL [Bob_Pentecost] +vnd.hp-hpid application/vnd.hp-hpid [Aloke_Gupta] +vnd.hp-hps application/vnd.hp-hps [Steve_Aubrey] +vnd.hp-jlyt application/vnd.hp-jlyt [Amir_Gaash] +vnd.hp-PCL application/vnd.hp-PCL [Bob_Pentecost] +vnd.hp-PCLXL application/vnd.hp-PCLXL [Bob_Pentecost] +vnd.httphone application/vnd.httphone [Franck_Lefevre] +vnd.hydrostatix.sof-data application/vnd.hydrostatix.sof-data [Allen_Gillam] +vnd.hyper-item+json application/vnd.hyper-item+json [Mario_Demuth] +vnd.hyper+json application/vnd.hyper+json [Irakli_Nadareishvili] +vnd.hyperdrive+json application/vnd.hyperdrive+json [Daniel_Sims] +vnd.hzn-3d-crossword application/vnd.hzn-3d-crossword [James_Minnis] +vnd.ibm.afplinedata (OBSOLETED in favor of vnd.afpc.afplinedata) application/vnd.ibm.afplinedata [Roger_Buis] +vnd.ibm.electronic-media application/vnd.ibm.electronic-media [Bruce_Tantlinger] +vnd.ibm.MiniPay application/vnd.ibm.MiniPay [Amir_Herzberg] +vnd.ibm.modcap (OBSOLETED in favor of application/vnd.afpc.modca) application/vnd.ibm.modcap [Reinhard_Hohensee] +vnd.ibm.rights-management application/vnd.ibm.rights-management [Bruce_Tantlinger] +vnd.ibm.secure-container application/vnd.ibm.secure-container [Bruce_Tantlinger] +vnd.iccprofile application/vnd.iccprofile [Phil_Green] +vnd.ieee.1905 application/vnd.ieee.1905 [Purva_R_Rajkotia] +vnd.igloader application/vnd.igloader [Tim_Fisher] +vnd.imagemeter.folder+zip application/vnd.imagemeter.folder+zip [Dirk_Farin] +vnd.imagemeter.image+zip application/vnd.imagemeter.image+zip [Dirk_Farin] +vnd.immervision-ivp application/vnd.immervision-ivp [Mathieu_Villegas] +vnd.immervision-ivu application/vnd.immervision-ivu [Mathieu_Villegas] +vnd.ims.imsccv1p1 application/vnd.ims.imsccv1p1 [Lisa_Mattson] +vnd.ims.imsccv1p2 application/vnd.ims.imsccv1p2 [Lisa_Mattson] +vnd.ims.imsccv1p3 application/vnd.ims.imsccv1p3 [Lisa_Mattson] +vnd.ims.lis.v2.result+json application/vnd.ims.lis.v2.result+json [Lisa_Mattson] +vnd.ims.lti.v2.toolconsumerprofile+json application/vnd.ims.lti.v2.toolconsumerprofile+json [Lisa_Mattson] +vnd.ims.lti.v2.toolproxy.id+json application/vnd.ims.lti.v2.toolproxy.id+json [Lisa_Mattson] +vnd.ims.lti.v2.toolproxy+json application/vnd.ims.lti.v2.toolproxy+json [Lisa_Mattson] +vnd.ims.lti.v2.toolsettings+json application/vnd.ims.lti.v2.toolsettings+json [Lisa_Mattson] +vnd.ims.lti.v2.toolsettings.simple+json application/vnd.ims.lti.v2.toolsettings.simple+json [Lisa_Mattson] +vnd.informedcontrol.rms+xml application/vnd.informedcontrol.rms+xml [Mark_Wahl] +vnd.infotech.project application/vnd.infotech.project [Charles_Engelke] +vnd.infotech.project+xml application/vnd.infotech.project+xml [Charles_Engelke] +vnd.informix-visionary (OBSOLETED in favor of application/vnd.visionary) application/vnd.informix-visionary [Christopher_Gales] +vnd.innopath.wamp.notification application/vnd.innopath.wamp.notification [Takanori_Sudo] +vnd.insors.igm application/vnd.insors.igm [Jon_Swanson] +vnd.intercon.formnet application/vnd.intercon.formnet [Tom_Gurak] +vnd.intergeo application/vnd.intergeo [Yves_Kreis_2] +vnd.intertrust.digibox application/vnd.intertrust.digibox [Luke_Tomasello] +vnd.intertrust.nncp application/vnd.intertrust.nncp [Luke_Tomasello] +vnd.intu.qbo application/vnd.intu.qbo [Greg_Scratchley] +vnd.intu.qfx application/vnd.intu.qfx [Greg_Scratchley] +vnd.ipld.car application/vnd.ipld.car [Marcin_Rataj] +vnd.ipld.raw application/vnd.ipld.raw [Marcin_Rataj] +vnd.iptc.g2.catalogitem+xml application/vnd.iptc.g2.catalogitem+xml [Michael_Steidl] +vnd.iptc.g2.conceptitem+xml application/vnd.iptc.g2.conceptitem+xml [Michael_Steidl] +vnd.iptc.g2.knowledgeitem+xml application/vnd.iptc.g2.knowledgeitem+xml [Michael_Steidl] +vnd.iptc.g2.newsitem+xml application/vnd.iptc.g2.newsitem+xml [Michael_Steidl] +vnd.iptc.g2.newsmessage+xml application/vnd.iptc.g2.newsmessage+xml [Michael_Steidl] +vnd.iptc.g2.packageitem+xml application/vnd.iptc.g2.packageitem+xml [Michael_Steidl] +vnd.iptc.g2.planningitem+xml application/vnd.iptc.g2.planningitem+xml [Michael_Steidl] +vnd.ipunplugged.rcprofile application/vnd.ipunplugged.rcprofile [Per_Ersson] +vnd.irepository.package+xml application/vnd.irepository.package+xml [Martin_Knowles] +vnd.is-xpr application/vnd.is-xpr [Satish_Navarajan] +vnd.isac.fcs application/vnd.isac.fcs [Ryan_Brinkman] +vnd.jam application/vnd.jam [Brijesh_Kumar] +vnd.iso11783-10+zip application/vnd.iso11783-10+zip [Frank_Wiebeler] +vnd.japannet-directory-service application/vnd.japannet-directory-service [Kiyofusa_Fujii] +vnd.japannet-jpnstore-wakeup application/vnd.japannet-jpnstore-wakeup [Jun_Yoshitake] +vnd.japannet-payment-wakeup application/vnd.japannet-payment-wakeup [Kiyofusa_Fujii] +vnd.japannet-registration application/vnd.japannet-registration [Jun_Yoshitake] +vnd.japannet-registration-wakeup application/vnd.japannet-registration-wakeup [Kiyofusa_Fujii] +vnd.japannet-setstore-wakeup application/vnd.japannet-setstore-wakeup [Jun_Yoshitake] +vnd.japannet-verification application/vnd.japannet-verification [Jun_Yoshitake] +vnd.japannet-verification-wakeup application/vnd.japannet-verification-wakeup [Kiyofusa_Fujii] +vnd.jcp.javame.midlet-rms application/vnd.jcp.javame.midlet-rms [Mikhail_Gorshenev] +vnd.jisp application/vnd.jisp [Sebastiaan_Deckers] +vnd.joost.joda-archive application/vnd.joost.joda-archive [Joost] +vnd.jsk.isdn-ngn application/vnd.jsk.isdn-ngn [Yokoyama_Kiyonobu] +vnd.kahootz application/vnd.kahootz [Tim_Macdonald] +vnd.kde.karbon application/vnd.kde.karbon [David_Faure] +vnd.kde.kchart application/vnd.kde.kchart [David_Faure] +vnd.kde.kformula application/vnd.kde.kformula [David_Faure] +vnd.kde.kivio application/vnd.kde.kivio [David_Faure] +vnd.kde.kontour application/vnd.kde.kontour [David_Faure] +vnd.kde.kpresenter application/vnd.kde.kpresenter [David_Faure] +vnd.kde.kspread application/vnd.kde.kspread [David_Faure] +vnd.kde.kword application/vnd.kde.kword [David_Faure] +vnd.kenameaapp application/vnd.kenameaapp [Dirk_DiGiorgio-Haag] +vnd.kidspiration application/vnd.kidspiration [Jack_Bennett] +vnd.Kinar application/vnd.Kinar [Hemant_Thakkar] +vnd.koan application/vnd.koan [Pete_Cole] +vnd.kodak-descriptor application/vnd.kodak-descriptor [Michael_J._Donahue] +vnd.las application/vnd.las [NCGIS][Bryan_Blank] +vnd.las.las+json application/vnd.las.las+json [Rob_Bailey] +vnd.las.las+xml application/vnd.las.las+xml [Rob_Bailey] +vnd.laszip application/vnd.laszip [NCGIS][Bryan_Blank] +vnd.leap+json application/vnd.leap+json [Mark_C_Fralick] +vnd.liberty-request+xml application/vnd.liberty-request+xml [Brett_McDowell] +vnd.llamagraphics.life-balance.desktop application/vnd.llamagraphics.life-balance.desktop [Catherine_E._White] +vnd.llamagraphics.life-balance.exchange+xml application/vnd.llamagraphics.life-balance.exchange+xml [Catherine_E._White] +vnd.logipipe.circuit+zip application/vnd.logipipe.circuit+zip [Victor_Kuchynsky] +vnd.loom application/vnd.loom [Sten_Linnarsson] +vnd.lotus-1-2-3 application/vnd.lotus-1-2-3 [Paul_Wattenberger] +vnd.lotus-approach application/vnd.lotus-approach [Paul_Wattenberger] +vnd.lotus-freelance application/vnd.lotus-freelance [Paul_Wattenberger] +vnd.lotus-notes application/vnd.lotus-notes [Michael_Laramie] +vnd.lotus-organizer application/vnd.lotus-organizer [Paul_Wattenberger] +vnd.lotus-screencam application/vnd.lotus-screencam [Paul_Wattenberger] +vnd.lotus-wordpro application/vnd.lotus-wordpro [Paul_Wattenberger] +vnd.macports.portpkg application/vnd.macports.portpkg [James_Berry] +vnd.mapbox-vector-tile application/vnd.mapbox-vector-tile [Blake_Thompson] +vnd.marlin.drm.actiontoken+xml application/vnd.marlin.drm.actiontoken+xml [Gary_Ellison] +vnd.marlin.drm.conftoken+xml application/vnd.marlin.drm.conftoken+xml [Gary_Ellison] +vnd.marlin.drm.license+xml application/vnd.marlin.drm.license+xml [Gary_Ellison] +vnd.marlin.drm.mdcf application/vnd.marlin.drm.mdcf [Gary_Ellison] +vnd.mason+json application/vnd.mason+json [Jorn_Wildt] +vnd.maxar.archive.3tz+zip application/vnd.maxar.archive.3tz+zip [Erik_Dahlström] +vnd.maxmind.maxmind-db application/vnd.maxmind.maxmind-db [William_Stevenson] +vnd.mcd application/vnd.mcd [Tadashi_Gotoh] +vnd.medcalcdata application/vnd.medcalcdata [Frank_Schoonjans] +vnd.mediastation.cdkey application/vnd.mediastation.cdkey [Henry_Flurry] +vnd.meridian-slingshot application/vnd.meridian-slingshot [Eric_Wedel] +vnd.MFER application/vnd.MFER [Masaaki_Hirai] +vnd.mfmp application/vnd.mfmp [Yukari_Ikeda] +vnd.micro+json application/vnd.micro+json [Dali_Zheng] +vnd.micrografx.flo application/vnd.micrografx.flo [Joe_Prevo] +vnd.micrografx.igx application/vnd.micrografx.igx [Joe_Prevo] +vnd.microsoft.portable-executable application/vnd.microsoft.portable-executable [Henrik_Andersson] +vnd.microsoft.windows.thumbnail-cache application/vnd.microsoft.windows.thumbnail-cache [Henrik_Andersson] +vnd.miele+json application/vnd.miele+json [Nils_Langhammer] +vnd.mif application/vnd.mif [Mike_Wexler] +vnd.minisoft-hp3000-save application/vnd.minisoft-hp3000-save [Chris_Bartram] +vnd.mitsubishi.misty-guard.trustweb application/vnd.mitsubishi.misty-guard.trustweb [Tanaka] +vnd.Mobius.DAF application/vnd.Mobius.DAF [Allen_K._Kabayama] +vnd.Mobius.DIS application/vnd.Mobius.DIS [Allen_K._Kabayama] +vnd.Mobius.MBK application/vnd.Mobius.MBK [Alex_Devasia] +vnd.Mobius.MQY application/vnd.Mobius.MQY [Alex_Devasia] +vnd.Mobius.MSL application/vnd.Mobius.MSL [Allen_K._Kabayama] +vnd.Mobius.PLC application/vnd.Mobius.PLC [Allen_K._Kabayama] +vnd.Mobius.TXF application/vnd.Mobius.TXF [Allen_K._Kabayama] +vnd.mophun.application application/vnd.mophun.application [Bjorn_Wennerstrom] +vnd.mophun.certificate application/vnd.mophun.certificate [Bjorn_Wennerstrom] +vnd.motorola.flexsuite application/vnd.motorola.flexsuite [Mark_Patton] +vnd.motorola.flexsuite.adsi application/vnd.motorola.flexsuite.adsi [Mark_Patton] +vnd.motorola.flexsuite.fis application/vnd.motorola.flexsuite.fis [Mark_Patton] +vnd.motorola.flexsuite.gotap application/vnd.motorola.flexsuite.gotap [Mark_Patton] +vnd.motorola.flexsuite.kmr application/vnd.motorola.flexsuite.kmr [Mark_Patton] +vnd.motorola.flexsuite.ttc application/vnd.motorola.flexsuite.ttc [Mark_Patton] +vnd.motorola.flexsuite.wem application/vnd.motorola.flexsuite.wem [Mark_Patton] +vnd.motorola.iprm application/vnd.motorola.iprm [Rafie_Shamsaasef] +vnd.mozilla.xul+xml application/vnd.mozilla.xul+xml [Braden_N_McDaniel] +vnd.ms-artgalry application/vnd.ms-artgalry [Dean_Slawson] +vnd.ms-asf application/vnd.ms-asf [Eric_Fleischman] +vnd.ms-cab-compressed application/vnd.ms-cab-compressed [Kim_Scarborough] +vnd.ms-3mfdocument application/vnd.ms-3mfdocument [Shawn_Maloney] +vnd.ms-excel application/vnd.ms-excel [Sukvinder_S._Gill] +vnd.ms-excel.addin.macroEnabled.12 application/vnd.ms-excel.addin.macroEnabled.12 [Chris_Rae] +vnd.ms-excel.sheet.binary.macroEnabled.12 application/vnd.ms-excel.sheet.binary.macroEnabled.12 [Chris_Rae] +vnd.ms-excel.sheet.macroEnabled.12 application/vnd.ms-excel.sheet.macroEnabled.12 [Chris_Rae] +vnd.ms-excel.template.macroEnabled.12 application/vnd.ms-excel.template.macroEnabled.12 [Chris_Rae] +vnd.ms-fontobject application/vnd.ms-fontobject [Kim_Scarborough] +vnd.ms-htmlhelp application/vnd.ms-htmlhelp [Anatoly_Techtonik] +vnd.ms-ims application/vnd.ms-ims [Eric_Ledoux] +vnd.ms-lrm application/vnd.ms-lrm [Eric_Ledoux] +vnd.ms-office.activeX+xml application/vnd.ms-office.activeX+xml [Chris_Rae] +vnd.ms-officetheme application/vnd.ms-officetheme [Chris_Rae] +vnd.ms-playready.initiator+xml application/vnd.ms-playready.initiator+xml [Daniel_Schneider] +vnd.ms-powerpoint application/vnd.ms-powerpoint [Sukvinder_S._Gill] +vnd.ms-powerpoint.addin.macroEnabled.12 application/vnd.ms-powerpoint.addin.macroEnabled.12 [Chris_Rae] +vnd.ms-powerpoint.presentation.macroEnabled.12 application/vnd.ms-powerpoint.presentation.macroEnabled.12 [Chris_Rae] +vnd.ms-powerpoint.slide.macroEnabled.12 application/vnd.ms-powerpoint.slide.macroEnabled.12 [Chris_Rae] +vnd.ms-powerpoint.slideshow.macroEnabled.12 application/vnd.ms-powerpoint.slideshow.macroEnabled.12 [Chris_Rae] +vnd.ms-powerpoint.template.macroEnabled.12 application/vnd.ms-powerpoint.template.macroEnabled.12 [Chris_Rae] +vnd.ms-PrintDeviceCapabilities+xml application/vnd.ms-PrintDeviceCapabilities+xml [Justin_Hutchings] +vnd.ms-PrintSchemaTicket+xml application/vnd.ms-PrintSchemaTicket+xml [Justin_Hutchings] +vnd.ms-project application/vnd.ms-project [Sukvinder_S._Gill] +vnd.ms-tnef application/vnd.ms-tnef [Sukvinder_S._Gill] +vnd.ms-windows.devicepairing application/vnd.ms-windows.devicepairing [Justin_Hutchings] +vnd.ms-windows.nwprinting.oob application/vnd.ms-windows.nwprinting.oob [Justin_Hutchings] +vnd.ms-windows.printerpairing application/vnd.ms-windows.printerpairing [Justin_Hutchings] +vnd.ms-windows.wsd.oob application/vnd.ms-windows.wsd.oob [Justin_Hutchings] +vnd.ms-wmdrm.lic-chlg-req application/vnd.ms-wmdrm.lic-chlg-req [Kevin_Lau] +vnd.ms-wmdrm.lic-resp application/vnd.ms-wmdrm.lic-resp [Kevin_Lau] +vnd.ms-wmdrm.meter-chlg-req application/vnd.ms-wmdrm.meter-chlg-req [Kevin_Lau] +vnd.ms-wmdrm.meter-resp application/vnd.ms-wmdrm.meter-resp [Kevin_Lau] +vnd.ms-word.document.macroEnabled.12 application/vnd.ms-word.document.macroEnabled.12 [Chris_Rae] +vnd.ms-word.template.macroEnabled.12 application/vnd.ms-word.template.macroEnabled.12 [Chris_Rae] +vnd.ms-works application/vnd.ms-works [Sukvinder_S._Gill] +vnd.ms-wpl application/vnd.ms-wpl [Dan_Plastina] +vnd.ms-xpsdocument application/vnd.ms-xpsdocument [Jesse_McGatha] +vnd.msa-disk-image application/vnd.msa-disk-image [Thomas_Huth] +vnd.mseq application/vnd.mseq [Gwenael_Le_Bodic] +vnd.msign application/vnd.msign [Malte_Borcherding] +vnd.multiad.creator application/vnd.multiad.creator [Steve_Mills] +vnd.multiad.creator.cif application/vnd.multiad.creator.cif [Steve_Mills] +vnd.musician application/vnd.musician [Greg_Adams] +vnd.music-niff application/vnd.music-niff [Tim_Butler] +vnd.muvee.style application/vnd.muvee.style [Chandrashekhara_Anantharamu] +vnd.mynfc application/vnd.mynfc [Franck_Lefevre] +vnd.nacamar.ybrid+json application/vnd.nacamar.ybrid+json [Sebastian_A._Weiss] +vnd.ncd.control application/vnd.ncd.control [Lauri_Tarkkala] +vnd.ncd.reference application/vnd.ncd.reference [Lauri_Tarkkala] +vnd.nearst.inv+json application/vnd.nearst.inv+json [Thomas_Schoffelen] +vnd.nebumind.line application/vnd.nebumind.line [Andreas_Molzer] +vnd.nervana application/vnd.nervana [Steve_Judkins] +vnd.netfpx application/vnd.netfpx [Andy_Mutz] +vnd.neurolanguage.nlu application/vnd.neurolanguage.nlu [Dan_DuFeu] +vnd.nimn application/vnd.nimn [Amit_Kumar_Gupta] +vnd.nintendo.snes.rom application/vnd.nintendo.snes.rom [Henrik_Andersson] +vnd.nintendo.nitro.rom application/vnd.nintendo.nitro.rom [Henrik_Andersson] +vnd.nitf application/vnd.nitf [Steve_Rogan] +vnd.noblenet-directory application/vnd.noblenet-directory [Monty_Solomon] +vnd.noblenet-sealer application/vnd.noblenet-sealer [Monty_Solomon] +vnd.noblenet-web application/vnd.noblenet-web [Monty_Solomon] +vnd.nokia.catalogs application/vnd.nokia.catalogs [Nokia] +vnd.nokia.conml+wbxml application/vnd.nokia.conml+wbxml [Nokia] +vnd.nokia.conml+xml application/vnd.nokia.conml+xml [Nokia] +vnd.nokia.iptv.config+xml application/vnd.nokia.iptv.config+xml [Nokia] +vnd.nokia.iSDS-radio-presets application/vnd.nokia.iSDS-radio-presets [Nokia] +vnd.nokia.landmark+wbxml application/vnd.nokia.landmark+wbxml [Nokia] +vnd.nokia.landmark+xml application/vnd.nokia.landmark+xml [Nokia] +vnd.nokia.landmarkcollection+xml application/vnd.nokia.landmarkcollection+xml [Nokia] +vnd.nokia.ncd application/vnd.nokia.ncd [Nokia] +vnd.nokia.n-gage.ac+xml application/vnd.nokia.n-gage.ac+xml [Nokia] +vnd.nokia.n-gage.data application/vnd.nokia.n-gage.data [Nokia] +vnd.nokia.n-gage.symbian.install (OBSOLETE; no replacement given) application/vnd.nokia.n-gage.symbian.install [Nokia] +vnd.nokia.pcd+wbxml application/vnd.nokia.pcd+wbxml [Nokia] +vnd.nokia.pcd+xml application/vnd.nokia.pcd+xml [Nokia] +vnd.nokia.radio-preset application/vnd.nokia.radio-preset [Nokia] +vnd.nokia.radio-presets application/vnd.nokia.radio-presets [Nokia] +vnd.novadigm.EDM application/vnd.novadigm.EDM [Janine_Swenson] +vnd.novadigm.EDX application/vnd.novadigm.EDX [Janine_Swenson] +vnd.novadigm.EXT application/vnd.novadigm.EXT [Janine_Swenson] +vnd.ntt-local.content-share application/vnd.ntt-local.content-share [Akinori_Taya] +vnd.ntt-local.file-transfer application/vnd.ntt-local.file-transfer [NTT-local] +vnd.ntt-local.ogw_remote-access application/vnd.ntt-local.ogw_remote-access [NTT-local] +vnd.ntt-local.sip-ta_remote application/vnd.ntt-local.sip-ta_remote [NTT-local] +vnd.ntt-local.sip-ta_tcp_stream application/vnd.ntt-local.sip-ta_tcp_stream [NTT-local] +vnd.oasis.opendocument.base application/vnd.oasis.opendocument.base [OASIS_TC_Admin][OASIS] +vnd.oasis.opendocument.chart application/vnd.oasis.opendocument.chart [OASIS_TC_Admin][OASIS] +vnd.oasis.opendocument.chart-template application/vnd.oasis.opendocument.chart-template [OASIS_TC_Admin][OASIS] +vnd.oasis.opendocument.database (OBSOLETED in favor of application/vnd.oasis.opendocument.database [OASIS_TC_Admin][OASIS] +application/vnd.oasis.opendocument.base) +vnd.oasis.opendocument.formula application/vnd.oasis.opendocument.formula [OASIS_TC_Admin][OASIS] +vnd.oasis.opendocument.formula-template application/vnd.oasis.opendocument.formula-template [OASIS_TC_Admin][OASIS] +vnd.oasis.opendocument.graphics application/vnd.oasis.opendocument.graphics [OASIS_TC_Admin][OASIS] +vnd.oasis.opendocument.graphics-template application/vnd.oasis.opendocument.graphics-template [OASIS_TC_Admin][OASIS] +vnd.oasis.opendocument.image application/vnd.oasis.opendocument.image [OASIS_TC_Admin][OASIS] +vnd.oasis.opendocument.image-template application/vnd.oasis.opendocument.image-template [OASIS_TC_Admin][OASIS] +vnd.oasis.opendocument.presentation application/vnd.oasis.opendocument.presentation [OASIS_TC_Admin][OASIS] +vnd.oasis.opendocument.presentation-template application/vnd.oasis.opendocument.presentation-template [OASIS_TC_Admin][OASIS] +vnd.oasis.opendocument.spreadsheet application/vnd.oasis.opendocument.spreadsheet [OASIS_TC_Admin][OASIS] +vnd.oasis.opendocument.spreadsheet-template application/vnd.oasis.opendocument.spreadsheet-template [OASIS_TC_Admin][OASIS] +vnd.oasis.opendocument.text application/vnd.oasis.opendocument.text [OASIS_TC_Admin][OASIS] +vnd.oasis.opendocument.text-master application/vnd.oasis.opendocument.text-master [OASIS_TC_Admin][OASIS] +vnd.oasis.opendocument.text-template application/vnd.oasis.opendocument.text-template [OASIS_TC_Admin][OASIS] +vnd.oasis.opendocument.text-web application/vnd.oasis.opendocument.text-web [OASIS_TC_Admin][OASIS] +vnd.obn application/vnd.obn [Matthias_Hessling] +vnd.ocf+cbor application/vnd.ocf+cbor [Michael_Koster] +vnd.oci.image.manifest.v1+json application/vnd.oci.image.manifest.v1+json [Steven_Lasker] +vnd.oftn.l10n+json application/vnd.oftn.l10n+json [Eli_Grey] +vnd.oipf.contentaccessdownload+xml application/vnd.oipf.contentaccessdownload+xml [Claire_DEsclercs] +vnd.oipf.contentaccessstreaming+xml application/vnd.oipf.contentaccessstreaming+xml [Claire_DEsclercs] +vnd.oipf.cspg-hexbinary application/vnd.oipf.cspg-hexbinary [Claire_DEsclercs] +vnd.oipf.dae.svg+xml application/vnd.oipf.dae.svg+xml [Claire_DEsclercs] +vnd.oipf.dae.xhtml+xml application/vnd.oipf.dae.xhtml+xml [Claire_DEsclercs] +vnd.oipf.mippvcontrolmessage+xml application/vnd.oipf.mippvcontrolmessage+xml [Claire_DEsclercs] +vnd.oipf.pae.gem application/vnd.oipf.pae.gem [Claire_DEsclercs] +vnd.oipf.spdiscovery+xml application/vnd.oipf.spdiscovery+xml [Claire_DEsclercs] +vnd.oipf.spdlist+xml application/vnd.oipf.spdlist+xml [Claire_DEsclercs] +vnd.oipf.ueprofile+xml application/vnd.oipf.ueprofile+xml [Claire_DEsclercs] +vnd.oipf.userprofile+xml application/vnd.oipf.userprofile+xml [Claire_DEsclercs] +vnd.olpc-sugar application/vnd.olpc-sugar [John_Palmieri] +vnd.oma.bcast.associated-procedure-parameter+xml application/vnd.oma.bcast.associated-procedure-parameter+xml [Uwe_Rauschenbach][Open_Mobile_Naming_Authority] +vnd.oma.bcast.drm-trigger+xml application/vnd.oma.bcast.drm-trigger+xml [Uwe_Rauschenbach][Open_Mobile_Naming_Authority] +vnd.oma.bcast.imd+xml application/vnd.oma.bcast.imd+xml [Uwe_Rauschenbach][Open_Mobile_Naming_Authority] +vnd.oma.bcast.ltkm application/vnd.oma.bcast.ltkm [Uwe_Rauschenbach][Open_Mobile_Naming_Authority] +vnd.oma.bcast.notification+xml application/vnd.oma.bcast.notification+xml [Uwe_Rauschenbach][Open_Mobile_Naming_Authority] +vnd.oma.bcast.provisioningtrigger application/vnd.oma.bcast.provisioningtrigger [Uwe_Rauschenbach][Open_Mobile_Naming_Authority] +vnd.oma.bcast.sgboot application/vnd.oma.bcast.sgboot [Uwe_Rauschenbach][Open_Mobile_Naming_Authority] +vnd.oma.bcast.sgdd+xml application/vnd.oma.bcast.sgdd+xml [Uwe_Rauschenbach][Open_Mobile_Naming_Authority] +vnd.oma.bcast.sgdu application/vnd.oma.bcast.sgdu [Uwe_Rauschenbach][Open_Mobile_Naming_Authority] +vnd.oma.bcast.simple-symbol-container application/vnd.oma.bcast.simple-symbol-container [Uwe_Rauschenbach][Open_Mobile_Naming_Authority] +vnd.oma.bcast.smartcard-trigger+xml application/vnd.oma.bcast.smartcard-trigger+xml [Uwe_Rauschenbach][Open_Mobile_Naming_Authority] +vnd.oma.bcast.sprov+xml application/vnd.oma.bcast.sprov+xml [Uwe_Rauschenbach][Open_Mobile_Naming_Authority] +vnd.oma.bcast.stkm application/vnd.oma.bcast.stkm [Uwe_Rauschenbach][Open_Mobile_Naming_Authority] +vnd.oma.cab-address-book+xml application/vnd.oma.cab-address-book+xml [Hao_Wang][OMA] +vnd.oma.cab-feature-handler+xml application/vnd.oma.cab-feature-handler+xml [Hao_Wang][OMA] +vnd.oma.cab-pcc+xml application/vnd.oma.cab-pcc+xml [Hao_Wang][OMA] +vnd.oma.cab-subs-invite+xml application/vnd.oma.cab-subs-invite+xml [Hao_Wang][OMA] +vnd.oma.cab-user-prefs+xml application/vnd.oma.cab-user-prefs+xml [Hao_Wang][OMA] +vnd.oma.dcd application/vnd.oma.dcd [Avi_Primo][Open_Mobile_Naming_Authority] +vnd.oma.dcdc application/vnd.oma.dcdc [Avi_Primo][Open_Mobile_Naming_Authority] +vnd.oma.dd2+xml application/vnd.oma.dd2+xml [Jun_Sato][Open_Mobile_Alliance_BAC_DLDRM_Working_Group] +vnd.oma.drm.risd+xml application/vnd.oma.drm.risd+xml [Uwe_Rauschenbach][Open_Mobile_Naming_Authority] +vnd.oma.group-usage-list+xml application/vnd.oma.group-usage-list+xml [Sean_Kelley][OMA_Presence_and_Availability_PAG_Working_Group] +vnd.oma.lwm2m+cbor application/vnd.oma.lwm2m+cbor [Open_Mobile_Naming_Authority][John_Mudge] +vnd.oma.lwm2m+json application/vnd.oma.lwm2m+json [Open_Mobile_Naming_Authority][John_Mudge] +vnd.oma.lwm2m+tlv application/vnd.oma.lwm2m+tlv [Open_Mobile_Naming_Authority][John_Mudge] +vnd.oma.pal+xml application/vnd.oma.pal+xml [Brian_McColgan][Open_Mobile_Naming_Authority] +vnd.oma.poc.detailed-progress-report+xml application/vnd.oma.poc.detailed-progress-report+xml [OMA_Push_to_Talk_over_Cellular_POC_Working_Group] +vnd.oma.poc.final-report+xml application/vnd.oma.poc.final-report+xml [OMA_Push_to_Talk_over_Cellular_POC_Working_Group] +vnd.oma.poc.groups+xml application/vnd.oma.poc.groups+xml [Sean_Kelley][OMA_Push_to_Talk_over_Cellular_POC_Working_Group] +vnd.oma.poc.invocation-descriptor+xml application/vnd.oma.poc.invocation-descriptor+xml [OMA_Push_to_Talk_over_Cellular_POC_Working_Group] +vnd.oma.poc.optimized-progress-report+xml application/vnd.oma.poc.optimized-progress-report+xml [OMA_Push_to_Talk_over_Cellular_POC_Working_Group] +vnd.oma.push application/vnd.oma.push [Bryan_Sullivan][OMA] +vnd.oma.scidm.messages+xml application/vnd.oma.scidm.messages+xml [Wenjun_Zeng][Open_Mobile_Naming_Authority] +vnd.oma.xcap-directory+xml application/vnd.oma.xcap-directory+xml [Sean_Kelley][OMA_Presence_and_Availability_PAG_Working_Group] +vnd.omads-email+xml application/vnd.omads-email+xml [OMA_Data_Synchronization_Working_Group] +vnd.omads-file+xml application/vnd.omads-file+xml [OMA_Data_Synchronization_Working_Group] +vnd.omads-folder+xml application/vnd.omads-folder+xml [OMA_Data_Synchronization_Working_Group] +vnd.omaloc-supl-init application/vnd.omaloc-supl-init [Julien_Grange] +vnd.oma-scws-config application/vnd.oma-scws-config [Ilan_Mahalal] +vnd.oma-scws-http-request application/vnd.oma-scws-http-request [Ilan_Mahalal] +vnd.oma-scws-http-response application/vnd.oma-scws-http-response [Ilan_Mahalal] +vnd.onepager application/vnd.onepager [Nathan_Black] +vnd.onepagertamp application/vnd.onepagertamp [Nathan_Black] +vnd.onepagertamx application/vnd.onepagertamx [Nathan_Black] +vnd.onepagertat application/vnd.onepagertat [Nathan_Black] +vnd.onepagertatp application/vnd.onepagertatp [Nathan_Black] +vnd.onepagertatx application/vnd.onepagertatx [Nathan_Black] +vnd.onvif.metadata application/vnd.onvif.metadata [Hans_Busch] +vnd.openblox.game-binary application/vnd.openblox.game-binary [Mark_Otaris] +vnd.openblox.game+xml application/vnd.openblox.game+xml [Mark_Otaris] +vnd.openeye.oeb application/vnd.openeye.oeb [Craig_Bruce] +vnd.openstreetmap.data+xml application/vnd.openstreetmap.data+xml [Paul_Norman] +vnd.opentimestamps.ots application/vnd.opentimestamps.ots [Peter_Todd] +vnd.openxmlformats-officedocument.custom-properties+xml application/vnd.openxmlformats-officedocument.custom-properties+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.customXmlProperties+xml application/vnd.openxmlformats-officedocument.customXmlProperties+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.drawing+xml application/vnd.openxmlformats-officedocument.drawing+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.drawingml.chart+xml application/vnd.openxmlformats-officedocument.drawingml.chart+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.drawingml.chartshapes+xml application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.drawingml.diagramColors+xml application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.drawingml.diagramData+xml application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.extended-properties+xml application/vnd.openxmlformats-officedocument.extended-properties+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml application/vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.comments+xml application/vnd.openxmlformats-officedocument.presentationml.comments+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.notesMaster+xml application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.notesSlide+xml application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.presentation application/vnd.openxmlformats-officedocument.presentationml.presentation [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.presentation.main+xml application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.presProps+xml application/vnd.openxmlformats-officedocument.presentationml.presProps+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.slide application/vnd.openxmlformats-officedocument.presentationml.slide [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.slide+xml application/vnd.openxmlformats-officedocument.presentationml.slide+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.slideLayout+xml application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.slideMaster+xml application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.slideshow application/vnd.openxmlformats-officedocument.presentationml.slideshow [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo+xml application/vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.tableStyles+xml application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.tags+xml application/vnd.openxmlformats-officedocument.presentationml.tags+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.template application/vnd.openxmlformats-officedocument.presentationml.template [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.template.main+xml application/vnd.openxmlformats-officedocument.presentationml.template.main+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.presentationml.viewProps+xml application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.comments+xml application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.connections+xml application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml application/vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+xml application/vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml application/vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.sheet application/vnd.openxmlformats-officedocument.spreadsheetml.sheet [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.styles+xml application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.table+xml application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml application/vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.template application/vnd.openxmlformats-officedocument.spreadsheetml.template [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml application/vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.theme+xml application/vnd.openxmlformats-officedocument.theme+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.themeOverride+xml application/vnd.openxmlformats-officedocument.themeOverride+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.vmlDrawing application/vnd.openxmlformats-officedocument.vmlDrawing [Makoto_Murata] +vnd.openxmlformats-officedocument.wordprocessingml.comments+xml application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.wordprocessingml.document application/vnd.openxmlformats-officedocument.wordprocessingml.document [Makoto_Murata] +vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.wordprocessingml.footer+xml application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.wordprocessingml.settings+xml application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.wordprocessingml.styles+xml application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.wordprocessingml.template application/vnd.openxmlformats-officedocument.wordprocessingml.template [Makoto_Murata] +vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml [Makoto_Murata] +vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml [Makoto_Murata] +vnd.openxmlformats-package.core-properties+xml application/vnd.openxmlformats-package.core-properties+xml [Makoto_Murata] +vnd.openxmlformats-package.digital-signature-xmlsignature+xml application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml [Makoto_Murata] +vnd.openxmlformats-package.relationships+xml application/vnd.openxmlformats-package.relationships+xml [Makoto_Murata] +vnd.oracle.resource+json application/vnd.oracle.resource+json [Ning_Dong] +vnd.orange.indata application/vnd.orange.indata [CHATRAS_Bruno] +vnd.osa.netdeploy application/vnd.osa.netdeploy [Steven_Klos] +vnd.osgeo.mapguide.package application/vnd.osgeo.mapguide.package [Jason_Birch] +vnd.osgi.bundle application/vnd.osgi.bundle [Peter_Kriens] +vnd.osgi.dp application/vnd.osgi.dp [Peter_Kriens] +vnd.osgi.subsystem application/vnd.osgi.subsystem [Peter_Kriens] +vnd.otps.ct-kip+xml application/vnd.otps.ct-kip+xml [Magnus_Nystrom] +vnd.oxli.countgraph application/vnd.oxli.countgraph [C._Titus_Brown] +vnd.pagerduty+json application/vnd.pagerduty+json [Steve_Rice] +vnd.palm application/vnd.palm [Gavin_Peacock] +vnd.panoply application/vnd.panoply [Natarajan_Balasundara] +vnd.paos.xml application/vnd.paos.xml [John_Kemp] +vnd.patentdive application/vnd.patentdive [Christian_Trosclair] +vnd.patientecommsdoc application/vnd.patientecommsdoc [Andrew_David_Kendall] +vnd.pawaafile application/vnd.pawaafile [Prakash_Baskaran] +vnd.pcos application/vnd.pcos [Slawomir_Lisznianski] +vnd.pg.format application/vnd.pg.format [April_Gandert] +vnd.pg.osasli application/vnd.pg.osasli [April_Gandert] +vnd.piaccess.application-licence application/vnd.piaccess.application-licence [Lucas_Maneos] +vnd.picsel application/vnd.picsel [Giuseppe_Naccarato] +vnd.pmi.widget application/vnd.pmi.widget [Rhys_Lewis] +vnd.poc.group-advertisement+xml application/vnd.poc.group-advertisement+xml [Sean_Kelley][OMA_Push_to_Talk_over_Cellular_POC_Working_Group] +vnd.pocketlearn application/vnd.pocketlearn [Jorge_Pando] +vnd.powerbuilder6 application/vnd.powerbuilder6 [David_Guy] +vnd.powerbuilder6-s application/vnd.powerbuilder6-s [David_Guy] +vnd.powerbuilder7 application/vnd.powerbuilder7 [Reed_Shilts] +vnd.powerbuilder75 application/vnd.powerbuilder75 [Reed_Shilts] +vnd.powerbuilder75-s application/vnd.powerbuilder75-s [Reed_Shilts] +vnd.powerbuilder7-s application/vnd.powerbuilder7-s [Reed_Shilts] +vnd.preminet application/vnd.preminet [Juoko_Tenhunen] +vnd.previewsystems.box application/vnd.previewsystems.box [Roman_Smolgovsky] +vnd.proteus.magazine application/vnd.proteus.magazine [Pete_Hoch] +vnd.psfs application/vnd.psfs [Kristopher_Durski] +vnd.publishare-delta-tree application/vnd.publishare-delta-tree [Oren_Ben-Kiki] +vnd.pvi.ptid1 application/vnd.pvi.ptid1 [Charles_P._Lamb] +vnd.pwg-multiplexed application/vnd.pwg-multiplexed [RFC3391] +vnd.pwg-xhtml-print+xml application/vnd.pwg-xhtml-print+xml [Don_Wright] +vnd.qualcomm.brew-app-res application/vnd.qualcomm.brew-app-res [Glenn_Forrester] +vnd.quarantainenet application/vnd.quarantainenet [Casper_Joost_Eyckelhof] +vnd.Quark.QuarkXPress application/vnd.Quark.QuarkXPress [Hannes_Scheidler] +vnd.quobject-quoxdocument application/vnd.quobject-quoxdocument [Matthias_Ludwig] +vnd.radisys.moml+xml application/vnd.radisys.moml+xml [RFC5707] +vnd.radisys.msml-audit-conf+xml application/vnd.radisys.msml-audit-conf+xml [RFC5707] +vnd.radisys.msml-audit-conn+xml application/vnd.radisys.msml-audit-conn+xml [RFC5707] +vnd.radisys.msml-audit-dialog+xml application/vnd.radisys.msml-audit-dialog+xml [RFC5707] +vnd.radisys.msml-audit-stream+xml application/vnd.radisys.msml-audit-stream+xml [RFC5707] +vnd.radisys.msml-audit+xml application/vnd.radisys.msml-audit+xml [RFC5707] +vnd.radisys.msml-conf+xml application/vnd.radisys.msml-conf+xml [RFC5707] +vnd.radisys.msml-dialog-base+xml application/vnd.radisys.msml-dialog-base+xml [RFC5707] +vnd.radisys.msml-dialog-fax-detect+xml application/vnd.radisys.msml-dialog-fax-detect+xml [RFC5707] +vnd.radisys.msml-dialog-fax-sendrecv+xml application/vnd.radisys.msml-dialog-fax-sendrecv+xml [RFC5707] +vnd.radisys.msml-dialog-group+xml application/vnd.radisys.msml-dialog-group+xml [RFC5707] +vnd.radisys.msml-dialog-speech+xml application/vnd.radisys.msml-dialog-speech+xml [RFC5707] +vnd.radisys.msml-dialog-transform+xml application/vnd.radisys.msml-dialog-transform+xml [RFC5707] +vnd.radisys.msml-dialog+xml application/vnd.radisys.msml-dialog+xml [RFC5707] +vnd.radisys.msml+xml application/vnd.radisys.msml+xml [RFC5707] +vnd.rainstor.data application/vnd.rainstor.data [Kevin_Crook] +vnd.rapid application/vnd.rapid [Etay_Szekely] +vnd.rar application/vnd.rar [Kim_Scarborough] +vnd.realvnc.bed application/vnd.realvnc.bed [Nick_Reeves] +vnd.recordare.musicxml application/vnd.recordare.musicxml [W3C_Music_Notation_Community_Group] +vnd.recordare.musicxml+xml application/vnd.recordare.musicxml+xml [W3C_Music_Notation_Community_Group] +vnd.RenLearn.rlprint application/vnd.RenLearn.rlprint [James_Wick] +vnd.resilient.logic application/vnd.resilient.logic [Benedikt_Muessig] +vnd.restful+json application/vnd.restful+json [Stephen_Mizell] +vnd.rig.cryptonote application/vnd.rig.cryptonote [Ken_Jibiki] +vnd.route66.link66+xml application/vnd.route66.link66+xml [Sybren_Kikstra] +vnd.rs-274x application/vnd.rs-274x [Lee_Harding] +vnd.ruckus.download application/vnd.ruckus.download [Jerry_Harris] +vnd.s3sms application/vnd.s3sms [Lauri_Tarkkala] +vnd.sailingtracker.track application/vnd.sailingtracker.track [Heikki_Vesalainen] +vnd.sar application/vnd.sar [Markus_Strehle] +vnd.sbm.cid application/vnd.sbm.cid [Shinji_Kusakari] +vnd.sbm.mid2 application/vnd.sbm.mid2 [Masanori_Murai] +vnd.scribus application/vnd.scribus [Craig_Bradney] +vnd.sealed.3df application/vnd.sealed.3df [John_Kwan] +vnd.sealed.csf application/vnd.sealed.csf [John_Kwan] +vnd.sealed.doc application/vnd.sealed.doc [David_Petersen] +vnd.sealed.eml application/vnd.sealed.eml [David_Petersen] +vnd.sealed.mht application/vnd.sealed.mht [David_Petersen] +vnd.sealed.net application/vnd.sealed.net [Martin_Lambert] +vnd.sealed.ppt application/vnd.sealed.ppt [David_Petersen] +vnd.sealed.tiff application/vnd.sealed.tiff [John_Kwan][Martin_Lambert] +vnd.sealed.xls application/vnd.sealed.xls [David_Petersen] +vnd.sealedmedia.softseal.html application/vnd.sealedmedia.softseal.html [David_Petersen] +vnd.sealedmedia.softseal.pdf application/vnd.sealedmedia.softseal.pdf [David_Petersen] +vnd.seemail application/vnd.seemail [Steve_Webb] +vnd.seis+json application/vnd.seis+json [ICT_Manager] +vnd.sema application/vnd.sema [Anders_Hansson] +vnd.semd application/vnd.semd [Anders_Hansson] +vnd.semf application/vnd.semf [Anders_Hansson] +vnd.shade-save-file application/vnd.shade-save-file [Connor_Horman] +vnd.shana.informed.formdata application/vnd.shana.informed.formdata [Guy_Selzler] +vnd.shana.informed.formtemplate application/vnd.shana.informed.formtemplate [Guy_Selzler] +vnd.shana.informed.interchange application/vnd.shana.informed.interchange [Guy_Selzler] +vnd.shana.informed.package application/vnd.shana.informed.package [Guy_Selzler] +vnd.shootproof+json application/vnd.shootproof+json [Ben_Ramsey] +vnd.shopkick+json application/vnd.shopkick+json [Ronald_Jacobs] +vnd.shp application/vnd.shp [Mi_Tar] +vnd.shx application/vnd.shx [Mi_Tar] +vnd.sigrok.session application/vnd.sigrok.session [Uwe_Hermann] +vnd.SimTech-MindMapper application/vnd.SimTech-MindMapper [Patrick_Koh] +vnd.siren+json application/vnd.siren+json [Kevin_Swiber] +vnd.smaf application/vnd.smaf [Hiroaki_Takahashi] +vnd.smart.notebook application/vnd.smart.notebook [Jonathan_Neitz] +vnd.smart.teacher application/vnd.smart.teacher [Michael_Boyle] +vnd.snesdev-page-table application/vnd.snesdev-page-table [Connor_Horman] +vnd.software602.filler.form+xml application/vnd.software602.filler.form+xml [Jakub_Hytka][Martin_Vondrous] +vnd.software602.filler.form-xml-zip application/vnd.software602.filler.form-xml-zip [Jakub_Hytka][Martin_Vondrous] +vnd.solent.sdkm+xml application/vnd.solent.sdkm+xml [Cliff_Gauntlett] +vnd.spotfire.dxp application/vnd.spotfire.dxp [Stefan_Jernberg] +vnd.spotfire.sfs application/vnd.spotfire.sfs [Stefan_Jernberg] +vnd.sqlite3 application/vnd.sqlite3 [Clemens_Ladisch] +vnd.sss-cod application/vnd.sss-cod [Asang_Dani] +vnd.sss-dtf application/vnd.sss-dtf [Eric_Bruno] +vnd.sss-ntf application/vnd.sss-ntf [Eric_Bruno] +vnd.stepmania.package application/vnd.stepmania.package [Henrik_Andersson] +vnd.stepmania.stepchart application/vnd.stepmania.stepchart [Henrik_Andersson] +vnd.street-stream application/vnd.street-stream [Glenn_Levitt] +vnd.sun.wadl+xml application/vnd.sun.wadl+xml [Marc_Hadley] +vnd.sus-calendar application/vnd.sus-calendar [Jonathan_Niedfeldt] +vnd.svd application/vnd.svd [Scott_Becker] +vnd.swiftview-ics application/vnd.swiftview-ics [Glenn_Widener] +vnd.sybyl.mol2 application/vnd.sybyl.mol2 [Finn_Rayk_Gärtner] +vnd.sycle+xml application/vnd.sycle+xml [Johann_Terblanche] +vnd.syft+json application/vnd.syft+json [Dan_Luhring] +vnd.syncml.dm.notification application/vnd.syncml.dm.notification [Peter_Thompson][OMA-DM_Work_Group] +vnd.syncml.dmddf+xml application/vnd.syncml.dmddf+xml [OMA-DM_Work_Group] +vnd.syncml.dmtnds+wbxml application/vnd.syncml.dmtnds+wbxml [OMA-DM_Work_Group] +vnd.syncml.dmtnds+xml application/vnd.syncml.dmtnds+xml [OMA-DM_Work_Group] +vnd.syncml.dmddf+wbxml application/vnd.syncml.dmddf+wbxml [OMA-DM_Work_Group] +vnd.syncml.dm+wbxml application/vnd.syncml.dm+wbxml [OMA-DM_Work_Group] +vnd.syncml.dm+xml application/vnd.syncml.dm+xml [Bindu_Rama_Rao][OMA-DM_Work_Group] +vnd.syncml.ds.notification application/vnd.syncml.ds.notification [OMA_Data_Synchronization_Working_Group] +vnd.syncml+xml application/vnd.syncml+xml [OMA_Data_Synchronization_Working_Group] +vnd.tableschema+json application/vnd.tableschema+json [Paul_Walsh] +vnd.tao.intent-module-archive application/vnd.tao.intent-module-archive [Daniel_Shelton] +vnd.tcpdump.pcap application/vnd.tcpdump.pcap [Guy_Harris][Glen_Turner] +vnd.think-cell.ppttc+json application/vnd.think-cell.ppttc+json [Arno_Schoedl] +vnd.tml application/vnd.tml [Joey_Smith] +vnd.tmd.mediaflex.api+xml application/vnd.tmd.mediaflex.api+xml [Alex_Sibilev] +vnd.tmobile-livetv application/vnd.tmobile-livetv [Nicolas_Helin] +vnd.tri.onesource application/vnd.tri.onesource [Rick_Rupp] +vnd.trid.tpt application/vnd.trid.tpt [Frank_Cusack] +vnd.triscape.mxs application/vnd.triscape.mxs [Steven_Simonoff] +vnd.trueapp application/vnd.trueapp [J._Scott_Hepler] +vnd.truedoc application/vnd.truedoc [Brad_Chase] +vnd.ubisoft.webplayer application/vnd.ubisoft.webplayer [Martin_Talbot] +vnd.ufdl application/vnd.ufdl [Dave_Manning] +vnd.uiq.theme application/vnd.uiq.theme [Tim_Ocock] +vnd.umajin application/vnd.umajin [Jamie_Riden] +vnd.unity application/vnd.unity [Unity3d] +vnd.uoml+xml application/vnd.uoml+xml [Arne_Gerdes] +vnd.uplanet.alert application/vnd.uplanet.alert [Bruce_Martin] +vnd.uplanet.alert-wbxml application/vnd.uplanet.alert-wbxml [Bruce_Martin] +vnd.uplanet.bearer-choice application/vnd.uplanet.bearer-choice [Bruce_Martin] +vnd.uplanet.bearer-choice-wbxml application/vnd.uplanet.bearer-choice-wbxml [Bruce_Martin] +vnd.uplanet.cacheop application/vnd.uplanet.cacheop [Bruce_Martin] +vnd.uplanet.cacheop-wbxml application/vnd.uplanet.cacheop-wbxml [Bruce_Martin] +vnd.uplanet.channel application/vnd.uplanet.channel [Bruce_Martin] +vnd.uplanet.channel-wbxml application/vnd.uplanet.channel-wbxml [Bruce_Martin] +vnd.uplanet.list application/vnd.uplanet.list [Bruce_Martin] +vnd.uplanet.listcmd application/vnd.uplanet.listcmd [Bruce_Martin] +vnd.uplanet.listcmd-wbxml application/vnd.uplanet.listcmd-wbxml [Bruce_Martin] +vnd.uplanet.list-wbxml application/vnd.uplanet.list-wbxml [Bruce_Martin] +vnd.uri-map application/vnd.uri-map [Sebastian_Baer] +vnd.uplanet.signal application/vnd.uplanet.signal [Bruce_Martin] +vnd.valve.source.material application/vnd.valve.source.material [Henrik_Andersson] +vnd.vcx application/vnd.vcx [Taisuke_Sugimoto] +vnd.vd-study application/vnd.vd-study [Luc_Rogge] +vnd.vectorworks application/vnd.vectorworks [Lyndsey_Ferguson][Biplab_Sarkar] +vnd.vel+json application/vnd.vel+json [James_Wigger] +vnd.verimatrix.vcas application/vnd.verimatrix.vcas [Petr_Peterka] +vnd.veritone.aion+json application/vnd.veritone.aion+json [Al_Brown] +vnd.veryant.thin application/vnd.veryant.thin [Massimo_Bertoli] +vnd.ves.encrypted application/vnd.ves.encrypted [Jim_Zubov] +vnd.vidsoft.vidconference application/vnd.vidsoft.vidconference [Robert_Hess] +vnd.visio application/vnd.visio [Troy_Sandal] +vnd.visionary application/vnd.visionary [Gayatri_Aravindakumar] +vnd.vividence.scriptfile application/vnd.vividence.scriptfile [Mark_Risher] +vnd.vsf application/vnd.vsf [Delton_Rowe] +vnd.wap.sic application/vnd.wap.sic [WAP-Forum] +vnd.wap.slc application/vnd.wap.slc [WAP-Forum] +vnd.wap.wbxml application/vnd.wap.wbxml [Peter_Stark] +vnd.wap.wmlc application/vnd.wap.wmlc [Peter_Stark] +vnd.wap.wmlscriptc application/vnd.wap.wmlscriptc [Peter_Stark] +vnd.wasmflow.wafl application/vnd.wasmflow.wafl [Fawad_Shaikh] +vnd.webturbo application/vnd.webturbo [Yaser_Rehem] +vnd.wfa.dpp application/vnd.wfa.dpp [Wi-Fi_Alliance][Dr._Jun_Tian] +vnd.wfa.p2p application/vnd.wfa.p2p [Mick_Conley] +vnd.wfa.wsc application/vnd.wfa.wsc [Wi-Fi_Alliance] +vnd.windows.devicepairing application/vnd.windows.devicepairing [Priya_Dandawate] +vnd.wmc application/vnd.wmc [Thomas_Kjornes] +vnd.wmf.bootstrap application/vnd.wmf.bootstrap [Thinh_Nguyenphu][Prakash_Iyer] +vnd.wolfram.mathematica application/vnd.wolfram.mathematica [Wolfram] +vnd.wolfram.mathematica.package application/vnd.wolfram.mathematica.package [Wolfram] +vnd.wolfram.player application/vnd.wolfram.player [Wolfram] +vnd.wordperfect application/vnd.wordperfect [Kim_Scarborough] +vnd.wqd application/vnd.wqd [Jan_Bostrom] +vnd.wrq-hp3000-labelled application/vnd.wrq-hp3000-labelled [Chris_Bartram] +vnd.wt.stf application/vnd.wt.stf [Bill_Wohler] +vnd.wv.csp+xml application/vnd.wv.csp+xml [John_Ingi_Ingimundarson] +vnd.wv.csp+wbxml application/vnd.wv.csp+wbxml [Matti_Salmi] +vnd.wv.ssp+xml application/vnd.wv.ssp+xml [John_Ingi_Ingimundarson] +vnd.xacml+json application/vnd.xacml+json [David_Brossard] +vnd.xara application/vnd.xara [David_Matthewman] +vnd.xfdl application/vnd.xfdl [Dave_Manning] +vnd.xfdl.webform application/vnd.xfdl.webform [Michael_Mansell] +vnd.xmi+xml application/vnd.xmi+xml [Fred_Waskiewicz] +vnd.xmpie.cpkg application/vnd.xmpie.cpkg [Reuven_Sherwin] +vnd.xmpie.dpkg application/vnd.xmpie.dpkg [Reuven_Sherwin] +vnd.xmpie.plan application/vnd.xmpie.plan [Reuven_Sherwin] +vnd.xmpie.ppkg application/vnd.xmpie.ppkg [Reuven_Sherwin] +vnd.xmpie.xlim application/vnd.xmpie.xlim [Reuven_Sherwin] +vnd.yamaha.hv-dic application/vnd.yamaha.hv-dic [Tomohiro_Yamamoto] +vnd.yamaha.hv-script application/vnd.yamaha.hv-script [Tomohiro_Yamamoto] +vnd.yamaha.hv-voice application/vnd.yamaha.hv-voice [Tomohiro_Yamamoto] +vnd.yamaha.openscoreformat.osfpvg+xml application/vnd.yamaha.openscoreformat.osfpvg+xml [Mark_Olleson] +vnd.yamaha.openscoreformat application/vnd.yamaha.openscoreformat [Mark_Olleson] +vnd.yamaha.remote-setup application/vnd.yamaha.remote-setup [Takehiro_Sukizaki] +vnd.yamaha.smaf-audio application/vnd.yamaha.smaf-audio [Keiichi_Shinoda] +vnd.yamaha.smaf-phrase application/vnd.yamaha.smaf-phrase [Keiichi_Shinoda] +vnd.yamaha.through-ngn application/vnd.yamaha.through-ngn [Takehiro_Sukizaki] +vnd.yamaha.tunnel-udpencap application/vnd.yamaha.tunnel-udpencap [Takehiro_Sukizaki] +vnd.yaoweme application/vnd.yaoweme [Jens_Jorgensen] +vnd.yellowriver-custom-menu application/vnd.yellowriver-custom-menu [Mr._Yellow] +vnd.youtube.yt (OBSOLETED in favor of video/vnd.youtube.yt) application/vnd.youtube.yt [Laura_Wood] +vnd.zul application/vnd.zul [Rene_Grothmann] +vnd.zzazz.deck+xml application/vnd.zzazz.deck+xml [Micheal_Hewett] +voicexml+xml application/voicexml+xml [RFC4267] +voucher-cms+json application/voucher-cms+json [RFC8366] +vq-rtcpxr application/vq-rtcpxr [RFC6035] +wasm application/wasm [W3C][Eric_Prudhommeaux] +watcherinfo+xml application/watcherinfo+xml [RFC3858] +webpush-options+json application/webpush-options+json [RFC8292] +whoispp-query application/whoispp-query [RFC2957] +whoispp-response application/whoispp-response [RFC2958] +widget application/widget [W3C][Steven_Pemberton][W3C-Widgets-2012] +wita application/wita [Larry_Campbell] +wordperfect5.1 application/wordperfect5.1 [Paul_Lindner] +wsdl+xml application/wsdl+xml [W3C] +wspolicy+xml application/wspolicy+xml [W3C] +x-pki-message application/x-pki-message [RFC8894] +x-www-form-urlencoded application/x-www-form-urlencoded [WHATWG][Anne_van_Kesteren] +x-x509-ca-cert application/x-x509-ca-cert [RFC8894] +x-x509-ca-ra-cert application/x-x509-ca-ra-cert [RFC8894] +x-x509-next-ca-cert application/x-x509-next-ca-cert [RFC8894] +x400-bp application/x400-bp [RFC1494] +xacml+xml application/xacml+xml [RFC7061] +xcap-att+xml application/xcap-att+xml [RFC4825] +xcap-caps+xml application/xcap-caps+xml [RFC4825] +xcap-diff+xml application/xcap-diff+xml [RFC5874] +xcap-el+xml application/xcap-el+xml [RFC4825] +xcap-error+xml application/xcap-error+xml [RFC4825] +xcap-ns+xml application/xcap-ns+xml [RFC4825] +xcon-conference-info-diff+xml application/xcon-conference-info-diff+xml [RFC6502] +xcon-conference-info+xml application/xcon-conference-info+xml [RFC6502] +xenc+xml application/xenc+xml [Joseph_Reagle][XENC_Working_Group] +xfdf application/xfdf [ISO-TC_171-SC_2][Betsy_Fanning] +xhtml+xml application/xhtml+xml [W3C][Robin_Berjon] +xliff+xml application/xliff+xml [OASIS][Chet_Ensign] +xml application/xml [RFC7303] +xml-dtd application/xml-dtd [RFC7303] +xml-external-parsed-entity application/xml-external-parsed-entity [RFC7303] +xml-patch+xml application/xml-patch+xml [RFC7351] +xmpp+xml application/xmpp+xml [RFC3923] +xop+xml application/xop+xml [Mark_Nottingham] +xslt+xml application/xslt+xml [W3C][http://www.w3.org/TR/2007/REC-xslt20-20070123/#media-type-registration] +xv+xml application/xv+xml [RFC4374] +yang application/yang [RFC6020] +yang-data+cbor application/yang-data+cbor [RFC9254] +yang-data+json application/yang-data+json [RFC8040] +yang-data+xml application/yang-data+xml [RFC8040] +yang-patch+json application/yang-patch+json [RFC8072] +yang-patch+xml application/yang-patch+xml [RFC8072] +yin+xml application/yin+xml [RFC6020] +zip application/zip [Paul_Lindner] +zlib application/zlib [RFC6713] +zstd application/zstd [RFC8878] + +audio + + Available Formats + [IMG] + CSV + + Name Template Reference + 1d-interleaved-parityfec audio/1d-interleaved-parityfec [RFC6015] + 32kadpcm audio/32kadpcm [RFC3802][RFC2421] + 3gpp audio/3gpp [RFC3839][RFC6381] + 3gpp2 audio/3gpp2 [RFC4393][RFC6381] + aac audio/aac [ISO-IEC_JTC1][Max_Neuendorf] + ac3 audio/ac3 [RFC4184] + AMR audio/AMR [RFC4867] + AMR-WB audio/AMR-WB [RFC4867] + amr-wb+ audio/amr-wb+ [RFC4352] + aptx audio/aptx [RFC7310] + asc audio/asc [RFC6295] + ATRAC-ADVANCED-LOSSLESS audio/ATRAC-ADVANCED-LOSSLESS [RFC5584] + ATRAC-X audio/ATRAC-X [RFC5584] + ATRAC3 audio/ATRAC3 [RFC5584] + basic audio/basic [RFC2045][RFC2046] + BV16 audio/BV16 [RFC4298] + BV32 audio/BV32 [RFC4298] + clearmode audio/clearmode [RFC4040] + CN audio/CN [RFC3389] + DAT12 audio/DAT12 [RFC3190] + dls audio/dls [RFC4613] + dsr-es201108 audio/dsr-es201108 [RFC3557] + dsr-es202050 audio/dsr-es202050 [RFC4060] + dsr-es202211 audio/dsr-es202211 [RFC4060] + dsr-es202212 audio/dsr-es202212 [RFC4060] + DV audio/DV [RFC6469] + DVI4 audio/DVI4 [RFC4856] + eac3 audio/eac3 [RFC4598] + encaprtp audio/encaprtp [RFC6849] + EVRC audio/EVRC [RFC4788] + EVRC-QCP audio/EVRC-QCP [RFC3625] + EVRC0 audio/EVRC0 [RFC4788] + EVRC1 audio/EVRC1 [RFC4788] + EVRCB audio/EVRCB [RFC5188] + EVRCB0 audio/EVRCB0 [RFC5188] + EVRCB1 audio/EVRCB1 [RFC4788] + EVRCNW audio/EVRCNW [RFC6884] + EVRCNW0 audio/EVRCNW0 [RFC6884] + EVRCNW1 audio/EVRCNW1 [RFC6884] + EVRCWB audio/EVRCWB [RFC5188] + EVRCWB0 audio/EVRCWB0 [RFC5188] + EVRCWB1 audio/EVRCWB1 [RFC5188] + EVS audio/EVS [_3GPP][Kyunghun_Jung] + example audio/example [RFC4735] + flexfec audio/flexfec [RFC8627] + fwdred audio/fwdred [RFC6354] + G711-0 audio/G711-0 [RFC7655] + G719 audio/G719 [RFC5404][RFC Errata 3245] + G7221 audio/G7221 [RFC5577] + G722 audio/G722 [RFC4856] + G723 audio/G723 [RFC4856] + G726-16 audio/G726-16 [RFC4856] + G726-24 audio/G726-24 [RFC4856] + G726-32 audio/G726-32 [RFC4856] + G726-40 audio/G726-40 [RFC4856] + G728 audio/G728 [RFC4856] + G729 audio/G729 [RFC4856] + G7291 audio/G7291 [RFC4749][RFC5459] + G729D audio/G729D [RFC4856] + G729E audio/G729E [RFC4856] + GSM audio/GSM [RFC4856] + GSM-EFR audio/GSM-EFR [RFC4856] + GSM-HR-08 audio/GSM-HR-08 [RFC5993] + iLBC audio/iLBC [RFC3952] + ip-mr_v2.5 audio/ip-mr_v2.5 [RFC6262] + L8 audio/L8 [RFC4856] + L16 audio/L16 [RFC4856] + L20 audio/L20 [RFC3190] + L24 audio/L24 [RFC3190] + LPC audio/LPC [RFC4856] + MELP audio/MELP [RFC8130] + MELP600 audio/MELP600 [RFC8130] + MELP1200 audio/MELP1200 [RFC8130] + MELP2400 audio/MELP2400 [RFC8130] + mhas audio/mhas [ISO-IEC_JTC1][Nils_Peters][Ingo_Hofmann] + mobile-xmf audio/mobile-xmf [RFC4723] + MPA audio/MPA [RFC3555] + mp4 audio/mp4 [RFC4337][RFC6381] + MP4A-LATM audio/MP4A-LATM [RFC6416] + mpa-robust audio/mpa-robust [RFC5219] + mpeg audio/mpeg [RFC3003] + mpeg4-generic audio/mpeg4-generic [RFC3640][RFC5691][RFC6295] + ogg audio/ogg [RFC5334][RFC7845] + opus audio/opus [RFC7587] + parityfec audio/parityfec [RFC3009] + PCMA audio/PCMA [RFC4856] + PCMA-WB audio/PCMA-WB [RFC5391] + PCMU audio/PCMU [RFC4856] + PCMU-WB audio/PCMU-WB [RFC5391] + prs.sid audio/prs.sid [Linus_Walleij] + QCELP audio/QCELP [RFC3555][RFC3625] + raptorfec audio/raptorfec [RFC6682] + RED audio/RED [RFC3555] + rtp-enc-aescm128 audio/rtp-enc-aescm128 [_3GPP] + rtploopback audio/rtploopback [RFC6849] + rtp-midi audio/rtp-midi [RFC6295] + rtx audio/rtx [RFC4588] + scip audio/scip [SCIP][Michael_Faller][Daniel_Hanson] + SMV audio/SMV [RFC3558] + SMV0 audio/SMV0 [RFC3558] + SMV-QCP audio/SMV-QCP [RFC3625] + sofa audio/sofa [AES][Piotr_Majdak] + sp-midi audio/sp-midi [Timo_Kosonen][Tom_White] + speex audio/speex [RFC5574] + t140c audio/t140c [RFC4351] + t38 audio/t38 [RFC4612] + telephone-event audio/telephone-event [RFC4733] + TETRA_ACELP audio/TETRA_ACELP [ETSI][Miguel_Angel_Reina_Ortega] + TETRA_ACELP_BB audio/TETRA_ACELP_BB [ETSI][Miguel_Angel_Reina_Ortega] + tone audio/tone [RFC4733] + TSVCIS audio/TSVCIS [RFC8817] + UEMCLIP audio/UEMCLIP [RFC5686] + ulpfec audio/ulpfec [RFC5109] + usac audio/usac [ISO-IEC_JTC1][Max_Neuendorf] + VDVI audio/VDVI [RFC4856] + VMR-WB audio/VMR-WB [RFC4348][RFC4424] + vnd.3gpp.iufp audio/vnd.3gpp.iufp [Thomas_Belling] + vnd.4SB audio/vnd.4SB [Serge_De_Jaham] + vnd.audiokoz audio/vnd.audiokoz [Vicki_DeBarros] + vnd.CELP audio/vnd.CELP [Serge_De_Jaham] + vnd.cisco.nse audio/vnd.cisco.nse [Rajesh_Kumar] + vnd.cmles.radio-events audio/vnd.cmles.radio-events [Jean-Philippe_Goulet] + vnd.cns.anp1 audio/vnd.cns.anp1 [Ann_McLaughlin] + vnd.cns.inf1 audio/vnd.cns.inf1 [Ann_McLaughlin] + vnd.dece.audio audio/vnd.dece.audio [Michael_A_Dolan] + vnd.digital-winds audio/vnd.digital-winds [Armands_Strazds] + vnd.dlna.adts audio/vnd.dlna.adts [Edwin_Heredia] + vnd.dolby.heaac.1 audio/vnd.dolby.heaac.1 [Steve_Hattersley] + vnd.dolby.heaac.2 audio/vnd.dolby.heaac.2 [Steve_Hattersley] + vnd.dolby.mlp audio/vnd.dolby.mlp [Mike_Ward] + vnd.dolby.mps audio/vnd.dolby.mps [Steve_Hattersley] + vnd.dolby.pl2 audio/vnd.dolby.pl2 [Steve_Hattersley] + vnd.dolby.pl2x audio/vnd.dolby.pl2x [Steve_Hattersley] + vnd.dolby.pl2z audio/vnd.dolby.pl2z [Steve_Hattersley] + vnd.dolby.pulse.1 audio/vnd.dolby.pulse.1 [Steve_Hattersley] + vnd.dra audio/vnd.dra [Jiang_Tian] + vnd.dts audio/vnd.dts [William_Zou] + vnd.dts.hd audio/vnd.dts.hd [William_Zou] + vnd.dts.uhd audio/vnd.dts.uhd [Phillip_Maness] + vnd.dvb.file audio/vnd.dvb.file [Peter_Siebert] + vnd.everad.plj audio/vnd.everad.plj [Shay_Cicelsky] + vnd.hns.audio audio/vnd.hns.audio [Swaminathan] + vnd.lucent.voice audio/vnd.lucent.voice [Greg_Vaudreuil] + vnd.ms-playready.media.pya audio/vnd.ms-playready.media.pya [Steve_DiAcetis] + vnd.nokia.mobile-xmf audio/vnd.nokia.mobile-xmf [Nokia] + vnd.nortel.vbk audio/vnd.nortel.vbk [Glenn_Parsons] + vnd.nuera.ecelp4800 audio/vnd.nuera.ecelp4800 [Michael_Fox] + vnd.nuera.ecelp7470 audio/vnd.nuera.ecelp7470 [Michael_Fox] + vnd.nuera.ecelp9600 audio/vnd.nuera.ecelp9600 [Michael_Fox] + vnd.octel.sbc audio/vnd.octel.sbc [Greg_Vaudreuil] + vnd.presonus.multitrack audio/vnd.presonus.multitrack [Matthias_Juwan] + vnd.qcelp - DEPRECATED in favor of audio/qcelp audio/vnd.qcelp [RFC3625] + vnd.rhetorex.32kadpcm audio/vnd.rhetorex.32kadpcm [Greg_Vaudreuil] + vnd.rip audio/vnd.rip [Martin_Dawe] + vnd.sealedmedia.softseal.mpeg audio/vnd.sealedmedia.softseal.mpeg [David_Petersen] + vnd.vmx.cvsd audio/vnd.vmx.cvsd [Greg_Vaudreuil] + vorbis audio/vorbis [RFC5215] + vorbis-config audio/vorbis-config [RFC5215] + +font + + Available Formats + [IMG] + CSV + + Name Template Reference + collection font/collection [RFC8081] + otf font/otf [RFC8081] + sfnt font/sfnt [RFC8081] + ttf font/ttf [RFC8081] + woff font/woff [RFC8081] + woff2 font/woff2 [RFC8081] + +example + + Note + + The 'example' media type is used for examples. Any subtype following the media + type syntax may be used in those examples. No subtype can be registered with + IANA. For more information see[RFC4735]. + + Note: The occurrence of an 'example' media type as a media type outside + of examples, e.g. in a Content-Type header, is an error and should be + reported to the implementor.[RFC2045][RFC2046] + specifies that Content Types, Content Subtypes, Character Sets, Access + Types, and conversion values for MIME mail will be assigned and listed + by the IANA. + + Note + + Other MIME Media Type Parameters: [IANA registry media-types-parameters] + + No registrations at this time. + +image + + Available Formats + [IMG] + CSV + + Name Template Reference + aces image/aces [SMPTE][Howard_Lukk] + avci image/avci [ISO-IEC_JTC1][David_Singer] + avcs image/avcs [ISO-IEC_JTC1][David_Singer] + avif image/avif [Alliance_for_Open_Media][Cyril_Concolato] + bmp image/bmp [RFC7903] + cgm image/cgm [Alan_Francis] + dicom-rle image/dicom-rle [DICOM_Standards_Committee][David_Clunie] + dpx image/dpx [SMPTE][SMPTE_Director_of_Standards_Development] + emf image/emf [RFC7903] + example image/example [RFC4735] + fits image/fits [RFC4047] + g3fax image/g3fax [RFC1494] + gif [RFC2045][RFC2046] + heic image/heic [ISO-IEC_JTC1][David_Singer] + heic-sequence image/heic-sequence [ISO-IEC_JTC1][David_Singer] + heif image/heif [ISO-IEC_JTC1][David_Singer] + heif-sequence image/heif-sequence [ISO-IEC_JTC1][David_Singer] + hej2k image/hej2k [ISO-IEC_JTC1][ITU-T] + hsj2 image/hsj2 [ISO-IEC_JTC1][ITU-T] + ief [RFC1314] + jls image/jls [DICOM_Standards_Committee][David_Clunie] + jp2 image/jp2 [RFC3745] + jpeg [RFC2045][RFC2046] + jph image/jph [ISO-IEC_JTC1][ITU-T] + jphc image/jphc [ISO-IEC_JTC1][ITU-T] + jpm image/jpm [RFC3745] + jpx image/jpx [RFC3745] + jxr image/jxr [ISO-IEC_JTC1][ITU-T] + jxrA image/jxrA [ISO-IEC_JTC1][ITU-T] + jxrS image/jxrS [ISO-IEC_JTC1][ITU-T] + jxs image/jxs [ISO-IEC_JTC1] + jxsc image/jxsc [ISO-IEC_JTC1] + jxsi image/jxsi [ISO-IEC_JTC1] + jxss image/jxss [ISO-IEC_JTC1] + ktx image/ktx [Khronos][Mark_Callow] + ktx2 image/ktx2 [Khronos][Mark_Callow] + naplps image/naplps [Ilya_Ferber] + png image/png [W3C][PNG_Working_Group] + prs.btif image/prs.btif [Ben_Simon] + prs.pti image/prs.pti [Juern_Laun] + pwg-raster image/pwg-raster [Michael_Sweet] + svg+xml image/svg+xml [W3C][http://www.w3.org/TR/SVG/mimereg.html] + t38 image/t38 [RFC3362] + tiff image/tiff [RFC3302] + tiff-fx image/tiff-fx [RFC3950] + vnd.adobe.photoshop image/vnd.adobe.photoshop [Kim_Scarborough] + vnd.airzip.accelerator.azv image/vnd.airzip.accelerator.azv [Gary_Clueit] + vnd.cns.inf2 image/vnd.cns.inf2 [Ann_McLaughlin] + vnd.dece.graphic image/vnd.dece.graphic [Michael_A_Dolan] + vnd.djvu image/vnd.djvu [Leon_Bottou] + vnd.dwg image/vnd.dwg [Jodi_Moline] + vnd.dxf image/vnd.dxf [Jodi_Moline] + vnd.dvb.subtitle image/vnd.dvb.subtitle [Peter_Siebert][Michael_Lagally] + vnd.fastbidsheet image/vnd.fastbidsheet [Scott_Becker] + vnd.fpx image/vnd.fpx [Marc_Douglas_Spencer] + vnd.fst image/vnd.fst [Arild_Fuldseth] + vnd.fujixerox.edmics-mmr image/vnd.fujixerox.edmics-mmr [Masanori_Onda] + vnd.fujixerox.edmics-rlc image/vnd.fujixerox.edmics-rlc [Masanori_Onda] + vnd.globalgraphics.pgb image/vnd.globalgraphics.pgb [Martin_Bailey] + vnd.microsoft.icon image/vnd.microsoft.icon [Simon_Butcher] + vnd.mix image/vnd.mix [Saveen_Reddy] + vnd.ms-modi image/vnd.ms-modi [Gregory_Vaughan] + vnd.mozilla.apng image/vnd.mozilla.apng [Stuart_Parmenter] + vnd.net-fpx image/vnd.net-fpx [Marc_Douglas_Spencer] + vnd.pco.b16 image/vnd.pco.b16 [PCO_AG][Jan_Zeman] + vnd.radiance image/vnd.radiance [Randolph_Fritz][Greg_Ward] + vnd.sealed.png image/vnd.sealed.png [David_Petersen] + vnd.sealedmedia.softseal.gif image/vnd.sealedmedia.softseal.gif [David_Petersen] + vnd.sealedmedia.softseal.jpg image/vnd.sealedmedia.softseal.jpg [David_Petersen] + vnd.svf image/vnd.svf [Jodi_Moline] + vnd.tencent.tap image/vnd.tencent.tap [Ni_Hui] + vnd.valve.source.texture image/vnd.valve.source.texture [Henrik_Andersson] + vnd.wap.wbmp image/vnd.wap.wbmp [Peter_Stark] + vnd.xiff image/vnd.xiff [Steven_Martin] + vnd.zbrush.pcx image/vnd.zbrush.pcx [Chris_Charabaruk] + wmf image/wmf [RFC7903] + x-emf - DEPRECATED in favor of image/emf image/emf [RFC7903] + x-wmf - DEPRECATED in favor of image/wmf image/wmf [RFC7903] + +message + + Available Formats + [IMG] + CSV + + Name Template Reference + bhttp message/bhttp [RFC9292] + CPIM message/CPIM [RFC3862] + delivery-status message/delivery-status [RFC1894] + disposition-notification message/disposition-notification [RFC8098] + example message/example [RFC4735] + external-body [RFC2045][RFC2046] + feedback-report message/feedback-report [RFC5965] + global message/global [RFC6532] + global-delivery-status message/global-delivery-status [RFC6533] + global-disposition-notification message/global-disposition-notification [RFC6533] + global-headers message/global-headers [RFC6533] + http message/http [RFC9112] + imdn+xml message/imdn+xml [RFC5438] + news (OBSOLETED by [RFC5537]) message/news [RFC5537][Henry_Spencer] + partial [RFC2045][RFC2046] + rfc822 [RFC2045][RFC2046] + s-http (OBSOLETE) message/s-http [RFC2660][status-change-http-experiments-to-historic] + sip message/sip [RFC3261] + sipfrag message/sipfrag [RFC3420] + tracking-status message/tracking-status [RFC3886] + vnd.si.simp (OBSOLETED by request) message/vnd.si.simp [Nicholas_Parks_Young] + vnd.wfa.wsc message/vnd.wfa.wsc [Mick_Conley] + +model + + Available Formats + [IMG] + CSV + + Name Template Reference + 3mf model/3mf [http://www.3mf.io/specification][_3MF][Michael_Sweet] + e57 model/e57 [ASTM] + example model/example [RFC4735] + gltf-binary model/gltf-binary [Khronos][Saurabh_Bhatia] + gltf+json model/gltf+json [Khronos][Uli_Klumpp] + iges model/iges [Curtis_Parks] + mesh [RFC2077] + mtl model/mtl [DICOM_Standards_Committee][Luiza_Kowalczyk] + obj model/obj [DICOM_Standards_Committee][Luiza_Kowalczyk] + prc model/prc [ISO-TC_171-SC_2][Betsy_Fanning] + step model/step [ISO-TC_184-SC_4][Dana_Tripp] + step+xml model/step+xml [ISO-TC_184-SC_4][Dana_Tripp] + step+zip model/step+zip [ISO-TC_184-SC_4][Dana_Tripp] + step-xml+zip model/step-xml+zip [ISO-TC_184-SC_4][Dana_Tripp] + stl model/stl [DICOM_Standards_Committee][Lisa_Spellman] + u3d model/u3d [PDF_Association][Peter_Wyatt] + vnd.collada+xml model/vnd.collada+xml [James_Riordon] + vnd.dwf model/vnd.dwf [Jason_Pratt] + vnd.flatland.3dml model/vnd.flatland.3dml [Michael_Powers] + vnd.gdl model/vnd.gdl [Attila_Babits] + vnd.gs-gdl model/vnd.gs-gdl [Attila_Babits] + vnd.gtw model/vnd.gtw [Yutaka_Ozaki] + vnd.moml+xml model/vnd.moml+xml [Christopher_Brooks] + vnd.mts model/vnd.mts [Boris_Rabinovitch] + vnd.opengex model/vnd.opengex [Eric_Lengyel] + vnd.parasolid.transmit.binary model/vnd.parasolid.transmit.binary [Parasolid] + vnd.parasolid.transmit.text model/vnd.parasolid.transmit.text [Parasolid] + vnd.pytha.pyox model/vnd.pytha.pyox [Daniel_Flassig] + vnd.rosette.annotated-data-model model/vnd.rosette.annotated-data-model [Benson_Margulies] + vnd.sap.vds model/vnd.sap.vds [SAP_SE][Igor_Afanasyev] + vnd.usdz+zip model/vnd.usdz+zip [Sebastian_Grassia] + vnd.valve.source.compiled-map model/vnd.valve.source.compiled-map [Henrik_Andersson] + vnd.vtu model/vnd.vtu [Boris_Rabinovitch] + vrml [RFC2077] + x3d-vrml model/x3d-vrml [Web3D][Web3D_X3D] + x3d+fastinfoset model/x3d+fastinfoset [Web3D_X3D] + x3d+xml model/x3d+xml [Web3D][Web3D_X3D] + +multipart + + Available Formats + [IMG] + CSV + + Name Template Reference + alternative [RFC2046][RFC2045] + appledouble multipart/appledouble [Patrik_Faltstrom] + byteranges multipart/byteranges [RFC9110] + digest [RFC2046][RFC2045] + encrypted multipart/encrypted [RFC1847] + example multipart/example [RFC4735] + form-data multipart/form-data [RFC7578] + header-set multipart/header-set [Dave_Crocker] + mixed [RFC2046][RFC2045] + multilingual multipart/multilingual [RFC8255] + parallel [RFC2046][RFC2045] + related multipart/related [RFC2387] + report multipart/report [RFC6522] + signed multipart/signed [RFC1847] + vnd.bint.med-plus multipart/vnd.bint.med-plus [Heinz-Peter_Schütz] + voice-message multipart/voice-message [RFC3801] + x-mixed-replace multipart/x-mixed-replace [W3C][Robin_Berjon] + +text + + Note + + See [RFC6657] for information about 'charset' parameter handling for text media types. + + Available Formats + [IMG] + CSV + + Name Template Reference + 1d-interleaved-parityfec text/1d-interleaved-parityfec [RFC6015] + cache-manifest text/cache-manifest [W3C][Robin_Berjon] + calendar text/calendar [RFC5545] + cql text/cql [HL7][Bryn_Rhodes] + cql-expression text/cql-expression [HL7][Bryn_Rhodes] + cql-identifier text/cql-identifier [HL7][Bryn_Rhodes] + css text/css [RFC2318] + csv text/csv [RFC4180][RFC7111] + csv-schema text/csv-schema [National_Archives_UK][David_Underdown] + directory - DEPRECATED by RFC6350 text/directory [RFC2425][RFC6350] + dns text/dns [RFC4027] + ecmascript (OBSOLETED in favor of text/javascript) text/ecmascript [RFC9239] + encaprtp text/encaprtp [RFC6849] + enriched [RFC1896] + example text/example [RFC4735] + fhirpath text/fhirpath [HL7][Bryn_Rhodes] + flexfec text/flexfec [RFC8627] + fwdred text/fwdred [RFC6354] + gff3 text/gff3 [Sequence_Ontology] + grammar-ref-list text/grammar-ref-list [RFC6787] + hl7v2 text/hl7v2 [HL7][Marc_Duteau] + html text/html [W3C][Robin_Berjon] + javascript text/javascript [RFC9239] + jcr-cnd text/jcr-cnd [Peeter_Piegaze] + markdown text/markdown [RFC7763] + mizar text/mizar [Jesse_Alama] + n3 text/n3 [W3C][Eric_Prudhommeaux] + parameters text/parameters [RFC7826] + parityfec text/parityfec [RFC3009] + plain [RFC2046][RFC3676][RFC5147] + provenance-notation text/provenance-notation [W3C][Ivan_Herman] + prs.fallenstein.rst text/prs.fallenstein.rst [Benja_Fallenstein] + prs.lines.tag text/prs.lines.tag [John_Lines] + prs.prop.logic text/prs.prop.logic [Hans-Dieter_A._Hiep] + raptorfec text/raptorfec [RFC6682] + RED text/RED [RFC4102] + rfc822-headers text/rfc822-headers [RFC6522] + richtext [RFC2045][RFC2046] + rtf text/rtf [Paul_Lindner] + rtp-enc-aescm128 text/rtp-enc-aescm128 [_3GPP] + rtploopback text/rtploopback [RFC6849] + rtx text/rtx [RFC4588] + SGML text/SGML [RFC1874] + shaclc text/shaclc [W3C_SHACL_Community_Group][Vladimir_Alexiev] + shex text/shex [W3C][Eric_Prudhommeaux] + spdx text/spdx [Linux_Foundation][Rose_Judge] + strings text/strings [IEEE-ISTO-PWG-PPP] + t140 text/t140 [RFC4103] + tab-separated-values text/tab-separated-values [Paul_Lindner] + troff text/troff [RFC4263] + turtle text/turtle [W3C][Eric_Prudhommeaux] + ulpfec text/ulpfec [RFC5109] + uri-list text/uri-list [RFC2483] + vcard text/vcard [RFC6350] + vnd.a text/vnd.a [Regis_Dehoux] + vnd.abc text/vnd.abc [Steve_Allen] + vnd.ascii-art text/vnd.ascii-art [Kim_Scarborough] + vnd.curl text/vnd.curl [Robert_Byrnes] + vnd.debian.copyright text/vnd.debian.copyright [Charles_Plessy] + vnd.DMClientScript text/vnd.DMClientScript [Dan_Bradley] + vnd.dvb.subtitle text/vnd.dvb.subtitle [Peter_Siebert][Michael_Lagally] + vnd.esmertec.theme-descriptor text/vnd.esmertec.theme-descriptor [Stefan_Eilemann] + vnd.exchangeable text/vnd.exchangeable [Martin_Cizek] + vnd.familysearch.gedcom text/vnd.familysearch.gedcom [Gordon_Clarke] + vnd.ficlab.flt text/vnd.ficlab.flt [Steve_Gilberd] + vnd.fly text/vnd.fly [John-Mark_Gurney] + vnd.fmi.flexstor text/vnd.fmi.flexstor [Kari_E._Hurtta] + vnd.gml text/vnd.gml [Mi_Tar] + vnd.graphviz text/vnd.graphviz [John_Ellson] + vnd.hans text/vnd.hans [Hill_Hanxv] + vnd.hgl text/vnd.hgl [Heungsub_Lee] + vnd.in3d.3dml text/vnd.in3d.3dml [Michael_Powers] + vnd.in3d.spot text/vnd.in3d.spot [Michael_Powers] + vnd.IPTC.NewsML text/vnd.IPTC.NewsML [IPTC] + vnd.IPTC.NITF text/vnd.IPTC.NITF [IPTC] + vnd.latex-z text/vnd.latex-z [Mikusiak_Lubos] + vnd.motorola.reflex text/vnd.motorola.reflex [Mark_Patton] + vnd.ms-mediapackage text/vnd.ms-mediapackage [Jan_Nelson] + vnd.net2phone.commcenter.command text/vnd.net2phone.commcenter.command [Feiyu_Xie] + vnd.radisys.msml-basic-layout text/vnd.radisys.msml-basic-layout [RFC5707] + vnd.senx.warpscript text/vnd.senx.warpscript [Pierre_Papin] + vnd.si.uricatalogue (OBSOLETED by request) text/vnd.si.uricatalogue [Nicholas_Parks_Young] + vnd.sun.j2me.app-descriptor text/vnd.sun.j2me.app-descriptor [Gary_Adams] + vnd.sosi text/vnd.sosi [Petter_Reinholdtsen] + vnd.trolltech.linguist text/vnd.trolltech.linguist [David_Lee_Lambert] + vnd.wap.si text/vnd.wap.si [WAP-Forum] + vnd.wap.sl text/vnd.wap.sl [WAP-Forum] + vnd.wap.wml text/vnd.wap.wml [Peter_Stark] + vnd.wap.wmlscript text/vnd.wap.wmlscript [Peter_Stark] + vtt text/vtt [W3C][Silvia_Pfeiffer] + xml text/xml [RFC7303] + xml-external-parsed-entity text/xml-external-parsed-entity [RFC7303] + +video + + Available Formats + [IMG] + CSV + + Name Template Reference + 1d-interleaved-parityfec video/1d-interleaved-parityfec [RFC6015] + 3gpp video/3gpp [RFC3839][RFC6381] + 3gpp2 video/3gpp2 [RFC4393][RFC6381] + 3gpp-tt video/3gpp-tt [RFC4396] + AV1 video/AV1 [Alliance_for_Open_Media] + BMPEG video/BMPEG [RFC3555] + BT656 video/BT656 [RFC3555] + CelB video/CelB [RFC3555] + DV video/DV [RFC6469] + encaprtp video/encaprtp [RFC6849] + example video/example [RFC4735] + FFV1 video/FFV1 [RFC9043] + flexfec video/flexfec [RFC8627] + H261 video/H261 [RFC4587] + H263 video/H263 [RFC3555] + H263-1998 video/H263-1998 [RFC4629] + H263-2000 video/H263-2000 [RFC4629] + H264 video/H264 [RFC6184] + H264-RCDO video/H264-RCDO [RFC6185] + H264-SVC video/H264-SVC [RFC6190] + H265 video/H265 [RFC7798] + iso.segment video/iso.segment [David_Singer][ISO-IEC_JTC1] + JPEG video/JPEG [RFC3555] + jpeg2000 video/jpeg2000 [RFC5371][RFC5372] + jxsv video/jxsv [RFC9134] + mj2 video/mj2 [RFC3745] + MP1S video/MP1S [RFC3555] + MP2P video/MP2P [RFC3555] + MP2T video/MP2T [RFC3555] + mp4 video/mp4 [RFC4337][RFC6381] + MP4V-ES video/MP4V-ES [RFC6416] + MPV video/MPV [RFC3555] + mpeg [RFC2045][RFC2046] + mpeg4-generic video/mpeg4-generic [RFC3640] + nv video/nv [RFC4856] + ogg video/ogg [RFC5334][RFC7845] + parityfec video/parityfec [RFC3009] + pointer video/pointer [RFC2862] + quicktime video/quicktime [RFC6381][Paul_Lindner] + raptorfec video/raptorfec [RFC6682] + raw video/raw [RFC4175] + rtp-enc-aescm128 video/rtp-enc-aescm128 [_3GPP] + rtploopback video/rtploopback [RFC6849] + rtx video/rtx [RFC4588] + scip video/scip [SCIP][Michael_Faller][Daniel_Hanson] + smpte291 video/smpte291 [RFC8331] + SMPTE292M video/SMPTE292M [RFC3497] + ulpfec video/ulpfec [RFC5109] + vc1 video/vc1 [RFC4425] + vc2 video/vc2 [RFC8450] + vnd.CCTV video/vnd.CCTV [Frank_Rottmann] + vnd.dece.hd video/vnd.dece.hd [Michael_A_Dolan] + vnd.dece.mobile video/vnd.dece.mobile [Michael_A_Dolan] + vnd.dece.mp4 video/vnd.dece.mp4 [Michael_A_Dolan] + vnd.dece.pd video/vnd.dece.pd [Michael_A_Dolan] + vnd.dece.sd video/vnd.dece.sd [Michael_A_Dolan] + vnd.dece.video video/vnd.dece.video [Michael_A_Dolan] + vnd.directv.mpeg video/vnd.directv.mpeg [Nathan_Zerbe] + vnd.directv.mpeg-tts video/vnd.directv.mpeg-tts [Nathan_Zerbe] + vnd.dlna.mpeg-tts video/vnd.dlna.mpeg-tts [Edwin_Heredia] + vnd.dvb.file video/vnd.dvb.file [Peter_Siebert][Kevin_Murray] + vnd.fvt video/vnd.fvt [Arild_Fuldseth] + vnd.hns.video video/vnd.hns.video [Swaminathan] + vnd.iptvforum.1dparityfec-1010 video/vnd.iptvforum.1dparityfec-1010 [Shuji_Nakamura] + vnd.iptvforum.1dparityfec-2005 video/vnd.iptvforum.1dparityfec-2005 [Shuji_Nakamura] + vnd.iptvforum.2dparityfec-1010 video/vnd.iptvforum.2dparityfec-1010 [Shuji_Nakamura] + vnd.iptvforum.2dparityfec-2005 video/vnd.iptvforum.2dparityfec-2005 [Shuji_Nakamura] + vnd.iptvforum.ttsavc video/vnd.iptvforum.ttsavc [Shuji_Nakamura] + vnd.iptvforum.ttsmpeg2 video/vnd.iptvforum.ttsmpeg2 [Shuji_Nakamura] + vnd.motorola.video video/vnd.motorola.video [Tom_McGinty] + vnd.motorola.videop video/vnd.motorola.videop [Tom_McGinty] + vnd.mpegurl video/vnd.mpegurl [Heiko_Recktenwald] + vnd.ms-playready.media.pyv video/vnd.ms-playready.media.pyv [Steve_DiAcetis] + vnd.nokia.interleaved-multimedia video/vnd.nokia.interleaved-multimedia [Petteri_Kangaslampi] + vnd.nokia.mp4vr video/vnd.nokia.mp4vr [Miska_M._Hannuksela] + vnd.nokia.videovoip video/vnd.nokia.videovoip [Nokia] + vnd.objectvideo video/vnd.objectvideo [John_Clark] + vnd.radgamettools.bink video/vnd.radgamettools.bink [Henrik_Andersson] + vnd.radgamettools.smacker video/vnd.radgamettools.smacker [Henrik_Andersson] + vnd.sealed.mpeg1 video/vnd.sealed.mpeg1 [David_Petersen] + vnd.sealed.mpeg4 video/vnd.sealed.mpeg4 [David_Petersen] + vnd.sealed.swf video/vnd.sealed.swf [David_Petersen] + vnd.sealedmedia.softseal.mov video/vnd.sealedmedia.softseal.mov [David_Petersen] + vnd.uvvu.mp4 video/vnd.uvvu.mp4 [Michael_A_Dolan] + vnd.youtube.yt video/vnd.youtube.yt [Google] + vnd.vivo video/vnd.vivo [John_Wolfe] + VP8 video/VP8 [RFC7741] + VP9 video/VP9 [RFC-ietf-payload-vp9-16] + + Contact Information + + ID Name Contact URI Last Updated + [_3GPP] 3GPP - Third Generation mailto:3gppContact&etsi.org + Partnership Project + [_3MF] 3MF Consortium http://3mf.io + [Gary_Adams] Gary Adams mailto:gary.adams&sun.com 2002-01-20 + [Greg_Adams] Greg Adams mailto:gadams&waynesworld.ucsd.edu 1997-03-19 + [AES] Audio Engineering Society http://www.aes.org + (AES) + [Igor_Afanasyev] Igor Afanasyev mailto:igor.afanasyev&sap.com 2021-03-23 + [Jesse_Alama] Jesse Alama mailto:j.alama&fct.unl.pt 2006-09-13 + [Vladimir_Alexiev] Vladimir Alexiev mailto:vladimir.alexiev&ontotext.com 2020-06-30 + [Ali_Teffahi] Ali Teffahi mailto:ateffahi&dtg.org.uk 2012-04-19 + [Steve_Allen] Steve Allen mailto:sla&ucolick.org 1997-09-19 + [Alliance_for_Open_Media] Alliance for Open Media mailto:registration&aomedia.org 2021-01-28 + [Harald_T._Alvestrand] Harald T. Alvestrand mailto:Harald.T.Alvestrand&uninett.no 1997-01-19 + [Mike_Amundsen] Mike Amundsen mailto:mca&amundsen.com 2011-07-12 + [Chandrashekhara_Anantharamu] Chandrashekhara mailto:chandra&muvee.com 2007-10-16 + Anantharamu + [Jim_Ancona] Jim Ancona mailto:jancona&constantcontact.com 2008-03-17 + [Carl_Anderson] Carl Anderson mailto:ca&deezer.com 2014-05-27 + [Henrik_Andersson] Henrik Andersson mailto:henke&henke37.cjb.net 2017-04-06 + [Apache_Arrow_Project] Apache Arrow Project mailto:private&arrow.apache.org 2021-06-23 + Management Committee + [David_Applebaum] David Applebaum mailto:applebau&battelle.org 1997-02-19 + [William_C._Appleton] William C. Appleton mailto:billappleton&dreamfactory.com 2002-01-20 + [Gayatri_Aravindakumar] Gayatri Aravindakumar mailto:gayatri&informix.com 2002-02-20 + [Gus_Asadi] Gus Asadi mailto:gasadi&me.com 2016-02-29 + [ASAM] ASAM mailto:info&asam.net 2014-12-04 + [Michael_Ashbridge] Michael Ashbridge mailto:mashbridge+iana&google.com 2006-12-14 + [Kazuyuki_Ashimura] Kazuyuki Ashimura mailto:ashimura&w3.org 2013-10-30 + American Society of + [ASHRAE] Heating, Refrigerating mailto:ashrae&ashrae.org 2014-01-29 + and Air-Conditioning + Engineers (ASHRAE) + [ASTM] ASTM International https://www.astm.org 2020-07-20 + [Peter_Astrand] Peter Ã…strand mailto:astrand&cendio.se 2007-06-12 + [ATSC] Advanced Television mailto:jwhitaker&atsc.org 2020-03-18 + Systems Committee + [AutomationML_e.V.] AutomationML e.V. mailto:office&automationml.org 2022-07-26 + [Steve_Aubrey] Steve Aubrey mailto:steve_aubrey&hp.com 1998-07-19 + [Yüksel_Aydemir] Yüksel Aydemir mailto:yay&capasystems.com 2017-05-03 + [Brijesh_Kumar] Brijesh Kumar mailto:bkumar&lightspeedaudiolabs.com 2007-04-26 + [Attila_Babits] Attila Babits mailto:ababits&graphisoft.hu 2000-05-20 + [Sebastian_Baer] Sebastian Baer mailto:sebastian.baer&elektrobit.com 2015-07-21 + [Martin_Bailey] Martin Bailey mailto:martin.bailey&globalgraphics.com 2002-12-20 + [Rob_Bailey] Rob Bailey mailto:sysadmin&laserapp.com 2017-03-09 + [Natarajan_Balasundara] Natarajan Balasundara mailto:rajan&ipanoramii.com 2014-05-01 + [Chris_Bartram] Chris Bartram mailto:RCB&3k.com 1998-05-19 + [Prakash_Baskaran] Prakash Baskaran mailto:prakash&pawaa.com 2009-12-15 + [Wesley_Beary] Wesley Beary mailto:api&heroku.com 2013-09-06 + [Scott_Becker] Scott Becker mailto:scottb&bxwa.com 1998-10-19 + [Thomas_Belling] Thomas Belling mailto:Thomas.Belling&siemens.com 2013-05-13 + [James_Bellinger] James Bellinger mailto:james&dimensionengineering.com 2017-07-18 + [Oren_Ben-Kiki] Oren Ben-Kiki mailto:oren&capella.co.il 1998-10-19 + [Jack_Bennett] Jack Bennett mailto:jbennett&inspiration.com 2003-07-20 + [Robin_Berjon] Robin Berjon mailto:robin&w3.org 2014-10-23 + [Gil_Bernabeu] Gil Bernabeu mailto:gil.bernabeu&globalglatform.org 2009-09-01 + [James_Berry] James Berry mailto:jberry&macports.org 2007-05-04 + [Massimo_Bertoli] Massimo Bertoli mailto:massimo.bertoli&veryant.com 2018-12-20 + [Saurabh_Bhatia] Saurabh Bhatia mailto:glTFIANAµsoft.com 2018-05-07 + [Peter_Bierman] Peter Bierman mailto:installer-mime&group.apple.com 2005-11-20 + [Jason_Birch] Jason Birch mailto:jason.birch&osgeo.org 2010-07-20 + [Peter_Biro] Peter Biro mailto:standard&mfsr.sk 2015-02-10 + [Simon_Birtwistle] Simon Birtwistle mailto:mime&20000.org 2007-01-18 + [Nathan_Black] Nathan Black mailto:nblack&onepager.com 2017-04-12 + [Bryan_Blank] Bryan Blank mailto:ntbchair&nga.mil 2019-08-21 + [Andrew_Block] Andrew Block mailto:andy.block&gmail.com 2022-09-01 + [Kevin_Blumberg] Kevin Blumberg mailto:kevin&amiga.com 2002-01-20 + [Malte_Borcherding] Malte Borcherding mailto:Malte.Borcherding&brokat.com 2000-08-20 + [Michael_Borcherds] Michael Borcherds mailto:office&geogebra.org 2020-10-19 + [Nathaniel_Borenstein] Nathaniel Borenstein mailto:NSB&bellcore.com 1994-04-19 + [Jan_Bostrom] Jan Bostrom mailto:jan&sundahus.se 2003-07-20 + [Leon_Bottou] Leon Bottou mailto:leonb&research.att.com 2002-01-20 + [Michael_Boyle] Michael Boyle mailto:michaelboyle&smarttech.com 2008-12-19 + [Dan_Bradley] Dan Bradley mailto:dan&dantom.com 2000-10-20 + [Craig_Bradney] Craig Bradney mailto:cbradney&zip.com.au 2006-06-20 + [Guenther_Brammer] Günther Brammer mailto:gbrammer&gmx.de 2007-06-12 + [Ron_Brill] Ron Brill mailto:Ronb&anglepoint.com 2019-09-03 + [John_Brinkman] John Brinkman mailto:john.brinkman&adobe.com 2007-05-22 + [Ryan_Brinkman] Ryan Brinkman mailto:rbrinkman&bccrc.ca 2010-03-26 + [Broadband_Forum] Broadband Forum mailto:help&broadband-forum.org 2019-12-13 + [Tim_Brody] Tim Brody mailto:tdb2&ecs.soton.ac.uk 2011-06-22 + [Christopher_Brooks] Christopher Brooks mailto:cxh&eecs.berkeley.edu 2006-08-20 + [David_Brossard] David Brossard mailto:david.brossard&axiomatics.com 2013-02-19 + [Patrick_Brosse] Patrick Brosse mailto:pbrosse&amadeus.com 2018-01-16 + [Al_Brown] Al Brown mailto:dev+aion&veritone.com 2021-06-24 + [C._Titus_Brown] C. Titus Brown mailto:khmer-project&idyll.org 2015-10-05 + [Craig_Bruce] Craig Bruce mailto:craig&eyesopen.com 2013-10-29 + [Eric_Bruno] Eric Bruno mailto:ebruno&solution-soft.com 2001-06-20 + [Gert_Buettgenbach] Gert Buettgenbach mailto:bue&sevencs.com 1997-05-19 + [Roger_Buis] Roger Buis mailto:buis&us.ibm.com 2001-03-20 + [Andrew_Burt] Andrew Burt mailto:andrewburt&cricksoft.com 2006-03-20 + [Marcus_Ligi_Büschleb] Marcus Ligi Büschleb mailto:marcus.bueschleb&googlemail.com 2016-08-12 + [Hans_Busch] Hans Busch mailto:hans.busch&de.bosch.com 2022-03-28 + [Simon_Butcher] Simon Butcher mailto:simon&alien.net.au 2003-09-20 + [Tim_Butler] Tim Butler mailto:tim&its.bldrdoc.gov 1996-04-19 + [Robert_Byrnes] Robert Byrnes mailto:systems-vendor&curl.com 2001-03-20 + [Marcos_Caceres] Marcos Caceres mailto:marcos&marcosc.com 2021-06-22 + [Mark_Callow] Mark Callow mailto:khronos&callow.im 2020-08-11 + [Larry_Campbell] Larry Campbell mailto:campbell&redsox.bsw.com + [CDISC] CDISC https://www.cdisc.org/ + [Ravinder_Chandhok] Ravinder Chandhok mailto:chandhok&within.com 1998-12-19 + [Chris_Charabaruk] Chris Charabaruk mailto:chris&charabaruk.com 2015-04-03 + [Brad_Chase] Brad Chase mailto:brad_chase&bitstream.com 1996-05-19 + [CHATRAS_Bruno] CHATRAS Bruno mailto:bruno.chatras&orange.com 2012-04-19 + [Tom_Christie] Tom Christie mailto:tom&tomchristie.com 2013-09-06 + [Shay_Cicelsky] Shay Cicelsky mailto:shayc&everad.com 2000-05-20 + [Martin_Cizek] Martin Cizek mailto:martin.cizek&cuzk.cz 2022-08-03 + [John_Clark] John Clark mailto:jclark&dbvision.net 2002-06-20 + [Gordon_Clarke] Gordon Clarke mailto:gedcom&familysearch.org 2021-12-07 + [Gary_Clueit] Gary Clueit mailto:clueit&airzip.com 2012-06-05 + [David_Clunie] David Clunie mailto:dclunie&dclunie.com 2016-09-26 + [Pete_Cole] Pete Cole mailto:pcole&sseyod.demon.co.uk 1996-06-19 + [Devyn_Collier_Johnson] Devyn Collier Johnson mailto:DevynCJohnson&Gmail.com 2014-11-21 + [Cyril_Concolato] Cyril Concolato mailto:cconcolato&netflix.com 2021-01-28 + [Mick_Conley] Mick Conley mailto:mconley&wi-fi.org 2013-09-06 + [Victor_Costan] Victor Costan mailto:pwnall&chromium.org 2017-06-12 + [Alex_Crawford] Alex Crawford mailto:alex.crawford&coreos.com 2016-04-15 + [Dave_Crocker] Dave Crocker mailto:dcrocker@mordor.stanford.edu%3E%3Cbr%3E 2011-03-10 + [Kevin_Crook] Kevin Crook mailto:productmanagement&rainstor.com 2011-03-10 + [Terry_Crowley] Terry Crowley + [Michael_R._Crusoe] Michael R. Crusoe mailto:mrc&commonwl.org 2022-05-09 + [Frank_Cusack] Frank Cusack mailto:frank&tri-dsystems.com 2007-07-05 + [CWL_Project] CWL Project mailto:commonworkflowlanguage&sfconservancy.org 2022-05-09 + [Claire_DEsclercs] Claire D'Esclercs mailto:contact&oipf.tv 2011-02-25 + [Erik_Dahlström] Erik Dahlström mailto:erik.dahlstrom&maxar.com 2022-02-01 + [Priya_Dandawate] Priya Dandawate mailto:pdandawµsoft.com 2013-07-23 + [Asang_Dani] Asang Dani mailto:adani&solution-soft.com 2001-06-20 + [Martin_Dawe] Martin Dawe mailto:mdawe&hitnmix.com 2011-01-27 + [Vicki_DeBarros] Vicki DeBarros mailto:vicki&chaoticom.com 2004-03-20 + [Serge_De_Jaham] Serge De Jaham mailto:sdejaham&aeta-audio.com 2006-03-27 + [Debian_Policy_mailing_list] Debian Policy mailing mailto:debian-policy&lists.debian.org 2014-05-20 + list + [Sebastiaan_Deckers] Sebastiaan Deckers mailto:cbas&rhymbox.com 2002-10-20 + [Regis_Dehoux] Regis Dehoux mailto:biznis®adou.net 2014-02-07 + [José_Del_Romano] José Del Romano mailto:development&banana.ch 2018-06-18 + [Steve_Dellutri] Steve Dellutri mailto:sdellutri&cosmocom.com 1998-03-19 + [Mario_Demuth] Mario Demuth mailto:mario&demuth-ww.de 2017-05-11 + [Alex_Devasia] Alex Devasia mailto:adevasia&mobius.com 2001-03-20 + [Steve_DiAcetis] Steve DiAcetis mailto:stevediaµsoft.com 2008-06-03 + [DICOM_Standards_Committee] DICOM Standards Committee mailto:dicom&medicalimaging.org 2020-02-13 + [Dirk_DiGiorgio-Haag] Dirk DiGiorgio-Haag mailto:dirk&kenamea.com 2002-03-20 + [Michael_Dixon] Michael Dixon mailto:michael.dixon&denovosoftware.com 2007-05-25 + [Jay_Doggett] Jay Doggett mailto:jdoggett&tiac.net 1997-02-19 + [Michael_A_Dolan] Michael A Dolan mailto:md.iana&newtbt.com 2011-09-06 + [Michael_Domino] Michael Domino mailto:michaeldomino&mediaone.net 1997-02-19 + [Michael_J._Donahue] Michael J. Donahue mailto:donahue&kodak.com 2008-01-04 + [Ning_Dong] Ning Dong mailto:ning.dong&oracle.com 2014-12-22 + [Iain_Downs] Iain Downs mailto:iain&easykaraoke.com 2010-11-16 + [Alex_Dubov] Alex Dubov mailto:adubov&ea.com 2015-09-01 + [Emily_DUBS] Emily DUBS mailto:dubs&dvb.org 2020-02-07 + [Dan_DuFeu] Dan DuFeu mailto:ddufeu&neurolanguage.com 2007-06-19 + [Michael_Duffy] Michael Duffy mailto:miked&psiaustin.com 1997-09-19 + [Kristopher_Durski] Kristopher Durski mailto:kris.durski&verumsecuritas.com 2018-04-23 + [Marc_Duteau] Marc Duteau mailto:marc&hl7.org 2022-07-07 + [Patrick_Dwyer] Patrick Dwyer mailto:patrick.dwyer&owasp.org 2021-01-28 + [Ioseb_Dzmanashvili] Ioseb Dzmanashvili mailto:ioseb.dzmanashvili&gmail.com 2012-04-20 + [Donald_E._Eastlake_3rd] Donald E. Eastlake 3rd mailto:d3e3e3&gmail.com 2021-07-06 + [Eclipse_Ditto_developers] Eclipse Ditto developers mailto:ditto-dev&eclipse.org 2022-01-26 + [Ecma_International_Helpdesk] Ecma International mailto:helpdesk&ecma-international.org 2011-05-17 + Helpdesk + [Matthias_Ehmann] Matthias Ehmann mailto:matthias.ehmann&uni-bayreuth.de 2009-07-28 + [Chris_Eich] Chris Eich mailto:ceich&enphaseenergy.com 2014-11-10 + [Stefan_Eilemann] Stefan Eilemann mailto:eile&esmertec.com 2004-12-20 + [ElectronicZombieCorp] Electronic Zombie, Corp. mailto:support&electroniczombie.com 2006-07-31 + [Samer_El-Haj-Mahmoud] Samer El-Haj-Mahmoud mailto:Samer.el-haj-mahmoud&hpe.com 2016-04-06 + [Gary_Ellison] Gary Ellison mailto:gary.ellison&intertrust.com 2008-07-23 + [John_Ellson] John Ellson mailto:ellson&research.att.com 2008-07-24 + [Charles_Engelke] Charles Engelke mailto:charles.engelke&infotechfl.com 2010-09-09 + [Hidekazu_Enjo] Hidekazu Enjo mailto:enjouh&ciedi.jp 2019-06-12 + [Chet_Ensign] Chet Ensign mailto:chet.ensign&oasis-open.org 2020-04-23 + [EPUB_3_WG] EPUB 3 Working Group mailto:public-epub-wg&w3.org 2022-08-16 + [Per_Ersson] Per Ersson mailto:per&ipunplugged.com 2003-12-20 + [ETSI] ETSI + [Casper_Joost_Eyckelhof] Casper Joost Eyckelhof mailto:info&quarantainenet.nl 2016-05-19 + [Joerg_Falkenberg] Jörg Falkenberg mailto:j.falkenberg&cabgmbh.com 2007-01-18 + [Benja_Fallenstein] Benja Fallenstein mailto:b.fallenstein&gmx.de 2003-10-20 + [Michael_Faller] Michael Faller mailto:michael.faller&gd-ms.com 2021-01-29 + [Patrik_Faltstrom] Patrik Faltstrom mailto:paf&nada.kth.se + [Betsy_Fanning] Betsy Fanning mailto:betsy.fanning&pdfa.org 2022-04-29 + [Michael_C._Fanning] Michael C. Fanning mailto:mikefanµsoft.com 2021-04-26 + [Dirk_Farin] Dirk Farin mailto:dirk.farin&gmail.com 2017-04-12 + [David_Faure] David Faure mailto:faure&kde.org 2002-08-20 + [Bill_Fenner] Bill Fenner mailto:fenner&aristanetworks.com 2009-08-18 + [Ilya_Ferber] Ilya Ferber mailto:iferber&netvision.net.il 1985-12-19 + [Lyndsey_Ferguson] Lyndsey Ferguson mailto:Lyndsey&nemetschek.net 2008-10-16 + [Javier_D._Fernández] Javier D. Fernández mailto:javier.fernandez&wu.ac.at 2016-01-07 + [Axel_Ferrazzini] Axel Ferrazzini mailto:aferrazzini&rim.com 2012-03-27 + [James_Fields] James Fields mailto:jaime&d-vision.com 2002-06-20 + [Frederic_Firmin] Frederic Firmin mailto:Frederic.Firmin&etsi.org 2020-10-05 + [Tim_Fisher] Tim Fisher mailto:tim.fisher&indiepath.com 2006-07-25 + [FIX_Trading_Community] FIX Trading Community mailto:fix&fixtrading.org 2020-04-14 + [Daniel_Flassig] Daniel_Flassig mailto:d.flassig&pytha.de 2021-04-07 + [Eric_Fleischman] Eric Fleischman mailto:ericfl&MICROSOFT.com 1997-04-19 + [Dick_Floersch] Dick Floersch mailto:floersch&echo.sound.net 1997-03-19 + [Henry_Flurry] Henry Flurry mailto:henryf&mediastation.com 1999-04-19 + [Adrian_Föder] Adrian Föder mailto:adrian.foeder&netformic.de 2017-02-07 + [Mike_Foley] Mike Foley mailto:mfoley&bluetooth.com 2008-12-19 + [Glenn_Forrester] Glenn Forrester mailto:glennf&qualcomm.com 2006-03-23 + [Michael_Fox] Michael Fox mailto:mfox&nuera.com 2001-01-20 + [Alan_Francis] Alan Francis mailto:A.H.Francis&open.ac.uk 1995-12-19 + [Mark_C_Fralick] Mark C Fralick mailto:mark&getusroi.com 2018-06-26 + [Torben_Frey] Torben Frey mailto:frey&genomatix.de 2003-10-20 + [Fränz_Friederes] Fränz Friederes mailto:fraenz&frieder.es 2019-07-25 + [Randolph_Fritz] Randolph Fritz mailto:rfritz&u.washington.edu 2009-03-04 + [Kiyofusa_Fujii] Kiyofusa Fujii mailto:kfujii&japannet.or.jp 1997-02-19 + [Shuji_Fujii] Shuji Fujii mailto:fujii¢urysys.co.jp 2012-04-20 + [Arild_Fuldseth] Arild Fuldseth mailto:Arild.Fuldseth&fast.no 2000-06-20 + [David_Furbeck] David Furbeck mailto:dfurbeck&blackberry.com 2013-09-06 + [Amir_Gaash] Amir Gaash mailto:amir.gaash&hp.com 2006-04-25 + [Christopher_Gales] Christopher Gales mailto:christopher.gales&informix.com 2000-08-20 + [Andrey_Galkin] Andrey Galkin mailto:andrey&futoin.org 2018-09-10 + [April_Gandert] April Gandert mailto:gandert.am&pg.com 1999-04-19 + [Cliff_Gauntlett] Cliff Gauntlett mailto:cliff&solenttec.net 2006-04-25 + [Finn_Rayk_Gärtner] Finn Rayk Gärtner mailto:finngaertner&protonmail.com 2022-08-09 + [GeoGebra] GeoGebra mailto:office&geogebra.org 2020-10-19 + [Arne_Gerdes] Arne Gerdes mailto:arnegerdes&uniqueobject.com 2006-04-25 + [Steve_Gilberd] Steve Gilberd mailto:steve&erayd.net 2019-09-25 + [Sukvinder_S._Gill] Sukvinder S. Gill mailto:sukvgµsoft.com 1996-04-19 + [Allen_Gillam] Allen Gillam mailto:allen.gillam&hydrostatix.com 2008-07-28 + [Sean_Gillies] Sean Gillies mailto:sean.gillies&gmail.com 2014-05-20 + [David_Glazer] David Glazer mailto:dglazer&best.com 1995-04-19 + [Benjamin_Goering] Benjamin Goering mailto:ben&bengo.co 2018-03-06 + [Laurence_J._Golding] Laurence J. Golding mailto:v-lgoldµsoft.com 2020-09-15 + [Google] Google LLC mailto:ytb-external&google.com 2019-06-07 + [Mikhail_Gorshenev] Mikhail Gorshenev mailto:Mikhail.Gorshenev&sun.com 2007-06-25 + [Philipp_Gortan] Philipp Gortan mailto:P.Gortan&gentics.com 2019-11-26 + [Tadashi_Gotoh] Tadashi Gotoh mailto:tgotoh&cadamsystems.co.jp 2000-06-20 + [Jean-Philippe_Goulet] Jean-Philippe Goulet mailto:jpgoulet&cmles.com 2005-11-20 + [Julien_Grange] Julien Grange mailto:julien.grange&francetelecom.com 2006-03-23 + [Sebastian_Grassia] Sebastian Grassia mailto:usd-iana&pixar.com 2018-06-01 + [Phil_Green] Phil Green mailto:green&colourspace.net 2021-02-09 + [Eli_Grey] Eli Grey mailto:me&eligrey.com 2011-04-07 + [Grahame_Grieve] Grahame Grieve mailto:fhir-director&hl7.org 2018-03-06 + [Paul_Grosso] Paul Grosso mailto:paul&arbortext.com 1996-08-19 + [Rene_Grothmann] Rene Grothmann mailto:rene.grothmann&ku-eichstaett.de 2009-02-24 + [Christian_Grothoff] Christian Grothoff mailto:grothoff&gnu.org 2022-02-28 + [Yu_Gu] Yu Gu mailto:guyu&rd.oda.epson.co.jp 1998-12-19 + [Giacomo_Guilizzoni] Giacomo Guilizzoni mailto:peldi&balsamiq.com 2015-05-06 + [Aloke_Gupta] Aloke Gupta mailto:Aloke_Gupta&ex.cv.hp.com 1999-04-19 + [Amit_Kumar_Gupta] Amit Kumar Gupta mailto:amitgupta.gwl&gmail.com 2018-05-22 + [Tom_Gurak] Tom Gurak mailto:assoc&intercon.roc.servtech.com 1997-03-19 + [John-Mark_Gurney] John-Mark Gurney mailto:jmg&flyidea.com 1999-08-19 + [David_Guy] David Guy mailto:dguy&powersoft.com 1997-06-19 + [Viktor_Haag] Viktor Haag mailto:Viktor.Haag&D2L.com 2016-09-13 + [Marc_Hadley] Marc Hadley mailto:marc.hadley&sun.com 2006-03-20 + [Eric_Hamilton] Eric Hamilton mailto:erich&hheld.com 2006-07-31 + [Ingo_Hammann] Ingo Hammann mailto:ingo.hammann&ppi.de 2003-05-20 + [Michael_Hammer] Michael Hammer mailto:mhammer&cisco.com 2005-10-28 + [Jiwan_Han] Jiwan Han mailto:jiwan.han&etsi.org 2013-05-13 + [Miska_M._Hannuksela] Miska M. Hannuksela mailto:mimetype.registration&nokia.com 2017-09-15 + [Daniel_Hanson] Daniel Hanson mailto:dan.hanson&gd-ms.com 2021-01-29 + [Anders_Hansson] Anders Hansson mailto:anders.hansson&securedemail.com 2006-09-13 + [Hill_Hanxv] Hill Hanxv mailto:i&hanxv.com 2020-09-15 + [Lee_Harding] Lee Harding mailto:lee.harding&circuitpeople.com 2012-02-16 + [Guy_Harris] Guy Harris mailto:guy&alum.mit.edu 2011-03-30 + [Jerry_Harris] Jerry Harris mailto:jharris&ruckusnetwork.com 2005-11-20 + [Steve_Hattersley] Steve Hattersley mailto:steve.hattersley&dolby.com 2009-06-02 + [Mike_Hearn] Mike Hearn mailto:mike&navi.cx 2005-11-20 + [Steven_Heintz] Steven Heintz mailto:heintz&adobe.com 2010-01-13 + [Nicolas_Helin] Nicolas Helin mailto:helin&pv.com 2007-01-23 + [Nor_Helmee] Nor Helmee mailto:helmee&my.cybank.net 1998-11-19 + [Jean-Baptiste_Henry] Jean-Baptiste Henry mailto:jean-baptiste.henry&thomson.net 2008-07-23 + [J._Scott_Hepler] J. Scott Hepler mailto:scott&truebasic.com 2000-05-20 + [Edwin_Heredia] Edwin Heredia mailto:eherediaµsoft.com 2005-12-20 + [Ivan_Herman] Ivan Herman mailto:ivan&w3.org 2020-03-18 + [Uwe_Hermann] Uwe Hermann mailto:uwe&hermann-uwe.de 2017-06-06 + [Amir_Herzberg] Amir Herzberg mailto:amirh&haifa.vnet.ibm.com 1997-02-19 + [Robert_Hess] Robert Hess mailto:hess&vidsoft.de 2001-03-20 + [Matthias_Hessling] Matthias Hessling mailto:info-mime-obn&blaupunkt.de 2002-12-20 + [Joerg_Heuer] Joerg Heuer mailto:Joerg.Heuer&siemens.com 2006-03-23 + [Micheal_Hewett] Michael Hewett mailto:mike&hewettresearch.com 2005-12-20 + [Hans-Dieter_A._Hiep] Hans-Dieter A. Hiep mailto:info&aexiz.com 2016-04-04 + [Ben_Hinman] Ben Hinman mailto:avalonjson.1&ben.hinman.me 2017-09-19 + [Masaaki_Hirai] Masaaki Hirai mailto:masaaki_hirai&mb1.nkc.co.jp 2006-09-20 + [HL7] HL7 http://www.hl7.org + [Pete_Hoch] Pete Hoch mailto:pete.hoch&efi.com 2005-11-20 + [Ingo_Hofmann] Ingo Hofmann mailto:ingo.hofmann&iis.fraunhofer.de 2020-04-04 + [Reinhard_Hohensee] Reinhard Hohensee mailto:rhohensee&VNET.IBM.COM 1997-09-19 + [Markus_Hohenwarter] Markus Hohenwarter mailto:markus&geogebra.org 2020-10-19 + [Thomas_Holmstrom] Thomas Holmstrom mailto:thomas&blueiceresearch.com 2002-04-20 + [Holstage] Mary Holstage mailto:holstege&firstfloor.com 1998-05-19 + [Connor_Horman] Connor Horman mailto:chorman64&gmail.com 2020-04-06 + [Shoji_Hoshina] Shoji Hoshina mailto:Hoshina.Shoji&exc.epson.co.jp 2000-09-20 + [Glenn_Howes] Glenn Howes mailto:ghowes&cambridgesoft.com 2007-06-25 + [Shicheng_Hu] Shicheng Hu mailto:shicheng.hu&etsi.org 2009-09-15 + [Ni_Hui] Ni Hui mailto:nihuini&tencent.com 2014-09-10 + [Sam_Hume] Sam Hume mailto:shume&cdisc.org 2018-11-28 + [Kari_E._Hurtta] Kari E. Hurtta mailto:flexstor&ozone.FMI.FI + [Justin_Hutchings] Justin Hutchings mailto:justhu at microsoft.com 2016-01-27 + [Thomas_Huth] Thomas Huth mailto:huth&tuxfamily.org 2015-03-02 + [Michael_Hvidsten] Michael Hvidsten mailto:hvidsten&gac.edu 2009-03-17 + [Jakub_Hytka] Jakub Hytka mailto:hytka&602.cz 2008-08-06 + [ICT_Manager] ICT Manager mailto:hostmaster&aaltronav.eu 2020-09-30 + [IETF] IETF mailto:ietf&ietf.org + [Kazuya_Iimura] Kazuya Iimura mailto:kazuya.iimura.pa&fujifilm.com 2021-04-20 + [Kiran_Kapale] Kiran Kapale mailto:kiran.kapale&samsung.com 2022-03-30 + ISTO-PWG Internet + [IEEE-ISTO-PWG-PPP] Printing Protocol mailto:ipp&pwg.org 2017-05-01 + Workgroup + [IMS_Global] IMS Global Learning https://www.imsglobal.org 2020-10-02 + Consortium Inc. + International Press + [IPTC] Telecommunications mailto:m_director_iptc&dial.pipex.com 2000-07-20 + Council (David Allen) + [ISO-IEC_JTC1] ISO/IEC JTC1 http://www.iso.org/iso/jtc1_home.html + [ISO-IEC_JTC1_SC6_ASN.1_Rapporteur] ISO/IEC JTC1/SC6 ASN.1 mailto:secretariat&jtc1sc06.org 2005-10-31 + Rapporteur + ISO/TC 171/SC 2 - + Document file formats, + [ISO-TC_171-SC_2] EDMS systems and https://www.iso.org + authenticity of + information + [ISO-TC_184-SC_4] ISO/TC 184/SC 4 - https://www.iso.org + Industrial Data + [ITS-IG-W3C] Internationalization Tag mailto:public-i18n-its-ig&w3.org 2013-08-20 + Set Interest Group (W3C) + [ITU-T] ITU Telecommunication + Standardization Sector + [ITU-T_ASN.1_Rapporteur] ITU-T ASN.1 Rapporteur mailto:tsb&itu.int 2005-10-31 + [Katsuhiko_Ichinose] Katsuhiko Ichinose mailto:xml-info&antenna.co.jp 2010-11-17 + [Yukari_Ikeda] Yukari Ikeda mailto:ikeda&tokoyo.faith.co.jp 2004-04-20 + [Philippe_Imoucha] Philippe Imoucha mailto:pimoucha&businessobjects.com 1996-10-19 + [John_Ingi_Ingimundarson] John Ingi Ingimundarson mailto:jii&oz.com 2003-12-20 + [Toby_Inkster] Toby Inkster mailto:mail&tobyinkster.co.uk 2010-10-19 + [Keitaro_Ishida] Keitaro Ishida mailto:keitaro.ishida.yu&fujifilm.com 2021-04-27 + [Prakash_Iyer] Prakash Iyer mailto:Prakash.iyer&intel.com 2008-02-07 + [Ronald_Jacobs] Ronald Jacobs mailto:ron.jacobs&shopkick.com 2019-07-08 + [Bill_Janssen] Bill Janssen mailto:bill&janssen.org 2002-06-20 + [Jegulsky] Alexander Jegulsky mailto:alexj&bekitzur.com 2013-11-20 + [Stefan_Jernberg] Stefan Jernberg mailto:SJernber&TIBCO.com 2006-09-13 + [Ken_Jibiki] Ken Jibiki mailto:ken&kenjij.com 2010-07-23 + [Simon_Johnston] Simon Johnston mailto:johnstonskj&gmail.com 2022-07-20 + [Randy_Jones] Randy Jones mailto:randy_jones&archipelago.com 2001-01-20 + [Oskar_Jonsson] Oskar Jonsson mailto:oskar.jonsson&mfd.se 2014-09-16 + [Joost] Joost Technologies B.V. mailto:joda&joost.com 2007-10-16 + [Jens_Jorgensen] Jens Jorgensen mailto:jbj1&ultraemail.net 2014-05-01 + [Todd_Joseph] Todd Joseph mailto:todd_joseph&groove.net 2002-10-20 + [Rose_Judge] Rose Judge mailto:rjudge&vmware.com 2021-11-03 + [Steve_Judkins] Steve Judkins mailto:stevenj007&hotmail.com 2003-07-20 + [Kyunghun_Jung] Kyunghun Jung mailto:kyunghun.jung&samsung.com 2017-12-04 + [Matthias_Juwan] Matthias Juwan mailto:mjuwan&presonus.com 2017-08-22 + [Allen_K._Kabayama] Allen K. Kabayama mailto:akabayam&mobius.com 1999-06-19 + [Sebastian_Kaebisch] Sebastian Kaebisch mailto:sebastian.kaebisch&siemens.com 2022-08-22 + [Greg_Kaiser] Greg Kaiser mailto:android-iana-mime-registration&google.com 2019-06-25 + [Petteri_Kangaslampi] Petteri Kangaslampi mailto:petteri.kangaslampi&nokia.com 2001-03-20 + [Hervé_Kasparian] Hervé Kasparian mailto:herve&kasparian.eu 2022-01-03 + [Steve_Katz] Steve Katz mailto:skatz&eshop.com 1995-06-19 + [David_Keaton] David Keaton mailto:dmk&dmk.com 2021-04-26 + [Sean_Kelley] Sean Kelley mailto:seankelley&motorola.com 2006-09-14 + [Mike_Kelly] Mike Kelly mailto:mike&stateless.co 2011-07-14 + [John_Kemp] John Kemp mailto:john.kemp&earthlink.net 2003-09-20 + [Andrew_David_Kendall] Andrew David Kendall mailto:andrew.kendall&atos.net 2018-10-26 + [Matt_Kern] Matt Kern mailto:matt.kern&cyantechnology.com 2012-09-19 + [Keith_Kester] Keith Kester mailto:kkester&digitalriver.com 2015-09-11 + [Khronos] The Khronos Group http://www.khronos.org + [Bill_Kidwell] Bill Kidwell mailto:bkidwell&opentext.com 2018-10-23 + [Sybren_Kikstra] Sybren Kikstra mailto:skikstra&66.com 2008-05-13 + [Yokoyama_Kiyonobu] Yokoyama Kiyonobu mailto:ngn&nihon-seigyo.co.jp 2012-03-27 + [Thomas_Kjornes] Thomas Kjørnes mailto:thomas&ipv.no 2007-10-11 + [Steve_Klabnik] Steve Klabnik mailto:steve&steveklabnik.com 2013-07-21 + [Steven_Klos] Steven Klos mailto:stevek&osa.com 1997-02-19 + [Uli_Klumpp] Uli Klumpp mailto:iana&uliklumpp.com 2016-07-01 + [Martin_Knowles] Martin Knowles mailto:mjk&irepository.net 2001-06-20 + [Patrick_Koh] Patrick Koh mailto:patrick.koh&mindmapper.com 2007-02-13 + [Bayard_Kohlhepp] Bayard Kohlhepp mailto:bayard&ctcexchange.com 2000-04-20 + [Ulrich_Kortenkamp] Ulrich Kortenkamp mailto:kortenkamp&cinderella.de 2002-04-20 + [Brian_Korver] Brian Korver mailto:briank&terisa.com 1996-10-19 + [Timo_Kosonen] Timo Kosonen mailto:timo.kosonen&nokia.com 2007-01-30 + [Michael_Koster] Michael Koster mailto:michael.koster&smartthings.com 2017-03-03 + [Matthias_Kovatsch] Matthias Kovatsch mailto:w3c&kovatsch.net 2020-02-12 + [Luiza_Kowalczyk] Luiza Kowalczyk mailto:LKowalczyk&dicomstandard.org 2020-02-13 + [Yves_Kreis] Yves Kreis mailto:yves&geogebra.org 2009-03-13 + [Yves_Kreis_2] Yves Kreis mailto:yves.kreis&inter2geo.eu 2010-06-16 + [Peter_Kriens] Peter Kriens mailto:Peter.Kriens&aQute.biz 2012-03-02 + [Tor_Kristensen] Tor Kristensen mailto:tor&lila.io 2010-10-19 + [Victor_Kuchynsky] Victor Kuchynsky mailto:vk&logipipe.com 2019-05-04 + [Rajesh_Kumar] Rajesh Kumar mailto:rkumar&cisco.com 2001-08-20 + [Shinji_Kusakari] Shinji Kusakari mailto:shinji.kusakari&mb.softbank.co.jp 2008-06-30 + [John_Kwan] John Kwan mailto:John.Kwan&sealedmedia.com 2006-03-23 + [Mike_Labatt] Mike Labatt mailto:mike-labatt.iana-mime&cloanto.net 2009-09-01 + [Clemens_Ladisch] Clemens Ladisch mailto:clemens&ladisch.de 2018-02-12 + [Michael_Lagally] Michael Lagally mailto:Michael.Lagally&oracle.com 2011-06-08 + [Charles_P._Lamb] Charles P. Lamb mailto:CLamb&pvimage.com 2001-06-20 + [David_Lee_Lambert] David Lee Lambert mailto:davidl&lmert.com 2006-09-14 + [Martin_Lambert] Martin Lambert mailto:martin.lambert&sealedmedia.com 2006-03-27 + [Divon_Lan] Divon Lan mailto:divon&genozip.com 2022-04-05 + [Bruno_Landais] Bruno Landais mailto:bruno.landais&nokia.com 2021-04-09 + [Nils_Langhammer] Nils Langhammer mailto:nils.langhammer&miele.de 2014-05-01 + [Michael_Laramie] Michael Laramie mailto:laramiem&btv.ibm.com 1999-02-19 + [Steven_Lasker] Steven Lasker mailto:StevenLasker&hotmail.com 2020-04-09 + [Kevin_Lau] Kevin Lau mailto:kevinlauµsoft.com 2006-09-29 + [Juern_Laun] Juern Laun mailto:juern.laun&gmx.de 1999-04-19 + [Jeff_Lawson] Jeff Lawson mailto:jlawson&ud.com 2006-09-14 + [Gwenael_Le_Bodic] Gwenael Le Bodic mailto:Gwenael.Le_Bodic&alcatel.fr 2001-03-20 + [Eric_Ledoux] Eric Ledoux mailto:ericleµsoft.com 2005-12-20 + [Heungsub_Lee] Heungsub Lee mailto:sub&subl.ee 2018-07-03 + [Franck_Lefevre] Franck Lefevre mailto:franck&k1info.com 2011-05-25 + [Eric_Lengyel] Eric Lengyel mailto:lengyel&terathon.com 2014-04-28 + [Steve_Leow] Steve Leow mailto:Leost01&accpac.com 1999-04-19 + [Levantovsky] Vladimir Levantovsky mailto:vladimir.levantovsky&monotypeimaging.com 2013-03-29 + [Glenn_Levitt] Glenn Levitt mailto:streetd1&ix.netcom.com 1996-10-19 + [Rhys_Lewis] Rhys Lewis mailto:rhys&qualcomm.com 2009-12-15 + [Paul_Lindner] Paul Lindner mailto:lindner&mudhoney.micro.umn.edu 1993-06-19 + [John_Lines] John Lines mailto:john&paladin.demon.co.uk 1998-01-19 + [Sten_Linnarsson] Sten Linnarsson mailto:sten.linnarsson&ki.se 2019-07-09 + [Linux_Foundation] Linux Foundation mailto:kstewart&linuxfoundation.org 2021-11-03 + [Slawomir_Lisznianski] Slawomir Lisznianski mailto:sl&pushcoin.com 2014-01-31 + [Jones_Lu_Yunjie] Jones Lu Yunjie mailto:jones.lu&ericsson.com 2021-04-09 + [Dovid_Lubin] Dovid Lubin mailto:dovid&acucorp.com 2002-06-20 + [Mikusiak_Lubos] Mikusiak Lubos mailto:lmikusia&blava-s.bratisla.ingr.com 1996-10-19 + [Matthias_Ludwig] Matthias Ludwig mailto:mludwig&quobject.com 2011-03-08 + [Dan_Luhring] Dan Luhring mailto:dan.luhring&anchore.com 2021-11-15 + [Howard_Lukk] Howard Lukk mailto:hlukk&smpte.org 2017-12-11 + [Tim_Macdonald] Tim Macdonald mailto:tim.macdonald&actf.com.au 2005-11-20 + [Ilan_Mahalal] Ilan Mahalal mailto:ilan.mahalal&gemalto.com 2006-09-13 + [AC_Mahendran] AC Mahendran mailto:ac&qualcomm.com 2007-11-06 + [Piotr_Majdak] Piotr Majdak mailto:piotr.majdak&oeaw.ac.at 2020-06-16 + [Shawn_Maloney] Shawn Maloney mailto:shawnmalµsoft.com 2014-04-11 + [Lucas_Maneos] Lucas Maneos mailto:iana&pragmaticomm.com 2005-12-20 + [Phillip_Maness] Phillip Maness mailto:phillip.maness&xperi.com 2018-11-14 + [Dave_Manning] Dave Manning mailto:dmanning&uwi.com 2014-01-01 + [Michael_Mansell] Michael Mansell mailto:mmansell&ca.ibm.com 2009-02-18 + [Jerome_Marcon] Jérôme Marcon mailto:jerome.marcon&alcatel-lucent.com 2010-02-24 + [Benson_Margulies] Benson Margulies mailto:benson&basistech.com 2016-01-27 + [Bruce_Martin] Bruce Martin mailto:iana-registrar&uplanet.com 1998-11-19 + [Steven_Martin] Steven Martin mailto:smartin&xis.xerox.com 1997-10-19 + [Takashi_Matsumoto] Takashi Matsumoto mailto:takashi.matsumoto&fujixerox.co.jp 2000-02-20 + [David_Matthewman] David Matthewman mailto:david&xara.com 1996-10-19 + [Lisa_Mattson] Lisa Mattson mailto:lmattson&imsglobal.org 2015-01-08 + [Pascal_Mayeux] Pascal Mayeux mailto:pmayeux&cirpack.com 2006-11-13 + [Brian_McColgan] Brian McColgan mailto:b_mccolgan&hotmail.com 2011-05-04 + [Braden_N_McDaniel] Braden N. McDaniel mailto:braden&endoframe.com 2000-10-20 + [Brett_McDowell] Brett McDowell mailto:brett.mcdowell&ieee-isto.org 2003-02-20 + [Jesse_McGatha] Jesse McGatha mailto:jessmcµsoft.com 2006-03-20 + [Tom_McGinty] Tom McGinty mailto:tmcginty&dma.isg.mot.com + [Ann_McLaughlin] Ann McLaughlin mailto:amclaughlin&comversens.com 1999-04-19 + [Alexey_Meandrov] Alexey Meandrov mailto:alexey.meandrov&xcds.com 2017-12-12 + [Roland_Mechling] Roland Mechling mailto:roland&mechling.de 2009-03-04 + [Ian_Medland] Ian Medland mailto:imedland&dtg.org.uk 2012-12-21 + [Roger_Meier] Roger Meier mailto:roger&apache.org 2014-11-25 + [Donald_L._Mendelson] Donald L. Mendelson mailto:donmendelson&silver-flash.net 2020-04-14 + [Christian_Mercat] Christian Mercat mailto:Christian.Mercat&univ-montp2.fr 2009-05-12 + [John_M_Meredith] John M. Meredith mailto:john.meredith&etsi.org 2009-10-13 + [Andy_Miller] Andy Miller mailto:amiller&imsglobal.org 2020-10-02 + [Steve_Mills] Steve Mills mailto:smills&multiad.com 2007-10-17 + [James_Minnis] James Minnis mailto:james-minnis&glimpse-of-tomorrow.com 2000-02-20 + [Stephen_Mizell] Stephen Mizell mailto:public&smizell.com 2018-02-01 + [Harms_Moeller] Harms Moeller mailto:harms&film-it.co.za 2015-12-23 + [Jodi_Moline] Jodi Moline mailto:jodim&softsource.com 1996-04-19 + [Andreas_Molzer] Andreas Molzer mailto:andreas.molzer&nebumind.com 2021-03-17 + [Brent_Moore] Brent Moore mailto:brent_moore&byu.edu 2018-07-11 + [Hiroyoshi_Mori] Hiroyoshi Mori mailto:mori&mm.rd.nttdata.co.jp 1998-08-19 + [Jay_Moskowitz] Jay Moskowitz mailto:jay&aethersystems.com 2001-03-20 + [Daniel_Mould] Daniel Mould mailto:dmould&airzip.com 2009-02-18 + [John_Mudge] John Mudge mailto:helpdesk&omaorg.org 2020-08-03 + [Benedikt_Muessig] Benedikt Muessig mailto:benedikt&resilient-group.de 2021-06-02 + [Masanori_Murai] Masanori Murai mailto:masanori.murai&mb.softbank.co.jp 2007-12-20 + [Makoto_Murata] Makoto Murata mailto:eb2m-mrt&asahi-net.or.jp 2011-03-23 + [Kevin_Murray] Kevin Murray mailto:kamurray&nds.com 2011-05-09 + [Shin_Muto] Shin Muto mailto:shinmuto&pure.cpdc.canon.co.jp 2000-05-20 + [Andy_Mutz] Andy Mutz mailto:andy_mutz&hp.com 1997-12-19 + [Giuseppe_Naccarato] Giuseppe Naccarato mailto:giuseppe.naccarato&picsel.com 2003-11 + [Irakli_Nadareishvili] Irakli Nadareishvili mailto:irakli&gmail.com 2018-03-06 + [Yasuhito_Nagatomo] Yasuhito Nagatomo mailto:naga&rd.oda.epson.co.jp 1998-01-19 + [Shuji_Nakamura] Shuji Nakamura mailto:shuji&mri.co.jp 2008-02-07 + [National_Archives_UK] The National Archives, UK mailto:DigitalPreservation&nationalarchives.gsi.gov.uk 2014-11-21 + [Satish_Navarajan] Satish Natarajan mailto:satish&infoseek.com 1998-08-19 + [Filip_Navara] Filip Navara mailto:navara&emclient.com 2009-02-18 + NGA National Center for + [NCGIS] Geospatial Intelligence 2019-08-21 + Standards (NCGIS) + [Jonathan_Neitz] Jonathan Neitz mailto:jneitz&smarttech.com 2009-12-17 + [Jan_Nelson] Jan Nelson mailto:jannµsoft.com 2000-05-20 + [Max_Neuendorf] Max Neuendorf mailto:max.neuendorf&iis.fraunhofer.de 2018-04-06 + [Thinh_Nguyenphu] Thinh Nguyenphu mailto:thinh.nguyenphu&nsn.com 2008-02-07 + [Jonathan_Niedfeldt] Jonathan Niedfeldt mailto:jniedfeldt&americature.org 2004-04-20 + [Node.js_TSC] Node.js Technical mailto:tsc&nodejs.org 2017-12-11 + Steering Committee + [Nokia] Nokia Corporation mailto:mime&nokia.com 2007-05-25 + [Paul_Norman] Paul Norman mailto:paul.norman&osmfoundation.org 2016-10-05 + Mark Nottingham, see + [Mark_Nottingham] registration template for 2004-12 + e-mail + [NTT-local] NTT-local mailto:ngn-fmt-regist&ml.east.ntt.co.jp 2014-07-25 + [Magnus_Nystrom] Magnus Nyström mailto:magnus&rsasecurity.com 2006-03-23 + [OASIS] OASIS https://www.oasis-open.org + mailto:tc-admin&oasis-open.org + OASIS Security Services + [OASIS_Security_Services_Technical_Committee_SSTC] Technical Committee 2004-12 + (SSTC), see registration + template for e-mail + [OASIS_TC_Admin] OASIS TC Admin mailto:project-admin&oasis-open.org 2022-06-07 + [OGC] Open Geospatial http://www.opengeospatial.org + Consortium (OGC) + [OMA] Open Mobile Alliance mailto:technical-comments&openmobilealliance.org 2010-07-20 + [OMA-DM_Work_Group] OMA-DM Work Group mailto:technical-comments&openmobilealliance.org 2012-06-15 + [OMA_Data_Synchronization_Working_Group] OMA Data Synchronization mailto:technical-comments&mail.openmobilealliance.org 2014-05-01 + Working Group + OMA Presence and + [OMA_Presence_and_Availability_PAG_Working_Group] Availability (PAG) mailto:technical-comments&mail.openmobilealliance.org 2006-09-14 + Working Group + OMA Push to Talk over + [OMA_Push_to_Talk_over_Cellular_POC_Working_Group] Cellular (POC) Working mailto:technical-comments&mail.openmobilealliance.org 2008-01-24 + Group + [Michael_OBrien] Michael O'Brien mailto:meobrien1&mmm.com 1998-01-19 + [Tim_Ocock] Tim Ocock mailto:tim.ocock&symbian.com 2003-09-20 + [Masumi_Ogita] Masumi Ogita mailto:ogita&oa.tfl.fujitsu.co.jp 1997-10-19 + [Seiji_Okudaira] Seiji Okudaira mailto:okudaira&candy.paso.fujitsu.co.jp 1997-10-19 + [Mark_Olleson] Mark Olleson mailto:mark.olleson&yamaha.co.uk 2009-03-11 + [Thomas_Olsson] Thomas Olsson mailto:thomas&vinga.se 1998-04-19 + [Franz_Ombler] Franz Ombler mailto:franz&1000minds.com 2017-06-06 + [Masanori_Onda] Masanori Onda mailto:Masanori.Onda&fujixerox.co.jp 2000-02-20 + [OPC_Foundation] OPC Foundation mailto:sysadmin&opcfoundation.org 2020-09-01 + [Open_Mobile_Naming_Authority] Open Mobile Naming mailto:OMA-OMNA&mail.openmobilealliance.org 2020-08-03 + Authority (OMNA) + [Open_Mobile_Alliance_BAC_DLDRM_Working_Group] Open Mobile Alliance's mailto:technical-comments&mail.openmobilealliance.org 2006-03-23 + BAC DLDRM Working Group + Organization for the + [OP3FT] Promotion, Protection and mailto:contact-standards&op3ft.org 2020-05-04 + Progress of Frogans + Technology (OP3FT) + [Miguel_Angel_Reina_Ortega] Miguel Angel Reina Ortega mailto:MiguelAngel.ReinaOrtega&etsi.org 2020-01-10 + [Tapani_Otala] Tapani Otala mailto:otala&adobe.com 2009-07-28 + [Mark_Otaris] Mark Otaris mailto:mark.otaris&openmailbox.org 2015-08-28 + [Ozgur_Oyman] Ozgur Oyman mailto:ozgur.oyman&intel.com 2014-04-16 + [Yutaka_Ozaki] Yutaka Ozaki mailto:yutaka_ozaki&gen.co.jp 1999-01-19 + [Jörg_Palmer] Jörg Palmer mailto:Joerg.Palmer&compart.com 2020-10-12 + [John_Palmieri] John Palmieri mailto:johnp&redhat.com 2007-06-07 + [Jorge_Pando] Jorge Pando mailto:jpan&pocketlearn.com 2006-03-23 + [Pierre_Papin] Pierre Papin mailto:pierre.papin&senx.io 2018-11-21 + [Parasolid] Parasolid mailto:ps-mimetype&ugs.com 2000-10-20 + [David_Parker] David Parker mailto:davidparker&davidparker.com 1999-08-19 + [Curtis_Parks] Curtis Parks mailto:parks&eeel.nist.gov 1995-04-19 + [Nicholas_Parks_Young] Nicholas Parks Young mailto:nick&shadyindustries.com 2007-12-03 + [Stuart_Parmenter] Stuart Parmenter mailto:pavlov&pavlov.net 2015-08-04 + [Glenn_Parsons] Glenn Parsons mailto:gparsons&nortelnetworks.com 1999-02-19 + [Pathway_Commons] Pathway Commons mailto:pc-info&pathwaycommons.org 2015-06-09 + mailto:biopax-discuss&googlegroups.org + [Mark_Patton] Mark Patton mailto:fmp014&email.mot.com 1999-03-19 + [Frank_Patz] Frank Patz mailto:fp&contact.de 2000-09-20 + [Gaige_Paulsen] Gaige Paulsen mailto:iana&gbp.gaige.net 2010-11-16 + [PCO_AG] PCO AG 2020-09-17 + [PDF_Association] PDF Association mailto:info&pdfa.org 2022-02-25 + [Gavin_Peacock] Gavin Peacock mailto:gpeacock&palm.com 2000-11-20 + [Steven_Pemberton] Steven Pemberton mailto:member-webapps&w3.org 2010-03-09 + [Bob_Pentecost] Bob Pentecost mailto:bpenteco&boi.hp.com 1997-03-19 + [Petr_Peterka] Petr Peterka mailto:info&verimatrix.com 2010-08-31 + [Nils_Peters] Nils Peters mailto:npeters&qti.qualcomm.com 2020-04-04 + [David_Petersen] David Petersen mailto:david.petersen&sealedmedia.com 2003-09-20 + [Silvia_Pfeiffer] Silvia Pfeiffer mailto:silviapfeiffer1&gmail.com 2019-10-14 + [Peeter_Piegaze] Peeter Piegaze mailto:ppiegaze at adobe.com 2003-09-20 + [Francois_Pirsch] Francois Pirsch mailto:fpirsch&geocube.fr 2009-12-15 + [Dan_Plastina] Dan Plastina mailto:danplµsoft.com 2002-03-20 + [Charles_Plessy] Charles Plessy mailto:plessy&debian.org 2014-05-20 + [PNG_Working_Group] PNG Working Group mailto:public-png&w3.org 2021-12-07 + [Clemens_Portele] Clemens Portele mailto:portele&interactive-instruments.de 2017-01-09 + [Mark_Powell] Mark Powell mailto:mpowell&bluetooth.com 2013-09-06 + [Michael_Powers] Michael Powers mailto:powers&insideout.net 1999-01-19 + [Jason_Pratt] Jason Pratt mailto:jason.pratt&autodesk.com 1997-08-19 + [Joe_Prevo] Joe Prevo mailto:joe&prevo.com 2002-01-20 + [Avi_Primo] Avi Primo mailto:aprimo&celltick.com 2008-04-28 + [Eric_Prudhommeaux] Eric Prud'hommeaux mailto:eric at w3.org 2021-05-10 + [Jann_Pruulman] Jann Pruulmann mailto:jaan&fut.ee 1998-12-19 + [Boris_Rabinovitch] Boris Rabinovitch mailto:boris&virtue3d.com 2000-02-20 + [Chris_Rae] Chris Rae mailto:chris.raeµsoft.com 2011-02-22 + [Purva_R_Rajkotia] Purva R Rajkotia mailto:prajkoti&qca.qualcomm.com 2012-06-05 + [Ben_Ramsey] Ben Ramsey mailto:ben&shootproof.com 2018-04-06 + [Bindu_Rama_Rao] Bindu Rama Rao mailto:brao&bitfone.com 2006-09-14 + [Marcin_Rataj] Marcin Rataj mailto:lidel&protocol.ai 2022-04-01 + [Uwe_Rauschenbach] Uwe Rauschenbach mailto:uwe.rauschenbach&nokia.com 2015-03-05 + [Joseph_Reagle] Joseph Reagle mailto:reagle&w3.org 2006-03-20 + [Heiko_Recktenwald] Heiko Recktenwald mailto:uzs106&uni-bonn.de 2000-11-20 + [Saveen_Reddy] Saveen Reddy mailto:saveenr&miscrosoft.com 1999-07-19 + [Nick_Reeves] Nick Reeves mailto:ndr&realvnc.com 2009-08-04 + [Yaser_Rehem] Yaser Rehem mailto:yrehem&sapient.com 1997-02-19 + [Petter_Reinholdtsen] Petter Reinholdtsen mailto:pere-iana&hungry.com 2019-06-03 + [Pete_Resnick] Pete Resnick mailto:presnick&qualcomm.com 2000-02-20 + [Cynthia_Revström] Cynthia Revström mailto:iana&cynthia.re 2020-09-29 + [Bryn_Rhodes] Bryn Rhodes mailto:bryn&dynamiccontentgroup.com 2020-12-08 + [Steve_Rice] Steve Rice mailto:developers&pagerduty.com 2015-09-30 + [Jamie_Riden] Jamie Riden mailto:jamie&umajin.com 2006-03-31 + [James_Riordon] James Riordon mailto:webmaster&khronos.org 2011-02-01 + [Mark_Risher] Mark Risher mailto:markr&vividence.com 2000-12-20 + [Dave_Robin] Dave Robin mailto:iana2&daverobin.com 2014-01-29 + [Steve_Rogan] Steve Rogan mailto:rogans&nga.mil 2012-02-16 + [Luc_Rogge] Luc Rogge mailto:rluc&villagedata.com 2006-05-18 + [Erik_Ronström] Erik Ronström mailto:erik.ronstrom&doremir.com 2014-07-25 + [Marshall_Rose] Marshall Rose mailto:mrose&dbc.mtview.ca.us 1995-04-19 + [Frank_Rottmann] Frank Rottmann mailto:Frank.Rottmann&web.de 2008-07-23 + [Delton_Rowe] Delton Rowe mailto:cadman>e.net 2002-01-20 + [Khemchart_Rungchavalnont] Khemchart Rungchavalnont mailto:khemcr&cpu.cp.eng.chula.ac.th 1997-07-19 + [Rick_Rupp] Rick Rupp mailto:onesource-mediatype&tr.com 2016-09-27 + [Manichandra_Sajjanapu] Manichandra Sajjanapu mailto:mcnc33&gmail.com 2018-08-09 + [Matti_Salmi] Matti Salmi mailto:matti.salmi&nokia.com 2003-03-20 + [Troy_Sandal] Troy Sandal mailto:troys&visio.com 1997-11-19 + [Anders_Sandholm] Anders Sandholm mailto:sandholm&google.com 2011-12-14 + [Gary_Sands] Gary Sands mailto:gsands&tycoint.com 2008-01-25 + [Paul_Santinelli_Jr.] Paul Santinelli, Jr. mailto:psantinelli&narrative.com 1996-10-19 + [SAP_SE] SAP SE mailto:igor.afanasyev&sap.com 2021-03-23 + [Samu_Sarivaara] Samu Sarivaara mailto:iana-contact&f-secure.com 2008-10-22 + [Biplab_Sarkar] Biplab Sarkar mailto:Biplab&nemetschek.net 2008-10-16 + [Jun_Sato] Jun Sato mailto:junn.sato&toshiba.co.jp 2006-03-23 + [Kim_Scarborough] Kim Scarborough mailto:user&unknown.nu 2017-12-11 + [Hannes_Scheidler] Hannes Scheidler mailto:hscheidler&quark.com 2002-10-20 + [Daniel_Schneider] Daniel Schneider mailto:danielscµsoft.com 2007-05-03 + [Thomas_Schoffelen] Thomas Schoffelen mailto:thomas&nearst.co 2016-08-12 + [Frank_Schoonjans] Frank Schoonjans mailto:info&medcalc.be 2006-03-20 + [Arno_Schoedl] Arno Schoedl mailto:aschoedl&think-cell.com 2018-04-16 + [Heinz-Peter_Schütz] Heinz-Peter Schütz mailto:heinz.schuetz&bint.ch 2017-04-07 + [Jan_Schütze] Jan Schütze mailto:jans&dracoblue.de 2017-01-09 + [Christian_V._Sciberras] Christian V. Sciberras mailto:contact&covac-software.com 2008-09-19 + [SCIP] SCIP Working Group mailto:ncia.cis3&ncia.nato.int 2021-01-29 + [Greg_Scratchley] Greg Scratchley mailto:greg_scratchley&intuit.com 1998-10-19 + [Meredith_Searcy] Meredith Searcy mailto:msearcy&newmoon.com 1997-06-19 + [Guy_Selzler] Guy Selzler mailto:gselzler&shana.com 1997-02-19 + [Sequence_Ontology] Sequence Ontology mailto:song-devel&lists.utah.edu 2020-08-17 + [Doug_R._Serres] Doug R. Serres mailto:doug.serres&hummingbird.com 2014-05-01 + [Fawad_Shaikh] Fawad Shaikh mailto:fawad&candle.dev 2022-09-05 + [Rafie_Shamsaasef] Rafie Shamsaasef mailto:rafie&motorola.com 2008-08-12 + [Ehud_Shapiro] Ehud Shapiro + [Daniel_Shelton] Daniel Shelton mailto:sheltond&tao-group.com 2007-08-14 + [Reuven_Sherwin] Reuven Sherwin mailto:reuven.sherwin&xmpie.com 2007-03-20 + [Reed_Shilts] Reed Shilts mailto:reed.shilts&sybase.com 1999-08-19 + [Keiichi_Shinoda] Keiichi Shinoda mailto:shino-k&post.yamaha.co.jp 2003-07-20 + [Alex_Sibilev] Alex Sibilev mailto:alex.sibilev&tmd.tv 2014-11-06 + [Peter_Siebert] Peter Siebert mailto:siebert&dvb.org 2011-06-08 + [Scott_Simmons] Scott Simmons mailto:ssimmons&opengeospatial.org 2022-01-13 + [Ben_Simon] Ben Simon mailto:BenS&crt.com 1998-09-19 + [Steven_Simonoff] Steven Simonoff mailto:scs&triscape.com 1999-08-19 + [Ray_Simpson] Ray Simpson mailto:ray&cnation.com 1998-01-19 + [Robby_Simpson] Robby Simpson mailto:rsimpson&gmail.com 2012-11-27 + [Daniel_Sims] Daniel Sims mailto:danielgarthsims&gmail.com 2015-06-01 + [David_Singer] David Singer mailto:singer&apple.com 2018-07-30 + [SIS] Swedish Standards http://www.sis.se/ 2014-07-09 + Institute (SIS) + [Fu_Siyuan] Fu Siyuan mailto:siyuan.fu&intel.com 2017-01-30 + [Dean_Slawson] Dean Slawson mailto:deanslµsoft.com 1996-05-19 + [Horia_Cristian_Slusanschi] Horia Cristian Slusanschi mailto:H.C.Slusanschi&massey.ac.nz 1998-01-19 + [Christopher_Smith] Christopher Smith mailto:christopher.smith+iana&artsquare.com 2014-08-01 + [Derek_Smith] Derek Smith mailto:derek&friendlysoftware.com 2000-11-20 + [Joey_Smith] Joey Smith mailto:joeysmith&gmail.com 2015-12-24 + [Nick_Smith] Nick Smith mailto:nas&ant.co.uk 1995-06-19 + [Roman_Smolgovsky] Roman Smolgovsky mailto:romans&previewsystems.com 1999-04-19 + Society of Motion Picture + [SMPTE] and Television Engineers http://www.smpte.org + (SMPTE) + [SMPTE_Director_of_Standards_Development] Director of Standards mailto:standards-support&smpte.org 2022-07-13 + Development + [Christopher_Snazell] Christopher Snazell mailto:csnazell&astraea-software.com 2011-05-04 + [Chris_Solc] Chris Solc mailto:csolc&adobe.com 2012-02-16 + [Monty_Solomon] Monty Solomon mailto:monty&noblenet.com 1997-02-19 + [Lisa_Spellman] Lisa Spellman mailto:LSpellman&medicalimaging.org 2018-03-06 + [Henry_Spencer] Henry Spencer mailto:henry&zoo.utoronto.ca 1993-06-19 + [Marc_Douglas_Spencer] Marc Douglas Spencer mailto:marcs&itc.kodak.com 1996-10-19 + [Jim_Spiller] Jim Spiller mailto:jspiller&criticaltools.com 2003-06-20 + [Kerrick_Staley] Kerrick Staley mailto:kerrick&kerrickstaley.com 2015-09-01 + [Peter_Stark] Peter Stark mailto:stark&uplanet.com 1999-03-19 + [Sebastian_Stenzel] Sebastian Stenzel mailto:sebastian.stenzel&skymatic.de 2021-04-12 + [Michael_Steidl] Michael Steidl mailto:mdirector&iptc.org 2014-01-29 + [William_Stevenson] William Stevenson mailto:dev&maxmind.com 2014-07-25 + [Thomas_Stockhammer] Thomas Stockhammer mailto:stockhammer&nomor.de 2013-03-29 + [Armands_Strazds] Armands Strazds mailto:armands.strazds&medienhaus-bremen.de 1999-01-19 + [Markus_Strehle] Markus Strehle mailto:markus.strehle&sap.com 2020-01-31 + [Maik_Stührenberg] Maik Stührenberg mailto:maik&xstandoff.net 2010-04-22 + [Takanori_Sudo] Takanori Sudo mailto:tsudo&innopath.com 2011-12-14 + [Masahiko_Sugimoto] Masahiko Sugimoto mailto:sugimoto&sz.sel.fujitsu.co.jp 1997-10-19 + [Taisuke_Sugimoto] Taisuke Sugimoto mailto:sugimototi&noanet.nttdata.co.jp 1999-04-19 + [Takehiro_Sukizaki] Takehiro Sukizaki mailto:sukizaki&soundnet.yamaha.co.jp 2011-04-07 + [Bryan_Sullivan] Bryan Sullivan mailto:bryan.sullivan&att.com 2009-08-20 + [Swaminathan] Kumar Swaminathan mailto:kswami&hns.com 2006-03-23 + [Jon_Swanson] Jon Swanson mailto:jswanson&insors.com 2010-10-12 + [Michael_Sweet] Michael Sweet mailto:msweet&apple.com 2017-05-12 + [Janine_Swenson] Janine Swenson mailto:janine&novadigm.com 1998-01-19 + [Kevin_Swiber] Kevin Swiber mailto:kswiber&gmail.com 2012-11-27 + [Etay_Szekely] Etay Szekely mailto:etay&emultek.co.il 1996-10-19 + [Stefan_Szilva] Stefan Szilva mailto:szilva&changenet.sk 2015-02-10 + [Tom_White] Tom White mailto:twhite&midi.org 2007-01-30 + [Hiroaki_Takahashi] Hiroaki Takahashi mailto:takaha&beat.yamaha.co.jp 2002-01-20 + [Martin_Talbot] Martin Talbot mailto:Martin.Talbot&ubisoft.com 2011-01-25 + [Alexis_Tamas] Alexis Tamas mailto:alexis.tamas&op3ft.org 2020-05-04 + [Fumio_Tanabe] Fumio Tanabe mailto:fumio.tanabe&fujixerox.co.jp 2006-05-18 + [Tanaka] Manabu Tanaka mailto:mtana&iss.isl.melco.co.jp 1997-09-19 + [Wei_Tang] Wei Tang mailto:hi&that.world 2017-09-08 + [Bruce_Tantlinger] Bruce Tantlinger mailto:bat&us.ibm.com 2003-05-20 + [Mi_Tar] Mi Tar mailto:mitar.iana&tnode.com 2020-09-18 + [Lauri_Tarkkala] Lauri Tarkkala mailto:Lauri.Tarkkala&sonera.com 2007-12-20 + [Kiyoshi_Tashiro] Kiyoshi Tashiro mailto:Kiyoshi.Tashiro&fujixerox.co.jp 2013-06-25 + [Akinori_Taya] Akinori Taya mailto:a.taya&west.east.ntt.co.jp 2012-07-09 + [Anatoly_Techtonik] Anatoly Techtonik mailto:techtonik&php.net 2006-04-11 + [Juoko_Tenhunen] Jouko Tenhunen mailto:jouko.tenhunen&nokia.com 2003-11-01 + [Johann_Terblanche] Johann Terblanche mailto:johann.terblanche&sycle.net 2020-09-02 + [Hemant_Thakkar] Hemant Thakkar mailto:hemant&kinar.com 2004-03-20 + [Blake_Thompson] Blake Thompson mailto:flippmoke&gmail.com 2015-12-21 + [Peter_Thompson] Peter Thompson mailto:pthompso&qualcomm.com 2009-03-17 + [Thomas_Thomsen] Thomas Thomsen mailto:thomas.thomsen&asam.net 2014-12-04 + [Jiang_Tian] Jiang Tian mailto:jiangt&digitalwave.cn 2009-06-02 + [Dr._Jun_Tian] Dr. Jun Tian mailto:jtian&wi-fi.org 2020-10-19 + [Paul_Tidwell] Paul Tidwell mailto:paul.tidwell&ericsson.com 2000-02-20 + [Peter_Todd] Peter Todd mailto:pete&petertodd.org 2021-06-24 + [Nobukazu_Togashi] Nobukazu Togashi mailto:togashi&ai.cs.fujitsu.co.jp 1997-06-19 + [Luke_Tomasello] Luke Tomasello mailto:luket&intertrust.com + [Szilveszter_Tóth] Szilveszter Tóth mailto:sztothµsec.hu 2007-07-31 + [Chad_Trabant] Chad Trabant mailto:chad&iris.washington.edu 2009-04-07 + [Dana_Tripp] Dana Tripp mailto:trippdsc4&gmail.com 2021-08-10 + [Christian_Trosclair] Christian Trosclair mailto:christian&patentdive.com 2017-10-19 + [Jeff_Tupper] Jeff Tupper mailto:tupper&peda.com 2000-02-20 + [Eric_Turcotte] Eric Turcotte mailto:eric.turcotte&ericsson.com 2013-07-23 + [Brad_Turner] Brad Turner mailto:vnd.artisan&clanofartisans.com 2018-05-07 + [Glen_Turner] Glen Turner mailto:gdt&gdt.id.au 2011-03-30 + [UEFI_Forum] UEFI Forum mailto:unst&uefi.org 2016-04-06 + [David_Underdown] David Underdown mailto:DigitalPreservation&nationalarchives.gsi.gov.uk 2014-11-21 + [Unity3d] Unity3d mailto:mime&unity3d.com 2007-07-03 + [Oleg_Uryutin] Oleg Uryutin mailto:oleg&aplextor.com 2019-11-21 + [Filippo_Valsorda] Filippo Valsorda mailto:age&filippo.io 2021-10-05 + [Anne_van_Kesteren] Anne van Kesteren mailto:annevk&annevk.nl 2020-07-14 + [Greg_Vaudreuil] Greg Vaudreuil mailto:gregv&lucent.com 1999-01-19 + [Gregory_Vaughan] Gregory Vaughan mailto:gregvaµsoft.com 2003-03-20 + [Heikki_Vesalainen] Heikki Vesalainen mailto:heikkivesalainen&yahoo.com 2009-10-13 + [Mathieu_Villegas] Mathieu Villegas mailto:mimetype&immervision.com 2007-03-20 + [Martin_Vondrous] Martin Vondrous mailto:vondrous&602.cz 2008-08-06 + [Vladimir_Vysotsky] Vladimir Vysotsky mailto:vvysotsky&avistar.com 2006-09-14 + [W3C] W3C http://www.w3.org/ + [W3C_Music_Notation_Community_Group] W3C Music Notation mailto:public-music-notation&w3.org 2017-01-19 + Community Group + [W3C_RDF_Working_Group] RDF Working Group mailto:public-rdf-comments&w3.org 2016-11-30 + [W3C_SHACL_Community_Group] W3C SHACL Community Group https://www.w3.org/community/shacl 2020-06-30 + [W3C_Timed_Text_Working_Group] Timed Text Working Group mailto:public-tt&w3.org 2016-09-27 + [Takatomo_Wakibayashi] Takatomo Wakibayashi mailto:takatomo.wakibayashi&fujixerox.co.jp 2019-12-03 + [WAP-Forum] WAP Forum Ltd. mailto:wap-feedback&mail.wapforum.org 2000-02-20 + [Mark_Wahl] Mark Wahl mailto:Mark.Wahl&informed-control.com 2006-10-24 + [Linus_Walleij] Linus Walleij mailto:triad&df.lth.se 2000-07-20 + [Paul_Walsh] Paul Walsh mailto:paul.walsh&okfn.org 2017-05-01 + [David_Waltermire] David Waltermire mailto:david.waltermire&nist.gov 2019-09-03 + [Hao_Wang] Hao Wang mailto:howard.wang&huawei.com 2012-08-09 + [Greg_Ward] Greg Ward mailto:gregoryjward&gmail.com 2009-03-04 + [Mike_Ward] Mike Ward mailto:mcw&dolby.com 2007-06-18 + [Fred_Waskiewicz] Fred Waskiewicz mailto:wask&omg.org 2008-05-01 + [Paul_Wattenberger] Paul Wattenberger mailto:Paul_Wattenberger&lotus.com 1997-06-19 + [Web3D] Web3D Consortium 2014-01-15 + [Web3D_X3D] Web3D X3D Working Group mailto:x3d-chair&web3d.org 2014-04-08 + Chairs + [Steve_Webb] Steve Webb mailto:steve&wynde.com 1996-10-19 + [Eric_Wedel] Eric Wedel mailto:ewedel&meridian-data.com 1996-10-19 + [Sebastian_A._Weiss] Sebastian A. Weiss mailto:sebastian.weiss&nacamar.de 2021-09-27 + [Linda_Welsh] Linda Welsh mailto:linda&intel.com 2000-11-20 + [Bjorn_Wennerstrom] Bjorn Wennerstrom mailto:bjorn.wennerstrom&synergenix.se 2002-06-20 + [Mike_Wexler] Mike Wexler mailto:mwexler&frame.com 1996-04-19 + [Thomas_Weyn] Thomas Weyn mailto:thomas.weyn&ucamco.com 2015-02-05 + [WHATWG] WHATWG https://www.whatwg.org/ 2020-07-14 + [Catherine_E._White] Catherine E. White mailto:cewhite&llamagraphics.com 2003-03-20 + [Wi-Fi_Alliance] Wi-Fi Alliance mailto:tech&wi-fi.org 2020-10-19 + [James_Wick] James Wick mailto:jrwick&renlearn.com 2004-02-20 + [Glenn_Widener] Glenn Widener mailto:glennw&ndg.com 1997-06-19 + [Frank_Wiebeler] Frank Wiebeler mailto:frank.wiebeler&dev4agriculture.de 2019-05-17 + [Ulrich_Wiehe] Ulrich Wiehe mailto:ulrich.wiehe&nokia.com 2021-04-28 + [James_Wigger] James Wigger mailto:james&rootstudio.co.uk 2016-04-04 + [Jorn_Wildt] Jørn Wildt mailto:jw&fjeldgruppen.dk 2014-04-08 + [George_Williams] George Williams mailto:gww&silcom.com 2008-05-13 + [Marc_Winter] Marc Winter mailto:info&fluxtime.com 2005-12-20 + [Bill_Wohler] Bill Wohler mailto:wohler&newt.com 1997-07-19 + [John_Wolfe] John Wolfe mailto:John_Wolfe.VIVO&vivo.com 1996-04-19 + [Wolfram] Wolfram Research mailto:info&wolfram.com 2009-10-15 + [Laura_Wood] Laura Wood mailto:ytb-external&google.com 2017-12-15 + [Tim_Woodward] Tim Woodward mailto:tim.woodward&motorolasolutions.com 2018-07-30 + [Don_Wright] Don Wright mailto:don&lexmark.com 2001-08-20 + [Peter_Wyatt] Peter Wyatt mailto:peter.wyatt&pdfa.org 2022-02-25 + [XENC_Working_Group] XENC Working Group mailto:xml-encryption&w3.org 2006-03-21 + [Feiyu_Xie] Feiyu Xie mailto:xie&net2phone.com 2002-01-20 + [Chunyun_Xiong] Chunyun Xiong mailto:frankxiong&chipnuts.com 2006-06-27 + [Yiling_Xu] Yiling Xu mailto:yiling.xu&samsung.com 2009-03-11 + [Tomohiro_Yamamoto] Tomohiro Yamamoto mailto:tyamamoto&asc.yamaha.co.jp 2003-04-20 + [Yamanaka] Yasuo Yamanaka mailto:yasuo.yamanaka&dir-bi.co.jp 2009-02-24 + [Haorui_Yang] Haorui Yang mailto:yanghaorui&oppo.com 2022-09-05 + [Mr._Yellow] Mr. Yellow mailto:yellowriversw&yahoo.com 1998-03-19 + [Yang_Yong] Yang Yong mailto:frank.yong.yang&ericsson.com 2021-04-09 + [Jun_Yoshitake] Jun Yoshitake mailto:yositake&iss.isl.melco.co.jp 1997-02-19 + [Roy_Yue] Roy Yue mailto:yuepeiyu&huawei.com 2009-03-04 + [Dmytro_Yunchyk] Dmytro Yunchyk mailto:yunchik&belightsoft.com 2022-06-27 + [Giulio_Zambon] Giulio Zambon mailto:giulio&giuliozambon.org 2013-11-06 + [Rintze_M._Zelle] Rintze M. Zelle mailto:rintze.zelle&gmail.com 2015-02-23 + [Jan_Zeman] Jan Zeman mailto:development&pco.de 2020-09-17 + [Wenjun_Zeng] Wenjun Zeng mailto:zengw&huawei.com 2009-12-15 + [Nathan_Zerbe] Nathan Zerbe mailto:nzerbe&directv.com 2010-09-22 + [Dali_Zheng] Dali Zheng mailto:d&liwa.li 2015-03-16 + [ZigBee] ZigBee Alliance mailto:http://www.zigbee.org 2012-11-27 + [Gottfried_Zimmermann] Gottfried Zimmermann mailto:gzimmermann at acm.org 2013-06-24 + [William_Zou] William Zou mailto:bill.zou&dts.com 2008-08-06 + [Jim_Zubov] Jim Zubov mailto:jz&vesvault.com 2019-05-02 + + Licensing Terms diff --git a/mime0.awk b/mime0.awk new file mode 100644 index 0000000..0197697 --- /dev/null +++ b/mime0.awk @@ -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 . + +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" +} diff --git a/mime1.awk b/mime1.awk new file mode 100644 index 0000000..4bc44c2 --- /dev/null +++ b/mime1.awk @@ -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 . + +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" +} diff --git a/mime2.awk b/mime2.awk new file mode 100644 index 0000000..45fd021 --- /dev/null +++ b/mime2.awk @@ -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 . + +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" +} diff --git a/mime3.awk b/mime3.awk new file mode 100644 index 0000000..65478ba --- /dev/null +++ b/mime3.awk @@ -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 . + +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" +} diff --git a/mime4.awk b/mime4.awk new file mode 100644 index 0000000..9e8c6b7 --- /dev/null +++ b/mime4.awk @@ -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 . + +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" +} diff --git a/output.c b/output.c new file mode 100644 index 0000000..cdc5073 --- /dev/null +++ b/output.c @@ -0,0 +1,1107 @@ +/* Wayland compositor running on top of an X server. + +Copyright (C) 2022 to various contributors. + +This file is part of 12to11. + +12to11 is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +12to11 is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with 12to11. If not, see . */ + +#include +#include +#include + +#include "compositor.h" + +typedef struct _Mode Mode; +typedef struct _Output Output; +typedef struct _ScaleChangeCallback ScaleChangeCallback; + +struct _Mode +{ + int flags; + unsigned int width, height; + unsigned long refresh; +}; + +struct _Output +{ + /* The output ID of this output. */ + RROutput output; + + /* Physical height of this output. */ + unsigned int mm_width, mm_height; + + /* List of display modes. */ + XLList *modes; + + /* The global associated with this output. */ + struct wl_global *global; + + /* A list of resources associated with this output. */ + XLList *resources; + + /* The X and Y position of this output. */ + int x, y; + + /* The width and height of this output. */ + int width, height; + + /* The name of the output. */ + char *name; + + /* The transform and subpixel layout of this output. */ + uint32_t transform, subpixel; + + /* The scale of this output. */ + int scale; +}; + +struct _ScaleChangeCallback +{ + /* The next and last callbacks in this list. */ + ScaleChangeCallback *next, *last; + + /* Callback to run when the display scale changes. */ + void (*scale_change) (void *, int); + + /* Data for the callback. */ + void *data; +}; + +enum + { + ModesChanged = 1, + GeometryChanged = (1 << 2), + /* N.B. that this isn't currently checked during comparisons, + since the rest of the code only supports a single global + scale. */ + ScaleChanged = (1 << 3), + }; + +/* List of all outputs registered. */ +static XLList *all_outputs; + +/* List of all scale factor change callbacks. */ +static ScaleChangeCallback scale_callbacks; + +/* The scale factor currently applied on a global basis. */ +int global_scale_factor; + +static Bool +ApplyEnvironment (const char *name, int *variable) +{ + char *env; + + env = getenv (name); + + if (!env || !atoi (env)) + return False; + + *variable = atoi (env); + return True; +} + +static void +FreeSingleOutputResource (void *data) +{ + struct wl_resource *resource; + + /* Set this to NULL so HandleResourceDestroy doesn't try to mutate + the list inside XLListFree. */ + + resource = data; + wl_resource_set_user_data (resource, NULL); + wl_resource_destroy (resource); +} + +static void +FreeOutput (Output *output) +{ + /* Free all resources. */ + XLListFree (output->resources, FreeSingleOutputResource); + + /* Free all modes. */ + XLListFree (output->modes, XLFree); + + /* Free the global if it exists. */ + if (output->global) + wl_global_destroy (output->global); + + /* Free the output name. */ + XLFree (output->name); + + /* Finally, free the output itself. */ + XLFree (output); +} + +static void +FreeSingleOutput (void *data) +{ + FreeOutput (data); +} + +static void +HandleResourceDestroy (struct wl_resource *resource) +{ + Output *output; + + output = wl_resource_get_user_data (resource); + + /* If output still exists, remove this resource. */ + + if (output) + output->resources = XLListRemove (output->resources, + resource); +} + +static void +HandleRelease (struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static const struct wl_output_interface wl_output_impl = + { + .release = HandleRelease, + }; + +static void +SendGeometry (Output *output, struct wl_resource *resource) +{ + wl_output_send_geometry (resource, output->x, output->y, + output->mm_width, output->mm_height, + output->subpixel, + ServerVendor (compositor.display), + output->name, output->transform); +} + +static void +SendScale (Output *output, struct wl_resource *resource) +{ + wl_output_send_scale (resource, output->scale); +} + +static void +SendMode (Mode *mode, struct wl_resource *resource) +{ + wl_output_send_mode (resource, mode->flags, mode->width, + mode->height, mode->refresh); +} + +static Bool +FindOutputId (RROutput *outputs, int noutputs, RROutput id) +{ + int i; + + for (i = 0; i < noutputs; ++i) + { + if (outputs[i] == id) + return True; + } + + return False; +} + +static void +HandleOutputBound (struct wl_client *client, Output *output, + struct wl_resource *resource) +{ + Surface *surface; + + /* When a new output is bound, see if a surface owned by the client + is inside. If it is, send the enter event to the surface. */ + + surface = all_surfaces.next; + + while (surface != &all_surfaces) + { + if (client == wl_resource_get_client (surface->resource) + && FindOutputId (surface->outputs, surface->n_outputs, + output->output)) + wl_surface_send_enter (surface->resource, resource); + + surface = surface->next; + } +} + +static void +HandleBind (struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + struct wl_resource *resource; + Output *output; + XLList *tem; + + output = data; + + resource = wl_resource_create (client, &wl_output_interface, + version, id); + + if (!resource) + { + wl_client_post_no_memory (client); + return; + } + + wl_resource_set_implementation (resource, &wl_output_impl, data, + HandleResourceDestroy); + output->resources = XLListPrepend (output->resources, resource); + + SendGeometry (output, resource); + SendScale (output, resource); + + for (tem = output->modes; tem; tem = tem->next) + SendMode (tem->data, resource); + + if (wl_resource_get_version (resource) >= 2) + wl_output_send_done (resource); + + HandleOutputBound (client, output, resource); +} + +static Output * +AddOutput (void) +{ + Output *output; + + output = XLCalloc (1, sizeof *output); + + return output; +} + +static Mode * +AddMode (Output *output) +{ + Mode *mode; + + mode = XLCalloc (1, sizeof *mode); + output->modes = XLListPrepend (output->modes, mode); + + return mode; +} + +static double +GetModeRefresh (XRRModeInfo *info) +{ + double rate, vscan; + + vscan = info->vTotal; + + if (info->modeFlags & RR_DoubleScan) + vscan *= 2; + + if (info->modeFlags & RR_Interlace) + vscan /= 2; + + if (info->hTotal && vscan) + rate = (info->dotClock + / (info->hTotal * vscan)); + else + rate = 0; + + return rate; +} + +static void +AppendRRMode (Output *output, RRMode id, XRRScreenResources *res, + XRRCrtcInfo *info, Bool preferred) +{ + Mode *mode; + int i; + + for (i = 0; i < res->nmode; ++i) + { + if (res->modes[i].id == id) + { + mode = AddMode (output); + + mode->width = res->modes[i].width; + mode->height = res->modes[i].height; + mode->refresh + = GetModeRefresh (&res->modes[i]) * 1000; + + if (info->mode == id) + mode->flags |= WL_OUTPUT_MODE_CURRENT; + + if (preferred) + mode->flags |= WL_OUTPUT_MODE_PREFERRED; + } + } +} + +static uint32_t +ComputeSubpixel (XRROutputInfo *info) +{ + switch (info->subpixel_order) + { + case SubPixelHorizontalRGB: + return WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB; + + case SubPixelHorizontalBGR: + return WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR; + + case SubPixelVerticalRGB: + return WL_OUTPUT_SUBPIXEL_VERTICAL_RGB; + + case SubPixelVerticalBGR: + return WL_OUTPUT_SUBPIXEL_VERTICAL_BGR; + + case SubPixelNone: + return WL_OUTPUT_SUBPIXEL_NONE; + + default: + return WL_OUTPUT_SUBPIXEL_UNKNOWN; + } +} + +static uint32_t +ComputeTransform (XRRCrtcInfo *info) +{ + /* TODO: support surface transforms. */ + return WL_OUTPUT_TRANSFORM_NORMAL; +} + +static XLList * +BuildOutputTree (void) +{ + XRRScreenResources *resources; + XRROutputInfo *info; + XRRCrtcInfo *crtc; + int i, j; + Output *output; + XLList *all_outputs; + + all_outputs = NULL; + + resources = XRRGetScreenResources (compositor.display, + DefaultRootWindow (compositor.display)); + + if (!resources || !resources->noutput) + { + if (resources) + XRRFreeScreenResources (resources); + + return NULL; + } + + for (i = 0; i < resources->noutput; ++i) + { + /* Catch errors in case the output disappears. */ + CatchXErrors (); + info = XRRGetOutputInfo (compositor.display, resources, + resources->outputs[i]); + UncatchXErrors (NULL); + + if (!info) + continue; + + if (info->connection != RR_Disconnected + && info->crtc != None) + { + /* Catch errors in case the CRT controller disappears. */ + CatchXErrors (); + crtc = XRRGetCrtcInfo (compositor.display, resources, + info->crtc); + UncatchXErrors (NULL); + + if (!crtc) + { + XRRFreeOutputInfo (info); + continue; + } + + output = AddOutput (); + output->output = resources->outputs[i]; + output->scale = global_scale_factor; + + ApplyEnvironment ("OUTPUT_SCALE", &output->scale); + + output->mm_width = info->mm_width; + output->mm_height = info->mm_height; + output->x = crtc->x; + output->y = crtc->y; + output->width = crtc->width; + output->height = crtc->height; + output->transform = ComputeTransform (crtc); + output->subpixel = ComputeSubpixel (info); + + all_outputs = XLListPrepend (all_outputs, output); + + output->name = XLStrdup (info->name); + + for (j = info->nmode - 1; j >= 0; --j) + { + if (info->npreferred != j) + AppendRRMode (output, info->modes[j], + resources, crtc, False); + } + + AppendRRMode (output, info->modes[info->npreferred], + resources, crtc, True); + + XRRFreeCrtcInfo (crtc); + } + + XRRFreeOutputInfo (info); + } + + XRRFreeScreenResources (resources); + return all_outputs; +} + +static Bool +AreModesIdentical (XLList *first, XLList *second) +{ + XLList *tem1, *tem2; + + /* This explicitly also checks the order in which the modes + appear. */ + for (tem1 = first, tem2 = second; tem1 && tem2; + tem1 = tem1->next, tem2 = tem2->next) + { + if (memcmp (tem1->data, tem2->data, sizeof (Mode))) + return False; + } + + /* If either tem1 or tem2 are not NULL, then one list ended early, + so the lists are not identical. */ + + return !tem1 && !tem2; +} + +static void +CompareOutputs (Output *output, Output *other, int *flags) +{ + int difference; + + difference = 0; + + if (!AreModesIdentical (output->modes, other->modes)) + difference |= ModesChanged; + + if (output->mm_width != other->mm_width + || output->mm_height != other->mm_height + || output->x != other->x + || output->y != other->y + || output->subpixel != other->subpixel + || output->transform != other->transform + || !strcmp (output->name, other->name)) + difference |= GeometryChanged; + + *flags = difference; +} + +static Output * +FindOutputById (RROutput output) +{ + XLList *tem; + Output *item; + + for (tem = all_outputs; tem; tem = tem->next) + { + item = tem->data; + + if (item->output == output) + return item; + } + + return NULL; +} + +static void +MakeGlobal (Output *output) +{ + XLAssert (!output->global); + + output->global = wl_global_create (compositor.wl_display, + &wl_output_interface, 2, + output, HandleBind); + + if (!output->global) + { + fprintf (stderr, "Failed to allocate global output\n"); + exit (1); + } +} + +static void +MakeGlobalsForOutputTree (XLList *list) +{ + for (; list; list = list->next) + MakeGlobal (list->data); +} + +static void +SendUpdates (Output *output, int difference) +{ + XLList *tem, *tem1; + + if (!difference) + return; + + for (tem = output->resources; tem; tem = tem->next) + { + if (difference & GeometryChanged) + SendGeometry (output, tem->data); + + if (difference & ModesChanged) + { + for (tem1 = output->modes; tem1; tem1 = tem->next) + SendMode (tem1->data, tem->data); + } + + if (difference & ScaleChanged) + SendScale (output, tem->data); + + if (wl_resource_get_version (tem->data) >= 2) + wl_output_send_done (tem->data); + } +} + +static void +UpdateResourceUserData (Output *output) +{ + XLList *tem; + + for (tem = output->resources; tem; tem = tem->next) + wl_resource_set_user_data (tem->data, output); +} + +static void +NoticeOutputsMaybeChanged (void) +{ + XLList *new_list, *tem; + Output *new, *current; + int change_flags; + Bool any_change; + Surface *surface; + + new_list = BuildOutputTree (); + any_change = False; + + /* Since it's hard and race-prone to figure out what changed from + the RandR notification events themselves, simply compare the + outputs before and after. */ + + for (tem = new_list; tem; tem = tem->next) + { + new = tem->data; + current = FindOutputById (new->output); + + if (!current) + { + /* This is an entirely new output. Create a new global. */ + MakeGlobal (new); + + /* Mark the list of outputs as having changed. */ + any_change = True; + continue; + } + + /* Otherwise, this output already exists. Tell clients about + any changes that happened, and transfer the existing global + and resources to the new output. */ + new->global = current->global; + new->resources = current->resources; + + /* Update the user data of the globals and resources. */ + wl_global_set_user_data (new->global, new); + UpdateResourceUserData (new); + + /* Clear the current output's globals. Otherwise, they will get + freed later on. */ + current->global = NULL; + current->resources = NULL; + + /* Compare the two outputs to determine what updates must be + made. */ + CompareOutputs (new, current, &change_flags); + + /* Send updates. */ + SendUpdates (new, change_flags); + + /* Mark the list of outputs as having changed if some attribute + of the output did change. */ + if (change_flags) + any_change = True; + } + + /* Free the current output list and make the new list current. */ + XLListFree (all_outputs, FreeSingleOutput); + all_outputs = new_list; + + if (any_change) + { + /* If something changed, clear each surface's output region. We + rely on the window manager to send a ConfigureNotify event + and move the windows around appropriately. */ + + surface = all_surfaces.next; + for (; surface != &all_surfaces; surface = surface->next) + pixman_region32_clear (&surface->output_region); + } +} + +static double +GetCurrentRefresh (Output *output) +{ + Mode *mode; + XLList *list; + + for (list = output->modes; list; list = list->next) + { + mode = list->data; + + if (mode->flags & WL_OUTPUT_MODE_CURRENT) + return (double) mode->refresh / 1000; + } + + /* No current mode! */ + return 0.0; +} + +static Output * +GetOutputAt (int x, int y) +{ + Output *output; + XLList *tem; + int x1, y1, x2, y2; + + for (tem = all_outputs; tem; tem = tem->next) + { + output = tem->data; + + x1 = output->x; + y1 = output->y; + x2 = output->x + output->width - 1; + y2 = output->y + output->height - 1; + + if (x >= x1 && x <= x2 + && y >= y1 && y <= y2) + return output; + } + + return NULL; +} + +static Bool +AnyIntersectionBetween (int x, int y, int x1, int y1, + int x2, int y2, int x3, int y3) +{ + return (x < x3 && x1 > x2 && y < y3 && y1 > y2); +} + +static int +ComputeSurfaceOutputs (int x, int y, int width, int height, + int noutputs, Output **outputs) +{ + XLList *tem; + Output *output; + int i; + + i = 0; + + for (tem = all_outputs; tem; tem = tem->next) + { + output = tem->data; + + /* Test all four corners in case some extend outside the screen + or output. */ + if (AnyIntersectionBetween (x, y, x + width - 1, y + height - 1, + output->x, output->y, + output->x + output->width - 1, + output->y + output->height - 1) + && i + 1 < noutputs) + outputs[i++] = output; + } + + return i; +} + +static Bool +FindOutput (Output **outputs, int noutputs, RROutput id) +{ + int i; + + for (i = 0; i < noutputs; ++i) + { + if (outputs[i]->output == id) + return True; + } + + return False; +} + +static struct wl_resource * +FindOutputResource (Output *output, Surface *client_surface) +{ + struct wl_client *client; + XLList *tem; + + client = wl_resource_get_client (client_surface->resource); + + for (tem = output->resources; tem; tem = tem->next) + { + if (wl_resource_get_client (tem->data) == client) + return tem->data; + } + + return NULL; +} + +static Bool +BoxContains (pixman_box32_t *box, int x, int y, int width, + int height) +{ + return (x >= box->x1 && y >= box->y1 + && x + width <= box->x2 + && y + height <= box->y2); +} + +void +XLUpdateSurfaceOutputs (Surface *surface, int x, int y, int width, + int height) +{ + Output *outputs[256], *output; + int n, i; + struct wl_resource *resource; + + if (BoxContains (pixman_region32_extents (&surface->output_region), + x, y, width, height)) + /* The surface didn't move past the output region. */ + return; + + if (width == -1) + width = ViewWidth (surface->view); + + if (height == -1) + height = ViewHeight (surface->view); + + /* TODO: store the number of outputs somewhere instead of hardcoding + 256. */ + n = ComputeSurfaceOutputs (x, y, width, height, + ArrayElements (outputs), + outputs); + + /* First, find and leave all the outputs that the surface is no + longer inside. */ + for (i = 0; i < surface->n_outputs; ++i) + { + if (!FindOutput (outputs, n, surface->outputs[i])) + { + output = FindOutputById (surface->outputs[i]); + + if (!output) + continue; + + resource = FindOutputResource (output, surface); + + if (resource) + wl_surface_send_leave (surface->resource, resource); + } + } + + /* Then, send enter events for all the outputs that the surface has + not previously entered. Also calculate a rectangle defining an + area in which output recomputation need not take place. */ + + if (!n) + pixman_region32_init (&surface->output_region); + else + pixman_region32_clear (&surface->output_region); + + for (i = 0; i < n; ++i) + { + if (!FindOutputId (surface->outputs, surface->n_outputs, + outputs[i]->output)) + { + resource = FindOutputResource (outputs[i], surface); + + if (resource) + wl_surface_send_enter (surface->resource, resource); + } + + if (!i) + pixman_region32_init_rect (&surface->output_region, + outputs[i]->x, outputs[i]->y, + outputs[i]->width, + outputs[i]->height); + else + pixman_region32_intersect_rect (&surface->output_region, + &surface->output_region, + outputs[i]->x, + outputs[i]->y, + outputs[i]->width, + outputs[i]->height); + } + + /* Copy the list of outputs to the surface as well. */ + surface->n_outputs = n; + + if (surface->n_outputs) + surface->outputs = XLRealloc (surface->outputs, + sizeof *surface->outputs + * surface->n_outputs); + else + { + XLFree (surface->outputs); + surface->outputs = NULL; + } + + for (i = 0; i < n; ++i) + surface->outputs[i] = outputs[i]->output; + + /* At the same time, also update outputs for attached + subsurfaces. */ + XLUpdateOutputsForChildren (surface, x, y); + + /* And make a record of the coordinates that were used to compute + outputs for this surface. */ + surface->output_x = x; + surface->output_y = y; +} + +Bool +XLGetOutputRectAt (int x, int y, int *x_out, int *y_out, + int *width_out, int *height_out) +{ + Output *output; + + output = GetOutputAt (x, y); + + /* There is no output at this position. */ + if (!output) + return False; + + if (x_out) + *x_out = output->x; + + if (y_out) + *y_out = output->y; + + if (width_out) + *width_out = output->width; + + if (height_out) + *height_out = output->height; + + return True; +} + +Bool +XLHandleOneXEventForOutputs (XEvent *event) +{ + if (event->type == compositor.rr_event_base + RRNotify) + { + NoticeOutputsMaybeChanged (); + return True; + } + + if (event->type == compositor.rr_event_base + RRScreenChangeNotify) + { + XRRUpdateConfiguration (event); + return True; + } + + return False; +} + +void +XLOutputGetMinRefresh (struct timespec *timespec) +{ + Output *output; + XLList *list; + double min_refresh, t, between; + + timespec->tv_sec = 0; + timespec->tv_nsec = 16000000 * 2; + min_refresh = 0.0; + + /* Find the output with the lowest current refresh rate, and use + that. */ + for (list = all_outputs; list; list = list->next) + { + output = list->data; + t = GetCurrentRefresh (output); + + if (t < min_refresh || min_refresh == 0.0) + min_refresh = t; + } + + /* min_refresh can be 0.0 if there are no valid modes. In that + case, fall back to 30 fps. */ + if (min_refresh == 0.0) + { + fprintf (stderr, "Warning: defaulting to 30fps refresh rate\n"); + return; + } + + /* This is vblank + the time it takes to scan a frame, in + seconds. */ + between = 1.0 / min_refresh; + + /* Now populate the timespec with it. */ + timespec->tv_nsec = MIN (1000000000, between * 1000000000); + timespec->tv_sec = (between - timespec->tv_nsec) / 1000000000; +} + +static void +RunScaleChangeCallbacks (void) +{ + ScaleChangeCallback *callback; + + callback = scale_callbacks.next; + + while (callback != &scale_callbacks) + { + callback->scale_change (callback->data, + global_scale_factor); + callback = callback->next; + } +} + +static void +HandleScaleChange (int scale) +{ + Output *output; + XLList *list; + + if (scale == global_scale_factor) + return; + + /* Update the scale factor. */ + global_scale_factor = scale; + + /* Update the scale for each output and send changes. */ + for (list = all_outputs; list; list = list->next) + { + output = list->data; + output->scale = scale; + + SendUpdates (output, ScaleChanged); + } + + /* Now, run every scale change function. */ + RunScaleChangeCallbacks (); +} + +void * +XLAddScaleChangeCallback (void *data, void (*func) (void *, int)) +{ + ScaleChangeCallback *callback; + + callback = XLMalloc (sizeof *callback); + callback->next = scale_callbacks.next; + callback->last = &scale_callbacks; + scale_callbacks.next->last = callback; + scale_callbacks.next = callback; + + callback->scale_change = func; + callback->data = data; + + return callback; +} + +void +XLRemoveScaleChangeCallback (void *key) +{ + ScaleChangeCallback *callback; + + callback = key; + callback->last->next = callback->next; + callback->next->last = callback->last; + + XLFree (callback); +} + +void +XLClearOutputs (Surface *surface) +{ + Output *output; + int i; + struct wl_resource *resource; + + for (i = 0; i < surface->n_outputs; ++i) + { + output = FindOutputById (surface->outputs[i]); + + if (!output) + continue; + + resource = FindOutputResource (output, surface); + + if (!resource) + continue; + + wl_surface_send_leave (surface->resource, resource); + } + + XLFree (surface->outputs); + surface->outputs = NULL; + surface->n_outputs = 0; +} + +void +XLInitRROutputs (void) +{ + Bool extension; + + extension = XRRQueryExtension (compositor.display, + &compositor.rr_event_base, + &compositor.rr_error_base); + + if (!extension) + { + fprintf (stderr, "Display '%s' does not support the RandR extension\n", + DisplayString (compositor.display)); + abort (); + } + + XRRQueryVersion (compositor.display, + &compositor.rr_major, + &compositor.rr_minor); + + if (compositor.rr_major < 1 + || (compositor.rr_major == 1 + && compositor.rr_minor < 3)) + { + fprintf (stderr, "Display '%s' does not support a" + " sufficiently new version of the RandR extension\n", + DisplayString (compositor.display)); + abort (); + } + + /* Set the initial scale. */ + global_scale_factor = 1; + + /* Start listening to changes to the scale factor, unless a scale + factor was specified for debugging. */ + if (!ApplyEnvironment ("GLOBAL_SCALE", &global_scale_factor)) + XLListenToIntegerSetting ("Gdk/WindowScalingFactor", + HandleScaleChange); + + /* Initialize the scale change callback list sentinel node. */ + scale_callbacks.next = &scale_callbacks; + scale_callbacks.last = &scale_callbacks; + + XRRSelectInput (compositor.display, + DefaultRootWindow (compositor.display), + (RRCrtcChangeNotifyMask + | RROutputChangeNotifyMask + | RROutputPropertyNotifyMask)); + + all_outputs = BuildOutputTree (); + MakeGlobalsForOutputTree (all_outputs); +} diff --git a/positioner.c b/positioner.c new file mode 100644 index 0000000..36719ad --- /dev/null +++ b/positioner.c @@ -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 . */ + +#include +#include + +#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; +} diff --git a/region.c b/region.c new file mode 100644 index 0000000..e559878 --- /dev/null +++ b/region.c @@ -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 . */ + +#include + +#include + +#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); +} diff --git a/run.c b/run.c new file mode 100644 index 0000000..2ffb6ee --- /dev/null +++ b/run.c @@ -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 . */ + +#include + +#include +#include +#include + +#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 (); +} diff --git a/seat.c b/seat.c new file mode 100644 index 0000000..cf0cfcc --- /dev/null +++ b/seat.c @@ -0,0 +1,5122 @@ +/* Wayland compositor running on top of an X server. + +Copyright (C) 2022 to various contributors. + +This file is part of 12to11. + +12to11 is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +12to11 is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with 12to11. If not, see . */ + +#include + +#include +#include +#include + +#include + +#include + +#include "compositor.h" + +#include + +#include + +#include +#include + +#include +#include + +#include "xdg-shell.h" + +/* X11 event opcode, event base, and error base for the input + extension. */ + +int xi2_opcode, xi_first_event, xi_first_error; + +/* The version of the input extension in use. */ + +static int xi2_major, xi2_minor; + +/* The current keymap file descriptor. */ + +static int keymap_fd; + +/* XKB event type. */ + +static int xkb_event_type; + +/* Keymap currently in use. */ + +static XkbDescPtr xkb_desc; + +/* Assocation between device IDs and seat objects. This includes both + keyboard and and pointer devices. */ + +static XLAssocTable *seats; + +/* Association between device IDs and "source device info" objects. + This includes both pointer and keyboard devices. */ +static XLAssocTable *devices; + +/* List of all seats that are not inert. */ + +XLList *live_seats; + +enum + { + IsInert = 1, + IsWindowMenuShown = (1 << 2), + IsDragging = (1 << 3), + IsDropped = (1 << 4), + }; + +enum + { + StateIsRaw = 1, + }; + +enum + { + AnyVerticalAxis = 1, + AnyHorizontalAxis = (1 << 1), + }; + +typedef struct _Seat Seat; +typedef struct _SeatClientInfo SeatClientInfo; +typedef struct _Pointer Pointer; +typedef struct _Keyboard Keyboard; +typedef struct _SeatCursor SeatCursor; +typedef struct _ResizeDoneCallback ResizeDoneCallback; +typedef struct _ScrollValuator ScrollValuator; +typedef struct _DestroyListener DestroyListener; +typedef struct _DeviceInfo DeviceInfo; +typedef struct _ModifierChangeCallback ModifierChangeCallback; + +typedef enum _ResizeEdge ResizeEdge; +typedef enum _WhatEdge WhatEdge; +typedef enum _Direction Direction; + +enum _ResizeEdge + { + NoneEdge = 65535, + TopLeftEdge = 0, + TopEdge = 1, + TopRightEdge = 2, + RightEdge = 3, + BottomRightEdge = 4, + BottomEdge = 5, + BottomLeftEdge = 6, + LeftEdge = 7, + MoveEdge = 8, + }; + +enum _WhatEdge + { + APointerEdge, + AKeyboardEdge, + }; + +enum _Direction + { + Vertical, + Horizontal, + }; + +enum + { + ResizeAxisTop = 1, + ResizeAxisLeft = (1 << 1), + ResizeAxisRight = (1 << 2), + ResizeAxisBottom = (1 << 3), + ResizeAxisMove = (1 << 16), + }; + +enum + { + DeviceCanFingerScroll = 1, + DeviceCanEdgeScroll = 2, + }; + +/* Array indiced by ResizeEdge containing axes along which the edge + resizes. */ + +static int resize_edges[] = + { + ResizeAxisTop | ResizeAxisLeft, + ResizeAxisTop, + ResizeAxisTop | ResizeAxisRight, + ResizeAxisRight, + ResizeAxisRight | ResizeAxisBottom, + ResizeAxisBottom, + ResizeAxisBottom | ResizeAxisLeft, + ResizeAxisLeft, + ResizeAxisMove, + }; + +struct _DestroyListener +{ + /* Function called when seat is destroyed. */ + void (*destroy) (void *); + + /* Data for that function. */ + void *data; + + /* Next and last destroy listeners in this list. */ + DestroyListener *next, *last; +}; + +struct _SeatCursor +{ + /* The parent role. Note that there is no wl_resource associated + with it. */ + Role role; + + /* The current cursor. */ + Cursor cursor; + + /* The seat this cursor is for. */ + Seat *seat; + + /* The hotspot of the cursor. */ + int hotspot_x, hotspot_y; + + /* The subcompositor for this cursor. */ + Subcompositor *subcompositor; + + /* The frame callback for this cursor. */ + void *cursor_frame_key; + + /* Whether or not this cursor is currently keeping the cursor clock + active. */ + Bool holding_cursor_clock; +}; + +struct _ResizeDoneCallback +{ + /* Function called when a resize operation finishes. */ + void (*done) (void *, void *); + + /* Data for this callback. */ + void *data; + + /* The next and last callbacks in this list. */ + ResizeDoneCallback *next, *last; +}; + +struct _ScrollValuator +{ + /* The next scroll valuator in this list. */ + ScrollValuator *next; + + /* The direction of this valuator. */ + Direction direction; + + /* The current value of this valuator. */ + double value; + + /* The increment of this valuator. */ + double increment; + + /* The number of this valuator. */ + int number; + + /* The serial of the last event to have updated this valuator. */ + unsigned long enter_serial; +}; + +struct _Pointer +{ + /* The seat this pointer object refers to. */ + Seat *seat; + + /* The struct wl_resource associated with this pointer. */ + struct wl_resource *resource; + + /* The next and last pointer devices attached to the seat client + info. */ + Pointer *next, *last; + + /* The seat client info associated with this pointer resource. */ + SeatClientInfo *info; + + /* Some state. */ + int state; +}; + +struct _Keyboard +{ + /* The keyboard this pointer object refers to. */ + Seat *seat; + + /* The struct wl_resource associated with this keyboard. */ + struct wl_resource *resource; + + /* The seat client info associated with this keyboard resource. */ + SeatClientInfo *info; + + /* The next and last keyboard attached to the seat client info and + the seat. */ + Keyboard *next, *next1, *last, *last1; +}; + +struct _SeatClientInfo +{ + /* Number of references to this seat client information. */ + int refcount; + + /* The next and last structures in the client info chain. */ + SeatClientInfo *next, *last; + + /* List of pointer objects on this seat for this client. */ + Pointer pointers; + + /* List of keyboard objects on this seat for this client. */ + Keyboard keyboards; + + /* The client corresponding to this object. */ + struct wl_client *client; + + /* The serial of the last enter event sent. */ + uint32_t last_enter_serial; +}; + +struct _ModifierChangeCallback +{ + /* Callback run when modifiers change. */ + void (*changed) (unsigned int, void *); + + /* Data for the callback. */ + void *data; + + /* Next and last callbacks in this list. */ + ModifierChangeCallback *next, *last; +}; + +struct _Seat +{ + /* XI device ID of the master keyboard device. */ + int master_keyboard; + + /* XI device ID of the master pointer device. */ + int master_pointer; + + /* wl_global associated with this seat. */ + struct wl_global *global; + + /* Number of references to this seat. */ + int refcount; + + /* Some flags associated with this seat. */ + int flags; + + /* The currently focused surface. */ + Surface *focus_surface; + + /* The destroy callback attached to that surface. */ + DestroyCallback *focus_destroy_callback; + + /* The last surface seen. */ + Surface *last_seen_surface; + + /* The destroy callback attached to that surface. */ + DestroyCallback *last_seen_surface_callback; + + /* The surface on which the last pointer click was made. */ + Surface *last_button_press_surface; + + /* The destroy callback attached to that surface. */ + DestroyCallback *last_button_press_surface_callback; + + /* The surface that the pointer focus should be unlocked to once the + mouse pointer is removed. */ + Surface *pointer_unlock_surface; + + /* The destroy callback attached to that surface. */ + DestroyCallback *pointer_unlock_surface_callback; + + /* Unmap callback used for cancelling the grab. */ + UnmapCallback *grab_unmap_callback; + + /* How many times the grab is held on this seat. */ + int grab_held; + + /* Array of keys currently held down. */ + struct wl_array keys; + + /* Modifier masks. */ + unsigned int base, locked, latched; + + /* Current base, locked and latched group. Normalized by the X + server. */ + int base_group, locked_group, latched_group; + + /* Current effective group. Also normalized. */ + int effective_group; + + /* Bitmask of whether or not a key was pressed. Length of the + mask is the max_keycode >> 3 + 1. */ + unsigned char *key_pressed; + + /* The current cursor attached to this seat. */ + SeatCursor *cursor; + + /* The serial of the last button event sent. */ + uint32_t last_button_serial; + + /* The serial of the last button press event sent. GTK 4 sends this + even when grabbing a popup in response to a button release + event. */ + uint32_t last_button_press_serial; + + /* The last serial used to obtain a grab. */ + uint32_t last_grab_serial; + + /* The last edge used to obtain a grab. */ + WhatEdge last_grab_edge; + + /* The last timestamp used to obtain a grab. */ + Time last_grab_time; + + /* The button of the last button event sent, and the root_x and + root_y of the last button or motion event. */ + int last_button, its_root_x, its_root_y; + + /* When it was sent. */ + Time its_press_time; + + /* The serial of the last key event sent. */ + uint32_t last_keyboard_serial; + + /* The time of the last key event sent. */ + Time its_depress_time; + + /* Whether or not a resize is in progress. */ + Bool resize_in_progress; + + /* Callbacks run after a resize completes. */ + ResizeDoneCallback resize_callbacks; + + /* List of scroll valuators on this seat. */ + ScrollValuator *valuators; + + /* Serial of the last crossing event. */ + unsigned long last_crossing_serial; + + /* List of destroy listeners. */ + DestroyListener destroy_listeners; + + /* Surface currently being resized, if any. */ + Surface *resize_surface; + + /* Unmap callback for that surface. */ + UnmapCallback *resize_surface_callback; + + /* Where that resize started. */ + int resize_start_root_x, resize_start_root_y; + + /* Where the pointer was last seen. */ + int resize_last_root_x, resize_last_root_y; + + /* The dimensions of the surface when it was first seen. */ + int resize_width, resize_height; + + /* The axises. */ + int resize_axis_flags; + + /* The button for the resize. */ + int resize_button; + + /* The time used to obtain the resize grab. */ + Time resize_time; + + /* The attached data device, if any. */ + DataDevice *data_device; + + /* List of seat client information. */ + SeatClientInfo client_info; + + /* List of all attached keyboards. */ + Keyboard keyboards; + + /* The root_x and root_y of the last motion or crossing event. */ + double last_motion_x, last_motion_y; + + /* The grab surface. While it exists, events for different clients + will be reported relative to it. */ + Surface *grab_surface; + + /* The unmap callback. */ + UnmapCallback *grab_surface_callback; + + /* The data source for drag-and-drop. */ + DataSource *data_source; + + /* The destroy callback for the data source. */ + void *data_source_destroy_callback; + + /* The surface on which this drag operation started. */ + Surface *drag_start_surface; + + /* The UnmapCallback for that surface. */ + UnmapCallback *drag_start_unmap_callback; + + /* The last surface to be entered during drag-and-drop. */ + Surface *drag_last_surface; + + /* The destroy callback for that surface. */ + DestroyCallback *drag_last_surface_destroy_callback; + + /* The time the active grab was acquired. */ + Time drag_grab_time; + + /* The icon surface. */ + IconSurface *icon_surface; + + /* The drag-and-drop grab window. This is a 1x1 InputOnly window + with an empty input region at 0, 0, used to differentiate between + events delivered to a surface during drag and drop, and events + delivered due to the grab. */ + Window grab_window; + + /* List of all modifier change callbacks attached to this seat. */ + ModifierChangeCallback modifier_callbacks; +}; + +struct _DeviceInfo +{ + /* Some flags associated with this device. */ + int flags; +}; + +#define SetMask(ptr, event) \ + (((unsigned char *) (ptr))[(event) >> 3] |= (1 << ((event) & 7))) +#define ClearMask(ptr, event) \ + (((unsigned char *) (ptr))[(event) >> 3] &= ~(1 << ((event) & 7))) +#define MaskIsSet(ptr, event) \ + (((unsigned char *) (ptr))[(event) >> 3] & (1 << ((event) & 7))) +#define MaskLen(event) \ + (((event) >> 3) + 1) + +#define CursorFromRole(role) ((SeatCursor *) (role)) + +/* Subcompositor targets used inside cursor subframes. */ +static Picture cursor_subframe_picture; + +/* Its associated pixmap. */ +static Pixmap cursor_subframe_pixmap; + + + +static Bool +QueryPointer (Seat *seat, Window relative_to, double *x, double *y) +{ + XIButtonState buttons; + XIModifierState modifiers; + XIGroupState group; + double root_x, root_y, win_x, win_y; + Window root, child; + Bool same_screen; + + buttons.mask = NULL; + same_screen = False; + + /* First, initialize default values in case the pointer is on a + different screen. */ + *x = 0; + *y = 0; + + if (XIQueryPointer (compositor.display, seat->master_pointer, + relative_to, &root, &child, &root_x, &root_y, + &win_x, &win_y, &buttons, &modifiers, + &group)) + { + *x = win_x; + *y = win_y; + same_screen = True; + } + + /* buttons.mask must be freed manually, even if the pointer is on a + different screen. */ + if (buttons.mask) + XFree (buttons.mask); + + return same_screen; +} + +static void +FinalizeSeatClientInfo (Seat *seat) +{ + SeatClientInfo *info, *last; + + info = seat->client_info.next; + + while (info != &seat->client_info) + { + last = info; + info = info->next; + + /* Mark this as invalid, so it won't be unchained later on. */ + last->last = NULL; + last->next = NULL; + } +} + +static SeatClientInfo * +GetSeatClientInfo (Seat *seat, struct wl_client *client) +{ + SeatClientInfo *info; + + info = seat->client_info.next; + + while (info != &seat->client_info) + { + if (info->client == client) + return info; + + info = info->next; + } + + return NULL; +} + +static SeatClientInfo * +CreateSeatClientInfo (Seat *seat, struct wl_client *client) +{ + SeatClientInfo *info; + + /* See if client has already created something on the seat. */ + info = GetSeatClientInfo (seat, client); + + /* Otherwise, create it ourselves. */ + if (!info) + { + info = XLCalloc (1, sizeof *info); + info->next = seat->client_info.next; + info->last = &seat->client_info; + seat->client_info.next->last = info; + seat->client_info.next = info; + + info->client = client; + info->pointers.next = &info->pointers; + info->pointers.last = &info->pointers; + info->keyboards.next = &info->keyboards; + info->keyboards.last = &info->keyboards; + } + + /* Increase the reference count of info. */ + info->refcount++; + + /* Return info. */ + return info; +} + +static void +ReleaseSeatClientInfo (SeatClientInfo *info) +{ + if (--info->refcount) + return; + + /* Assert that there are no more keyboards or pointers attached. */ + XLAssert (info->keyboards.next == &info->keyboards); + XLAssert (info->pointers.next == &info->pointers); + + /* Unlink the client info structure if it is still linked. */ + if (info->next) + { + info->next->last = info->last; + info->last->next = info->next; + } + + /* Free the client info. */ + XLFree (info); +} + +static void +RetainSeat (Seat *seat) +{ + seat->refcount++; +} + +static void +UpdateCursorOutput (SeatCursor *cursor, int root_x, int root_y) +{ + int hotspot_x, hotspot_y; + + /* Scale the hotspot coordinates up by the scale. */ + hotspot_x = cursor->hotspot_x * global_scale_factor; + hotspot_y = cursor->hotspot_y * global_scale_factor; + + /* We use a rectangle 1 pixel wide and tall, originating from the + hotspot of the pointer. */ + XLUpdateSurfaceOutputs (cursor->role.surface, root_x + hotspot_x, + root_y + hotspot_y, 1, 1); +} + +static Window +CursorWindow (SeatCursor *cursor) +{ + /* When dragging, use the surface on which the active grab is + set. */ + if (cursor->seat->flags & IsDragging) + return cursor->seat->grab_window; + + /* The cursor should be cleared along with + seat->last_seen_surface. */ + XLAssert (cursor->seat->last_seen_surface != NULL); + + return XLWindowFromSurface (cursor->seat->last_seen_surface); +} + +static void +HandleCursorFrame (void *data, struct timespec time) +{ + SeatCursor *cursor; + + cursor = data; + + if (cursor->role.surface) + XLSurfaceRunFrameCallbacks (cursor->role.surface, time); +} + +static void +StartCursorClock (SeatCursor *cursor) +{ + if (cursor->holding_cursor_clock) + return; + + cursor->cursor_frame_key + = XLAddCursorClockCallback (HandleCursorFrame, + cursor); + cursor->holding_cursor_clock = True; +} + +static void +EndCursorClock (SeatCursor *cursor) +{ + if (!cursor->holding_cursor_clock) + return; + + XLStopCursorClockCallback (cursor->cursor_frame_key); + cursor->holding_cursor_clock = False; +} + +static void +FreeCursor (SeatCursor *cursor) +{ + Window window; + + if (cursor->role.surface) + XLSurfaceReleaseRole (cursor->role.surface, + &cursor->role); + + /* Now any attached views should have been released, so free the + subcompositor. */ + SubcompositorFree (cursor->subcompositor); + + cursor->seat->cursor = NULL; + + window = CursorWindow (cursor); + + if (cursor->cursor != None) + XFreeCursor (compositor.display, cursor->cursor); + + if (!(cursor->seat->flags & IsInert) && window) + XIDefineCursor (compositor.display, + cursor->seat->master_pointer, + window, InitDefaultCursor ()); + + /* Maybe release the cursor clock if it was active for this + cursor. */ + EndCursorClock (cursor); + XLFree (cursor); +} + +static void +FreeValuators (Seat *seat) +{ + ScrollValuator *last, *tem; + + tem = seat->valuators; + + while (tem) + { + last = tem; + tem = tem->next; + + XLFree (last); + } + + seat->valuators = NULL; +} + +static void +FreeDestroyListeners (Seat *seat) +{ + DestroyListener *listener, *last; + + listener = seat->destroy_listeners.next; + + while (listener != &seat->destroy_listeners) + { + last = listener; + listener = listener->next; + + XLFree (last); + } +} + +static void +FreeModifierCallbacks (Seat *seat) +{ + ModifierChangeCallback *callback, *last; + + callback = seat->modifier_callbacks.next; + + while (callback != &seat->modifier_callbacks) + { + last = callback; + callback = callback->next; + + XLFree (last); + } +} + +static void +ReleaseSeat (Seat *seat) +{ + if (--seat->refcount) + return; + + if (seat->icon_surface) + XLReleaseIconSurface (seat->icon_surface); + + if (seat->focus_destroy_callback) + XLSurfaceCancelRunOnFree (seat->focus_destroy_callback); + + if (seat->last_seen_surface_callback) + XLSurfaceCancelRunOnFree (seat->last_seen_surface_callback); + + if (seat->pointer_unlock_surface_callback) + XLSurfaceCancelRunOnFree (seat->pointer_unlock_surface_callback); + + if (seat->last_button_press_surface_callback) + XLSurfaceCancelRunOnFree (seat->last_button_press_surface_callback); + + if (seat->drag_last_surface_destroy_callback) + XLSurfaceCancelRunOnFree (seat->drag_last_surface_destroy_callback); + + if (seat->grab_surface_callback) + XLSurfaceCancelUnmapCallback (seat->grab_surface_callback); + + if (seat->grab_unmap_callback) + XLSurfaceCancelUnmapCallback (seat->grab_unmap_callback); + + if (seat->resize_surface_callback) + XLSurfaceCancelUnmapCallback (seat->resize_surface_callback); + + if (seat->drag_start_unmap_callback) + XLSurfaceCancelUnmapCallback (seat->drag_start_unmap_callback); + + if (seat->data_source_destroy_callback) + XLDataSourceCancelDestroyCallback (seat->data_source_destroy_callback); + + if (seat->grab_window != None) + XDestroyWindow (compositor.display, seat->grab_window); + + wl_array_release (&seat->keys); + + if (seat->cursor) + FreeCursor (seat->cursor); + + if (seat->data_device) + { + XLDataDeviceClearSeat (seat->data_device); + XLReleaseDataDevice (seat->data_device); + } + + FinalizeSeatClientInfo (seat); + FreeValuators (seat); + FreeDestroyListeners (seat); + FreeModifierCallbacks (seat); + + XLFree (seat->key_pressed); + XLFree (seat); +} + +static Picture +PictureForCursor (Drawable drawable) +{ + XRenderPictureAttributes attrs; + Picture picture; + + /* This is only required to pacfy -Wmaybe-uninitialized, since + valuemask is 0. */ + memset (&attrs, 0, sizeof attrs); + + picture = XRenderCreatePicture (compositor.display, drawable, + compositor.argb_format, 0, + &attrs); + return picture; +} + +static void +ComputeHotspot (SeatCursor *cursor, int min_x, int min_y, + int *x, int *y) +{ + int dx, dy; + int hotspot_x, hotspot_y; + + /* Scale the hotspot coordinates up by the scale. */ + hotspot_x = cursor->hotspot_x * global_scale_factor; + hotspot_y = cursor->hotspot_y * global_scale_factor; + + /* Apply the surface offsets to the hotspot as well. */ + dx = dy = 0; + + if (cursor->role.surface) + { + dx = (cursor->role.surface->current_state.x + * global_scale_factor); + dy = (cursor->role.surface->current_state.y + * global_scale_factor); + } + + *x = min_x + hotspot_x - dx; + *y = min_y + hotspot_y - dy; +} + +static void +ApplyCursor (SeatCursor *cursor, Picture picture, + int min_x, int min_y) +{ + Window window; + int x, y; + + if (cursor->cursor) + XFreeCursor (compositor.display, cursor->cursor); + + ComputeHotspot (cursor, min_x, min_y, &x, &y); + cursor->cursor = XRenderCreateCursor (compositor.display, + picture, MAX (0, x), + MAX (0, y)); + window = CursorWindow (cursor); + + if (!(cursor->seat->flags & IsInert) && window != None) + XIDefineCursor (compositor.display, + cursor->seat->master_pointer, + window, cursor->cursor); +} + +static void +UpdateCursorFromSubcompositor (SeatCursor *cursor) +{ + static XRenderColor empty; + Picture picture; + Pixmap pixmap; + int min_x, min_y, max_x, max_y, width, height, x, y; + Bool need_clear; + + /* First, compute the bounds of the subcompositor. */ + SubcompositorBounds (cursor->subcompositor, + &min_x, &min_y, &max_x, &max_y); + + /* Then, its width and height. */ + width = max_x - min_x + 1; + height = max_y - min_y + 1; + + /* If the cursor hotspot extends outside width and height, extend + the picture. */ + ComputeHotspot (cursor, min_x, min_y, &x, &y); + + if (x < 0 || y < 0 || x >= width || y >= height) + { + if (x >= width) + width = x; + if (y >= height) + height = y; + + if (x < 0) + width += -x; + if (y < 0) + height += -y; + + need_clear = True; + } + else + need_clear = False; + + /* This is unbelivably the only way to dynamically update the cursor + under X. Rather inefficient with animated cursors. */ + pixmap = XCreatePixmap (compositor.display, + DefaultRootWindow (compositor.display), + width, height, compositor.n_planes); + picture = PictureForCursor (pixmap); + + /* If the bounds extend beyond the subcompositor, clear the + picture. */ + if (need_clear) + XRenderFillRectangle (compositor.display, PictOpClear, + picture, &empty, 0, 0, width, height); + + /* Garbage the subcompositor, since cursor contents are not + preserved. */ + SubcompositorGarbage (cursor->subcompositor); + + /* Set the right transform if the hotspot is negative. */ + SubcompositorSetProjectiveTransform (cursor->subcompositor, + MAX (0, -x), MAX (0, -x)); + + SubcompositorSetTarget (cursor->subcompositor, picture); + SubcompositorUpdate (cursor->subcompositor); + SubcompositorSetTarget (cursor->subcompositor, None); + + ApplyCursor (cursor, picture, min_x, min_y); + + XRenderFreePicture (compositor.display, picture); + XFreePixmap (compositor.display, pixmap); +} + +static void +UpdateCursor (SeatCursor *cursor, int x, int y) +{ + cursor->hotspot_x = x; + cursor->hotspot_y = y; + + UpdateCursorFromSubcompositor (cursor); +} + +static void +ApplyEmptyCursor (SeatCursor *cursor) +{ + Window window; + + if (cursor->cursor) + XFreeCursor (compositor.display, cursor->cursor); + + cursor->cursor = None; + window = CursorWindow (cursor); + + if (window != None) + XIDefineCursor (compositor.display, + cursor->seat->master_pointer, + window, InitDefaultCursor ()); +} + +static void +Commit (Surface *surface, Role *role) +{ + SeatCursor *cursor; + + cursor = CursorFromRole (role); + + if (SubcompositorIsEmpty (cursor->subcompositor)) + { + ApplyEmptyCursor (cursor); + return; + } + + UpdateCursorFromSubcompositor (cursor); + + /* If the surface now has frame callbacks, start the cursor frame + clock. */ + + if (surface->current_state.frame_callbacks.next + != &surface->current_state.frame_callbacks) + StartCursorClock (cursor); +} + +static void +Teardown (Surface *surface, Role *role) +{ + role->surface = NULL; + + ViewUnparent (surface->view); + ViewUnparent (surface->under); + + ViewSetSubcompositor (surface->view, NULL); + ViewSetSubcompositor (surface->under, NULL); +} + +static Bool +Setup (Surface *surface, Role *role) +{ + SeatCursor *cursor; + + cursor = CursorFromRole (role); + role->surface = surface; + + /* First, attach the subcompositor. */ + ViewSetSubcompositor (surface->under, cursor->subcompositor); + ViewSetSubcompositor (surface->view, cursor->subcompositor); + + /* Then, insert the view. */ + SubcompositorInsert (cursor->subcompositor, surface->under); + SubcompositorInsert (cursor->subcompositor, surface->view); + + return True; +} + +static void +ReleaseBuffer (Surface *surface, Role *role, ExtBuffer *buffer) +{ + /* Cursors are generally committed only once, so syncing here is + OK in terms of efficiency. */ + XSync (compositor.display, False); + XLReleaseBuffer (buffer); +} + +static Bool +Subframe (Surface *surface, Role *role) +{ + static XRenderColor empty; + Picture picture; + Pixmap pixmap; + int min_x, min_y, max_x, max_y, width, height, x, y; + Bool need_clear; + SeatCursor *cursor; + + cursor = CursorFromRole (role); + + if (SubcompositorIsEmpty (cursor->subcompositor)) + /* The subcompositor is empty. Don't set up the cursor + pixmap. */ + return False; + + /* First, compute the bounds of the subcompositor. */ + SubcompositorBounds (cursor->subcompositor, + &min_x, &min_y, &max_x, &max_y); + + /* Then, its width and height. */ + width = max_x - min_x + 1; + height = max_y - min_y + 1; + + /* If the cursor hotspot extends outside width and height, extend + the picture. */ + ComputeHotspot (cursor, min_x, min_y, &x, &y); + + if (x < 0 || y < 0 || x >= width || y >= height) + { + if (x >= width) + width = x; + if (y >= width) + height = y; + + if (x < 0) + width += -x; + if (y < 0) + height += -y; + + need_clear = True; + } + else + need_clear = False; + + /* This is unbelivably the only way to dynamically update the cursor + under X. Rather inefficient with animated cursors. */ + pixmap = XCreatePixmap (compositor.display, + DefaultRootWindow (compositor.display), + width, height, compositor.n_planes); + picture = PictureForCursor (pixmap); + + /* If the bounds extend beyond the subcompositor, clear the + picture. */ + if (need_clear) + XRenderFillRectangle (compositor.display, PictOpClear, + picture, &empty, 0, 0, width, height); + + /* Garbage the subcompositor, since cursor contents are not + preserved. */ + SubcompositorGarbage (cursor->subcompositor); + + /* Set the right transform if the hotspot is negative. */ + SubcompositorSetProjectiveTransform (cursor->subcompositor, + MAX (0, -x), MAX (0, -x)); + SubcompositorSetTarget (cursor->subcompositor, picture); + + /* Set the subframe picture to the picture in question. */ + cursor_subframe_picture = picture; + + /* Return True to let the drawing proceed. */ + return True; +} + +static void +EndSubframe (Surface *surface, Role *role) +{ + SeatCursor *cursor; + int min_x, min_y, max_x, max_y; + + cursor = CursorFromRole (role); + + if (cursor_subframe_picture) + { + /* First, compute the bounds of the subcompositor. */ + SubcompositorBounds (cursor->subcompositor, + &min_x, &min_y, &max_x, &max_y); + + /* Apply the cursor. */ + ApplyCursor (cursor, cursor_subframe_picture, min_x, min_y); + + /* Then, free the temporary target. */ + XRenderFreePicture (compositor.display, + cursor_subframe_picture); + + /* Finally, clear the target. */ + SubcompositorSetTarget (cursor->subcompositor, None); + } + else + ApplyEmptyCursor (cursor); + + if (cursor_subframe_pixmap) + XFreePixmap (compositor.display, + cursor_subframe_pixmap); +} + +static void +MakeCurrentCursor (Seat *seat, Surface *surface, int x, int y) +{ + SeatCursor *role; + Window window; + + window = XLWindowFromSurface (seat->last_seen_surface); + + if (window == None || (seat->flags & IsInert)) + return; + + role = XLCalloc (1, sizeof *role); + XIDefineCursor (compositor.display, + seat->master_pointer, + window, + InitDefaultCursor ()); + + role->hotspot_x = x; + role->hotspot_y = y; + role->seat = seat; + + ApplyEmptyCursor (role); + + /* Set up role callbacks. */ + + 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; + + /* Set up the subcompositor. */ + + role->subcompositor = MakeSubcompositor (); + + if (!XLSurfaceAttachRole (surface, &role->role)) + abort (); + + seat->cursor = role; + + /* Tell the cursor surface what output(s) it is in. */ + + UpdateCursorOutput (role, seat->last_motion_x, + seat->last_motion_y); + + /* If something was committed, update the cursor now. */ + + if (!SubcompositorIsEmpty (role->subcompositor)) + UpdateCursorFromSubcompositor (role); +} + +static void +SetCursor (struct wl_client *client, struct wl_resource *resource, + uint32_t serial, struct wl_resource *surface_resource, + int32_t hotspot_x, int32_t hotspot_y) +{ + Surface *surface, *seen; + Pointer *pointer; + Seat *seat; + + pointer = wl_resource_get_user_data (resource); + seat = pointer->seat; + seen = seat->last_seen_surface; + + if (serial < pointer->info->last_enter_serial) + return; + + if (!surface_resource) + { + if (!seen || (wl_resource_get_client (seen->resource) + != client)) + return; + + if (seat->cursor) + FreeCursor (seat->cursor); + + return; + } + + surface = wl_resource_get_user_data (surface_resource); + + /* Do nothing at all if the last seen surface isn't owned by client + and we are not updating the current pointer surface. */ + + if (!seat->cursor + || surface->role != &seat->cursor->role) + { + if (!seen || (wl_resource_get_client (seen->resource) + != client)) + return; + } + + /* If surface already has another role, raise an error. */ + + if (surface->role_type != AnythingType + && surface->role_type != CursorType) + { + wl_resource_post_error (resource, WL_POINTER_ERROR_ROLE, + "surface already has or had a different role"); + return; + } + + if (surface->role && !seat->cursor + && surface->role != &seat->cursor->role) + { + wl_resource_post_error (resource, WL_POINTER_ERROR_ROLE, + "surface already has a cursor role" + " on another seat"); + return; + } + + if (surface->role) + { + UpdateCursor (CursorFromRole (surface->role), + hotspot_x, hotspot_y); + return; + } + + /* Free any cursor that already exists. */ + if (seat->cursor) + FreeCursor (seat->cursor); + + MakeCurrentCursor (seat, surface, hotspot_x, hotspot_y); +} + +static void +ReleasePointer (struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static void +ReleaseKeyboard (struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static const struct wl_pointer_interface wl_pointer_impl = + { + .set_cursor = SetCursor, + .release = ReleasePointer, + }; + +static const struct wl_keyboard_interface wl_keyboard_impl = + { + .release = ReleaseKeyboard, + }; + +static void +HandlePointerResourceDestroy (struct wl_resource *resource) +{ + Pointer *pointer; + + pointer = wl_resource_get_user_data (resource); + pointer->last->next = pointer->next; + pointer->next->last = pointer->last; + + ReleaseSeatClientInfo (pointer->info); + ReleaseSeat (pointer->seat); + + XLFree (pointer); +} + +static void +HandleKeyboardResourceDestroy (struct wl_resource *resource) +{ + Keyboard *keyboard; + + keyboard = wl_resource_get_user_data (resource); + keyboard->last->next = keyboard->next; + keyboard->next->last = keyboard->last; + keyboard->last1->next1 = keyboard->next1; + keyboard->next1->last1 = keyboard->last1; + + ReleaseSeatClientInfo (keyboard->info); + ReleaseSeat (keyboard->seat); + + XLFree (keyboard); +} + +static void +GetPointer (struct wl_client *client, struct wl_resource *resource, + uint32_t id) +{ + Seat *seat; + SeatClientInfo *info; + Pointer *pointer; + struct wl_resource *pointer_resource; + + pointer_resource + = wl_resource_create (client, &wl_pointer_interface, + wl_resource_get_version (resource), + id); + + if (!pointer_resource) + { + wl_resource_post_no_memory (resource); + return; + } + + pointer = XLSafeMalloc (sizeof *pointer); + + if (!pointer) + { + wl_resource_post_no_memory (resource); + wl_resource_destroy (pointer_resource); + + return; + } + + seat = wl_resource_get_user_data (resource); + RetainSeat (seat); + + memset (pointer, 0, sizeof *pointer); + + info = CreateSeatClientInfo (seat, client); + pointer->resource = pointer_resource; + pointer->seat = seat; + pointer->info = info; + pointer->next = info->pointers.next; + pointer->last = &info->pointers; + + /* This flag means the pointer object has just been created, and + button presses should send a corresponding entry event. */ + pointer->state |= StateIsRaw; + + info->pointers.next->last = pointer; + info->pointers.next = pointer; + + wl_resource_set_implementation (pointer_resource, &wl_pointer_impl, + pointer, HandlePointerResourceDestroy); +} + +static void +SendRepeatKeys (struct wl_resource *resource) +{ + if (wl_resource_get_version (resource) < 4) + return; + + wl_keyboard_send_repeat_info (resource, + 1000 / xkb_desc->ctrls->repeat_interval, + xkb_desc->ctrls->repeat_delay); +} + +static void +UpdateSingleKeyboard (Keyboard *keyboard) +{ + struct stat statb; + + if (fstat (keymap_fd, &statb) < 0) + { + perror ("fstat"); + exit (0); + } + + wl_keyboard_send_keymap (keyboard->resource, + WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, + keymap_fd, statb.st_size); + + SendRepeatKeys (keyboard->resource); +} + +static void +GetKeyboard (struct wl_client *client, struct wl_resource *resource, + uint32_t id) +{ + SeatClientInfo *info; + Seat *seat; + Keyboard *keyboard; + struct wl_resource *keyboard_resource; + + keyboard_resource + = wl_resource_create (client, &wl_keyboard_interface, + wl_resource_get_version (resource), + id); + + if (!keyboard_resource) + { + wl_resource_post_no_memory (resource); + return; + } + + keyboard = XLSafeMalloc (sizeof *keyboard); + + if (!keyboard) + { + wl_resource_post_no_memory (resource); + wl_resource_destroy (keyboard_resource); + + return; + } + + seat = wl_resource_get_user_data (resource); + RetainSeat (seat); + + memset (keyboard, 0, sizeof *keyboard); + + info = CreateSeatClientInfo (seat, client); + keyboard->resource = keyboard_resource; + + /* First, link the keyboard onto the seat client info. */ + keyboard->info = info; + keyboard->next = info->keyboards.next; + keyboard->last = &info->keyboards; + info->keyboards.next->last = keyboard; + info->keyboards.next = keyboard; + + /* Then, the seat. */ + keyboard->seat = seat; + keyboard->next1 = seat->keyboards.next1; + keyboard->last1 = &seat->keyboards; + seat->keyboards.next1->last1 = keyboard; + seat->keyboards.next1 = keyboard; + + wl_resource_set_implementation (keyboard_resource, &wl_keyboard_impl, + keyboard, HandleKeyboardResourceDestroy); + + UpdateSingleKeyboard (keyboard); + + /* Update the keyboard's focus surface too. */ + + if (seat->focus_surface + && (wl_resource_get_client (seat->focus_surface->resource) + == client)) + wl_keyboard_send_enter (keyboard_resource, + wl_display_next_serial (compositor.wl_display), + seat->focus_surface->resource, &seat->keys); +} + +static void +GetTouch (struct wl_client *client, struct wl_resource *resource, + uint32_t id) +{ + wl_resource_post_error (resource, WL_SEAT_ERROR_MISSING_CAPABILITY, + "touch support not yet implemented"); +} + +static void +Release (struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static const struct wl_seat_interface wl_seat_impl = + { + .get_pointer = GetPointer, + .get_keyboard = GetKeyboard, + .get_touch = GetTouch, + .release = Release, + }; + +static void +HandleResourceDestroy (struct wl_resource *resource) +{ + Seat *seat; + + seat = wl_resource_get_user_data (resource); + ReleaseSeat (seat); +} + +static void +HandleBind (struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + struct wl_resource *resource; + char *name; + ptrdiff_t length; + Seat *seat; + + seat = data; + resource = wl_resource_create (client, &wl_seat_interface, + version, id); + + if (!resource) + { + wl_client_post_no_memory (client); + return; + } + + wl_resource_set_implementation (resource, &wl_seat_impl, data, + HandleResourceDestroy); + + wl_seat_send_capabilities (resource, (WL_SEAT_CAPABILITY_POINTER + | WL_SEAT_CAPABILITY_KEYBOARD)); + + length = snprintf (NULL, 0, "X11 master device %d %d", + seat->master_pointer, seat->master_keyboard); + name = alloca (length + 1); + snprintf (name, length + 1, "X11 master device %d %d", + seat->master_pointer, seat->master_keyboard); + + if (wl_resource_get_version (resource) > 2) + wl_seat_send_name (resource, name); + + RetainSeat (data); +} + +static void +AddValuator (Seat *seat, XIScrollClassInfo *info) +{ + ScrollValuator *valuator; + + valuator = XLCalloc (1, sizeof *valuator); + valuator->next = seat->valuators; + valuator->increment = info->increment; + valuator->number = info->number; + + if (info->scroll_type == XIScrollTypeHorizontal) + valuator->direction = Horizontal; + else + valuator->direction = Vertical; + + seat->valuators = valuator; +} + +static void +UpdateValuators (Seat *seat, XIDeviceInfo *device) +{ + int i; + + /* First, free any existing valuators. */ + FreeValuators (seat); + + /* Next, add each valuator. */ + for (i = 0; i < device->num_classes; ++i) + { + if (device->classes[i]->type == XIScrollClass) + AddValuator (seat, (XIScrollClassInfo *) device->classes[i]); + } +} + +static void +MakeSeatForDevicePair (int master_keyboard, int master_pointer, + XIDeviceInfo *pointer_info) +{ + Seat *seat; + XkbStateRec state; + + seat = XLCalloc (1, sizeof *seat); + seat->master_keyboard = master_keyboard; + seat->master_pointer = master_pointer; + seat->global = wl_global_create (compositor.wl_display, + &wl_seat_interface, 7, + seat, HandleBind); + seat->client_info.next = &seat->client_info; + seat->client_info.last = &seat->client_info; + + seat->keyboards.next1 = &seat->keyboards; + seat->keyboards.last1 = &seat->keyboards; + + seat->resize_callbacks.next = &seat->resize_callbacks; + seat->resize_callbacks.last = &seat->resize_callbacks; + + seat->destroy_listeners.next = &seat->destroy_listeners; + seat->destroy_listeners.last = &seat->destroy_listeners; + + seat->modifier_callbacks.next = &seat->modifier_callbacks; + seat->modifier_callbacks.last = &seat->modifier_callbacks; + + wl_array_init (&seat->keys); + + XLMakeAssoc (seats, master_keyboard, seat); + XLMakeAssoc (seats, master_pointer, seat); + + live_seats = XLListPrepend (live_seats, seat); + + /* Now update the seat state from the X server. */ + XkbGetState (compositor.display, XkbUseCoreKbd, &state); + + seat->base = state.base_mods; + seat->locked = state.locked_mods; + seat->latched = state.latched_mods; + seat->base_group = state.base_group; + seat->locked_group = state.locked_group; + seat->latched_group = state.latched_group; + seat->effective_group = state.group; + + UpdateValuators (seat, pointer_info); + RetainSeat (seat); +} + +static void +UpdateScrollMethods (DeviceInfo *info, int deviceid) +{ + unsigned char *data; + Status rc; + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + + data = NULL; + + /* This only works with the libinput driver. */ + rc = XIGetProperty (compositor.display, deviceid, + libinput_Scroll_Methods_Available, + 0, 3, False, XIAnyPropertyType, + &actual_type, &actual_format, + &nitems, &bytes_after, &data); + + /* If there aren't enough items in the data, or the format is wrong, + return. */ + if (rc != Success || nitems < 3 || actual_format != 8 || !data) + { + if (data) + XFree (data); + + return; + } + + /* First, clear all flags that this function sets. */ + info->flags &= ~DeviceCanFingerScroll; + info->flags &= ~DeviceCanEdgeScroll; + + if (data[0]) + info->flags |= DeviceCanFingerScroll; + + if (data[1]) + info->flags |= DeviceCanEdgeScroll; + + if (data) + XFree (data); +} + +static void +RecordDeviceInformation (XIDeviceInfo *deviceinfo) +{ + DeviceInfo *info; + + info = XLLookUpAssoc (devices, deviceinfo->deviceid); + + /* If info doesn't exist, allocate it now. */ + if (!info) + { + info = XLMalloc (sizeof *info); + XLMakeAssoc (devices, deviceinfo->deviceid, info); + } + + /* Now clear info->flags, and determine what the device can do. */ + info->flags = 0; + + /* Initialize scrolling attributes pertinent to pointer devices. */ + if (deviceinfo->use == XISlavePointer) + { + /* Catch errors in case the device disappears. */ + CatchXErrors (); + + /* Obtain the "libinput Scroll Methods Enabled" property and use it + to compute what scroll methods are available. This naturally + only works with the libinput driver. */ + UpdateScrollMethods (info, deviceinfo->deviceid); + + /* Uncatch errors. */ + UncatchXErrors (NULL); + } +} + +static void +SetupInitialDevices (void) +{ + XIDeviceInfo *deviceinfo; + int ndevices, i; + + deviceinfo = XIQueryDevice (compositor.display, + XIAllDevices, &ndevices); + + if (!deviceinfo) + return; + + for (i = 0; i < ndevices; ++i) + { + if (deviceinfo[i].use == XIMasterPointer) + MakeSeatForDevicePair (deviceinfo[i].attachment, + deviceinfo[i].deviceid, + &deviceinfo[i]); + + RecordDeviceInformation (&deviceinfo[i]); + } + + XIFreeDeviceInfo (deviceinfo); +} + +static void +RunResizeDoneCallbacks (Seat *seat) +{ + ResizeDoneCallback *callback, *last; + + callback = seat->resize_callbacks.next; + + while (callback != &seat->resize_callbacks) + { + last = callback; + callback = callback->next; + + last->done (last, last->data); + XLFree (last); + } + + /* Empty the list since all elements are free again. */ + seat->resize_callbacks.next = &seat->resize_callbacks; + seat->resize_callbacks.last = &seat->resize_callbacks; +} + +static void +CancelResizeOperation (Seat *seat, Time time) +{ + /* Stop the resize operation. */ + XLSurfaceCancelUnmapCallback (seat->resize_surface_callback); + seat->resize_surface = NULL; + + /* Run resize completion callbacks. */ + RunResizeDoneCallbacks (seat); + + /* Ungrab the pointer. */ + XIUngrabDevice (compositor.display, seat->master_pointer, + time); +} + +static Bool +InterceptButtonEventForResize (Seat *seat, XIDeviceEvent *xev) +{ + if (xev->type == XI_ButtonPress) + return True; + + /* If the button starting the resize has been released, cancel the + resize operation. */ + if (xev->detail == seat->resize_button) + CancelResizeOperation (seat, xev->time); + + return True; +} + +/* Forward declaration. */ + +static Bool HandleValuatorMotion (Seat *, Surface *, double, double, + XIDeviceEvent *); + +#define MoveLeft(flags, i) ((flags) & ResizeAxisLeft ? (i) : 0) +#define MoveTop(flags, i) ((flags) & ResizeAxisTop ? (i) : 0) + +static void +HandleMovement (Seat *seat, int west, int north) +{ + XLSurfaceMoveBy (seat->resize_surface, west, north); +} + +static Bool +InterceptMotionEventForResize (Seat *seat, XIDeviceEvent *xev) +{ + int root_x, root_y, diff_x, diff_y, abs_diff_x, abs_diff_y; + + root_x = lrint (xev->root_x); + root_y = lrint (xev->root_y); + + /* Handle valuator motion anyway. Otherwise, the values could get + out of date. */ + HandleValuatorMotion (seat, NULL, xev->event_x, xev->event_y, xev); + + if (root_x == seat->resize_last_root_x + && root_y == seat->resize_last_root_y) + /* No motion really happened. */ + return True; + + /* If this is a move and not a resize, simply move the surface's + window. */ + if (seat->resize_axis_flags & ResizeAxisMove) + { + HandleMovement (seat, seat->resize_last_root_x - root_x, + seat->resize_last_root_y - root_y); + + seat->resize_last_root_x = root_x; + seat->resize_last_root_y = root_y; + return True; + } + + /* Compute the amount by which to move the window. The movement is + towards the geographical north and west. */ + diff_x = seat->resize_last_root_x - root_x; + diff_y = seat->resize_last_root_y - root_y; + + abs_diff_x = 0; + abs_diff_y = 0; + + if (seat->resize_axis_flags & ResizeAxisLeft) + /* diff_x will move the surface leftwards. This is by how + much to extend the window the other way as well. */ + abs_diff_x = seat->resize_start_root_x - root_x; + + if (seat->resize_axis_flags & ResizeAxisTop) + /* Likewise for diff_y. */ + abs_diff_y = seat->resize_start_root_y - root_y; + + if (seat->resize_axis_flags & ResizeAxisRight) + /* diff_x is computed differently here, since root_x grows in the + correct resize direction. */ + abs_diff_x = root_x - seat->resize_start_root_x; + + if (seat->resize_axis_flags & ResizeAxisBottom) + /* The same applies for the direction of root_y. */ + abs_diff_y = root_y - seat->resize_start_root_y; + + if (!abs_diff_x && !abs_diff_y) + /* No resizing has to take place. */ + return True; + + seat->resize_last_root_x = root_x; + seat->resize_last_root_y = root_y; + + /* Now, post a new configure event. Upon ack, also move the window + leftwards and topwards by diff_x and diff_y, should the resize + direction go that way. */ + XLSurfacePostResize (seat->resize_surface, + MoveLeft (seat->resize_axis_flags, diff_x), + MoveTop (seat->resize_axis_flags, diff_y), + seat->resize_width + abs_diff_x, + seat->resize_height + abs_diff_y); + + return True; +} + +static Bool +InterceptResizeEvent (Seat *seat, XIDeviceEvent *xev) +{ + if (!seat->resize_surface) + return False; + + switch (xev->evtype) + { + case XI_ButtonRelease: + return InterceptButtonEventForResize (seat, xev); + + case XI_Motion: + return InterceptMotionEventForResize (seat, xev); + } + + return True; +} + +static void +RunDestroyListeners (Seat *seat) +{ + DestroyListener *listeners; + + listeners = seat->destroy_listeners.next; + + while (listeners != &seat->destroy_listeners) + { + listeners->destroy (listeners->data); + listeners = listeners->next; + } +} + +static void +NoticeDeviceDisabled (int deviceid) +{ + Seat *seat; + DeviceInfo *info; + + /* First, see if there is any deviceinfo related to the disabled + device. If there is, free it. */ + info = XLLookUpAssoc (devices, deviceid); + + if (info) + { + XLDeleteAssoc (devices, deviceid); + XLFree (info); + } + + /* It doesn't matter if this is the keyboard or pointer, since + paired master devices are always destroyed together. */ + + seat = XLLookUpAssoc (seats, deviceid); + + if (seat) + { + /* The device has been disabled, mark the seat inert and + dereference it. The seat is still referred to by the + global. */ + + seat->flags |= IsInert; + + /* Run destroy handlers. */ + + RunDestroyListeners (seat); + + /* Since the seat is now inert, remove it from the assoc + table and destroy the global. */ + + XLDeleteAssoc (seats, seat->master_keyboard); + XLDeleteAssoc (seats, seat->master_pointer); + + /* Also remove it from the list of live seats. */ + + live_seats = XLListRemove (live_seats, seat); + + /* Run and remove all resize completion callbacks. */ + + RunResizeDoneCallbacks (seat); + + /* Finally, destroy the global. */ + + wl_global_destroy (seat->global); + + /* And release the seat. */ + + ReleaseSeat (seat); + } +} + +static void +NoticeDeviceEnabled (int deviceid) +{ + XIDeviceInfo *info; + int ndevices; + + CatchXErrors (); + info = XIQueryDevice (compositor.display, deviceid, + &ndevices); + UncatchXErrors (NULL); + + if (info && info->use == XIMasterPointer) + /* ndevices doesn't have to checked here. */ + MakeSeatForDevicePair (info->attachment, deviceid, info); + + if (info) + { + /* Update device information for this device. */ + RecordDeviceInformation (info); + + /* And free the device. */ + XIFreeDeviceInfo (info); + } +} + +static void +NoticeSlaveAttached (int deviceid) +{ + XIDeviceInfo *info; + int ndevices; + + CatchXErrors (); + info = XIQueryDevice (compositor.display, deviceid, + &ndevices); + UncatchXErrors (NULL); + + /* A slave device was attached. Take this opportunity to update its + device information. */ + + if (info) + { + /* Update device information for this device. */ + RecordDeviceInformation (info); + + /* And free the device. */ + XIFreeDeviceInfo (info); + } +} + +static void +HandleHierarchyEvent (XIHierarchyEvent *event) +{ + int i; + + for (i = 0; i < event->num_info; ++i) + { + if (event->info[i].flags & XIDeviceDisabled) + NoticeDeviceDisabled (event->info[i].deviceid); + else if (event->info[i].flags & XIDeviceEnabled) + NoticeDeviceEnabled (event->info[i].deviceid); + else if (event->info[i].flags & XISlaveAttached) + NoticeSlaveAttached (event->info[i].deviceid); + } +} + +#define KeyIsPressed(seat, keycode) \ + (MaskIsSet ((seat)->key_pressed, \ + (keycode) - xkb_desc->min_key_code)) +#define KeySetPressed(seat, keycode, pressed) \ + (!pressed \ + ? ClearMask ((seat)->key_pressed, \ + (keycode) - xkb_desc->min_key_code) \ + : SetMask ((seat)->key_pressed, \ + (keycode) - xkb_desc->min_key_code)) \ + +#define WaylandKeycode(keycode) ((keycode) - 8) + +static void +InsertKeyIntoSeat (Seat *seat, int32_t keycode) +{ + int32_t *data; + + data = wl_array_add (&seat->keys, sizeof *data); + + if (data) + *data = keycode; +} + +static void +ArrayRemove (struct wl_array *array, void *item, size_t size) +{ + size_t bytes; + char *arith; + + arith = item; + + bytes = array->size - (arith + size + - (char *) array->data); + if (bytes > 0) + memmove (item, arith + size, bytes); + array->size -= size; +} + +static void +RemoveKeyFromSeat (Seat *seat, int32_t keycode) +{ + int32_t *data; + + wl_array_for_each (data, &seat->keys) + { + if (*data == keycode) + { + ArrayRemove (&seat->keys, data, sizeof *data); + break; + } + } +} + +static SeatClientInfo * +ClientInfoForResource (Seat *seat, struct wl_resource *resource) +{ + return GetSeatClientInfo (seat, wl_resource_get_client (resource)); +} + +static void +SendKeyboardKey (Seat *seat, Surface *focus, Time time, + uint32_t key, uint32_t state) +{ + Keyboard *keyboard; + SeatClientInfo *info; + uint32_t serial; + + serial = wl_display_next_serial (compositor.wl_display); + seat->last_keyboard_serial = serial; + + info = ClientInfoForResource (seat, focus->resource); + + if (!info) + return; + + keyboard = info->keyboards.next; + + for (; keyboard != &info->keyboards; keyboard = keyboard->next) + wl_keyboard_send_key (keyboard->resource, serial, time, + key, state); +} + +static void +HandleKeyPressed (Seat *seat, KeyCode keycode, Time time) +{ + if (KeyIsPressed (seat, keycode)) + return; + + KeySetPressed (seat, keycode, True); + InsertKeyIntoSeat (seat, WaylandKeycode (keycode)); +} + +static void +HandleKeyReleased (Seat *seat, KeyCode keycode, Time time) +{ + if (!KeyIsPressed (seat, keycode)) + return; + + KeySetPressed (seat, keycode, False); + RemoveKeyFromSeat (seat, WaylandKeycode (keycode)); +} + +static void +HandleRawKey (XIRawEvent *event) +{ + Seat *seat; + + /* We select for raw events from the X server in order to track the + keys that are currently pressed. In order to respect grabs, key + press and release events are only reported in response to + regular device events. */ + + seat = XLLookUpAssoc (seats, event->deviceid); + + if (!seat) + return; + + if (event->detail < xkb_desc->min_key_code + || event->detail > xkb_desc->max_key_code) + return; + + if (event->evtype == XI_RawKeyPress) + HandleKeyPressed (seat, event->detail, event->time); + else + HandleKeyReleased (seat, event->detail, event->time); + + /* This is used for tracking grabs. */ + seat->its_depress_time = event->time; +} + +static void +HandleResizeComplete (Seat *seat) +{ + Surface *surface; + XEvent msg; + + surface = seat->last_button_press_surface; + + if (!surface || !XLWindowFromSurface (surface)) + goto finish; + + /* We might have gotten the button release before the window manager + set the grab. Cancel the resize operation in that case. */ + + memset (&msg, 0, sizeof msg); + msg.xclient.type = ClientMessage; + msg.xclient.window = XLWindowFromSurface (surface); + msg.xclient.format = 32; + msg.xclient.message_type = _NET_WM_MOVERESIZE; + msg.xclient.data.l[0] = seat->its_root_x; + msg.xclient.data.l[1] = seat->its_root_y; + msg.xclient.data.l[2] = 11; /* _NET_WM_MOVERESIZE_CANCEL. */ + msg.xclient.data.l[3] = seat->last_button; + msg.xclient.data.l[4] = 1; /* Source indication. */ + + XSendEvent (compositor.display, + DefaultRootWindow (compositor.display), + False, + SubstructureRedirectMask | SubstructureNotifyMask, + &msg); + + finish: + + /* Now say that resize operations have stopped. */ + seat->resize_in_progress = False; + + /* And run callbacks. */ + RunResizeDoneCallbacks (seat); +} + +/* Forward declarations. */ + +static int GetXButton (int); +static void TransformToView (View *, double, double, double *, double *); +static void SendButton (Seat *, Surface *, Time, uint32_t, uint32_t, + double, double); + +static void +HandleRawButton (XIRawEvent *event) +{ + Seat *seat; + int button; + double win_x, win_y; + double dispatch_x, dispatch_y; + Window window; + + seat = XLLookUpAssoc (seats, event->deviceid); + + if (!seat) + return; + + if (seat->resize_in_progress + || seat->flags & IsWindowMenuShown) + { + if (seat->last_seen_surface) + { + window = XLWindowFromSurface (seat->last_seen_surface); + + if (window == None) + goto complete; + + button = GetXButton (event->detail); + + if (button < 0) + goto complete; + + /* When a RawButtonPress is received while resizing is still + in progress, release the button on the current surface. + + Since leave and entry events generated by grabs are + ignored, the client will not get leave events correctly + while Metacity is resizing a frame. This results in + programs such as GTK tracking the button state + incorrectly if the pointer never leaves the surface + during a resize operation. + + Something similar applies to the window menu. + + (It would be good to avoid this sync by fetching the + actual modifier values from the raw event.) */ + + if (QueryPointer (seat, window, &win_x, &win_y)) + { + /* Otherwise, the pointer is on a different screen! */ + + TransformToView (seat->last_seen_surface->view, + win_x, win_y, &dispatch_x, &dispatch_y); + SendButton (seat, seat->last_seen_surface, event->time, + button, WL_POINTER_BUTTON_STATE_RELEASED, + dispatch_x, dispatch_y); + } + } + + complete: + + if (event->detail == seat->last_button + && seat->resize_in_progress) + HandleResizeComplete (seat); + } +} + +static void +HandleDeviceChanged (XIDeviceChangedEvent *event) +{ + Seat *seat; + XIDeviceInfo *info; + int ndevices; + + seat = XLLookUpAssoc (seats, event->deviceid); + + if (!seat || event->deviceid != seat->master_pointer) + return; + + /* Now, update scroll valuators from the new device info. */ + + CatchXErrors (); + info = XIQueryDevice (compositor.display, event->deviceid, + &ndevices); + UncatchXErrors (NULL); + + if (!info) + /* The device was disabled, return now. */ + return; + + UpdateValuators (seat, info); + XIFreeDeviceInfo (info); +} + +static void +HandlePropertyChanged (XIPropertyEvent *event) +{ + DeviceInfo *info; + + info = XLLookUpAssoc (devices, event->deviceid); + + if (!info) + return; + + if (event->property == libinput_Scroll_Methods_Available) + /* Update scroll methods for the device whose property + changed. */ + UpdateScrollMethods (info, event->deviceid); +} + +static Seat * +FindSeatByDragWindow (Window window) +{ + Seat *seat; + XLList *tem; + + for (tem = live_seats; tem; tem = tem->next) + { + seat = tem->data; + + if (seat->grab_window == window) + return seat; + } + + return NULL; +} + +static Bool +HandleDragMotionEvent (XIDeviceEvent *xev) +{ + Seat *seat; + + seat = FindSeatByDragWindow (xev->event); + + if (!seat) + return False; + + /* When an event is received for the drag window, it means the event + is outside any surface. Dispatch it to the external drag and + drop code. */ + + /* Move the drag-and-drop icon window. */ + if (seat->icon_surface) + XLMoveIconSurface (seat->icon_surface, xev->root_x, + xev->root_y); + + /* Update information used for resize tracking. */ + seat->its_root_x = xev->root_x; + seat->its_root_y = xev->root_y; + + /* Dispatch the drag motion to external programs. */ + if (seat->data_source) + XLDoDragMotion (seat, xev->root_x, xev->root_y); + + return True; +} + +/* Forward declaration. */ + +static void DragButton (Seat *, XIDeviceEvent *); + +static Bool +HandleDragButtonEvent (XIDeviceEvent *xev) +{ + Seat *seat; + + seat = FindSeatByDragWindow (xev->event); + + if (!seat) + return False; + + DragButton (seat, xev); + return True; +} + +static Bool +HandleOneGenericEvent (XGenericEventCookie *xcookie) +{ + switch (xcookie->evtype) + { + case XI_HierarchyChanged: + HandleHierarchyEvent (xcookie->data); + return True; + + case XI_DeviceChanged: + HandleDeviceChanged (xcookie->data); + return True; + + case XI_PropertyEvent: + HandlePropertyChanged (xcookie->data); + return True; + + case XI_RawKeyPress: + case XI_RawKeyRelease: + HandleRawKey (xcookie->data); + return True; + + case XI_RawButtonRelease: + HandleRawButton (xcookie->data); + return True; + + case XI_Motion: + return HandleDragMotionEvent (xcookie->data); + + case XI_ButtonPress: + case XI_ButtonRelease: + return HandleDragButtonEvent (xcookie->data); + } + + return False; +} + +static void +SelectDeviceEvents (void) +{ + XIEventMask mask; + ptrdiff_t length; + + length = XIMaskLen (XI_LASTEVENT); + mask.mask = alloca (length); + mask.mask_len = length; + mask.deviceid = XIAllDevices; + + memset (mask.mask, 0, length); + + XISetMask (mask.mask, XI_PropertyEvent); + XISetMask (mask.mask, XI_HierarchyChanged); + XISetMask (mask.mask, XI_DeviceChanged); + + XISelectEvents (compositor.display, + DefaultRootWindow (compositor.display), + &mask, 1); + + memset (mask.mask, 0, length); + + mask.deviceid = XIAllMasterDevices; + + XISetMask (mask.mask, XI_RawKeyPress); + XISetMask (mask.mask, XI_RawKeyRelease); + XISetMask (mask.mask, XI_RawButtonRelease); + + XISelectEvents (compositor.display, + DefaultRootWindow (compositor.display), + &mask, 1); +} + +static void +ClearFocusSurface (void *data) +{ + Seat *seat; + + seat = data; + + seat->focus_surface = NULL; + seat->focus_destroy_callback = NULL; +} + +static void +SendKeyboardLeave (Seat *seat, Surface *focus) +{ + Keyboard *keyboard; + uint32_t serial; + SeatClientInfo *info; + + serial = wl_display_next_serial (compositor.wl_display); + info = ClientInfoForResource (seat, focus->resource); + + if (!info) + return; + + keyboard = info->keyboards.next; + + for (; keyboard != &info->keyboards; keyboard = keyboard->next) + wl_keyboard_send_leave (keyboard->resource, + serial, focus->resource); +} + +static void +UpdateSingleModifiers (Seat *seat, Keyboard *keyboard, uint32_t serial) +{ + wl_keyboard_send_modifiers (keyboard->resource, serial, + seat->base, seat->latched, + seat->locked, + seat->effective_group); +} + +static void +SendKeyboardEnter (Seat *seat, Surface *enter) +{ + Keyboard *keyboard; + uint32_t serial; + SeatClientInfo *info; + + serial = wl_display_next_serial (compositor.wl_display); + info = ClientInfoForResource (seat, enter->resource); + + if (!info) + return; + + keyboard = info->keyboards.next; + + for (; keyboard != &info->keyboards; keyboard = keyboard->next) + { + wl_keyboard_send_enter (keyboard->resource, serial, + enter->resource, &seat->keys); + UpdateSingleModifiers (seat, keyboard, serial); + } +} + +static void +SendKeyboardModifiers (Seat *seat, Surface *focus) +{ + Keyboard *keyboard; + uint32_t serial; + SeatClientInfo *info; + + serial = wl_display_next_serial (compositor.wl_display); + info = ClientInfoForResource (seat, focus->resource); + + if (!info) + return; + + keyboard = info->keyboards.next; + + for (; keyboard != &info->keyboards; keyboard = keyboard->next) + UpdateSingleModifiers (seat, keyboard, serial); +} + +static void +SendUpdatedModifiers (Seat *seat) +{ + ModifierChangeCallback *callback; + + for (callback = seat->modifier_callbacks.next; + callback != &seat->modifier_callbacks; + callback = callback->next) + /* Send the effective modifiers. */ + callback->changed (seat->base | seat->locked | seat->latched, + callback->data); + + /* If drag and drop is in progress, update the data source + actions. */ + if (seat->flags & IsDragging && seat->data_source + /* Don't do this during external drag and drop. */ + && seat->drag_last_surface) + XLDataSourceUpdateDeviceActions (seat->data_source); + + if (seat->focus_surface) + SendKeyboardModifiers (seat, seat->focus_surface); +} + +static void +UpdateModifiersForSeats (unsigned int base, unsigned int locked, + unsigned int latched, int base_group, + int locked_group, int latched_group, + int effective_group) +{ + Seat *seat; + XLList *tem; + + for (tem = live_seats; tem; tem = tem->next) + { + seat = tem->data; + + seat->base = base; + seat->locked = locked; + seat->latched = latched; + seat->base_group = base_group; + seat->locked_group = locked_group; + seat->latched_group = latched_group; + seat->effective_group = effective_group; + + SendUpdatedModifiers (seat); + } +} + +static void +SetFocusSurface (Seat *seat, Surface *focus) +{ + if (focus == seat->focus_surface) + return; + + if (seat->focus_surface) + { + SendKeyboardLeave (seat, seat->focus_surface); + + XLSurfaceCancelRunOnFree (seat->focus_destroy_callback); + seat->focus_destroy_callback = NULL; + seat->focus_surface = NULL; + } + + if (!focus) + return; + + seat->focus_surface = focus; + seat->focus_destroy_callback + = XLSurfaceRunOnFree (focus, ClearFocusSurface, seat); + + SendKeyboardEnter (seat, focus); + + if (seat->data_device) + XLDataDeviceHandleFocusChange (seat->data_device); +} + +static void +DispatchFocusIn (Surface *surface, XIFocusInEvent *event) +{ + Seat *seat; + + seat = XLLookUpAssoc (seats, event->deviceid); + + if (!seat) + return; + + SetFocusSurface (seat, surface); +} + +static void +DispatchFocusOut (Surface *surface, XIFocusOutEvent *event) +{ + Seat *seat; + + seat = XLLookUpAssoc (seats, event->deviceid); + + if (!seat) + return; + + if (seat->focus_surface == surface) + SetFocusSurface (seat, NULL); +} + +static Surface * +FindSurfaceUnder (Subcompositor *subcompositor, double x, double y) +{ + int x_off, y_off; + View *view; + + view = SubcompositorLookupView (subcompositor, lrint (x), + lrint (y), &x_off, &y_off); + + if (view) + return ViewGetData (view); + + return NULL; +} + +/* Forward declaration. */ + +static void CancelDrag (Seat *, Window, double, double); + +static void +DragLeave (Seat *seat) +{ + if (seat->drag_last_surface) + { + if (seat->flags & IsDragging) + XLDataDeviceSendLeave (seat, seat->drag_last_surface, + seat->data_source); + else + /* If nothing is being dragged anymore, avoid sending flags to + the source after drop or cancel. */ + XLDataDeviceSendLeave (seat, seat->drag_last_surface, + NULL); + + XLSurfaceCancelRunOnFree (seat->drag_last_surface_destroy_callback); + + seat->drag_last_surface_destroy_callback = NULL; + seat->drag_last_surface = NULL; + } +} + +static void +HandleDragLastSurfaceDestroy (void *data) +{ + Seat *seat; + + seat = data; + + /* Unfortunately there's no way to send a leave message to the + client, as the surface's resource no longer exists. Oh well. */ + + seat->drag_last_surface = NULL; + seat->drag_last_surface_destroy_callback = NULL; +} + +static void +DragEnter (Seat *seat, Surface *surface, double x, double y) +{ + if (seat->drag_last_surface) + DragLeave (seat); + + /* If no data source is specified, only send motion events to + surfaces created by the same client. */ + if (!seat->data_source + && (wl_resource_get_client (seat->drag_start_surface->resource) + != wl_resource_get_client (surface->resource))) + return; + + seat->drag_last_surface = surface; + seat->drag_last_surface_destroy_callback + = XLSurfaceRunOnFree (surface, HandleDragLastSurfaceDestroy, + seat); + + XLDataDeviceSendEnter (seat, surface, x, y, seat->data_source); +} + +static void +DragMotion (Seat *seat, Surface *surface, double x, double y, + Time time) +{ + if (!seat->drag_last_surface) + return; + + if (surface != seat->drag_last_surface) + return; + + XLDataDeviceSendMotion (seat, surface, x, y, time); +} + +static int +MaskPopCount (XIButtonState *mask) +{ + int population, i; + + population = 0; + + for (i = 0; i < mask->mask_len; ++i) + population += __builtin_popcount (mask->mask[i]); + + return population; +} + +static void +DragButton (Seat *seat, XIDeviceEvent *xev) +{ + if (xev->evtype != XI_ButtonRelease) + return; + + /* If a button release event is received with only 1 button + remaining, then the drag is complete; send the drop. */ + if (MaskPopCount (&xev->buttons) == 1) + { + /* Drop on any external drag and drop that may be in + progress. */ + if (seat->data_source && XLDoDragDrop (seat)) + XLDataSourceSendDropPerformed (seat->data_source); + else + { + if (seat->drag_last_surface) + { + if (!seat->data_source + || XLDataSourceCanDrop (seat->data_source)) + { + XLDataDeviceSendDrop (seat, seat->drag_last_surface); + XLDataSourceSendDropPerformed (seat->data_source); + } + else + /* Otherwise, the data source is not eligible for + dropping; simply send cancel. */ + XLDataSourceSendDropCancelled (seat->data_source); + } + else if (seat->data_source) + XLDataSourceSendDropCancelled (seat->data_source); + } + + /* This means that CancelDrag will not send the drop cancelled + event to the data source again. */ + seat->flags |= IsDropped; + + CancelDrag (seat, xev->event, xev->event_x, + xev->event_y); + } +} + +static void +SendMotion (Seat *seat, Surface *surface, double x, double y, + Time time) +{ + Pointer *pointer; + uint32_t serial; + SeatClientInfo *info; + + serial = wl_display_next_serial (compositor.wl_display); + info = ClientInfoForResource (seat, surface->resource); + + if (!info) + return; + + pointer = info->pointers.next; + + for (; pointer != &info->pointers; pointer = pointer->next) + { + if (pointer->state & StateIsRaw) + { + wl_pointer_send_enter (pointer->resource, serial, + surface->resource, + wl_fixed_from_double (x), + wl_fixed_from_double (y)); + pointer->info->last_enter_serial = serial; + } + + wl_pointer_send_motion (pointer->resource, time, + wl_fixed_from_double (x), + wl_fixed_from_double (y)); + + if (wl_resource_get_version (pointer->resource) >= 5) + wl_pointer_send_frame (pointer->resource); + + pointer->state &= ~StateIsRaw; + } +} + +static void +SendLeave (Seat *seat, Surface *surface) +{ + Pointer *pointer; + uint32_t serial; + SeatClientInfo *info; + + serial = wl_display_next_serial (compositor.wl_display); + info = ClientInfoForResource (seat, surface->resource); + + if (!info) + return; + + pointer = info->pointers.next; + + for (; pointer != &info->pointers; pointer = pointer->next) + { + wl_pointer_send_leave (pointer->resource, serial, + surface->resource); + + /* Apparently this is necessary on both leave and enter + events. */ + if (wl_resource_get_version (pointer->resource) >= 5) + wl_pointer_send_frame (pointer->resource); + } +} + +static Bool +SendEnter (Seat *seat, Surface *surface, double x, double y) +{ + Pointer *pointer; + uint32_t serial; + Bool sent; + SeatClientInfo *info; + + serial = wl_display_next_serial (compositor.wl_display); + sent = False; + info = ClientInfoForResource (seat, surface->resource); + + if (!info) + return False; + + pointer = info->pointers.next; + + if (pointer != &info->pointers) + /* If no pointer devices have been created, don't set the + serial. */ + info->last_enter_serial = serial; + + for (; pointer != &info->pointers; pointer = pointer->next) + { + pointer->state &= ~StateIsRaw; + + wl_pointer_send_enter (pointer->resource, serial, + surface->resource, + wl_fixed_from_double (x), + wl_fixed_from_double (y)); + + /* Apparently this is necessary on both leave and enter + events. */ + if (wl_resource_get_version (pointer->resource) >= 5) + wl_pointer_send_frame (pointer->resource); + + sent = True; + } + + return sent; +} + +static void +SendButton (Seat *seat, Surface *surface, Time time, + uint32_t button, uint32_t state, double x, + double y) +{ + Pointer *pointer; + uint32_t serial; + SeatClientInfo *info; + + serial = wl_display_next_serial (compositor.wl_display); + + /* This is later used to track the seat for resize operations. */ + seat->last_button_serial = serial; + + if (state == WL_POINTER_BUTTON_STATE_PRESSED) + /* This is used for popup grabs. */ + seat->last_button_press_serial = serial; + + info = ClientInfoForResource (seat, surface->resource); + + if (!info) + return; + + pointer = info->pointers.next; + + for (; pointer != &info->pointers; pointer = pointer->next) + { + if (pointer->state & StateIsRaw) + { + wl_pointer_send_enter (pointer->resource, serial, + surface->resource, + wl_fixed_from_double (x), + wl_fixed_from_double (y)); + pointer->info->last_enter_serial = serial; + } + + wl_pointer_send_button (pointer->resource, + serial, time, button, state); + + if (wl_resource_get_version (pointer->resource) >= 5) + wl_pointer_send_frame (pointer->resource); + + pointer->state &= ~StateIsRaw; + } +} + +static void +ClearPointerUnlockSurface (void *data) +{ + Seat *seat; + + seat = data; + + seat->pointer_unlock_surface = NULL; + seat->pointer_unlock_surface_callback = NULL; +} + +static void +ClearGrabSurface (void *data) +{ + Seat *seat; + + seat = data; + + /* Cancel the unmap callback. */ + XLSurfaceCancelUnmapCallback (seat->grab_surface_callback); + + seat->grab_surface = NULL; + seat->grab_surface_callback = NULL; +} + +static void +SwapGrabSurface (Seat *seat, Surface *surface) +{ + if (seat->grab_surface == surface) + return; + + if (seat->grab_surface) + { + XLSurfaceCancelUnmapCallback (seat->grab_surface_callback); + seat->grab_surface = NULL; + seat->grab_surface_callback = NULL; + } + + if (surface) + { + seat->grab_surface = surface; + seat->grab_surface_callback + = XLSurfaceRunAtUnmap (surface, ClearGrabSurface, seat); + } +} + +static void +SwapUnlockSurface (Seat *seat, Surface *surface) +{ + if (seat->pointer_unlock_surface == surface) + return; + + if (seat->pointer_unlock_surface) + { + XLSurfaceCancelRunOnFree (seat->pointer_unlock_surface_callback); + seat->pointer_unlock_surface_callback = NULL; + seat->pointer_unlock_surface = NULL; + } + + if (surface) + { + seat->pointer_unlock_surface = surface; + seat->pointer_unlock_surface_callback + = XLSurfaceRunOnFree (surface, ClearPointerUnlockSurface, seat); + } +} + +static void +ClearLastSeenSurface (void *data) +{ + Seat *seat; + + seat = data; + + /* The surface underneath the pointer was destroyed, so clear the + cursor. */ + if (seat->cursor) + FreeCursor (seat->cursor); + + seat->last_seen_surface = NULL; + seat->last_seen_surface_callback = NULL; +} + +static void +UndefineCursorOn (Seat *seat, Surface *surface) +{ + Window window; + + window = XLWindowFromSurface (surface); + + if (window == None) + return; + + XIUndefineCursor (compositor.display, + seat->master_pointer, + window); + + /* In addition to undefining the seat specific cursor, also undefine + the core cursor specified during window creation. */ + XUndefineCursor (compositor.display, window); +} + +static void +EnteredSurface (Seat *seat, Surface *surface, Time time, + double x, double y, Bool preserve_cursor) +{ + if (seat->grab_held && surface != seat->last_seen_surface) + { + /* If the seat is grabbed, delay this for later. */ + SwapUnlockSurface (seat, surface); + return; + } + + if (seat->last_seen_surface == surface) + return; + + if (seat->last_seen_surface) + { + if (seat->flags & IsDragging) + DragLeave (seat); + else + { + SendLeave (seat, seat->last_seen_surface); + + /* The surface underneath the pointer was destroyed, so + clear the cursor. */ + if (seat->cursor && !preserve_cursor) + FreeCursor (seat->cursor); + } + + XLSurfaceCancelRunOnFree (seat->last_seen_surface_callback); + seat->last_seen_surface = NULL; + seat->last_seen_surface_callback = NULL; + } + + if (surface) + { + seat->last_seen_surface = surface; + seat->last_seen_surface_callback + = XLSurfaceRunOnFree (surface, ClearLastSeenSurface, seat); + + if (seat->flags & IsDragging) + DragEnter (seat, surface, x, y); + else if (!SendEnter (seat, surface, x, y)) + /* Apparently what is done by other compositors when no + wl_pointer object exists for the surface's client is to + revert back to the default cursor. */ + UndefineCursorOn (seat, surface); + } +} + +static void +TransformToView (View *view, double event_x, double event_y, + double *view_x_out, double *view_y_out) +{ + int int_x, int_y, x, y; + double view_x, view_y; + + /* Even though event_x and event_y are doubles, they cannot exceed + 65535.0, so this cannot overflow. */ + int_x = (int) event_x; + int_y = (int) event_y; + + ViewTranslate (view, int_x, int_y, &x, &y); + + /* Add the fractional part back to the final result. */ + view_x = ((double) x) + event_x - int_x; + view_y = ((double) y) + event_y - int_y; + + /* Finally, transform the coordinates by the global output + scale. */ + *view_x_out = view_x / global_scale_factor; + *view_y_out = view_y / global_scale_factor; +} + +static Bool +CanDeliverEvents (Seat *seat, Surface *dispatch) +{ + if (!seat->grab_surface) + return True; + + /* Otherwise, an owner-events grab is in effect; only dispatch + events to the client who owns the grab. */ + return (wl_resource_get_client (dispatch->resource) + == wl_resource_get_client (seat->grab_surface->resource)); +} + +static void +TranslateCoordinates (Window source, Window target, double x, double y, + double *x_out, double *y_out) +{ + Window child_return; + int int_x, int_y, t1, t2; + + int_x = (int) x; + int_y = (int) y; + + XTranslateCoordinates (compositor.display, source, + target, int_x, int_y, &t1, &t2, + &child_return); + + /* Add the fractional part back. */ + *x_out = (x - int_x) + t1; + *y_out = (y - int_y) + t2; +} + +static Surface * +ComputeGrabPosition (Seat *seat, Surface *dispatch, + double *event_x, double *event_y) +{ + Window toplevel, grab; + + toplevel = XLWindowFromSurface (dispatch); + grab = XLWindowFromSurface (seat->grab_surface); + + TranslateCoordinates (toplevel, grab, *event_x, *event_y, + event_x, event_y); + return seat->grab_surface; +} + +static void +TranslateGrabPosition (Seat *seat, Window window, double *event_x, + double *event_y) +{ + Window grab; + + grab = XLWindowFromSurface (seat->grab_surface); + + TranslateCoordinates (window, grab, *event_x, *event_y, + event_x, event_y); + return; +} + +static void +DispatchEntryExit (Subcompositor *subcompositor, XIEnterEvent *event) +{ + Seat *seat; + Surface *dispatch; + double x, y, event_x, event_y; + + seat = XLLookUpAssoc (seats, event->deviceid); + + if (!seat) + return; + + if (event->mode == XINotifyUngrab + && seat->grab_surface) + /* Any explicit grab was released, so release the grab surface as + well. */ + SwapGrabSurface (seat, NULL); + + if (event->mode == XINotifyUngrab + && seat->flags & IsDragging) + /* The active grab was released. */ + CancelDrag (seat, event->event, event->event_x, + event->event_y); + + if (event->evtype == XI_Leave + && (event->mode == XINotifyGrab + || event->mode == XINotifyUngrab)) + /* Ignore grab-related weirdness in XI_Leave events. */ + return; + + if (event->evtype == XI_Enter + && event->mode == XINotifyGrab) + /* Accepting entry events with XINotifyGrab leads to bad results + when they arrive on a popup that has just been grabbed. */ + return; + + seat->flags &= ~IsWindowMenuShown; + seat->last_crossing_serial = event->serial; + + if (event->evtype == XI_Leave) + dispatch = NULL; + else + dispatch = FindSurfaceUnder (subcompositor, event->event_x, + event->event_y); + + event_x = event->event_x; + event_y = event->event_y; + + if (seat->grab_surface) + { + /* If the grab surface is set, translate the coordinates to + it and use it instead. */ + TranslateGrabPosition (seat, event->event, + &event_x, &event_y); + dispatch = seat->grab_surface; + + goto after_dispatch_set; + } + + if (!dispatch) + EnteredSurface (seat, NULL, event->time, 0, 0, False); + else + { + /* If dispatching during an active grab, and the event is for + the wrong client, translate the coordinates to the grab + window. */ + if (!CanDeliverEvents (seat, dispatch)) + dispatch = ComputeGrabPosition (seat, dispatch, + &event_x, &event_y); + + after_dispatch_set: + + TransformToView (dispatch->view, event_x, + event_y, &x, &y); + + EnteredSurface (seat, dispatch, event->time, x, y, False); + } + + seat->last_motion_x = event->root_x; + seat->last_motion_y = event->root_y; +} + +static Bool +ProcessValuator (Seat *seat, XIDeviceEvent *event, ScrollValuator *valuator, + double value, double *total_x, double *total_y, int *flags) +{ + double diff; + Bool valid; + + valid = False; + + if (seat->last_crossing_serial > valuator->enter_serial) + /* The valuator is out of date. Set its serial, value, and + return. */ + goto out; + + diff = value - valuator->value; + + if (valuator->direction == Horizontal) + *total_x += diff / valuator->increment; + else + *total_y += diff / valuator->increment; + + if (valuator->direction == Horizontal) + *flags |= AnyVerticalAxis; + else + *flags |= AnyHorizontalAxis; + + valid = True; + + out: + valuator->value = value; + valuator->enter_serial = event->serial; + + return valid; +} + +static ScrollValuator * +FindScrollValuator (Seat *seat, int number) +{ + ScrollValuator *valuator; + + valuator = seat->valuators; + + for (; valuator; valuator = valuator->next) + { + if (valuator->number == number) + return valuator; + } + + return NULL; +} + +static void +SendScrollAxis (Seat *seat, Surface *surface, Time time, + double x, double y, double axis_x, double axis_y, + int flags, int sourceid) +{ + Pointer *pointer; + uint32_t serial; + SeatClientInfo *info; + DeviceInfo *deviceinfo; + + serial = wl_display_next_serial (compositor.wl_display); + info = ClientInfoForResource (seat, surface->resource); + + if (!info) + return; + + pointer = info->pointers.next; + deviceinfo = XLLookUpAssoc (devices, sourceid); + + for (; pointer != &info->pointers; pointer = pointer->next) + { + if (pointer->state & StateIsRaw) + { + wl_pointer_send_enter (pointer->resource, serial, + surface->resource, + wl_fixed_from_double (x), + wl_fixed_from_double (y)); + pointer->info->last_enter_serial = serial; + } + + if (axis_x != 0.0) + wl_pointer_send_axis (pointer->resource, time, + WL_POINTER_AXIS_HORIZONTAL_SCROLL, + wl_fixed_from_double (axis_x)); + + if (axis_y != 0.0) + wl_pointer_send_axis (pointer->resource, time, + WL_POINTER_AXIS_VERTICAL_SCROLL, + wl_fixed_from_double (axis_y)); + + if (axis_y == 0.0 && axis_x == 0.0) + { + /* This behavior is specific to a few X device + drivers! */ + + if (wl_resource_get_version (pointer->resource) >= 5) + { + /* wl_pointer_send_axis_stop is only present on + version 5 or later. */ + + if (flags & AnyVerticalAxis) + wl_pointer_send_axis_stop (pointer->resource, time, + WL_POINTER_AXIS_VERTICAL_SCROLL); + + if (flags & AnyHorizontalAxis) + wl_pointer_send_axis_stop (pointer->resource, time, + WL_POINTER_AXIS_HORIZONTAL_SCROLL); + } + } + + if (wl_resource_get_version (pointer->resource) >= 5) + { + /* Send the source of this axis movement if it can be + determined. We assume that axis movement from any + device capable finger or edge scrolling comes from a + touchpad. */ + + if (deviceinfo + && (deviceinfo->flags & DeviceCanFingerScroll + || deviceinfo->flags & DeviceCanEdgeScroll)) + wl_pointer_send_axis_source (pointer->resource, + WL_POINTER_AXIS_SOURCE_FINGER); + } + + if (axis_x != 0.0 || axis_y != 0.0 + || flags || pointer->state & StateIsRaw) + { + if (wl_resource_get_version (pointer->resource) >= 5) + wl_pointer_send_frame (pointer->resource); + } + + pointer->state &= ~StateIsRaw; + } +} + +static Bool +HandleValuatorMotion (Seat *seat, Surface *dispatch, double x, double y, + XIDeviceEvent *event) +{ + double total_x, total_y, *values; + ScrollValuator *valuator; + int i, flags; + Bool value; + + total_x = 0.0; + total_y = 0.0; + value = False; + values = event->valuators.values; + flags = 0; + + for (i = 0; i < event->valuators.mask_len * 8; ++i) + { + if (!XIMaskIsSet (event->valuators.mask, i)) + continue; + + valuator = FindScrollValuator (seat, i); + + if (!valuator) + /* We still have to increment values even if we don't know + about the valuator in question. */ + goto next; + + value |= ProcessValuator (seat, event, valuator, *values, + &total_x, &total_y, &flags); + + next: + values++; + } + + if (value && dispatch) + SendScrollAxis (seat, dispatch, event->time, x, y, + /* FIXME: this is how GTK converts those values, + but is it really right? */ + total_x * 10, total_y * 10, flags, + /* Also pass the event source device ID, which is + used in an attempt to determine the axis + source. */ + event->sourceid); + return value; +} + +static void +DispatchMotion (Subcompositor *subcompositor, XIDeviceEvent *xev) +{ + Seat *seat; + Surface *dispatch, *actual_dispatch; + double x, y, event_x, event_y; + + seat = XLLookUpAssoc (seats, xev->deviceid); + + if (!seat) + return; + + if (InterceptResizeEvent (seat, xev)) + return; + + /* Move the drag-and-drop icon window. */ + if (seat->icon_surface) + XLMoveIconSurface (seat->icon_surface, xev->root_x, + xev->root_y); + + /* Update information used for resize tracking. */ + seat->its_root_x = xev->root_x; + seat->its_root_y = xev->root_y; + seat->its_press_time = xev->time; + + actual_dispatch = FindSurfaceUnder (subcompositor, xev->event_x, + xev->event_y); + + if (seat->grab_held) + { + /* If the grab is held, make the surface underneath the pointer + the pending unlock surface. */ + SwapUnlockSurface (seat, actual_dispatch); + dispatch = seat->last_seen_surface; + } + else + dispatch = FindSurfaceUnder (subcompositor, xev->event_x, + xev->event_y); + + event_x = xev->event_x; + event_y = xev->event_y; + + if (!dispatch) + { + if (seat->grab_surface) + { + /* If the grab surface is set, translate the coordinates to + it and use it instead. */ + TranslateGrabPosition (seat, xev->event, + &event_x, &event_y); + dispatch = seat->grab_surface; + + goto after_dispatch_set; + } + + EnteredSurface (seat, dispatch, xev->time, 0, 0, False); + + /* If drag and drop is in progress, handle "external" drag and + drop. */ + if (seat->flags & IsDragging + && seat->data_source) + XLDoDragMotion (seat, xev->root_x, xev->root_y); + + return; + } + + /* Update the outputs the pointer surface is currently displayed + inside, since it evidently moved. */ + if (seat->cursor) + UpdateCursorOutput (seat->cursor, xev->root_x, + xev->root_y); + + /* If dispatching during an active grab, and the event is for the + wrong client, translate the coordinates to the grab window. */ + if (!CanDeliverEvents (seat, dispatch)) + dispatch = ComputeGrabPosition (seat, dispatch, + &event_x, &event_y); + + after_dispatch_set: + + if (seat->flags & IsDragging + && seat->data_source) + /* Inside a surface; cancel external drag and drop. */ + XLDoDragLeave (seat); + + TransformToView (dispatch->view, event_x, event_y, + &x, &y); + EnteredSurface (seat, dispatch, xev->time, x, y, False); + + if (!HandleValuatorMotion (seat, dispatch, x, y, xev)) + { + if (seat->flags & IsDragging) + DragMotion (seat, dispatch, x, y, xev->time); + else + SendMotion (seat, dispatch, x, y, xev->time); + } + + /* These values are for tracking the output that a cursor is in. */ + + seat->last_motion_x = xev->root_x; + seat->last_motion_y = xev->root_y; +} + +static int +GetXButton (int detail) +{ + switch (detail) + { + case Button1: + return BTN_LEFT; + + case Button2: + return BTN_MIDDLE; + + case Button3: + return BTN_RIGHT; + + default: + return -1; + } +} + +static void +CancelGrab (Seat *seat, Time time, Window source, + double x, double y) +{ + Window target; + + if (!seat->grab_held) + return; + + if (--seat->grab_held) + return; + + if (seat->pointer_unlock_surface) + { + target = XLWindowFromSurface (seat->pointer_unlock_surface); + + if (target == None) + /* If the window is gone, make the target surface NULL. */ + SwapUnlockSurface (seat, NULL); + else + { + if (source != target) + /* If the source is something other than the target, + translate the coordinates to the target. */ + TranslateCoordinates (source, target, x, y, &x, &y); + + /* Finally, translate the coordinates to the target + view. */ + TransformToView (seat->pointer_unlock_surface->view, + x, y, &x, &y); + } + } + + EnteredSurface (seat, seat->pointer_unlock_surface, + time, x, y, False); + SwapUnlockSurface (seat, NULL); + + /* Cancel the unmap callback. */ + XLSurfaceCancelUnmapCallback (seat->grab_unmap_callback); + seat->grab_unmap_callback = NULL; +} + +static void +CancelGrabEarly (Seat *seat) +{ + /* Do this to make sure the grab is immediately canceled. */ + seat->grab_held = 1; + + /* Cancelling the grab should also result in the unmap callback + being cancelled. */ + CancelGrab (seat, seat->its_press_time, + DefaultRootWindow (compositor.display), + seat->its_root_x, seat->its_root_y); +} + +static void +HandleGrabUnmapped (void *data) +{ + CancelGrabEarly (data); +} + +static void +LockSurfaceFocus (Seat *seat) +{ + UnmapCallback *callback; + + /* As long as an active grab is held, ignore the passive grab. */ + if (seat->grab_surface) + return; + + seat->grab_held++; + + /* Initially, make the focus revert back to the last seen + surface. */ + if (seat->grab_held == 1) + { + SwapUnlockSurface (seat, seat->last_seen_surface); + + /* Also cancel the grab upon the surface being unmapped. */ + callback = XLSurfaceRunAtUnmap (seat->last_seen_surface, + HandleGrabUnmapped, seat); + seat->grab_unmap_callback = callback; + } +} + +static void +ClearLastButtonPressSurface (void *data) +{ + Seat *seat; + + seat = data; + + seat->last_button_press_surface = NULL; + seat->last_button_press_surface_callback = NULL; +} + +static void +SetButtonSurface (Seat *seat, Surface *surface) +{ + DestroyCallback *callback; + + if (surface == seat->last_button_press_surface) + return; + + callback = seat->last_button_press_surface_callback; + + if (seat->last_button_press_surface) + { + XLSurfaceCancelRunOnFree (callback); + seat->last_button_press_surface_callback = NULL; + seat->last_button_press_surface = NULL; + } + + if (!surface) + return; + + seat->last_button_press_surface = surface; + seat->last_button_press_surface_callback + = XLSurfaceRunOnFree (surface, ClearLastButtonPressSurface, seat); +} + +static void +DispatchButton (Subcompositor *subcompositor, XIDeviceEvent *xev) +{ + Seat *seat; + Surface *dispatch, *actual_dispatch; + double x, y, event_x, event_y; + int button; + uint32_t state; + + if (xev->flags & XIPointerEmulated) + return; + + seat = XLLookUpAssoc (seats, xev->deviceid); + + if (!seat) + return; + + if (InterceptResizeEvent (seat, xev)) + return; + + if (seat->flags & IsDragging) + { + DragButton (seat, xev); + return; + } + + button = GetXButton (xev->detail); + + if (button < 0) + return; + + actual_dispatch = FindSurfaceUnder (subcompositor, xev->event_x, + xev->event_y); + + if (seat->grab_held) + { + /* If the grab is held, make the surface underneath the pointer + the pending unlock surface. */ + SwapUnlockSurface (seat, actual_dispatch); + dispatch = seat->last_seen_surface; + } + else + dispatch = actual_dispatch; + + event_x = xev->event_x; + event_y = xev->event_y; + + if (!dispatch) + { + if (seat->grab_surface) + { + /* If the grab surface is set, translate the coordinates to + it and use it instead. */ + TranslateGrabPosition (seat, xev->event, + &event_x, &event_y); + dispatch = seat->grab_surface; + + goto after_dispatch_set; + } + + EnteredSurface (seat, dispatch, xev->time, 0, 0, False); + return; + } + + /* Allow popups to be dismissed when the mouse button is released on + some other client's window. */ + if (XLHandleButtonForXdgPopups (seat, dispatch)) + /* Ignore the button event that resulted in popup(s) being + dismissed. */ + return; + + /* If dispatching during an active grab, and the event is for the + wrong client, translate the coordinates to the grab window. */ + if (!CanDeliverEvents (seat, dispatch)) + dispatch = ComputeGrabPosition (seat, dispatch, + &event_x, &event_y); + + after_dispatch_set: + + TransformToView (dispatch->view, xev->event_x, + xev->event_y, &x, &y); + EnteredSurface (seat, dispatch, xev->time, x, y, + False); + + state = (xev->evtype == XI_ButtonPress + ? WL_POINTER_BUTTON_STATE_PRESSED + : WL_POINTER_BUTTON_STATE_RELEASED); + + SendButton (seat, dispatch, xev->time, button, + state, x, y); + + if (xev->evtype == XI_ButtonPress) + { + /* These values are used for resize grip tracking. */ + seat->its_root_x = lrint (xev->root_x); + seat->its_root_y = lrint (xev->root_y); + seat->its_press_time = xev->time; + seat->last_button = xev->detail; + + SetButtonSurface (seat, dispatch); + } + + if (xev->evtype == XI_ButtonPress) + LockSurfaceFocus (seat); + else + CancelGrab (seat, xev->time, xev->event, + xev->event_x, xev->event_y); +} + +static void +DispatchKey (XIDeviceEvent *xev) +{ + Seat *seat; + + seat = XLLookUpAssoc (seats, xev->deviceid); + + if (!seat) + return; + + /* Report key state changes here. A side effect is that the key + state reported in enter events will include grabbed keys, but + that seems to be an acceptable tradeoff. */ + + if (seat->focus_surface) + { + if (xev->evtype == XI_KeyPress) + SendKeyboardKey (seat, seat->focus_surface, + xev->time, WaylandKeycode (xev->detail), + WL_KEYBOARD_KEY_STATE_PRESSED); + else + SendKeyboardKey (seat, seat->focus_surface, + xev->time, WaylandKeycode (xev->detail), + WL_KEYBOARD_KEY_STATE_RELEASED); + } +} + +static void +WriteKeymap (void) +{ + FILE *file; + XkbFileInfo result; + Bool ok; + + if (keymap_fd != -1) + close (keymap_fd); + + keymap_fd = XLOpenShm (); + + if (keymap_fd < 0) + { + fprintf (stderr, "Failed to allocate keymap fd\n"); + exit (1); + } + + memset (&result, 0, sizeof result); + result.type = XkmKeymapFile; + result.xkb = xkb_desc; + + file = fdopen (dup (keymap_fd), "w"); + + if (!file) + { + perror ("fdopen"); + exit (1); + } + + ok = XkbWriteXKBFile (file, &result, + /* libxkbcommon doesn't read comments in + virtual_modifier lines. */ + False, NULL, NULL); + + if (!ok) + fprintf (stderr, "Warning: the XKB keymap could not be written\n" + "Programs might not continue to interpret keyboard input" + " correctly.\n"); + + fclose (file); +} + +static void +AfterMapUpdate (void) +{ + if (XkbGetIndicatorMap (compositor.display, ~0, xkb_desc) != Success) + { + fprintf (stderr, "Could not load indicator map\n"); + exit (1); + } + + if (XkbGetControls (compositor.display, + XkbAllControlsMask, xkb_desc) != Success) + { + fprintf (stderr, "Could not load keyboard controls\n"); + exit (1); + } + + if (XkbGetCompatMap (compositor.display, + XkbAllCompatMask, xkb_desc) != Success) + { + fprintf (stderr, "Could not load compatibility map\n"); + exit (1); + } + + if (XkbGetNames (compositor.display, + XkbAllNamesMask, xkb_desc) != Success) + { + fprintf (stderr, "Could not load names\n"); + exit (1); + } +} + +static void +UpdateKeymapInfo (void) +{ + XLList *tem; + Seat *seat; + Keyboard *keyboard; + + for (tem = live_seats; tem; tem = tem->next) + { + seat = tem->data; + + if (!seat->key_pressed) + /* max_keycode is small enough for this to not matter + memory-wise. */ + seat->key_pressed + = XLCalloc (MaskLen (xkb_desc->max_key_code + - xkb_desc->min_key_code), 1); + else + seat->key_pressed + = XLRealloc (seat->key_pressed, + MaskLen (xkb_desc->max_key_code + - xkb_desc->min_key_code)); + + for (keyboard = seat->keyboards.next1; + keyboard != &seat->keyboards; + keyboard = keyboard->next1) + UpdateSingleKeyboard (keyboard); + } +} + +static void +SetupKeymap (void) +{ + int xkb_major, xkb_minor, xkb_op, xkb_error_code, mask; + xkb_major = XkbMajorVersion; + xkb_minor = XkbMinorVersion; + + if (!XkbLibraryVersion (&xkb_major, &xkb_minor) + || !XkbQueryExtension (compositor.display, &xkb_op, &xkb_event_type, + &xkb_error_code, &xkb_major, &xkb_minor)) + { + fprintf (stderr, "Failed to set up Xkb\n"); + exit (1); + } + + xkb_desc = XkbGetMap (compositor.display, + XkbAllMapComponentsMask, + XkbUseCoreKbd); + + if (!xkb_desc) + { + fprintf (stderr, "Failed to retrieve keymap from X server\n"); + exit (1); + } + + AfterMapUpdate (); + WriteKeymap (); + + XkbSelectEvents (compositor.display, XkbUseCoreKbd, + XkbMapNotifyMask | XkbNewKeyboardNotifyMask, + XkbMapNotifyMask | XkbNewKeyboardNotifyMask); + + /* Select for keyboard state changes too. */ + mask = 0; + + mask |= XkbModifierStateMask; + mask |= XkbModifierBaseMask; + mask |= XkbModifierLatchMask; + mask |= XkbModifierLockMask; + mask |= XkbGroupStateMask; + mask |= XkbGroupBaseMask; + mask |= XkbGroupLatchMask; + mask |= XkbGroupLockMask; + + XkbSelectEventDetails (compositor.display, XkbUseCoreKbd, + /* Now enable everything in that mask. */ + XkbStateNotify, mask, mask); + + UpdateKeymapInfo (); +} + +static Bool +HandleXkbEvent (XkbEvent *event) +{ + if (event->any.xkb_type == XkbMapNotify + || event->any.xkb_type == XkbNewKeyboardNotify) + { + XkbRefreshKeyboardMapping (&event->map); + XkbFreeKeyboard (xkb_desc, XkbAllMapComponentsMask, + True); + + xkb_desc = XkbGetMap (compositor.display, + XkbAllMapComponentsMask, + XkbUseCoreKbd); + + if (!xkb_desc) + { + fprintf (stderr, "Failed to retrieve keymap from X server\n"); + exit (1); + } + + AfterMapUpdate (); + WriteKeymap (); + UpdateKeymapInfo (); + + return True; + } + else if (event->any.xkb_type == XkbStateNotify) + { + UpdateModifiersForSeats (event->state.base_mods, + event->state.locked_mods, + event->state.latched_mods, + event->state.base_group, + event->state.locked_group, + event->state.latched_group, + event->state.group); + return True; + } + + return False; +} + +static Seat * +IdentifySeat (WhatEdge *edge, uint32_t serial) +{ + Seat *seat; + XLList *tem; + + for (tem = live_seats; tem; tem = tem->next) + { + seat = tem->data; + + if (seat->last_button_serial == serial + || seat->last_button_press_serial == serial) + { + /* This serial belongs to a button press. */ + *edge = APointerEdge; + return seat; + } + + if (seat->last_keyboard_serial == serial) + { + /* This serial belongs to a keyboard press. */ + *edge = AKeyboardEdge; + return seat; + } + } + + /* No seat was found. */ + return NULL; +} + +static Time +GetLastUserTime (Seat *seat) +{ + return MAX (seat->its_press_time, + seat->its_depress_time); +} + +static Bool +HandleKeyboardEdge (Seat *seat, Surface *target, uint32_t serial, + ResizeEdge edge) +{ + Surface *surface; + XEvent msg; + + surface = seat->last_button_press_surface; + + if (!surface || surface != target) + return False; + + memset (&msg, 0, sizeof msg); + msg.xclient.type = ClientMessage; + msg.xclient.window = XLWindowFromSurface (surface); + msg.xclient.format = 32; + msg.xclient.message_type = _NET_WM_MOVERESIZE; + msg.xclient.data.l[0] = seat->its_root_x; + msg.xclient.data.l[1] = seat->its_root_y; + msg.xclient.data.l[2] = edge; + msg.xclient.data.l[3] = seat->last_button; + msg.xclient.data.l[4] = edge == MoveEdge ? 10 : 9; + + /* Release all grabs to the pointer device in question. */ + XIUngrabDevice (compositor.display, seat->master_pointer, + seat->its_press_time); + + /* Also release all grabs to the keyboard device. */ + XIUngrabDevice (compositor.display, seat->master_keyboard, + seat->its_press_time); + + /* Clear the grab immediately since it is no longer used. */ + if (seat->grab_held) + CancelGrabEarly (seat); + + /* Send the message to the window manager. */ + XSendEvent (compositor.display, + DefaultRootWindow (compositor.display), + False, + SubstructureRedirectMask | SubstructureNotifyMask, + &msg); + + /* There's no way to determine whether or not a keyboard resize has + ended. */ + return False; +} + +static void +HandleResizeUnmapped (void *data) +{ + Seat *seat; + + seat = data; + CancelResizeOperation (seat, seat->resize_time); +} + +static Bool +FakePointerEdge (Seat *seat, Surface *target, uint32_t serial, + ResizeEdge edge) +{ + Cursor cursor; + Status state; + Window window; + XIEventMask mask; + ptrdiff_t length; + + if (edge == NoneEdge) + return False; + + if (seat->resize_surface) + /* Some surface is already being resized. Prohibit this resize + request. */ + return False; + + window = XLWindowFromSurface (target); + + if (window == None) + /* No window exists. */ + return False; + + seat->resize_start_root_x = seat->its_root_x; + seat->resize_start_root_y = seat->its_root_y; + + seat->resize_last_root_x = seat->its_root_x; + seat->resize_last_root_y = seat->its_root_y; + + /* Get an appropriate cursor. */ + cursor = (seat->cursor ? seat->cursor->cursor : None); + + /* Get the dimensions of the surface when it was first seen. + This can fail if the surface does not support the operation. */ + if (!XLSurfaceGetResizeDimensions (target, &seat->resize_width, + &seat->resize_height)) + return False; + + /* Set up the event mask for the pointer grab. */ + length = XIMaskLen (XI_LASTEVENT); + mask.mask = alloca (length); + mask.mask_len = length; + mask.deviceid = XIAllMasterDevices; + + memset (mask.mask, 0, length); + + XISetMask (mask.mask, XI_FocusIn); + XISetMask (mask.mask, XI_FocusOut); + XISetMask (mask.mask, XI_Enter); + XISetMask (mask.mask, XI_Leave); + XISetMask (mask.mask, XI_Motion); + XISetMask (mask.mask, XI_ButtonPress); + XISetMask (mask.mask, XI_ButtonRelease); + + /* Grab the pointer, and don't let go until the button is + released. */ + state = XIGrabDevice (compositor.display, seat->master_pointer, + window, seat->its_press_time, cursor, + XIGrabModeAsync, XIGrabModeAsync, False, &mask); + + if (state != Success) + return False; + + /* On the other hand, cancel focus locking, since we will not be + reporting motion events until the resize operation completes. + + Send leave events to any surface, since the pointer is + (logically) no longer inside. */ + if (seat->grab_held) + CancelGrabEarly (seat); + + /* Set the surface as the surface undergoing resize. */ + seat->resize_surface = target; + seat->resize_surface_callback + = XLSurfaceRunAtUnmap (seat->resize_surface, + HandleResizeUnmapped, seat); + seat->resize_axis_flags = resize_edges[edge]; + seat->resize_button = seat->last_button; + seat->resize_time = seat->its_press_time; + + return True; +} + +static Bool +HandlePointerEdge (Seat *seat, Surface *target, uint32_t serial, + ResizeEdge edge) +{ + Surface *surface; + XEvent msg; + + surface = seat->last_button_press_surface; + + if (!surface || surface != target) + return False; + + if (!XLWmSupportsHint (_NET_WM_MOVERESIZE) + || getenv ("USE_BUILTIN_RESIZE")) + return FakePointerEdge (seat, target, serial, edge); + + memset (&msg, 0, sizeof msg); + msg.xclient.type = ClientMessage; + msg.xclient.window = XLWindowFromSurface (surface); + msg.xclient.format = 32; + msg.xclient.message_type = _NET_WM_MOVERESIZE; + msg.xclient.data.l[0] = seat->its_root_x; + msg.xclient.data.l[1] = seat->its_root_y; + msg.xclient.data.l[2] = edge; + msg.xclient.data.l[3] = seat->last_button; + msg.xclient.data.l[4] = 1; /* Source indication. */ + + /* Release all grabs to the pointer device in question. */ + XIUngrabDevice (compositor.display, seat->master_pointer, + seat->its_press_time); + + /* Also clear the core grab, even though it's not used anywhere. */ + XUngrabPointer (compositor.display, seat->its_press_time); + + /* Clear the grab immediately since it is no longer used. */ + if (seat->grab_held) + CancelGrabEarly (seat); + + /* Send the message to the window manager. */ + XSendEvent (compositor.display, + DefaultRootWindow (compositor.display), + False, + SubstructureRedirectMask | SubstructureNotifyMask, + &msg); + + /* Assume a resize is in progress. Stop resizing upon + seat->last_button being released. */ + return seat->resize_in_progress = True; +} + +static Bool +StartResizeTracking (Seat *seat, Surface *surface, uint32_t serial, + ResizeEdge edge) +{ + WhatEdge type; + + if (seat != IdentifySeat (&type, serial)) + return False; + + if (type == AKeyboardEdge) + return HandleKeyboardEdge (seat, surface, serial, edge); + else + return HandlePointerEdge (seat, surface, serial, edge); +} + +Bool +XLHandleOneXEventForSeats (XEvent *event) +{ + if (event->type == GenericEvent + && event->xgeneric.extension == xi2_opcode) + return HandleOneGenericEvent (&event->xcookie); + + if (event->type == xkb_event_type) + return HandleXkbEvent ((XkbEvent *) event); + + return False; +} + +Window +XLGetGEWindowForSeats (XEvent *event) +{ + XIFocusInEvent *focusin; + XIEnterEvent *enter; + XIDeviceEvent *xev; + + if (event->type == GenericEvent + && event->xgeneric.extension == xi2_opcode) + { + switch (event->xgeneric.evtype) + { + case XI_FocusIn: + case XI_FocusOut: + focusin = event->xcookie.data; + return focusin->event; + + case XI_Motion: + case XI_ButtonPress: + case XI_ButtonRelease: + case XI_KeyPress: + case XI_KeyRelease: + xev = event->xcookie.data; + return xev->event; + + case XI_Enter: + case XI_Leave: + enter = event->xcookie.data; + return enter->event; + } + } + + return None; +} + +void +XLSelectStandardEvents (Window window) +{ + XIEventMask mask; + ptrdiff_t length; + + length = XIMaskLen (XI_LASTEVENT); + mask.mask = alloca (length); + mask.mask_len = length; + mask.deviceid = XIAllMasterDevices; + + memset (mask.mask, 0, length); + + XISetMask (mask.mask, XI_FocusIn); + XISetMask (mask.mask, XI_FocusOut); + XISetMask (mask.mask, XI_Enter); + XISetMask (mask.mask, XI_Leave); + XISetMask (mask.mask, XI_Motion); + XISetMask (mask.mask, XI_ButtonPress); + XISetMask (mask.mask, XI_ButtonRelease); + XISetMask (mask.mask, XI_KeyPress); + XISetMask (mask.mask, XI_KeyRelease); + + XISelectEvents (compositor.display, window, &mask, 1); +} + +void +XLDispatchGEForSeats (XEvent *event, Surface *surface, + Subcompositor *subcompositor) +{ + if (event->xgeneric.evtype == XI_FocusIn) + DispatchFocusIn (surface, event->xcookie.data); + else if (event->xgeneric.evtype == XI_FocusOut) + DispatchFocusOut (surface, event->xcookie.data); + else if (event->xgeneric.evtype == XI_Enter + || event->xgeneric.evtype == XI_Leave) + DispatchEntryExit (subcompositor, event->xcookie.data); + else if (event->xgeneric.evtype == XI_Motion) + DispatchMotion (subcompositor, event->xcookie.data); + else if (event->xgeneric.evtype == XI_ButtonPress + || event->xgeneric.evtype == XI_ButtonRelease) + DispatchButton (subcompositor, event->xcookie.data); + else if (event->xgeneric.evtype == XI_KeyPress + || event->xgeneric.evtype == XI_KeyRelease) + DispatchKey (event->xcookie.data); +} + +Cursor +InitDefaultCursor (void) +{ + static Cursor empty_cursor; + Pixmap pixmap; + char no_data[1]; + XColor color; + + if (empty_cursor == None) + { + no_data[0] = 0; + + pixmap = XCreateBitmapFromData (compositor.display, + DefaultRootWindow (compositor.display), + no_data, 1, 1); + color.pixel = 0; + color.red = 0; + color.green = 0; + color.blue = 0; + color.flags = DoRed | DoGreen | DoBlue; + + empty_cursor = XCreatePixmapCursor (compositor.display, + pixmap, pixmap, + &color, &color, 0, 0); + + XFreePixmap (compositor.display, pixmap); + } + + return empty_cursor; +} + +Bool +XLResizeToplevel (Seat *seat, Surface *surface, uint32_t serial, + uint32_t xdg_edge) +{ + ResizeEdge edge; + + if (seat->resize_in_progress) + return False; + + switch (xdg_edge) + { + case XDG_TOPLEVEL_RESIZE_EDGE_NONE: + edge = NoneEdge; + break; + + case XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT: + edge = TopLeftEdge; + break; + + case XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT: + edge = TopRightEdge; + break; + + case XDG_TOPLEVEL_RESIZE_EDGE_TOP: + edge = TopEdge; + break; + + case XDG_TOPLEVEL_RESIZE_EDGE_RIGHT: + edge = RightEdge; + break; + + case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM: + edge = BottomEdge; + break; + + case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT: + edge = BottomRightEdge; + break; + + case XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT: + edge = BottomLeftEdge; + break; + + case XDG_TOPLEVEL_RESIZE_EDGE_LEFT: + edge = LeftEdge; + break; + + default: + edge = NoneEdge; + break; + } + + return StartResizeTracking (seat, surface, serial, edge); +} + +void +XLMoveToplevel (Seat *seat, Surface *surface, uint32_t serial) +{ + StartResizeTracking (seat, surface, serial, MoveEdge); +} + +void * +XLSeatRunAfterResize (Seat *seat, void (*func) (void *, void *), + void *data) +{ + ResizeDoneCallback *callback; + + callback = XLMalloc (sizeof *callback); + callback->next = seat->resize_callbacks.next; + callback->last = &seat->resize_callbacks; + + seat->resize_callbacks.next->last = callback; + seat->resize_callbacks.next = callback; + + callback->data = data; + callback->done = func; + + return callback; +} + +void +XLSeatCancelResizeCallback (void *key) +{ + ResizeDoneCallback *callback; + + callback = key; + callback->last->next = callback->next; + callback->next->last = callback->last; + + callback->last = callback; + callback->next = callback; + + XLFree (callback); +} + +void * +XLSeatRunOnDestroy (Seat *seat, void (*destroy_func) (void *), + void *data) +{ + DestroyListener *listener; + + if (seat->flags & IsInert) + return NULL; + + listener = XLMalloc (sizeof *listener); + listener->next = seat->destroy_listeners.next; + listener->last = &seat->destroy_listeners; + + listener->destroy = destroy_func; + listener->data = data; + + seat->destroy_listeners.next->last = listener; + seat->destroy_listeners.next = listener; + + return listener; +} + +void +XLSeatCancelDestroyListener (void *key) +{ + DestroyListener *listener; + + listener = key; + listener->next->last = listener->last; + listener->last->next = listener->next; + + XLFree (listener); +} + +Bool +XLSeatExplicitlyGrabSurface (Seat *seat, Surface *surface, uint32_t serial) +{ + Status state; + Window window; + WhatEdge edge; + Time time; + XIEventMask mask; + ptrdiff_t length; + Cursor cursor; + + if (seat->flags & IsInert + /* This would interfere with the drag-and-drop grab. */ + || seat->flags & IsDragging) + return False; + + window = XLWindowFromSurface (surface); + + if (!window) + return False; + + if (serial && serial == seat->last_grab_serial) + { + /* This probably means we are trying to revert the grab to a + popup's parent after the child is destroyed. */ + + edge = seat->last_grab_edge; + time = seat->last_grab_time; + } + else + { + if (seat != IdentifySeat (&edge, serial)) + return False; + + if (edge == AKeyboardEdge) + time = seat->its_depress_time; + else + time = seat->its_press_time; + } + + /* Record these values; they can be used to revert the grab to the + parent. */ + seat->last_grab_serial = serial; + seat->last_grab_edge = edge; + seat->last_grab_time = time; + + length = XIMaskLen (XI_LASTEVENT); + mask.mask = alloca (length); + mask.mask_len = length; + mask.deviceid = XIAllMasterDevices; + + memset (mask.mask, 0, length); + + XISetMask (mask.mask, XI_FocusIn); + XISetMask (mask.mask, XI_FocusOut); + XISetMask (mask.mask, XI_Enter); + XISetMask (mask.mask, XI_Leave); + XISetMask (mask.mask, XI_Motion); + XISetMask (mask.mask, XI_ButtonPress); + XISetMask (mask.mask, XI_ButtonRelease); + + cursor = (seat->cursor ? seat->cursor->cursor : None); + + state = XIGrabDevice (compositor.display, seat->master_pointer, + window, time, cursor, XIGrabModeAsync, + XIGrabModeAsync, True, &mask); + + if (state != Success) + return False; + + /* The grab was obtained. Since this grab is owner_events, remove + any focus locking. */ + if (seat->grab_held) + CancelGrabEarly (seat); + + /* Now, grab the keyboard. Note that we just grab the keyboard so + that keyboard focus cannot be changed; key events are still + reported based on raw events. */ + + state = XIGrabDevice (compositor.display, seat->master_keyboard, + window, time, None, XIGrabModeAsync, + XIGrabModeAsync, True, &mask); + + /* And record the grab surface, so that owner_events can be + implemented correctly. */ + SwapGrabSurface (seat, surface); + + return True; +} + +DataDevice * +XLSeatGetDataDevice (Seat *seat) +{ + return seat->data_device; +} + +void +XLSeatSetDataDevice (Seat *seat, DataDevice *data_device) +{ + seat->data_device = data_device; + + XLRetainDataDevice (data_device); +} + +Bool +XLSeatIsInert (Seat *seat) +{ + return seat->flags & IsInert; +} + +Bool +XLSeatIsClientFocused (Seat *seat, struct wl_client *client) +{ + struct wl_client *surface_client; + + if (!seat->focus_surface) + return False; + + surface_client + = wl_resource_get_client (seat->focus_surface->resource); + + return client == surface_client; +} + +void +XLSeatShowWindowMenu (Seat *seat, Surface *surface, int root_x, + int root_y) +{ + XEvent msg; + Window window; + + if (!XLWmSupportsHint (_GTK_SHOW_WINDOW_MENU)) + return; + + if (seat->flags & IsDragging) + /* The window menu cannot be displayed while the drag-and-drop + grab is in effect. */ + return; + + window = XLWindowFromSurface (surface); + + if (window == None) + return; + + /* Ungrab the pointer. Also cancel any focus locking, if + active. */ + XIUngrabDevice (compositor.display, seat->master_pointer, + seat->its_press_time); + + /* Also clear the core grab, even though it's not used anywhere. */ + XUngrabPointer (compositor.display, seat->its_press_time); + + /* Cancel focus locking. */ + if (seat->grab_held) + CancelGrabEarly (seat); + + /* Signal that the window menu is now shown. The assumption is that + the window manager will grab the pointer device; the flag is then + cleared once once any kind of crossing event is received. + + This is race-prone for two reasons. If the window manager does + not receive the event in time, the last-grab-time could have + changed. Since there is no timestamp provided in the + _GTK_SHOW_WINDOW_MENU message, there is no way for the window + manager to know if the time it issued the grab is valid, as it + will need to obtain the grab time via a server roundtrip. In + addition, there is no way for the client to cancel the window + menu grab, should it receive an event changing the last-grab-time + before the window manager has a chance to grab the pointer. + + So the conclusion is that _GTK_SHOW_WINDOW_MENU is defective from + improper design. In the meantime, the solution mentioned above + seems to work well enough in most cases. */ + seat->flags |= IsWindowMenuShown; + + /* Send the message to the window manager with the device to grab, + and the coordinates where the window menu should be shown. */ + memset (&msg, 0, sizeof msg); + msg.xclient.type = ClientMessage; + msg.xclient.window = window; + msg.xclient.format = 32; + msg.xclient.message_type = _GTK_SHOW_WINDOW_MENU; + msg.xclient.data.l[0] = seat->master_pointer; + msg.xclient.data.l[1] = root_x; + msg.xclient.data.l[2] = root_y; + + XSendEvent (compositor.display, + DefaultRootWindow (compositor.display), + False, + SubstructureRedirectMask | SubstructureNotifyMask, + &msg); +} + +static void +ForceEntry (Seat *seat, Window source, double x, double y) +{ + Surface *surface; + Window target; + + if (seat->last_seen_surface) + { + surface = seat->last_seen_surface; + target = XLWindowFromSurface (surface); + + if (target == None) + /* If the window is gone, make the target surface NULL. */ + SwapUnlockSurface (seat, NULL); + else + { + if (source != target) + /* If the source is something other than the target, + translate the coordinates to the target. */ + TranslateCoordinates (source, target, x, y, &x, &y); + + /* Finally, translate the coordinates to the target + view. */ + TransformToView (surface->view, x, y, &x, &y); + } + } + else + return; + + if (!SendEnter (seat, surface, x, y)) + /* Apparently what is done by other compositors when no + wl_pointer object exists for the surface's client is to + revert back to the default cursor. */ + UndefineCursorOn (seat, surface); +} + +static void +CancelDrag (Seat *seat, Window event_source, double x, double y) +{ + if (!(seat->flags & IsDragging)) + return; + + /* If the last seen surface is now different from the drag start + surface, clear the cursor on the latter. */ + if (seat->drag_start_surface != seat->last_seen_surface + && seat->cursor) + FreeCursor (seat->cursor); + + if (seat->data_source) + XLDoDragFinish (seat); + + /* And cancel the drag flag. */ + seat->flags &= ~IsDragging; + + /* Clear the surface and free its unmap callback. */ + seat->drag_start_surface = NULL; + XLSurfaceCancelUnmapCallback (seat->drag_start_unmap_callback); + + /* Release the active grab as well, and leave any surface we + entered. */ + if (seat->drag_last_surface) + DragLeave (seat); + + XIUngrabDevice (compositor.display, seat->master_pointer, + seat->drag_grab_time); + + if (seat->data_source) + { + /* Attach a NULL drag device to this source. */ + XLDataSourceAttachDragDevice (seat->data_source, NULL); + + /* Cancel the destroy callback. */ + XLDataSourceCancelDestroyCallback (seat->data_source_destroy_callback); + + /* If a data source is attached, clear it now. */ + seat->data_source = NULL; + seat->data_source_destroy_callback = NULL; + } + + /* If nothing was dropped, emit the cancelled event. */ + if (seat->data_source && !(seat->flags & IsDropped)) + XLDataSourceSendDropCancelled (seat->data_source); + + /* Next, enter the last seen surface. This is necessary when + releasing the pointer on top of a different surface after the + drag and drop operation completes. */ + ForceEntry (seat, event_source, x, y); + + /* Destroy the grab window. */ + XDestroyWindow (compositor.display, seat->grab_window); + seat->grab_window = None; + + /* Cancel the icon surface. */ + if (seat->icon_surface) + XLReleaseIconSurface (seat->icon_surface); + seat->icon_surface = NULL; +} + +static void +HandleDragSurfaceUnmapped (void *data) +{ + Seat *seat; + double root_x, root_y; + + seat = data; + + /* Cancel the drag and drop operation. We don't know where the + pointer is ATM, so query for that information. */ + + QueryPointer (seat, DefaultRootWindow (compositor.display), + &root_x, &root_y); + + /* And cancel the drag with the pointer position. */ + + CancelDrag (seat, DefaultRootWindow (compositor.display), + root_x, root_y); +} + +static void +HandleDataSourceDestroyed (void *data) +{ + Seat *seat; + double root_x, root_y; + + seat = data; + + /* Clear those fields first, since their contents are now + destroyed. */ + seat->data_source = NULL; + seat->data_source_destroy_callback = NULL; + + /* Cancel the drag and drop operation. We don't know where the + pointer is ATM, so query for that information. */ + + QueryPointer (seat, DefaultRootWindow (compositor.display), + &root_x, &root_y); + + /* And cancel the drag with the pointer position. */ + + CancelDrag (seat, DefaultRootWindow (compositor.display), + root_x, root_y); +} + +static Window +MakeGrabWindow (void) +{ + Window window; + XSetWindowAttributes attrs; + + /* Make the window override redirect. */ + attrs.override_redirect = True; + + /* The window has to be mapped and visible, or the grab will + fail. */ + window = XCreateWindow (compositor.display, + DefaultRootWindow (compositor.display), + 0, 0, 1, 1, 0, CopyFromParent, InputOnly, + CopyFromParent, CWOverrideRedirect, &attrs); + + /* Clear the input region of the window. */ + XShapeCombineRectangles (compositor.display, window, + ShapeInput, 0, 0, NULL, 0, ShapeSet, + Unsorted); + + /* Map and return it. */ + XMapRaised (compositor.display, window); + return window; +} + +void +XLSeatBeginDrag (Seat *seat, DataSource *data_source, Surface *start_surface, + Surface *icon_surface, uint32_t serial) +{ + Window window; + Time time; + XIEventMask mask; + ptrdiff_t length; + WhatEdge edge; + Status state; + + /* If the surface is unmapped or window-less, don't allow dragging + from it. */ + + window = XLWindowFromSurface (start_surface); + + if (window == None) + return; + + /* To begin a drag and drop session, first look up the given serial. + Then, acquire an active grab on the pointer with owner_events set + to true. + + While the grab is active, all crossing and motion event dispatch + is hijacked to send events to the appropriate data device manager + instead. + + If, for some reason, data_source is destroyed, or the source + surface is destroyed, the grab is cancelled and the drag and drop + operation terminates. Otherwise, drop is sent to the correct + data device manager once all the pointer buttons are + released. */ + + if (seat->flags & IsDragging) + return; + + if (seat != IdentifySeat (&edge, serial)) + return; + + if (edge == AKeyboardEdge) + return; + + /* Use the time of the last button press or release. */ + time = seat->its_press_time; + + /* Initialize the event mask used for the grab. */ + length = XIMaskLen (XI_LASTEVENT); + mask.mask = alloca (length); + mask.mask_len = length; + mask.deviceid = XIAllMasterDevices; + + memset (mask.mask, 0, length); + + /* These are just the events we want reported relative to the grab + window. */ + + XISetMask (mask.mask, XI_Enter); + XISetMask (mask.mask, XI_Leave); + XISetMask (mask.mask, XI_Motion); + XISetMask (mask.mask, XI_ButtonPress); + XISetMask (mask.mask, XI_ButtonRelease); + + /* Create the window that will be used for the grab. */ + + XLAssert (seat->grab_window == None); + seat->grab_window = MakeGrabWindow (); + + /* Record that the drag has started, temporarily, for cursor + updates. */ + seat->flags |= IsDragging; + + /* Move the cursor to the drag grab window. */ + if (seat->cursor) + UpdateCursorFromSubcompositor (seat->cursor); + + /* Unset the drag flag again. */ + seat->flags &= ~IsDragging; + + /* Now, try to grab the pointer device with events reported relative + to the grab window. */ + + state = XIGrabDevice (compositor.display, seat->master_pointer, + seat->grab_window, time, None, XIGrabModeAsync, + XIGrabModeAsync, True, &mask); + + if (state != Success) + { + /* Destroy the grab window. */ + XDestroyWindow (compositor.display, seat->grab_window); + seat->grab_window = None; + + return; + } + + /* Since the active grab is now held and is owner_events, remove + focus locking. */ + if (seat->grab_held) + CancelGrabEarly (seat); + + /* Record the originating surface of the grab and add an unmap + callback. */ + seat->drag_start_surface = start_surface; + seat->drag_start_unmap_callback + = XLSurfaceRunAtUnmap (start_surface, HandleDragSurfaceUnmapped, + seat); + + seat->flags &= ~IsDragging; + + /* Since dragging has started, leave the last seen surface now. + Preserve the cursor, since that surface is where the cursor is + currently set. */ + EnteredSurface (seat, NULL, CurrentTime, 0, 0, True); + + if (data_source) + { + /* Record the data source. */ + XLDataSourceAttachDragDevice (data_source, + seat->data_device); + seat->data_source = data_source; + + /* Add a destroy callback. */ + seat->data_source_destroy_callback + = XLDataSourceAddDestroyCallback (data_source, + HandleDataSourceDestroyed, + seat); + } + else + /* This should've been destroyed by CancelGrab. */ + XLAssert (seat->data_source == NULL); + + /* If the icon surface was specified, give it the right type and + attach the role. */ + if (icon_surface) + { + /* Note that the caller is responsible for validating the type + of the icon surface. */ + icon_surface->role_type = DndIconType; + seat->icon_surface = XLGetIconSurface (icon_surface); + + /* Move the icon surface to the last known root window position + of the pointer. */ + XLMoveIconSurface (seat->icon_surface, seat->its_root_x, + seat->its_root_y); + } + + /* Record that the drag has really started. */ + seat->drag_grab_time = time; + seat->flags |= IsDragging; + seat->flags &= ~IsDropped; +} + +Time +XLSeatGetLastUserTime (Seat *seat) +{ + return GetLastUserTime (seat); +} + +void +XLInitSeats (void) +{ + int rc; + + /* This is the version of the input extension that we want. */ + xi2_major = 2; + xi2_minor = 3; + + if (XQueryExtension (compositor.display, "XInputExtension", + &xi2_opcode, &xi_first_event, &xi_first_error)) + { + rc = XIQueryVersion (compositor.display, &xi2_major, &xi2_minor); + + if (xi2_major < 2 || (xi2_major == 2 && xi2_minor < 3) || rc) + { + fprintf (stderr, "version 2.3 or later of of the X Input Extension is" + " not present on the X server\n"); + exit (1); + } + } + + seats = XLCreateAssocTable (25); + devices = XLCreateAssocTable (25); + keymap_fd = -1; + + SelectDeviceEvents (); + SetupInitialDevices (); + SetupKeymap (); +} + +DataSource * +XLSeatGetDragDataSource (Seat *seat) +{ + return seat->data_source; +} + +void * +XLSeatAddModifierCallback (Seat *seat, void (*changed) (unsigned int, void *), + void *data) +{ + ModifierChangeCallback *callback; + + callback = XLMalloc (sizeof *callback); + callback->next = seat->modifier_callbacks.next; + callback->last = &seat->modifier_callbacks; + seat->modifier_callbacks.next->last = callback; + seat->modifier_callbacks.next = callback; + + callback->changed = changed; + callback->data = data; + + return callback; +} + +void +XLSeatRemoveModifierCallback (void *key) +{ + ModifierChangeCallback *callback; + + callback = key; + callback->next->last = callback->last; + callback->last->next = callback->next; + + XLFree (callback); +} + +unsigned int +XLSeatGetEffectiveModifiers (Seat *seat) +{ + return seat->base | seat->locked | seat->latched; +} diff --git a/select.c b/select.c new file mode 100644 index 0000000..1f50d86 --- /dev/null +++ b/select.c @@ -0,0 +1,1844 @@ +/* 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 . */ + +#include +#include +#include + +#include "compositor.h" + +/* Caveat: the MULTIPLE target implementation is completely untested + and probably doesn't work. */ + +typedef struct _PropertyAtom PropertyAtom; +typedef struct _SelectInputData SelectInputData; +typedef struct _SelectionOwnerInfo SelectionOwnerInfo; +typedef struct _QueuedTransfer QueuedTransfer; +typedef struct _MultipleRecord MultipleRecord; + +/* Structure representing a single property allocated for selection + transfers. + + Since Wayland data sources are not read from a wl_surface, there is + no way to associate each transfer with a specific window. In + addition, the compositor might want to obtain selection data in the + background as well. Because of this, we must rely on all ongoing + data transfers from other owners having a unique property. */ + +struct _PropertyAtom +{ + /* The atom in question. */ + Atom atom; + + /* The id used to generate the atom. */ + uint32_t counter; + + /* The next and last atoms in this chain. */ + PropertyAtom *next, *last; +}; + +struct _SelectionOwnerInfo +{ + /* When this selection was last owned. */ + Time time; + + /* The targets of this atom. */ + Atom *targets; + + /* The number of targets. */ + int ntargets; + + /* The callback for this selection. NULL means the selection is no + longer owned. */ + GetDataFunc (*get_transfer_function) (WriteTransfer *, + Atom, Atom *); +}; + +enum + { + IsFinished = 1, + IsIncr = (1 << 1), + IsWaitingForChunk = (1 << 2), + IsStarted = (1 << 3), + IsFailed = (1 << 4), + IsWaitingForDelete = (1 << 5), + IsWaitingForIncr = (1 << 6), + IsReadable = (1 << 7), + IsFlushed = (1 << 8), + }; + +struct _ReadTransfer +{ + /* The selection owner. */ + Window owner; + + /* Some state associated with this transfer. */ + int state; + + /* The atom being used to transfer data from the selection + owner. */ + PropertyAtom *property; + + /* The timestamp at which the selection request was issued. */ + Time time; + + /* The selection and target that are being requested. */ + Atom selection, target; + + /* The current offset, in 4-byte multiples, into the property data. + Reset each time a new chunk of data is available. */ + unsigned long read_offset; + + /* The format of the property data. */ + unsigned long read_format; + + /* A function called once selection data begins to be read from the + selection. The second arg is the type of the selection data, and + the third arg is the format of the selection data. */ + void (*data_start_func) (ReadTransfer *, Atom, int); + + /* A function called once data some data can be read from the + selection. It may be called multiple times, depending on how big + the response from the owner is. + + The args are as follows: a pointer to the transfer, the atom + describing the type of the data, the format of the data (see the + doc for XGetWindowProperty), and the size of the data that can be + read in bytes. */ + void (*data_read_func) (ReadTransfer *, Atom, int, ptrdiff_t); + + /* A function called after the selection transfer completes. The + second arg means whether or not the transfer was successful. */ + Bool (*data_finish_func) (ReadTransfer *, Bool); + + /* Caller-specified data for those functions. */ + void *data; + + /* The next and last transfers on this chain. */ + ReadTransfer *next, *last; + + /* A timer that times out after 5 seconds of inactivity. */ + Timer *timeout; +}; + +struct _WriteTransfer +{ + /* The next and last transfers on this chain. */ + WriteTransfer *next, *last; + + /* The requestor of the transfer. Spelt with an "O" because that's + what the ICCCM does. */ + Window requestor; + + /* Some state associated with this transfer. */ + int state; + + /* The selection being requested from us, the target, the property, + and the type of the data. */ + Atom selection, target, property, type; + + /* The time of the request. */ + Time time; + + /* Data buffer. */ + unsigned char *buffer; + + /* The size of the data buffer, and how much has been read from + it. */ + ptrdiff_t size, offset; + + /* The SelectionNotify event that should be sent. */ + XEvent event; + + /* Or a MultipleRecord that should be worked on. */ + MultipleRecord *record; + + /* The offset of this transfer into record->atoms. */ + unsigned long multiple_offset; + + /* Function called to return a piece of selection data in a buffer. + If size arg is -1, free associated data. Return False if EOF was + reached. */ + GetDataFunc transfer_function; + + /* User data for the write transfer. */ + void *data; + + /* A timer that times out after 5 seconds of inactivity. */ + Timer *timeout; + +#ifdef DEBUG + /* Total bytes of data written to the X server as part of this + transfer. */ + size_t total_written; +#endif +}; + +struct _QueuedTransfer +{ + /* Queued event. */ + XEvent event; + + /* Next and last items in the queue. */ + QueuedTransfer *next, *last; +}; + +struct _SelectInputData +{ + /* Number of requests from this requestor that are in progress. */ + int refcount; +}; + +struct _MultipleRecord +{ + /* Number of conversions that still have not been made. */ + int pending; + + /* The notification event that should be sent. */ + XEvent event; + + /* The atom pair array. Free this with XFree! */ + Atom *atoms; + + /* The number of atoms in the atom pair array. */ + unsigned long nitems; +}; + +/* Window used for selection data transfer. */ +Window selection_transfer_window; + +/* Assoc table used to keep track of PropertyNotify selections. */ +static XLAssocTable *foreign_notify_table; + +/* Counter used for determining the selection property atom. */ +static uint32_t prop_counter; + +/* Chain of selection property atoms currently in use. */ +static PropertyAtom prop_atoms; + +/* Chain of selection property atoms that can be reused. */ +static PropertyAtom free_list; + +/* Circular queue of all outstanding selection data transfers to this + client. */ +static ReadTransfer read_transfers; + +/* List of all outstanding selection data transfers to other + clients. */ +static WriteTransfer write_transfers; + +/* Table of atoms to selection owner info. */ +static XLAssocTable *selection_owner_info; + +/* Circular queue of SelectionRequests, to be drained once all + outstanding write transfers finish. */ +static QueuedTransfer queued_transfers; + +#ifdef DEBUG + +static void __attribute__ ((__format__ (gnu_printf, 1, 2))) +DebugPrint (const char *format, ...) +{ + va_list ap; + + va_start (ap, format); + vfprintf (stderr, format, ap); + va_end (ap); +} + +#else +#define DebugPrint(fmt, ...) ((void) 0) +#endif + +/* Allocate a new atom to use for selection property transfers. This + atom is guaranteed to not be currently in use for a selection + transfer to selection_transfer_window. + + Once the transfer completes, ReleasePropAtom must be called with + the returned PropertyAtom object to allow reusing the atom in the + future. */ + +static PropertyAtom * +AllocPropAtom (void) +{ + PropertyAtom *atom; + char name[sizeof "_XL_UXXXXXXXX" + 1]; + + if (free_list.next != &free_list) + { + /* There is a free atom that we can use. Remove it from the + free list. */ + atom = free_list.next; + atom->next->last = atom->last; + atom->last->next = atom->next; + } + else + { + if (IntAddWrapv (prop_counter, 1, &prop_counter)) + { + fprintf (stderr, "Failed to allocate selection property" + " after running out of valid values!"); + abort (); + } + + sprintf (name, "_XL_U%x", prop_counter); + atom = XLMalloc (sizeof *atom); + + /* Use XInternAtom instead of InternAtom. These atoms should + only be interned once, so there is no point allocating memory + in the global atoms table. */ + atom->atom = XInternAtom (compositor.display, name, False); + atom->counter = prop_counter; + } + + /* Now, link atom onto the used list and return it. */ + atom->last = &prop_atoms; + atom->next = prop_atoms.next; + + prop_atoms.next->last = atom; + prop_atoms.next = atom; + + return atom; +} + +static void +ReleasePropAtom (PropertyAtom *atom) +{ + /* Move atom from the used list onto the free list. */ + + atom->next->last = atom->last; + atom->last->next = atom->next; + + atom->last = &free_list; + atom->next = free_list.next; + + free_list.next->last = atom; + free_list.next = atom; + + /* Now the atom has been released and can also be used for future + selection data transfers. */ +} + +static void +FinishReadTransfer (ReadTransfer *transfer, Bool success) +{ + Bool delay; + + if (transfer->data_finish_func + /* This means to delay deallocating the transfer for a + while. */ + && !transfer->data_finish_func (transfer, success)) + delay = True; + else + delay = False; + + transfer->next->last = transfer->last; + transfer->last->next = transfer->next; + + ReleasePropAtom (transfer->property); + RemoveTimer (transfer->timeout); + + if (!delay) + XLFree (transfer); +} + +static void +HandleTimeout (Timer *timer, void *data, struct timespec time) +{ + ReadTransfer *transfer; + + transfer = data; + + /* Delete the data transfer property from the window. */ + XDeleteProperty (compositor.display, + selection_transfer_window, + transfer->property->atom); + + /* Finish the read transfer. */ + FinishReadTransfer (transfer, False); +} + +static void +CancelTransferEarly (ReadTransfer *transfer) +{ + /* Delete the data transfer property from the window. */ + XDeleteProperty (compositor.display, + selection_transfer_window, + transfer->property->atom); + + /* Mark the transfer as finished. */ + transfer->state |= IsFailed | IsFinished; +} + +/* After calling this function, set the appropriate functions! */ + +static ReadTransfer * +ConvertSelection (Atom selection, Atom target, Time time) +{ + ReadTransfer *transfer; + + /* If time is CurrentTime, obtain the time from the X server. */ + if (time == CurrentTime) + time = XLGetServerTimeRoundtrip (); + + transfer = XLCalloc (1, sizeof *transfer); + transfer->property = AllocPropAtom (); + transfer->time = time; + transfer->selection = selection; + transfer->target = target; + + /* Link the transfer onto the list of outstanding selection + transfers. */ + transfer->next = read_transfers.next; + transfer->last = &read_transfers; + + /* Add a timeout for 5 seconds. */ + transfer->timeout = AddTimer (HandleTimeout, transfer, + MakeTimespec (5, 0)); + + read_transfers.next->last = transfer; + read_transfers.next = transfer; + + /* Delete the property from the window beforehand. The property + might be left over from a failed transfer. */ + XDeleteProperty (compositor.display, selection_transfer_window, + transfer->property->atom); + + /* Now issue the ConvertSelection request. */ + XConvertSelection (compositor.display, selection, target, + transfer->property->atom, + selection_transfer_window, time); + + return transfer; +} + +ReadTransfer * +ConvertSelectionFuncs (Atom selection, Atom target, Time time, void *data, + void (*data_start) (ReadTransfer *, Atom, int), + void (*data_read) (ReadTransfer *, Atom, int, ptrdiff_t), + Bool (*data_finish) (ReadTransfer *, Bool)) +{ + ReadTransfer *transfer; + + transfer = ConvertSelection (selection, target, time); + transfer->data = data; + transfer->data_start_func = data_start; + transfer->data_read_func = data_read; + transfer->data_finish_func = data_finish; + + return transfer; +} + +/* Find the first outstanding read transfer for SELECTION, TARGET and + TIME in the queue of outstanding selection transfers. */ + +static ReadTransfer * +FindReadTransfer (Atom selection, Atom target, Time time) +{ + ReadTransfer *transfer; + + transfer = read_transfers.last; + + while (transfer != &read_transfers) + { + if (transfer->selection == selection + && transfer->target == target + && transfer->time == time) + return transfer; + + transfer = transfer->last; + } + + return NULL; +} + +long +SelectionQuantum (void) +{ + long request; + + request = XExtendedMaxRequestSize (compositor.display); + + if (!request) + request = XMaxRequestSize (compositor.display); + + return request; +} + +static ptrdiff_t +FormatTypeSize (int format) +{ + switch (format) + { + case 8: + return sizeof (char); + + case 16: + return sizeof (short); + + case 32: + return sizeof (long); + } + + abort (); +} + +void +FinishTransfers (void) +{ + ReadTransfer *transfer, *last; + + transfer = read_transfers.last; + + while (transfer != &read_transfers) + { + last = transfer; + transfer = transfer->last; + + if (last->state & IsFinished) + FinishReadTransfer (last, !(last->state & IsFailed)); + } +} + +/* Signal that the current chunk of data from TRANSFER has been + completely read. */ + +static void +FinishChunk (ReadTransfer *transfer) +{ + if (transfer->state & IsIncr) + { + transfer->state |= IsWaitingForChunk; + return; + } + + /* If INCR transfer is not in progress, post a timer to finish + TRANSFER entirely, the next time the event loop is entered. */ + transfer->state |= IsFinished; +} + +void +SkipChunk (ReadTransfer *transfer) +{ + /* Just delete the property. */ + XDeleteProperty (compositor.display, + selection_transfer_window, + transfer->property->atom); + + /* And mark the chunk as finished. */ + FinishChunk (transfer); +} + +/* Read a chunk of data from TRANSFER. LONG_LENGTH gives the length + of the data to read. Return a pointer to the data, or NULL if + reading the data failed, and return the actual length of the data + in *NBYTES. Free the data returned using Xlib functions! */ + +unsigned char * +ReadChunk (ReadTransfer *transfer, int long_length, ptrdiff_t *nbytes, + ptrdiff_t *bytes_after_return) +{ + unsigned char *prop_data; + Atom actual_type; + int actual_format, rc; + unsigned long nitems, bytes_after; + + prop_data = NULL; + + /* Now read the actual property data. */ + rc = XGetWindowProperty (compositor.display, selection_transfer_window, + transfer->property->atom, transfer->read_offset, + long_length, True, AnyPropertyType, &actual_type, + &actual_format, &nitems, &bytes_after, &prop_data); + + /* Reading the property data failed. Signal failure by returning + NULL. Also, cancel the whole transfer here too. */ + if (!prop_data) + { + CancelTransferEarly (transfer); + return NULL; + } + + if (actual_type == None + || actual_format != transfer->read_format + || rc != Success) + { + /* Same goes here. */ + if (prop_data) + XFree (prop_data); + + CancelTransferEarly (transfer); + return NULL; + } + + if (!bytes_after) + /* The property has now been deleted, so finish the chunk. */ + FinishChunk (transfer); + + /* Now return the number of bytes read. */ + *nbytes = nitems * FormatTypeSize (actual_format); + + /* Bump the read offset. */ + transfer->read_offset += long_length; + + /* Return bytes_after to the caller. */ + if (bytes_after_return) + { + if (actual_format == 32) + *bytes_after_return = bytes_after * (sizeof (long) / 4); + else + *bytes_after_return = bytes_after; + } + + return prop_data; +} + +static void +StartSelectionRead (ReadTransfer *transfer) +{ + unsigned char *prop_data; + Atom actual_type; + int actual_format, rc; + unsigned long nitems, bytes_after; + + prop_data = NULL; + + /* First, figure out how big the property data is. */ + rc = XGetWindowProperty (compositor.display, selection_transfer_window, + transfer->property->atom, 0, 0, True, AnyPropertyType, + &actual_type, &actual_format, &nitems, &bytes_after, + &prop_data); + + if (prop_data) + XFree (prop_data); + + if (rc != Success) + { + FinishReadTransfer (transfer, False); + return; + } + + /* If the property is of type INCR, this is the beginning of an INCR + transfer. In this case, delete prop_data, the property, and wait + for a the property to be set with a new value by the owner. */ + + if (actual_type == INCR) + { + transfer->state |= IsIncr; + transfer->state |= IsWaitingForChunk; + + XDeleteProperty (compositor.display, selection_transfer_window, + transfer->property->atom); + + return; + } + + /* If actual_type is None, the property does not exist. Signal + failure. */ + if (actual_type == None || !actual_format) + { + FinishReadTransfer (transfer, False); + return; + } + + /* If bytes_after is 0, the property is empty. Immediately call + data_finish_func. If the transfer is incremental, then an empty + property means the data has been fully transferred. */ + if (!bytes_after) + goto finish_early; + + /* Otherwise, we now know how long the selection is, and the type of + the data. Announce that information, along with the beginning of + this chunk. */ + + transfer->read_offset = 0; + + /* Also announce the format. */ + transfer->read_format = actual_format; + + if (transfer->data_start_func + /* Make sure this isn't run multiple times in the case of INCR + selections. */ + && !(transfer->state & IsStarted)) + transfer->data_start_func (transfer, actual_type, + actual_format); + + transfer->state |= IsStarted; + + if (actual_format == 32) + /* Give actual data size, depending on the size of long. */ + bytes_after = bytes_after * (sizeof (long) / 4); + + if (transfer->data_read_func) + transfer->data_read_func (transfer, actual_type, actual_format, + bytes_after); + + return; + + finish_early: + FinishReadTransfer (transfer, True); + return; +} + +static ReadTransfer * +FindReadTransferByProp (Atom atom) +{ + ReadTransfer *transfer; + + transfer = read_transfers.last; + + while (transfer != &read_transfers) + { + if (transfer->property->atom == atom) + return transfer; + + transfer = transfer->last; + } + + return NULL; +} + +static Bool +SendEvent (XEvent *event) +{ + CatchXErrors (); + XSendEvent (compositor.display, event->xany.window, + False, NoEventMask, event); + return UncatchXErrors (NULL); +} + +static void +SendEventUnsafe (XEvent *event) +{ + XSendEvent (compositor.display, event->xany.window, + False, NoEventMask, event); +} + +static Bool +CanConvertTarget (SelectionOwnerInfo *info, Atom target) +{ + int i; + + if (target == TARGETS + || target == TIMESTAMP) + return True; + + for (i = 0; i < info->ntargets; ++i) + { + if (target == info->targets[i]) + return True; + } + + return False; +} + +static GetDataFunc +GetTransferFunction (SelectionOwnerInfo *info, + WriteTransfer *transfer, + Atom target, Atom *type) +{ + return info->get_transfer_function (transfer, target, type); +} + +static WriteTransfer * +FindWriteTransfer (Window requestor, Atom property) +{ + WriteTransfer *transfer; + + transfer = write_transfers.next; + + while (transfer != &write_transfers) + { + if (transfer->requestor == requestor + && transfer->property == property) + return transfer; + + transfer = transfer->next; + } + + return NULL; +} + +static Bool +FindQueuedTransfer (Window requestor, Atom property) +{ + QueuedTransfer *transfer; + + transfer = queued_transfers.next; + + while (transfer != &queued_transfers) + { + if (transfer->event.xselectionrequest.requestor == requestor + || transfer->event.xselectionrequest.property == property) + return True; + + transfer = transfer->next; + } + + return False; +} + +static void +SignalConversionPerformed (WriteTransfer *transfer) +{ + if (transfer->record) + { + /* This conversion is part of a larger conversion to MULTIPLE. + Subtract this conversion from the number of conversions that + are still pending. If there are no more pending conversions, + send the SelectionNotify event and return. */ + + DebugPrint ("Conversion complete; %d conversions are still pending\n", + transfer->record->pending); + + if (!(--transfer->record->pending)) + { + SendEventUnsafe (&transfer->record->event); + + XFree (transfer->record->atoms); + XLFree (transfer->record); + } + + /* In any case, make transfer->record NULL. */ + transfer->record = NULL; + } + + /* If the SelectionNotify has not yet been sent, send it now. */ + if (transfer->event.type) + SendEventUnsafe (&transfer->event); + transfer->event.type = 0; +} + +static void +FlushTransfer (WriteTransfer *transfer, Bool force) +{ + long size; + + /* Flushing the buffer while waiting for property deletion is an + invalid operation. */ + XLAssert (!(transfer->state & IsWaitingForDelete)); + + if ((transfer->state & IsStarted) || force) + { + if (force) + DebugPrint ("Forcing transfer\n"); + else + DebugPrint ("Starting transfer\n"); + + /* If the transfer has already started, or FORCE is set, write + the property to the window. */ + CatchXErrors (); + DebugPrint ("Writing property of size %zd\n", + transfer->offset); + XChangeProperty (compositor.display, transfer->requestor, + transfer->property, transfer->type, 8, + PropModeReplace, transfer->buffer, + transfer->offset); +#ifdef DEBUG + transfer->total_written += transfer->offset; +#endif + SignalConversionPerformed (transfer); + UncatchXErrors (NULL); + + transfer->offset = 0; + } + else + { + DebugPrint ("Writing INCR property...\n"); + + /* Otherwise, write an INCR property and wait for it to be + deleted. */ + size = transfer->size; + + CatchXErrors (); + XChangeProperty (compositor.display, transfer->requestor, + transfer->property, INCR, 32, + PropModeReplace, (unsigned char *) &size, 1); + SignalConversionPerformed (transfer); + UncatchXErrors (NULL); + + transfer->state |= IsWaitingForIncr; + } + + /* Now, begin to wait for the property to be deleted. */ + transfer->state |= IsWaitingForDelete; + + /* And mark the transfer as having been flushed. */ + transfer->state |= IsFlushed; +} + +static void +SelectPropertyNotify (Window window) +{ + SelectInputData *data; + + data = XLLookUpAssoc (foreign_notify_table, window); + + if (data) + data->refcount++; + else + { + data = XLMalloc (sizeof *data); + data->refcount = 1; + XLMakeAssoc (foreign_notify_table, window, data); + + /* Actually select for input from the window. */ + CatchXErrors (); + XSelectInput (compositor.display, window, + PropertyChangeMask); + UncatchXErrors (NULL); + + DebugPrint ("Selecting for PropertyChangeMask on %lu\n", + window); + } +} + +static void +DeselectPropertyNotify (Window window) +{ + SelectInputData *data; + + data = XLLookUpAssoc (foreign_notify_table, window); + XLAssert (data != NULL); + + if (--data->refcount) + return; + + DebugPrint ("De-selecting for PropertyChangeMask on %lu\n", + window); + + CatchXErrors (); + XSelectInput (compositor.display, window, NoEventMask); + UncatchXErrors (NULL); + + XLDeleteAssoc (foreign_notify_table, window); + XLFree (data); +} + +/* Forward declaration. */ + +static void DrainQueuedTransfers (void); + +static void +FreeTransfer (WriteTransfer *transfer) +{ + DebugPrint ("Deallocating transfer data\n"); + + /* The transfer completed. Unlink it, and also deselect for + PropertyNotify events from the requestor. */ + + transfer->next->last = transfer->last; + transfer->last->next = transfer->next; + + if (transfer->state & IsStarted) + { +#ifdef DEBUG + DebugPrint ("Writing zero-length property data; total: %zu\n", + transfer->total_written); +#endif + + CatchXErrors (); + /* Write zero-length property data to complete the INCR + transfer. */ + XChangeProperty (compositor.display, transfer->requestor, + transfer->property, transfer->type, 8, + PropModeReplace, NULL, 0); + UncatchXErrors (NULL); + } + + if (transfer->record) + { + /* Since FreeTransfer was called without transfer->record having + being released earlier, the conversion associated with this + transfer has failed. Write the conversion failure. */ + + transfer->record->atoms[transfer->multiple_offset] = None; + + DebugPrint ("Conversion at offset %lu failed\n", + transfer->multiple_offset); + + CatchXErrors (); + XChangeProperty (compositor.display, transfer->requestor, + transfer->record->event.xselection.property, + ATOM_PAIR, 32, PropModeReplace, + (unsigned char *) transfer->record->atoms, + transfer->record->nitems); + UncatchXErrors (NULL); + + /* Now, dereference the record, and send the SelectionNotify if + there are no more pending conversions. */ + + if (!(--transfer->record->pending)) + { + DebugPrint ("Completing MULTIPLE transfer\n"); + + SendEventUnsafe (&transfer->record->event); + + XFree (transfer->record->atoms); + XLFree (transfer->record); + } + } + + /* If there are no more transfers, drain queued ones. */ + if (write_transfers.next == &write_transfers) + DrainQueuedTransfers (); + + DeselectPropertyNotify (transfer->requestor); + RemoveTimer (transfer->timeout); + XLFree (transfer->buffer); + XLFree (transfer); +} + +static void +FinishTransferEarly (WriteTransfer *transfer) +{ + /* Call the transfer function with negative values, to force it to + free data. */ + + transfer->transfer_function (transfer, NULL, -1, NULL); + + /* Finish the transfer now. */ + FreeTransfer (transfer); +} + +static void +TransferFinished (WriteTransfer *transfer) +{ + if (transfer->state & IsWaitingForDelete) + { + /* If we are still waiting for property deletion, simply set the + IsFinished flag. Zero-length property data will be written the + next time the property is deleted to mark the completion of + this transfer. */ + transfer->state |= IsFinished; + + DebugPrint ("Transfer finished; waiting for property deletion\n"); + } + else if (transfer->offset + /* Even if there is no more data, we must still write + zero-length property data to complete the selection + transfer if nothing has previously been written. */ + || !(transfer->state & IsFlushed)) + { + /* There is still property data left to be written. */ + FlushTransfer (transfer, True); + transfer->state |= IsFinished; + + DebugPrint ("Transfer finished, but there is still property data" + " unwritten\n"); + } + else + /* The transfer is really finished. */ + FreeTransfer (transfer); +} + +static void +TransferBecameReadable (WriteTransfer *transfer) +{ + ptrdiff_t bytes_read; + ReadStatus status; + + /* If we are still waiting for the INCR property to be deleted, + don't read anything, but flag the read as pending. */ + + if (transfer->state & IsWaitingForDelete) + { + DebugPrint ("Transfer became readable, but we are still waiting" + " for a property deletion\n"); + transfer->state |= IsReadable; + return; + } + + DebugPrint ("Reading from transfer function\n"); + + /* The data source became readable. Every time the source becomes + readable, try to fill up the transfer buffer. + + If we get EOF without filling up the buffer, simply set the + property to the data and the correct type and finish the + transfer; otherwise, initiate INCR transfer. */ + + transfer->state &= ~IsReadable; + + status = transfer->transfer_function (transfer, + (transfer->buffer + + transfer->offset), + (transfer->size + - transfer->offset), + &bytes_read); + + if (status != EndOfFile) + { + transfer->offset += bytes_read; + DebugPrint ("Read %td bytes, offset is now %td into %td\n", + bytes_read, transfer->offset, transfer->size); + + /* Make sure nothing overflowed. */ + XLAssert (transfer->offset <= transfer->size); + + if (transfer->offset == transfer->size + /* If a bigger buffer is required, flush the transfer now, + so the entire transfer buffer can be reused. */ + || status == NeedBiggerBuffer) + { + if (status == NeedBiggerBuffer) + /* Since a bigger buffer is needed, that means there is + still data buffered up to be read. */ + transfer->state |= IsReadable; + + FlushTransfer (transfer, False); + } + } + else + { + DebugPrint ("Transfer complete, bytes read as part of EOF: %td, off: %td\n", + transfer->offset, transfer->size); + + transfer->offset += bytes_read; + TransferFinished (transfer); + } +} + +static void +ConvertSelectionTargets1 (SelectionOwnerInfo *info, + Window requestor, Atom property) +{ + Atom *targets; + + targets = alloca ((3 + info->ntargets) + * sizeof *targets); + + /* Add the required targets, TARGETS and MULTIPLE. */ + targets[0] = TARGETS; + targets[1] = MULTIPLE; + targets[2] = TIMESTAMP; + + /* And the rest of the targets. */ + memcpy (&targets[3], info->targets, + info->ntargets * sizeof *targets); + + XChangeProperty (compositor.display, requestor, + property, XA_ATOM, 32, PropModeReplace, + (unsigned char *) targets, + 2 + info->ntargets); +} + +static void +ConvertSelectionTargets (SelectionOwnerInfo *info, XEvent *notify) +{ + + /* Set the property and send the SelectionNotify event. */ + CatchXErrors (); + ConvertSelectionTargets1 (info, notify->xselection.requestor, + notify->xselection.property); + SendEventUnsafe (notify); + UncatchXErrors (NULL); +} + +static void +ConvertSelectionTimestamp1 (SelectionOwnerInfo *info, Window requestor, + Window property) +{ + XChangeProperty (compositor.display, requestor, property, + XA_ATOM, 32, PropModeReplace, + (unsigned char *) &info->time, 1); +} + +static void +ConvertSelectionTimestamp (SelectionOwnerInfo *info, XEvent *notify) +{ + /* Set the property and send the SelectionNotify event. */ + CatchXErrors (); + ConvertSelectionTimestamp1 (info, notify->xselection.requestor, + notify->xselection.property); + SendEventUnsafe (notify); + UncatchXErrors (NULL); +} + +static void +QueueTransfer (XEvent *event) +{ + QueuedTransfer *transfer; + + transfer = XLMalloc (sizeof *transfer); + transfer->event = *event; + transfer->next = queued_transfers.next; + transfer->last = &queued_transfers; + queued_transfers.next->last = transfer; + queued_transfers.next = transfer; +} + +static void +HandleWriteTimeout (Timer *timer, void *data, struct timespec time) +{ + DebugPrint ("Transfer timeout\n"); + + FinishTransferEarly (data); +} + +static void +ConvertSelectionMultiple (SelectionOwnerInfo *info, XEvent *event, + XEvent *notify) +{ + Status rc; + unsigned char *prop_data; + Atom *atoms; + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after, i, quantum; + WriteTransfer *transfer; + Bool prop_data_changed; + MultipleRecord *record; + + prop_data = NULL; + prop_data_changed = False; + + /* Try to get the ATOM_PAIRs describing the targets and properties + from the source. */ + CatchXErrors (); + rc = XGetWindowProperty (compositor.display, + event->xselectionrequest.requestor, + event->xselectionrequest.property, + 0, 65535, False, ATOM_PAIR, + &actual_type, &actual_format, + &nitems, &bytes_after, &prop_data); + UncatchXErrors (NULL); + + if (rc != Success || actual_format != 32 || nitems % 2 + || actual_type != ATOM_PAIR || !prop_data) + { + if (prop_data) + XFree (prop_data); + + DebugPrint ("Failed to retrieve ATOM_PAIR parameter\n"); + return; + } + + atoms = (Atom *) prop_data; + + DebugPrint ("Number of items in atom pair: %lu\n", nitems / 2); + + for (i = 0; i < nitems; i += 2) + { + DebugPrint ("Verifying MULTIPLE transfer; target = %lu, property = %lu\n", + atoms[i + 0], atoms[i + 1]); + + if (FindWriteTransfer (event->xselectionrequest.requestor, atoms[1]) + || FindQueuedTransfer (event->xselectionrequest.requestor, atoms[1])) + { + DebugPrint ("Found ongoing selection transfer with same requestor " + "and property; this MULTIPLE request will have to be queued.\n"); + + QueueTransfer (event); + + if (prop_data) + XLFree (prop_data); + + return; + } + } + + record = XLMalloc (sizeof *record); + record->pending = 0; + record->event = *notify; + record->nitems = nitems; + record->atoms = atoms; + + CatchXErrors (); + + for (i = 0; i < nitems; i += 2) + { + /* Fortunately, we don't have to verify every transfer here to + make sure the client didn't specify duplicate property names, + as the ICCCM doesn't explicitly specify how programs should + behave in that case. Things will simply go wrong later on, + and the transfer will time out. */ + + DebugPrint ("Starting MULTIPLE transfer; target = %lu, property = %lu\n", + atoms[i + 0], atoms[i + 1]); + + if (atoms[0] == MULTIPLE) + { + DebugPrint ("Saw nested MULTIPLE transfer; " + "such conversions are not allowed\n"); + atoms[0] = None; + prop_data_changed = True; + + continue; + } + + if (!CanConvertTarget (info, atoms[0])) + { + DebugPrint ("Couldn't convert to target for a simple reason;" + " replacing atom with NULL\n"); + atoms[0] = None; + prop_data_changed = True; + + continue; + } + + if (atoms[0] == TARGETS) + { + DebugPrint ("Converting to special target TARGETS...\n"); + ConvertSelectionTargets1 (info, event->xselectionrequest.requestor, + atoms[1]); + + continue; + } + + if (atoms[0] == TIMESTAMP) + { + DebugPrint ("Converting to special target TIMESTAMP...\n"); + ConvertSelectionTimestamp1 (info, event->xselectionrequest.requestor, + atoms[1]); + + continue; + } + + /* Create the write transfer and link it onto the list. */ + transfer = XLCalloc (1, sizeof *transfer); + + /* This seems to be a sufficiently reasonable value. */ + quantum = MIN (SelectionQuantum (), 65535 * 2); + + transfer->next = write_transfers.next; + transfer->last = &write_transfers; + transfer->requestor = event->xselectionrequest.requestor; + transfer->selection = event->xselectionrequest.selection; + transfer->target = event->xselectionrequest.target; + transfer->property = atoms[1]; + transfer->time = event->xselectionrequest.time; + transfer->buffer = XLMalloc (quantum); + transfer->size = quantum; + + /* Add a timeout for 5 seconds. */ + transfer->timeout = AddTimer (HandleWriteTimeout, transfer, + MakeTimespec (5, 0)); + + write_transfers.next->last = transfer; + write_transfers.next = transfer; + + SelectPropertyNotify (transfer->requestor); + + transfer->transfer_function + = GetTransferFunction (info, transfer, + transfer->target, + &transfer->type); + + if (!transfer->transfer_function) + { + /* Something failed (most probably, we ran out of fds to + make the pipe). Cancel the transfer and signal + failure. */ + atoms[0] = None; + prop_data_changed = True; + + FreeTransfer (transfer); + } + else + { + /* Otherwise, add the transfer record. */ + record->pending++; + transfer->record = record; + transfer->multiple_offset = i; + } + } + + /* If prop_data changed, which happens when a conversion fails, + write its new contents back onto the requestor to let it know + which conversions could not be made. */ + if (prop_data_changed) + XChangeProperty (compositor.display, + event->xselectionrequest.requestor, + event->xselectionrequest.property, + ATOM_PAIR, 32, PropModeReplace, + prop_data, nitems); + + if (prop_data && !record->pending) + /* If record->pending, then the individual write transfers still + reference the atom pair data. */ + XFree (prop_data); + + /* If no pending conversions remain, immediately send success and + free the conversion. */ + if (!record->pending) + { + SendEvent (&record->event); + XLFree (record); + } + + UncatchXErrors (NULL); +} + +static Bool +HandleSelectionRequest (XEvent *event) +{ + XEvent notify; + WriteTransfer *transfer; + long quantum; + SelectionOwnerInfo *info; + + DebugPrint ("Received SelectionRequest. Time: %lu, requestor: %lu" + ", target: %lu, selection: %lu, property: %lu, serial: %lu\n", + event->xselectionrequest.time, + event->xselectionrequest.requestor, + event->xselectionrequest.target, + event->xselectionrequest.selection, + event->xselectionrequest.property, + event->xselectionrequest.serial); + + notify.xselection.type = SelectionNotify; + notify.xselection.requestor = event->xselectionrequest.requestor; + notify.xselection.time = event->xselectionrequest.time; + notify.xselection.target = event->xselectionrequest.target; + notify.xselection.selection = event->xselectionrequest.selection; + notify.xselection.property = None; + + info = XLLookUpAssoc (selection_owner_info, + event->xselectionrequest.selection); + + if (!info + || !info->get_transfer_function + || event->xselectionrequest.time < info->time + || !CanConvertTarget (info, event->xselectionrequest.target)) + { + DebugPrint ("Couldn't convert selection due to simple reason\n"); + + /* The event is out of date, or the selection cannot be + converted to the given target. Send failure. */ + SendEvent (¬ify); + return True; + } + + /* If a selection request with the same property and window already + exists, delay this request for later. */ + if (FindWriteTransfer (event->xselectionrequest.requestor, + event->xselectionrequest.property) + /* We need to look at the queue too; otherwise, events from the + future might be handled out of order, if the original write + transfer is gone, but some events are still queued. */ + || FindQueuedTransfer (event->xselectionrequest.requestor, + event->xselectionrequest.property)) + { + DebugPrint ("Queueing this selection request for later, because" + " an identical transfer is already taking place\n"); + QueueTransfer (event); + + return True; + } + + /* Otherwise, begin a selection transfer to the requestor. */ + notify.xselection.property = event->xselectionrequest.property; + + if (notify.xselection.property == None) + /* This is an obsolete client; use the target as the property. */ + notify.xselection.property = event->xselectionrequest.target; + + /* Handle special targets, such as TARGETS and TIMESTAMP. */ + if (notify.xselection.target == TARGETS) + { + DebugPrint ("Converting selection to special target TARGETS\n"); + ConvertSelectionTargets (info, ¬ify); + return True; + } + else if (notify.xselection.target == TIMESTAMP) + { + DebugPrint ("Converting selection to special target TIMESTAMP\n"); + ConvertSelectionTimestamp (info, ¬ify); + return True; + } + else if (notify.xselection.target == MULTIPLE) + { + if (event->xselectionrequest.property == None) + { + DebugPrint ("Got malformed MULTIPLE request with no property\n"); + notify.xselection.property = None; + SendEvent (¬ify); + + return True; + } + + DebugPrint ("Converting selection to special target MULTIPLE\n"); + ConvertSelectionMultiple (info, event, ¬ify); + return True; + } + + DebugPrint ("Starting selection transfer\n"); + + /* Create the write transfer and link it onto the list. */ + transfer = XLCalloc (1, sizeof *transfer); + + /* This seems to be a sufficiently reasonable value. */ + quantum = MIN (SelectionQuantum (), 65535 * 2); + + transfer->next = write_transfers.next; + transfer->last = &write_transfers; + transfer->requestor = event->xselectionrequest.requestor; + transfer->selection = event->xselectionrequest.selection; + transfer->target = event->xselectionrequest.target; + transfer->property = notify.xselection.property; + transfer->time = event->xselectionrequest.time; + transfer->event = notify; + transfer->buffer = XLMalloc (quantum); + transfer->size = quantum; + + /* Add a timeout for 5 seconds. */ + transfer->timeout = AddTimer (HandleWriteTimeout, transfer, + MakeTimespec (5, 0)); + + write_transfers.next->last = transfer; + write_transfers.next = transfer; + + SelectPropertyNotify (transfer->requestor); + + transfer->transfer_function + = GetTransferFunction (info, transfer, + transfer->target, + &transfer->type); + + if (!transfer->transfer_function) + { + /* Something failed (most likely, we ran out of fds to make the + pipe). Cancel the transfer and signal failure. */ + notify.xselection.property = None; + SendEvent (¬ify); + + FreeTransfer (transfer); + } + + return True; +} + +static void +DrainQueuedTransfers (void) +{ + QueuedTransfer temp, *item, *last; + + /* If nothing is queued, return. */ + + if (queued_transfers.next == &queued_transfers) + return; + + /* First, relink everything onto this temp sentinel. That way, if + stuff gets queued again in HandleSelectionRequest, the list + structure won't change underneath our noses. */ + + queued_transfers.last->next = &temp; + queued_transfers.next->last = &temp; + temp.next = queued_transfers.next; + temp.last = queued_transfers.last; + + queued_transfers.next = &queued_transfers; + queued_transfers.last = &queued_transfers; + + /* Then, read from the other side of the queue, and handle + everything. */ + item = queued_transfers.last; + + while (item != &queued_transfers) + { + last = item; + item = item->last; + + DebugPrint ("Draining one request with serial: %lu\n", + last->event.xselectionrequest.serial); + + HandleSelectionRequest (&last->event); + XLFree (last); + } +} + +static Bool +HandleSelectionNotify (XEvent *event) +{ + ReadTransfer *transfer; + + /* If event->property is None, then the selection transfer could not + be made. Find which transfer couldn't be made by looking in the + queue for the first transfer matching event->selection, + event->target and event->time, and unlink it. */ + if (event->xselection.property == None) + { + transfer = FindReadTransfer (event->xselection.selection, + event->xselection.target, + event->xselection.time); + + if (!transfer) + return True; + + FinishReadTransfer (transfer, False); + return True; + } + + /* Otherwise, find the outstanding selection transfer using the + property. */ + transfer = FindReadTransferByProp (event->xselection.property); + + if (!transfer) + return True; + + /* And start transferring data from it. */ + StartSelectionRead (transfer); + return True; +} + +static Bool +HandlePropertyDelete (XEvent *event) +{ + WriteTransfer *transfer; + + transfer = FindWriteTransfer (event->xproperty.window, + event->xproperty.atom); + if (!transfer) + return False; + + DebugPrint ("Handling property deletion for %lu\n", + event->xproperty.atom); + + if (transfer->state & IsFinished) + { + DebugPrint ("Completing transfer\n"); + + /* The transfer is now complete; finish it by freeing its data, + and potentially writing zero-length data. */ + FreeTransfer (transfer); + } + else if (transfer->state & IsWaitingForIncr) + { + /* If transfer is waiting for the INCR property to be deleted, mark + it as started, and flush it again to write the first piece of + property data. */ + + DebugPrint ("Starting transfer in response to INCR property deletion\n"); + + transfer->state |= IsStarted; + transfer->state &= ~IsWaitingForIncr; + transfer->state &= ~IsWaitingForDelete; + + /* Flush the transfer again to write the property data. */ + FlushTransfer (transfer, False); + } + else + { + DebugPrint ("Continuing transfer\n"); + + /* Clear IsWaitingForRelease. */ + transfer->state &= ~IsWaitingForDelete; + + if (transfer->state & IsReadable) + { + DebugPrint ("Picking read back up from where it was left\n"); + + /* And signal that the transfer is readable again. */ + TransferBecameReadable (transfer); + } + } + + return True; +} + +static Bool +HandlePropertyNotify (XEvent *event) +{ + ReadTransfer *transfer; + + if (event->xproperty.state != PropertyNewValue) + return HandlePropertyDelete (event); + + /* Find the transfer corresponding to this property. */ + transfer = FindReadTransferByProp (event->xproperty.atom); + + if (!transfer) + return False; + + /* By the time transfer->state & IsIncr, no more out-of-date + PropertyNotify events from previous selection requests can be + received. */ + + if (transfer->state & IsIncr + && transfer->state & IsWaitingForChunk) + { + /* Clear the waiting-for-chunk flag, and begin reading from this + chunk. */ + transfer->state &= ~IsWaitingForChunk; + StartSelectionRead (transfer); + } + + return True; +} + +Bool +HookSelectionEvent (XEvent *event) +{ + if (event->xany.type == SelectionNotify) + return HandleSelectionNotify (event); + + if (event->xany.type == SelectionRequest) + return HandleSelectionRequest (event); + + if (event->xany.type == PropertyNotify) + return HandlePropertyNotify (event); + + return False; +} + +void * +GetTransferData (ReadTransfer *transfer) +{ + return transfer->data; +} + +Time +GetTransferTime (ReadTransfer *transfer) +{ + return transfer->time; +} + +void * +GetWriteTransferData (WriteTransfer *transfer) +{ + return transfer->data; +} + +void +SetWriteTransferData (WriteTransfer *transfer, void *data) +{ + transfer->data = data; +} + +void +CompleteDelayedTransfer (ReadTransfer *transfer) +{ + /* Right now, we don't have to do any more than this. */ + XFree (transfer); +} + +Bool +OwnSelection (Time time, Atom selection, + GetDataFunc (*hook) (WriteTransfer *transfer, + Atom, Atom *), + Atom *targets, int ntargets) +{ + Window owner; + SelectionOwnerInfo *info; + + info = XLLookUpAssoc (selection_owner_info, selection); + + if (time == CurrentTime) + time = XLGetServerTimeRoundtrip (); + + if (info && time < info->time) + /* The timestamp is out of date. */ + return False; + + XSetSelectionOwner (compositor.display, selection, + selection_transfer_window, time); + + /* Check if selection ownership was actually set. */ + owner = XGetSelectionOwner (compositor.display, selection); + + /* If ownership wasn't successfully set, return. */ + if (owner != selection_transfer_window) + return False; + + /* Otherwise, update or allocate the info structure. */ + + if (!info) + { + info = XLMalloc (sizeof *info); + + XLMakeAssoc (selection_owner_info, selection, + info); + } + else + XLFree (info->targets); + + info->time = time; + info->targets = XLMalloc (sizeof *info->targets * ntargets); + info->ntargets = ntargets; + info->get_transfer_function = hook; + memcpy (info->targets, targets, sizeof *targets * ntargets); + + return True; +} + +void +DisownSelection (Atom selection) +{ + SelectionOwnerInfo *info; + + info = XLLookUpAssoc (selection_owner_info, selection); + + if (info && info->get_transfer_function) + { + XSetSelectionOwner (compositor.display, selection, + None, info->time); + + /* Also free info->targets. */ + XLFree (info->targets); + info->targets = NULL; + + /* And set the selection to disowned. */ + info->get_transfer_function = NULL; + } +} + +void +StartReading (WriteTransfer *transfer) +{ + TransferBecameReadable (transfer); +} + +void +InitSelections (void) +{ + XSetWindowAttributes attrs; + int flags; + + /* Set up sentinel nodes for various lists. */ + prop_atoms.next = &prop_atoms; + prop_atoms.last = &prop_atoms; + free_list.next = &free_list; + free_list.last = &free_list; + read_transfers.next = &read_transfers; + read_transfers.last = &read_transfers; + write_transfers.next = &write_transfers; + write_transfers.last = &write_transfers; + queued_transfers.next = &queued_transfers; + queued_transfers.last = &queued_transfers; + + /* Make the window used to transfer selection data. */ + attrs.override_redirect = True; + attrs.event_mask = PropertyChangeMask; + flags = CWEventMask | CWOverrideRedirect; + + selection_transfer_window + = XCreateWindow (compositor.display, + DefaultRootWindow (compositor.display), + -1, -1, 1, 1, 0, CopyFromParent, InputOnly, + CopyFromParent, flags, &attrs); + + /* Make the assoc tables used to keep track of input masks and + selection data. */ + foreign_notify_table = XLCreateAssocTable (32); + selection_owner_info = XLCreateAssocTable (32); + + DebugPrint ("Selection transfer window is %lu\n", + selection_transfer_window); +} diff --git a/shm.c b/shm.c new file mode 100644 index 0000000..6e5cf76 --- /dev/null +++ b/shm.c @@ -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 . */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#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); +} diff --git a/subcompositor.c b/subcompositor.c new file mode 100644 index 0000000..0124aeb --- /dev/null +++ b/subcompositor.c @@ -0,0 +1,2433 @@ +/* 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 . */ + +#ifndef TEST + +#include +#include +#include +#include + +#include "compositor.h" + +#define TEST_STATIC +#else + +typedef int Bool; + +#define True 1 +#define False 0 + +typedef struct _View View; +typedef struct _List List; +typedef struct _Subcompositor Subcompositor; + +#define TEST_STATIC static + +#endif + +/* This module implements a "subcompositor" that composites together + the contents of hierarchies of "views", each of which have attached + ExtBuffers and other assorted state. + + Each view has a parent (which can be the subcompositor itself), and + a list of children, which is sorted according to Z order. In + addition to the list of children of the subcompositor itself, every + view in the subcompositor is stored in a single doubly-linked list, + ordered implicitly according to the in which every inferior (direct + or indirect children of the subcompositor) will be composited. + This list is updated whenever a new view is inserted or the Z order + or parent of one of the views change. + + For example, assume the subcompositor has the following children: + + [A] [B] [C] + | | | + [D] [E] [F] [G] [H] [I] + + Then, the contents of the list will be: + + [A], [D], [E], [B], [F], [G], [C], [H], [I] + + To aid in updating the linked list, each view maintains a pointer + to the link in the list containing the view itself, and the link + containing the last inferior (direct or indirect children of the + view) of the view. So, in the above example, the view "A" will + also point to: + + + = link pointer of "A" + + = last inferior pointer of "A" + [A], [D], [E], [B], [F], [G], [C], [H], [I] + + To add view to another view, the view is first appended to the end + of the other view's list of children, and the links between its + link and its last inferior link are linked after its last inferior + link. Finally, the other view and each of its parents is iterated + through, and the last inferior pointer is updated to the last + inferior link of the view that was inserted if it is equal to the + other view's original last inferior pointer. + + If a view named "J" with no children were to be inserted at the end + of "A", then "J" would first be added to the end of "A"'s list of + children, creating such a hierarchy: + + [A] + | + [D] [E] [J] + + Then, "J"'s link and inferior pointers would be inserted after "E" + (where + represents the current location of "A"'s last inferior + pointer), resulting in the subcompositor's list of inferiors + looking like this: + + + * = link pointer of "J" + + * = last inferior pointer of "J" + [A], [D], [E], [J], [B], [F], [G], [C], [H], [I] + + Finally, the inferior pointer of each of "E"'s parents that + previously pointed to "E" is updated, like so: + + + * + +* + [A], [D], [E], [J], [B], [F], [G], [C], [H], [I] + + A similar procedure applies to adding a view to the subcompositor + itself. + + Unparenting a view (thereby removing it from the view hierarchy) is + is done by unlinking the implicitly-formed list between the view's + link pointer and the view's last inferior pointer from its + surroundings, and removing it from its parent's list of children. + This in turn creates a separate, implicitly-formed list, that + allows for view hierarchy operations to be performed on a detached + view. Unlinking "A" from the above hierarchy would produce two + separate lists: + + + * + +* + [A], [D], [E], [J] = the implicit sub-list of "A" + [B], [F], [G], [C], [H], [I] = the + subcompositor inferior list + + Finally, the inferior pointer of all parents pointing to the + unparented view's inferior pointer are updated to the + next-bottom-most sibling view's inferior pointer. This cannot be + demonstrated using the chart above, since "A" is a toplevel. + + Unlike the Wayland protocol itself, this does not support placing + children of a view before the view itself. That is implemented + manually by moving such children to a separate sibling of the + parent that is always stacked below that view. */ + +enum + { + /* This means that the view hierarchy has changed, and all + subcompositing optimisations should be skipped. */ + SubcompositorIsGarbaged = 1, + /* This means that the opaque region of one of the views + changed. */ + SubcompositorIsOpaqueDirty = (1 << 2), + /* This means that the input region of one of the views + changed. */ + SubcompositorIsInputDirty = (1 << 3), + /* This means that there is at least one unmapped view in this + subcompositor. */ + SubcompositorIsPartiallyMapped = (1 << 4), + /* This means that the subcompositor is frozen and updates should + do nothing. */ + SubcompositorIsFrozen = (1 << 5), + }; + +#define IsGarbaged(subcompositor) \ + ((subcompositor)->state & SubcompositorIsGarbaged) +#define SetGarbaged(subcompositor) \ + ((subcompositor)->state |= SubcompositorIsGarbaged) + +#define SetOpaqueDirty(subcompositor) \ + ((subcompositor)->state |= SubcompositorIsOpaqueDirty) +#define IsOpaqueDirty(subcompositor) \ + ((subcompositor)->state & SubcompositorIsOpaqueDirty) + +#define SetInputDirty(subcompositor) \ + ((subcompositor)->state |= SubcompositorIsInputDirty) +#define IsInputDirty(subcompositor) \ + ((subcompositor)->state & SubcompositorIsInputDirty) + +#define SetPartiallyMapped(subcompositor) \ + ((subcompositor)->state |= SubcompositorIsPartiallyMapped) +#define IsPartiallyMapped(subcompositor) \ + ((subcompositor)->state & SubcompositorIsPartiallyMapped) + +#define SetFrozen(subcompositor) \ + ((subcompositor)->state |= SubcompositorIsFrozen) +#define IsFrozen(subcompositor) \ + ((subcompositor)->state & SubcompositorIsFrozen) + +#ifndef TEST + +enum + { + /* This means that the view and all its inferiors should be + skipped in bounds computation, input tracking, et cetera. */ + ViewIsUnmapped = 1, + }; + +#define IsViewUnmapped(view) \ + ((view)->flags & ViewIsUnmapped) +#define SetUnmapped(view) \ + ((view)->flags |= ViewIsUnmapped) +#define ClearUnmapped(view) \ + ((view)->flags &= ~ViewIsUnmapped) + +#endif + +struct _List +{ + /* Pointer to the next element of this list. + This list itself if this is the sentinel link. */ + List *next; + + /* Pointer to the last element of this list. + This list itself if this is the sentinel link. */ + List *last; + + /* The view of this list. */ + View *view; +}; + +struct _View +{ + /* Subcompositor this view belongs to. NULL at first; callers are + supposed to call ViewSetSubcompositor before inserting a view + into a compositor. */ + Subcompositor *subcompositor; + +#ifndef TEST + /* Picture this subcompositor draws to. */ + Picture picture; +#endif + + /* Pointer to the parent view. NULL if the parent is the + subcompositor itself. */ + View *parent; + + /* Pointer to the link containing the view itself. */ + List *link; + + /* Pointer to another such link used in the view hierarchy. */ + List *self; + + /* Pointer to the link containing the view's last inferior. */ + List *inferior; + + /* List of children. */ + List *children; + + /* The end of that list. */ + List *children_last; + + /* Buffer data. */ + +#ifndef TEST + /* The buffer associated with this view, or None if nothing is + attached. */ + ExtBuffer *buffer; + + /* The damaged and opaque regions. */ + pixman_region32_t damage, opaque; + + /* The input region. */ + pixman_region32_t input; + + /* The position of this view relative to its parent. */ + int x, y; + + /* The absolute position of this view relative to the subcompositor + (or topmost parent if the view hierarchy is detached). */ + int abs_x, abs_y; + + /* Some data associated with this view. Can be a surface or + something else. */ + void *data; + + /* The scale of this view. */ + int scale; + + /* Flags; whether or not this view is unmapped, etc. */ + int flags; +#else + /* Label used during tests. */ + const char *label; +#endif +}; + +struct _Subcompositor +{ + /* Various flags describing the state of this subcompositor. */ + int state; + + /* List of all inferiors in compositing order. */ + List *inferiors, *last; + + /* Toplevel children of this subcompositor. */ + List *children, *last_children; + +#ifndef TEST + /* The picture that is rendered to. */ + Picture target; + + /* Function called when the opaque region changes. */ + void (*opaque_change) (Subcompositor *, void *, + pixman_region32_t *); + + /* Function called when the input region changes. */ + void (*input_change) (Subcompositor *, void *, + pixman_region32_t *); + + /* Function called with the bounds before each update. */ + void (*note_bounds) (void *, int, int, int, int); + + /* Data for those three functions. */ + void *opaque_change_data, *input_change_data, *note_bounds_data; + + /* The minimum origin of any surface in this subcompositor. Used to + compute the actual size of the subcompositor. */ + int min_x, min_y; + + /* The maximum position of any surface in this subcompositor. Used + to compute the actual size of the subcompositor. */ + int max_x, max_y; + + /* An additional offset to apply when drawing to the target. */ + int tx, ty; +#endif +}; + +#ifndef TEST + +enum + { + DoMinX = 1, + DoMinY = (1 << 1), + DoMaxX = (1 << 2), + DoMaxY = (1 << 3), + DoAll = 0xf, + }; + +/* The identity transform. */ + +static XTransform identity_transform; + +#endif + + +/* Circular doubly linked list of views. These lists work unusually: + for example, only some lists have a "sentinel" node at the + beginning with the value NULL. This is so that sub-lists can be + extracted from them without consing. */ + +static List * +ListInit (View *value) +{ + List *link; + + link = XLCalloc (1, sizeof *link); + link->next = link; + link->last = link; + link->view = value; + + return link; +} + +static void +ListRelinkAfter (List *start, List *end, List *dest) +{ + end->next = dest->next; + start->last = dest; + + dest->next->last = end; + dest->next = start; +} + +static void +ListInsertAfter (List *after, List *item) +{ + ListRelinkAfter (item, item, after); +} + +static void +ListInsertBefore (List *before, List *item) +{ + ListRelinkAfter (item, item, before->last); +} + +static void +ListRelinkBefore (List *start, List *end, List *dest) +{ + ListRelinkAfter (start, end, dest->last); +} + +/* Unlink the list between START and END from their surroundings. + Then, turn START and END into a proper list. This requires that + START is not the sentinel node. */ + +static void +ListUnlink (List *start, List *end) +{ + /* First, make the list skip past END. */ + start->last->next = end->next; + end->next->last = start->last; + + /* Then, unlink the list. */ + start->last = end; + end->next = start; +} + +TEST_STATIC Subcompositor * +MakeSubcompositor (void) +{ + Subcompositor *subcompositor; + + subcompositor = XLCalloc (1, sizeof *subcompositor); + subcompositor->inferiors = ListInit (NULL); + subcompositor->children = ListInit (NULL); + + subcompositor->last = subcompositor->inferiors; + subcompositor->last_children = subcompositor->children; + + return subcompositor; +} + +TEST_STATIC View * +MakeView (void) +{ + View *view; + + view = XLCalloc (1, sizeof *view); + view->subcompositor = NULL; + view->parent = NULL; + + /* Note that view->link is not supposed to have a sentinel; it can + only be part of a larger list. */ + view->link = ListInit (view); + view->inferior = view->link; + + /* Likewise for view->self. */ + view->self = ListInit (view); + + /* But view->children is a complete list by itself. */ + view->children = ListInit (NULL); + view->children_last = view->children; + +#ifndef TEST + view->buffer = NULL; + + pixman_region32_init (&view->damage); + pixman_region32_init (&view->opaque); + pixman_region32_init (&view->input); +#endif + + return view; +} + +#ifndef TEST + +static int +ViewMaxX (View *view) +{ + return view->abs_x + ViewWidth (view) - 1; +} + +static int +ViewMaxY (View *view) +{ + return view->abs_y + ViewHeight (view) - 1; +} + +static Bool +ViewIsMapped (View *view) +{ + if (view->subcompositor + && !IsPartiallyMapped (view->subcompositor)) + return True; + + if (IsViewUnmapped (view)) + return False; + + if (view->parent) + return ViewIsMapped (view->parent); + + return True; +} + +static void +SubcompositorUpdateBounds (Subcompositor *subcompositor, int doflags) +{ + List *list; + int min_x, min_y, max_x, max_y; + + /* Updates were optimized out. */ + if (!doflags) + return; + + list = subcompositor->inferiors->next; + min_x = max_x = min_y = max_y = 0; + + while (list != subcompositor->inferiors) + { + if (list->view) + { + /* If the view is unmapped, skip past its children. */ + if (IsViewUnmapped (list->view)) + { + list = list->view->inferior; + goto next; + } + + if ((doflags & DoMinX) && min_x > list->view->abs_x) + min_x = list->view->abs_x; + + if ((doflags & DoMinY) && min_x > list->view->abs_y) + min_y = list->view->abs_y; + + if ((doflags & DoMaxX) && max_x < ViewMaxX (list->view)) + max_x = ViewMaxX (list->view); + + if ((doflags & DoMaxY) && max_y < ViewMaxY (list->view)) + max_y = ViewMaxY (list->view); + } + + next: + list = list->next; + } + + if (doflags & DoMinX) + subcompositor->min_x = min_x; + + if (doflags & DoMinY) + subcompositor->min_y = min_y; + + if (doflags & DoMaxX) + subcompositor->max_x = max_x; + + if (doflags & DoMaxY) + subcompositor->max_y = max_y; + + SetGarbaged (subcompositor); +} + +static void +SubcompositorUpdateBoundsForInsert (Subcompositor *subcompositor, + View *view) +{ + XLAssert (view->subcompositor == subcompositor); + + if (!ViewIsMapped (view)) + /* If the view is unmapped, do nothing. */ + return; + + /* Inserting a view cannot shrink the subcompositor. */ + + if (view->abs_x < subcompositor->min_x) + subcompositor->min_x = view->abs_x; + + if (view->abs_x < view->subcompositor->min_y) + subcompositor->min_y = view->abs_y; + + if (view->subcompositor->max_x < ViewMaxX (view)) + subcompositor->max_x = ViewMaxX (view); + + if (view->subcompositor->max_y < ViewMaxY (view)) + subcompositor->max_y = ViewMaxY (view); +} + +#endif + +#ifndef TEST + +void +SubcompositorSetTarget (Subcompositor *compositor, Picture picture) +{ + compositor->target = picture; + + /* We don't know if the new picture has the previous state left + over. */ + SetGarbaged (compositor); +} + +#endif + +TEST_STATIC void +SubcompositorInsert (Subcompositor *compositor, View *view) +{ + /* Link view into the list of children. */ + ListInsertBefore (compositor->last_children, view->self); + + /* Make view's inferiors part of the compositor. */ + ListRelinkBefore (view->link, view->inferior, + compositor->last); + + /* Now that the view hierarchy has been changed, garbage the + subcompositor. */ + SetGarbaged (compositor); + +#ifndef TEST + /* And update bounds. */ + SubcompositorUpdateBoundsForInsert (compositor, view); +#endif +} + +TEST_STATIC void +SubcompositorInsertBefore (Subcompositor *compositor, View *view, + View *sibling) +{ + /* Link view into the list of children, before the given + sibling. */ + ListInsertBefore (sibling->self, view->self); + + /* Make view's inferiors part of the compositor. */ + ListRelinkBefore (view->link, view->inferior, sibling->link); + + /* Now that the view hierarchy has been changed, garbage the + subcompositor. */ + SetGarbaged (compositor); + +#ifndef TEST + /* And update bounds. */ + SubcompositorUpdateBoundsForInsert (compositor, view); +#endif +} + +TEST_STATIC void +SubcompositorInsertAfter (Subcompositor *compositor, View *view, + View *sibling) +{ + /* Link view into the list of children, after the given sibling. */ + ListInsertAfter (sibling->self, view->self); + + /* Make view's inferiors part of the compositor. */ + ListRelinkAfter (view->link, view->inferior, sibling->inferior); + + /* Now that the view hierarchy has been changed, garbage the + subcompositor. */ + SetGarbaged (compositor); + +#ifndef TEST + /* And update bounds. */ + SubcompositorUpdateBoundsForInsert (compositor, view); +#endif +} + +#ifndef TEST + +static Bool +ViewVisibilityState (View *view, Bool *mapped) +{ + if (IsViewUnmapped (view) && mapped) + { + *mapped = False; + + /* Clear mapped, so it will never be set again. */ + mapped = NULL; + } + + if (view->parent) + return ViewVisibilityState (view->parent, mapped); + + if (mapped) + *mapped = !IsViewUnmapped (view); + + return view->link->next != view->link; +} + +static void +ViewRecomputeChildren (View *view, int *doflags) +{ + List *list; + View *child; + Bool attached, mapped; + + list = view->children; + attached = ViewVisibilityState (view, &mapped); + + do + { + list = list->next; + + if (list->view) + { + child = list->view; + + child->abs_x = view->abs_x + child->x; + child->abs_y = view->abs_y + child->y; + + if (view->subcompositor + /* Don't operate on the subcompositor should the view be + detached. */ + && attached + /* Or if it isn't mapped, or none of its parents are + mapped. */ + && mapped) + { + if (child->abs_x < view->subcompositor->min_x) + { + view->subcompositor->min_x = child->abs_x; + + if (doflags) + *doflags &= ~DoMinX; + } + + if (child->abs_x < view->subcompositor->min_y) + { + view->subcompositor->min_y = child->abs_y; + + if (doflags) + *doflags &= ~DoMinY; + } + + if (view->subcompositor->max_x < ViewMaxX (child)) + { + view->subcompositor->max_x = ViewMaxX (child); + + if (doflags) + *doflags &= ~DoMaxX; + } + + if (view->subcompositor->max_y < ViewMaxY (child)) + { + view->subcompositor->max_y = ViewMaxY (child); + + if (doflags) + *doflags &= ~DoMaxY; + } + } + + ViewRecomputeChildren (child, doflags); + } + } + while (list != view->children); +} + +static void +ViewUpdateBoundsForInsert (View *view) +{ + if (view->subcompositor) + SubcompositorUpdateBoundsForInsert (view->subcompositor, + view); +} + + +#endif + +TEST_STATIC void +ViewInsert (View *view, View *child) +{ + View *parent; + List *prior; + + /* Make child's parent view. */ + child->parent = view; + + /* Insert child into the hierarchy list. */ + ListInsertBefore (view->children_last, child->self); + + /* Insert child's inferior list. */ + ListRelinkAfter (child->link, child->inferior, view->inferior); + + /* Note what the previous last inferior pointer of view was. */ + prior = view->inferior; + + /* Update the entire view hierarchy's inferior pointers, starting + from view. */ + for (parent = view; parent; parent = parent->parent) + { + /* The last inferior of this view has been changed already; + update it. */ + if (parent->inferior == prior) + parent->inferior = child->inferior; + } + + /* Now that the view hierarchy has been changed, garbage the + subcompositor. */ + + if (view->subcompositor) + SetGarbaged (view->subcompositor); + +#ifndef TEST + /* Also update the absolute positions of the child. */ + child->abs_x = view->abs_x + child->x; + child->abs_y = view->abs_y + child->y; + ViewRecomputeChildren (child, NULL); + + /* And update bounds. */ + ViewUpdateBoundsForInsert (view); +#endif +} + +TEST_STATIC void +ViewInsertAfter (View *view, View *child, View *sibling) +{ + View *parent; + List *prior; + + /* Make child's parent view. */ + child->parent = view; + + /* Insert child into the hierarchy list. */ + ListInsertAfter (sibling->self, child->self); + + /* Insert child's inferior list. */ + ListRelinkAfter (child->link, child->inferior, + sibling->inferior); + + /* Change the inferior pointers if sibling->inferior was the old + one. */ + + if (sibling->inferior == view->inferior) + { + /* Note what the previous last inferior pointer of view was. */ + prior = sibling->inferior; + + /* Update the entire view hierarchy's inferior pointers, starting + from view. */ + for (parent = view; parent; parent = parent->parent) + { + /* The last inferior of this view has been changed already; + update it. */ + if (parent->inferior == prior) + parent->inferior = child->inferior; + } + } + + /* Now that the view hierarchy has been changed, garbage the + subcompositor. */ + + if (view->subcompositor) + SetGarbaged (view->subcompositor); + +#ifndef TEST + /* Also update the absolute positions of the child. */ + child->abs_x = view->abs_x + child->x; + child->abs_y = view->abs_y + child->y; + ViewRecomputeChildren (child, NULL); + + /* And update bounds. */ + ViewUpdateBoundsForInsert (view); +#endif +} + +TEST_STATIC void +ViewInsertBefore (View *view, View *child, View *sibling) +{ + /* Make child's parent view. */ + child->parent = view; + + /* Insert child into the hierarchy list. */ + ListInsertBefore (sibling->self, child->self); + + /* Insert child's inferior list. */ + ListRelinkBefore (child->link, child->inferior, + sibling->link); + + /* Now that the view hierarchy has been changed, garbage the + subcompositor. */ + + if (view->subcompositor) + SetGarbaged (view->subcompositor); + +#ifndef TEST + /* Also update the absolute positions of the child. */ + child->abs_x = view->abs_x + child->x; + child->abs_y = view->abs_y + child->y; + ViewRecomputeChildren (child, NULL); + + /* Update subcompositor bounds. Inserting a view cannot shrink + anything. */ + ViewUpdateBoundsForInsert (view); +#endif + + /* Inserting inferiors before a sibling can never bump the inferior + pointer. */ +} + +TEST_STATIC void +ViewInsertStart (View *view, View *child) +{ + /* If view has no children, just call ViewInsert. Note that + view->children is a sentinel node whose value is NULL. */ + if (view->children->next == view->children) + ViewInsert (view, child); + else + /* Otherwise, insert child before the first child. */ + ViewInsertBefore (view, child, + view->children->next->view); +} + +TEST_STATIC void +ViewUnparent (View *child) +{ + View *parent; + + /* Parent is either the subcompositor or another view. */ + ListUnlink (child->self, child->self); + + if (child->parent) + { + /* Now update the inferior pointer of each parent currently + pointing to child->inferior to the inferior of its leftmost + sibling, or its parent itself. */ + + for (parent = child->parent; parent; parent = parent->parent) + { + if (parent->inferior == child->inferior) + /* If this is the bottom-most child, then + child->link->last will be the parent itself. */ + parent->inferior = child->link->last; + } + + /* And reset the pointer to the parent. */ + child->parent = NULL; + } + + /* Unlink the sub-list between the link pointer and the last + inferior pointer from that of the parent. */ + ListUnlink (child->link, child->inferior); + + /* Reset the absolute positions of child, and recompute that of its + children. This is done after unlinking, because + ViewRecomputeChildren will otherwise try to operate on the + subcompositor. */ +#ifndef TEST + child->abs_x = child->x; + child->abs_y = child->y; + + ViewRecomputeChildren (child, NULL); +#endif + + /* Now that the view hierarchy has been changed, garbage the + subcompositor. TODO: an optimization for removing views would be + to damage each intersecting view before child->link instead, if + view bounds did not change. */ + if (child->subcompositor) + { +#ifndef TEST + /* Update the bounds of the subcompositor. */ + SubcompositorUpdateBounds (child->subcompositor, DoAll); +#endif + + /* Then, garbage the subcompositor. */ + SetGarbaged (child->subcompositor); + } +} + +TEST_STATIC void +ViewSetSubcompositor (View *view, Subcompositor *subcompositor) +{ + List *list; + + list = view->link; + + /* Attach the subcompositor recursively for all of view's + inferiors. */ + + do + { + if (list->view) + list->view->subcompositor = subcompositor; + + list = list->next; + } + while (list != view->link); +} + +#ifdef TEST + +/* The depth of the current view being printed. */ + +static int print_level; + +static void +PrintView (View *view) +{ + List *list; + + printf ("%*c%s\n", print_level * 2, ' ', + view->label); + + print_level++; + list = view->children; + do + { + if (list->view) + PrintView (list->view); + list = list->next; + } + while (list != view->children); + print_level--; +} + +static void +PrintSubcompositor (Subcompositor *compositor) +{ + List *list; + + list = compositor->children; + do + { + if (list->view) + PrintView (list->view); + list = list->next; + } + while (list != compositor->children); + + list = compositor->inferiors; + do + { + if (list->view) + printf ("[%s], ", list->view->label); + list = list->next; + fflush (stdout); + } + while (list != compositor->inferiors); + + fflush (stdout); + printf ("\n"); + fflush (stdout); + + for (list = compositor->last->last; + list != compositor->last; list = list->last) + { + if (list->view) + printf ("(%s), ", list->view->label); + fflush (stdout); + } + printf ("\n"); + fflush (stdout); +} + +static View * +TestView (Subcompositor *compositor, const char *label) +{ + View *view; + + view = MakeView (); + view->label = label; + + ViewSetSubcompositor (view, compositor); + + return view; +} + +static void +TestSubcompositor (void) +{ + Subcompositor *compositor; + View *a, *b, *c, *d, *e, *f, *g, *h, *i, *j; + View *k, *l, *m, *n, *o, *p; + + compositor = MakeSubcompositor (); + a = TestView (compositor, "A"); + b = TestView (compositor, "B"); + c = TestView (compositor, "C"); + d = TestView (compositor, "D"); + e = TestView (compositor, "E"); + f = TestView (compositor, "F"); + g = TestView (compositor, "G"); + h = TestView (compositor, "H"); + i = TestView (compositor, "I"); + j = TestView (compositor, "J"); + k = TestView (compositor, "K"); + l = TestView (compositor, "L"); + m = TestView (compositor, "M"); + n = TestView (compositor, "N"); + o = TestView (compositor, "O"); + p = TestView (compositor, "P"); + + printf ("SubcompositorInsert (COMPOSITOR, A)\n"); + SubcompositorInsert (compositor, a); + PrintSubcompositor (compositor); + printf ("ViewInsert (A, D)\n"); + ViewInsert (a, d); + PrintSubcompositor (compositor); + printf ("ViewInsert (A, E)\n"); + ViewInsert (a, e); + PrintSubcompositor (compositor); + printf ("ViewInsert (B, F)\n"); + ViewInsert (b, f); + printf ("ViewInsert (B, G)\n"); + ViewInsert (b, g); + printf ("SubcompositorInsert (COMPOSITOR, B)\n"); + SubcompositorInsert (compositor, b); + PrintSubcompositor (compositor); + printf ("ViewInsert (C, H)\n"); + ViewInsert (c, h); + printf ("SubcompositorInsert (COMPOSITOR, C)\n"); + SubcompositorInsert (compositor, c); + PrintSubcompositor (compositor); + printf ("ViewInsert (C, I)\n"); + ViewInsert (c, i); + PrintSubcompositor (compositor); + + printf ("ViewInsert (A, J)\n"); + ViewInsert (a, j); + PrintSubcompositor (compositor); + + printf ("ViewUnparent (A)\n"); + ViewUnparent (a); + PrintSubcompositor (compositor); + + printf ("ViewUnparent (C)\n"); + ViewUnparent (c); + PrintSubcompositor (compositor); + + printf ("ViewUnparent (G)\n"); + ViewUnparent (g); + printf ("ViewUnparent (J)\n"); + ViewUnparent (j); + printf ("ViewInsert (G, J)\n"); + ViewInsert (g, j); + printf ("SubcompositorInsert (COMPOSITOR, G)\n"); + SubcompositorInsert (compositor, g); + PrintSubcompositor (compositor); + + printf ("ViewInsertBefore (G, C, J)\n"); + ViewInsertBefore (g, c, j); + PrintSubcompositor (compositor); + + printf ("ViewInsertAfter (C, A, H)\n"); + ViewInsertAfter (c, a, h); + PrintSubcompositor (compositor); + + printf ("ViewInsert (K, L)\n"); + ViewInsert (k, l); + + printf ("SubcompositorInsertBefore (COMPOSITOR, K, G)\n"); + SubcompositorInsertBefore (compositor, k, g); + PrintSubcompositor (compositor); + + printf ("SubcompositorInsertAfter (COMPOSITOR, M, B)\n"); + SubcompositorInsertAfter (compositor, m, b); + PrintSubcompositor (compositor); + + printf ("ViewInsert (M, N)\n"); + ViewInsert (m, n); + PrintSubcompositor (compositor); + + printf ("ViewInsertStart (M, O)\n"); + ViewInsertStart (m, o); + PrintSubcompositor (compositor); + + printf ("ViewInsertStart (L, P)\n"); + ViewInsertStart (l, p); + PrintSubcompositor (compositor); +} + +int +main (int argc, char **argv) +{ + TestSubcompositor (); +} + +#endif + + +/* The subcompositor composites its inferior views to a drawable, + normally a window, each time the SubcompositorUpdate function is + called. Since it is not very efficient to draw every view every + time an update occurs, the subcompositor keeps track of which parts + of the inferiors have changed, and uses that information to only + composite a reasonable minimum set of inferiors and screen areas on + each update (reasonable meaning whatever can be computed quickly + while keeping graphics updates fast). The subcompositor also keeps + track of which areas of an inferior are opaque, and uses that + information to avoid compositing in response to damage on inferiors + that are obscured from above. + + The subcompositor normally assumes that the contents of the target + drawable are what was drawn by the subcompositor during previous + updates. With that in mind, the subcompositor tries to calculate a + "global damage region" consisting of the areas of the target that + have to be updated, and a "update inferior", the first inferior + that will be composited onto the target drawable, by unioning up + damage and opaque regions of each inferior until the first + unobscured inferior is found. Then, the contents of all inferiors + that intersect with the global damage region are composited onto + the target drawable. Afterwards, the damage region of each + inferior is cleared, and the process can begin again. + + Such computation is not reliable, however, if the size or position + of a view changes. In the interest of keeping thing simple, every + inferior is composited onto the target drawable whenever a view + change is detected. These changes are marked by calls to the macro + SetGarbaged. + + Further more, the X server can sometimes erase the contents of an + area of the target window, in response to it being obscured. When + that happens, that area is entirely composited to the target + window. See SubcompositorExpose for more details. */ + +#ifndef TEST + +static void +ViewAfterSizeUpdate (View *view) +{ + int doflags; + Bool mapped; + + if (!view->subcompositor || !ViewVisibilityState (view, &mapped) + || !mapped) + return; + + /* First, assume we will have to compute both max_x and max_y. */ + doflags = DoMaxX | DoMaxY; + + /* If the view is now wider than max_x and/or max_y, update those + now. */ + + if (view->subcompositor->max_x < ViewMaxX (view)) + { + view->subcompositor->max_x = ViewMaxX (view); + + /* We don't have to update max_x anymore. */ + doflags &= ~DoMaxX; + } + + if (view->subcompositor->max_y < ViewMaxY (view)) + { + view->subcompositor->max_y = ViewMaxY (view); + + /* We don't have to update max_x anymore. */ + doflags &= ~DoMaxY; + } + + /* Finally, update the bounds. */ + SubcompositorUpdateBounds (view->subcompositor, doflags); +} + +void +ViewAttachBuffer (View *view, ExtBuffer *buffer) +{ + ExtBuffer *old; + + old = view->buffer; + view->buffer = buffer; + + if (!old != !buffer) + { + /* TODO: just damage intersecting views before view->link if the + buffer was removed. */ + if (view->subcompositor) + SetGarbaged (view->subcompositor); + } + + if ((buffer && !old) + || (old && !buffer) + || (buffer && old + && (XLBufferWidth (buffer) != XLBufferWidth (old) + || XLBufferHeight (buffer) != XLBufferHeight (old)))) + { + if (view->subcompositor) + { + /* A new buffer was attached, so garbage the subcompositor + as well. */ + SetGarbaged (view->subcompositor); + + /* Recompute view bounds. */ + ViewAfterSizeUpdate (view); + } + } + + if (buffer && IsViewUnmapped (view)) + { + /* A buffer is now attached. Automatically map the view, should + it be unmapped. */ + ClearUnmapped (view); + + if (view->subcompositor) + { + /* Garbage the subcompositor and recompute bounds. */ + SetGarbaged (view->subcompositor); + SubcompositorUpdateBounds (view->subcompositor, DoAll); + } + } + + if (old) + XLDereferenceBuffer (old); + + if (view->buffer) + XLRetainBuffer (buffer); +} + +void +ViewMove (View *view, int x, int y) +{ + int doflags; + Bool mapped; + + doflags = 0; + + if (x != view->x || y != view->y) + { + view->x = x; + view->y = y; + + if (view->parent) + { + view->abs_x = view->parent->abs_x + x; + view->abs_y = view->parent->abs_y + y; + } + else + { + view->abs_x = x; + view->abs_y = x; + } + + if (view->subcompositor && ViewVisibilityState (view, &mapped) + /* If this view isn't mapped, then do nothing. The bounds + will be recomputed later. */ + && mapped) + { + /* First assume everything will have to be updated. */ + doflags |= DoMaxX | DoMaxY | DoMinY | DoMinX; + + /* If this view was moved before subcompositor.min_x and/or + subcompositor.min_y, don't recompute those values + unnecessarily. */ + + if (view->abs_x < view->subcompositor->min_x) + { + view->subcompositor->min_x = view->abs_x; + + /* min_x has already been updated so there is no need to + recompute it later. */ + doflags &= ~DoMinX; + } + + if (view->abs_y < view->subcompositor->min_x) + { + view->subcompositor->min_y = view->abs_y; + + /* min_y has already been updated so there is no need to + recompute it later. */ + doflags &= ~DoMinY; + } + + /* If moving this biew bumps subcompositor.max_x and/or + subcompositor.max_y, don't recompute either. */ + + if (view->subcompositor->max_x < ViewMaxX (view)) + { + view->subcompositor->max_x = ViewMaxX (view); + + /* max_x has been updated so there is no need to + recompute it later. If a child is bigger, then + ViewRecomputeChildren will handle it as well. */ + doflags &= ~DoMaxX; + } + + if (view->subcompositor->max_y < ViewMaxX (view)) + { + view->subcompositor->max_y = ViewMaxX (view); + + /* max_y has been updated so there is no need to + recompute it later. If a child is bigger, then + ViewRecomputeChildren will handle it as well. */ + doflags &= ~DoMaxY; + } + + /* Also garbage the subcompositor since those values + changed. TODO: just damage intersecting views before + view->link. */ + SetGarbaged (view->subcompositor); + } + + /* Now calculate the absolute position for this view and all of + its children. N.B. that this operation can also update + subcompositor.min_x or subcompositor.min_y. */ + ViewRecomputeChildren (view, &doflags); + + /* Update subcompositor bounds. */ + if (view->subcompositor) + SubcompositorUpdateBounds (view->subcompositor, doflags); + } +} + +void +ViewDetach (View *view) +{ + ViewAttachBuffer (view, NULL); +} + +void +ViewMap (View *view) +{ + if (!IsViewUnmapped (view)) + return; + + ClearUnmapped (view); + + if (view->subcompositor + && (view->link != view->inferior || view->buffer)) + { + /* Garbage the subcompositor and recompute bounds, if something + is attached to the view or it is not empty. */ + SetGarbaged (view->subcompositor); + SubcompositorUpdateBounds (view->subcompositor, DoAll); + } +} + +void +ViewUnmap (View *view) +{ + if (IsViewUnmapped (view)) + return; + + /* Mark the view as unmapped. */ + SetUnmapped (view); + + if (view->subcompositor) + { + /* Mark the subcompositor as having unmapped views. */ + SetPartiallyMapped (view->subcompositor); + + /* If the link pointer is the inferior pointer and there is no + buffer attached to the view, it is empty. There is no need + to do anything other than marking the subcompositor as + partially mapped. */ + if (view->link != view->inferior || view->buffer) + { + /* Recompute the bounds of the subcompositor. */ + SubcompositorUpdateBounds (view->subcompositor, + DoAll); + + /* Garbage the view's subcompositor. */ + SetGarbaged (view->subcompositor); + } + } +} + +void +ViewFree (View *view) +{ + /* It's not valid to call this function on a view with children or a + parent. */ + XLAssert (view->link == view->inferior); + XLAssert (view->link->last == view->link); + + if (view->buffer) + ViewDetach (view); + + XLFree (view->link); + XLFree (view->self); + XLFree (view->children); + + pixman_region32_fini (&view->damage); + pixman_region32_fini (&view->opaque); + pixman_region32_fini (&view->input); + + XLFree (view); +} + +void +ViewDamage (View *view, pixman_region32_t *damage) +{ + pixman_region32_union (&view->damage, + &view->damage, + damage); +} + +void +ViewSetOpaque (View *view, pixman_region32_t *opaque) +{ + pixman_region32_copy (&view->opaque, opaque); + + if (view->subcompositor) + SetOpaqueDirty (view->subcompositor); +} + +void +ViewSetInput (View *view, pixman_region32_t *input) +{ + if (pixman_region32_equal (input, &view->input)) + return; + + pixman_region32_copy (&view->input, input); + + if (view->subcompositor) + SetInputDirty (view->subcompositor); +} + +Subcompositor * +ViewGetSubcompositor (View *view) +{ + return view->subcompositor; +} + +int +ViewWidth (View *view) +{ + int width; + + if (!view->buffer) + return 0; + + width = XLBufferWidth (view->buffer); + + if (view->scale < 0) + return width * (abs (view->scale) + 1); + else + return width / (view->scale + 1); +} + +int +ViewHeight (View *view) +{ + int height; + + if (!view->buffer) + return 0; + + height = XLBufferHeight (view->buffer); + + if (view->scale < 0) + return height * (abs (view->scale) + 1); + else + return height / (view->scale + 1); +} + +void +ViewSetScale (View *view, int scale) +{ + int doflags; + + /* First, assume we will have to compute both max_x and max_y. */ + doflags = DoMaxX | DoMaxY; + + if (view->scale == scale) + return; + + view->scale = scale; + + if (view->subcompositor) + { + /* If the view is now wider than max_x and/or max_y, update those + now. */ + + if (view->subcompositor->max_x < ViewMaxX (view)) + { + view->subcompositor->max_x = ViewMaxX (view); + + /* We don't have to update max_x anymore. */ + doflags &= ~DoMaxX; + } + + if (view->subcompositor->max_y < ViewMaxY (view)) + { + view->subcompositor->max_y = ViewMaxY (view); + + /* We don't have to update max_x anymore. */ + doflags &= ~DoMaxY; + } + + /* Finally, update the bounds. */ + SubcompositorUpdateBounds (view->subcompositor, + doflags); + } +} + +static double +GetTxTy (int scale) +{ + if (scale > 0) + return scale + 1; + + return 1.0 / (-scale + 1); +} + +static XTransform +ViewGetTransform (View *view) +{ + XTransform transform; + double transform_value; + + /* Perform scaling in the transform matrix. */ + + memset (&transform, 0, sizeof transform); + transform_value = GetTxTy (view->scale); + transform.matrix[0][0] = XDoubleToFixed (transform_value); + transform.matrix[1][1] = XDoubleToFixed (transform_value); + transform.matrix[2][2] = XDoubleToFixed (1); + + return transform; +} + +/* TODO: the callers of this can be optimized by setting the picture + transform on the attached buffer if that buffer is not attached to + any other view. */ + +static Bool +ViewHaveTransform (View *view) +{ + /* view->scale is the amount by which to scale _down_ the view. If + it is 0, then no scaling will be performed. */ + return view->scale; +} + +void +SubcompositorSetOpaqueCallback (Subcompositor *subcompositor, + void (*opaque_changed) (Subcompositor *, + void *, + pixman_region32_t *), + void *data) +{ + subcompositor->opaque_change = opaque_changed; + subcompositor->opaque_change_data = data; +} + +void +SubcompositorSetInputCallback (Subcompositor *subcompositor, + void (*input_changed) (Subcompositor *, + void *, + pixman_region32_t *), + void *data) +{ + subcompositor->input_change = input_changed; + subcompositor->input_change_data = data; +} + +void +SubcompositorSetBoundsCallback (Subcompositor *subcompositor, + void (*note_bounds) (void *, int, int, + int, int), + void *data) +{ + subcompositor->note_bounds = note_bounds; + subcompositor->note_bounds_data = data; +} + +static void +FillBoxesWithTransparency (Subcompositor *subcompositor, + pixman_box32_t *boxes, int nboxes) +{ + XRectangle *rects; + static XRenderColor color; + int i; + Picture picture; + + picture = subcompositor->target; + + if (nboxes < 256) + rects = alloca (sizeof *rects * nboxes); + else + rects = XLMalloc (sizeof *rects * nboxes); + + for (i = 0; i < nboxes; ++i) + { + rects[i].x = BoxStartX (boxes[i]) - subcompositor->min_x; + rects[i].y = BoxStartY (boxes[i]) - subcompositor->min_y; + rects[i].width = BoxWidth (boxes[i]); + rects[i].height = BoxHeight (boxes[i]); + } + + XRenderFillRectangles (compositor.display, PictOpClear, picture, + &color, rects, nboxes); + + if (nboxes >= 256) + XLFree (rects); +} + +static Bool +ViewContainsExtents (View *view, pixman_box32_t *box) +{ + int x, y, width, height; + + x = view->abs_x; + y = view->abs_y; + width = ViewWidth (view); + height = ViewHeight (view); + + return (box->x1 >= x && box->y1 >= y + && box->x2 <= x + width + && box->y2 <= x + height); +} + +void +SubcompositorBounds (Subcompositor *subcompositor, + int *min_x, int *min_y, int *max_x, int *max_y) +{ + *min_x = subcompositor->min_x; + *min_y = subcompositor->min_y; + *max_x = subcompositor->max_x; + *max_y = subcompositor->max_y; +} + +Bool +SubcompositorIsEmpty (Subcompositor *subcompositor) +{ + return (subcompositor->min_x == subcompositor->max_x + && subcompositor->min_y == subcompositor->max_y); +} + +void +SubcompositorUpdate (Subcompositor *subcompositor) +{ + pixman_region32_t update_region, temp, start_opaque; + pixman_region32_t total_opaque, total_input; + View *start, *original_start, *view, *first; + List *list; + pixman_box32_t *boxes, *extents, temp_boxes; + int nboxes, i, op, tx, ty; + Picture picture; + XTransform transform; + int min_x, min_y; + + /* Just return if no target was specified. */ + if (subcompositor->target == None) + return; + + /* Likewise if the subcompositor is "frozen". */ + if (IsFrozen (subcompositor)) + return; + + list = subcompositor->inferiors; + min_x = subcompositor->min_x; + min_y = subcompositor->min_y; + tx = subcompositor->tx; + ty = subcompositor->ty; + start = NULL; + original_start = NULL; + pixman_region32_init (&temp); + pixman_region32_init (&update_region); + + start = subcompositor->inferiors->next->view; + original_start = subcompositor->inferiors->next->view; + + /* Clear the "is partially mapped" flag. It will be set later on if + there is actually a partially mapped view. */ + subcompositor->state &= ~SubcompositorIsPartiallyMapped; + + if (subcompositor->note_bounds) + subcompositor->note_bounds (subcompositor->note_bounds_data, + min_x, min_y, subcompositor->max_x, + subcompositor->max_y); + + if (!IsGarbaged (subcompositor)) + { + start = NULL; + original_start = NULL; + + if (IsOpaqueDirty (subcompositor)) + pixman_region32_init (&total_opaque); + + if (IsInputDirty (subcompositor)) + pixman_region32_init (&total_input); + + pixman_region32_init (&start_opaque); + + do + { + view = list->view; + + if (!view) + goto next; + + if (IsViewUnmapped (view)) + { + /* The view is unmapped. Skip past it and all its + children. */ + list = view->inferior; + + /* Set the "is partially mapped" flag. This is an + optimization used to make inserting views in deeply + nested hierarchies faster. */ + SetPartiallyMapped (subcompositor); + goto next; + } + + if (!view->buffer) + goto next; + + if (!start) + { + start = view; + original_start = view; + } + + if (pixman_region32_not_empty (&list->view->opaque)) + { + /* Translate the region into the subcompositor + coordinate space. */ + pixman_region32_translate (&list->view->opaque, + list->view->abs_x, + list->view->abs_y); + + /* Only use the intersection between the opaque region + and the rectangle of the view, since the opaque areas + cannot extend outside it. */ + + pixman_region32_intersect_rect (&temp, &view->opaque, + view->abs_x, view->abs_y, + ViewWidth (view), + ViewHeight (view)); + + if (IsOpaqueDirty (subcompositor)) + pixman_region32_union (&total_opaque, &total_opaque, &temp); + + pixman_region32_subtract (&update_region, + &update_region, &temp); + + /* This view will obscure all preceding damaged areas, + so make start here. */ + if (!pixman_region32_not_empty (&update_region)) + { + start = list->view; + + /* Now that start changed, record the opaque region. + That way, if some damage happens outside the + opaque region in the future, this operation can + be undone. */ + pixman_region32_copy (&start_opaque, &view->opaque); + } + + pixman_region32_translate (&list->view->opaque, + -list->view->abs_x, + -list->view->abs_y); + } + + if (pixman_region32_not_empty (&list->view->input) + && IsInputDirty (subcompositor)) + { + /* Translate the region into the subcompositor + coordinate space. */ + pixman_region32_translate (&list->view->input, + list->view->abs_x, + list->view->abs_y); + + pixman_region32_intersect_rect (&temp, &view->input, + view->abs_x, view->abs_y, + ViewWidth (view), + ViewHeight (view)); + + pixman_region32_union (&total_input, &total_input, &temp); + + /* Restore the original input region. */ + pixman_region32_translate (&list->view->input, + -list->view->abs_x, + -list->view->abs_y); + } + + if (pixman_region32_not_empty (&list->view->damage)) + { + /* Translate the region into the subcompositor + coordinate space. */ + pixman_region32_translate (&list->view->damage, + list->view->abs_x, + list->view->abs_y); + + /* Similarly intersect the damage region with the + clipping. */ + pixman_region32_intersect_rect (&temp, &list->view->damage, + view->abs_x, view->abs_y, + ViewWidth (view), + ViewHeight (view)); + + pixman_region32_union (&update_region, &temp, &update_region); + + /* If the damage extends outside the area known to be + obscured by the current start, reset start back to + the original starting point. */ + if (start != original_start) + { + pixman_region32_subtract (&temp, &list->view->damage, + &start_opaque); + + if (pixman_region32_not_empty (&temp)) + start = original_start; + } + + /* Clear the damaged area, since it will either be drawn + or be obscured. */ + pixman_region32_clear (&list->view->damage); + } + + next: + list = list->next; + } + while (list != subcompositor->inferiors); + + if (IsOpaqueDirty (subcompositor)) + { + /* The opaque region changed, so run any callbacks. */ + if (subcompositor->opaque_change) + { + /* Translate this to appear in the "virtual" coordinate + space. */ + pixman_region32_translate (&total_opaque, -min_x, -min_y); + + subcompositor->opaque_change (subcompositor, + subcompositor->opaque_change_data, + &total_opaque); + } + + pixman_region32_fini (&total_opaque); + } + + if (IsInputDirty (subcompositor)) + { + /* The input region changed, so run any callbacks. */ + if (subcompositor->input_change) + { + /* Translate this to appear in the "virtual" coordinate + space. */ + pixman_region32_translate (&total_input, -min_x, -min_y); + + subcompositor->input_change (subcompositor, + subcompositor->input_change_data, + &total_input); + } + + pixman_region32_fini (&total_input); + } + + pixman_region32_fini (&start_opaque); + } + else + { + /* To save from iterating over all the views twice, perform the + input and opaque region updates in the draw loop instead. */ + pixman_region32_init (&total_opaque); + pixman_region32_init (&total_input); + } + + /* If there's nothing to do, return. */ + + if (!start) + goto complete; + + /* Now update all views from start onwards. */ + + list = start->link; + first = NULL; + + do + { + view = list->view; + + if (!view) + goto next_1; + + if (IsViewUnmapped (view)) + { + /* Skip the unmapped view. */ + list = view->inferior; + + /* Set the "is partially mapped" flag. This is an + optimization used to make inserting views in deeply + nested hierarchies faster. */ + SetPartiallyMapped (subcompositor); + goto next_1; + } + + if (!view->buffer) + goto next_1; + + picture = XLPictureFromBuffer (view->buffer); + + if (!first) + { + /* The first view with an attached buffer should be drawn + with PictOpSrc so that transparency is applied correctly, + if it contains the entire update region. */ + + if (IsGarbaged (subcompositor)) + { + extents = &temp_boxes; + + /* Make extents the entire region, since that's what is + being updated. */ + temp_boxes.x1 = min_x; + temp_boxes.y1 = min_y; + temp_boxes.x2 = subcompositor->max_x + 1; + temp_boxes.y2 = subcompositor->max_y + 1; + } + else + extents = pixman_region32_extents (&update_region); + + if (ViewContainsExtents (view, extents)) + /* The update region is contained by the entire view, so + use PictOpSrc. */ + op = PictOpSrc; + else + { + /* Otherwise, fill the whole update region with + transparency. */ + + if (IsGarbaged (subcompositor)) + { + /* Use the entire subcompositor bounds if + garbaged. */ + boxes = &temp_boxes; + nboxes = 1; + } + else + boxes = pixman_region32_rectangles (&update_region, + &nboxes); + + /* Fill with transparency. */ + FillBoxesWithTransparency (subcompositor, + boxes, nboxes); + + /* And use PictOpOver as usual. */ + op = PictOpOver; + } + } + else + op = PictOpOver; + + first = view; + + if (ViewHaveTransform (view)) + { + transform = ViewGetTransform (view); + + XRenderSetPictureTransform (compositor.display, picture, + &transform); + } + + if (!IsGarbaged (subcompositor)) + { + /* First, obtain a new region that is the intersection of + the view with the global update region. */ + pixman_region32_intersect_rect (&temp, &update_region, + view->abs_x, view->abs_y, + ViewWidth (view), + ViewHeight (view)); + + /* Next, composite every rectangle in that region. */ + boxes = pixman_region32_rectangles (&temp, &nboxes); + + for (i = 0; i < nboxes; ++i) + XRenderComposite (compositor.display, op, picture, + None, subcompositor->target, + /* src-x. */ + BoxStartX (boxes[i]) - view->abs_x, + /* src-y. */ + BoxStartY (boxes[i]) - view->abs_y, + /* mask-x, mask-y. */ + 0, 0, + /* dst-x. */ + BoxStartX (boxes[i]) - min_x + tx, + /* dst-y. */ + BoxStartY (boxes[i]) - min_y + ty, + /* width, height. */ + BoxWidth (boxes[i]), BoxHeight (boxes[i])); + } + else + { + /* Clear the damaged area, since it will either be drawn or + be obscured. We didn't get a chance to clear the damage + earlier, since the compositor was garbaged. */ + pixman_region32_clear (&view->damage); + + /* If the subcompositor is garbaged, composite the entire view + to the right location. */ + XRenderComposite (compositor.display, op, picture, + None, subcompositor->target, + /* src-x, src-y. */ + 0, 0, + /* mask-x, mask-y. */ + 0, 0, + /* dst-x. */ + view->abs_x - min_x + tx, + /* dst-y. */ + view->abs_y - min_y + ty, + /* width. */ + ViewWidth (view), + /* height. */ + ViewHeight (view)); + + /* Also adjust the opaque and input regions here. */ + + if (pixman_region32_not_empty (&view->opaque)) + { + /* Translate the region into the global coordinate + space. */ + pixman_region32_translate (&list->view->opaque, + list->view->abs_x, + list->view->abs_y); + + pixman_region32_intersect_rect (&temp, &view->opaque, + view->abs_x, view->abs_y, + ViewWidth (view), + ViewHeight (view)); + pixman_region32_union (&total_opaque, &temp, &total_opaque); + + /* Translate it back. */ + pixman_region32_translate (&list->view->opaque, + -list->view->abs_x, + -list->view->abs_y); + } + + if (pixman_region32_not_empty (&view->input)) + { + /* Translate the region into the global coordinate + space. */ + pixman_region32_translate (&list->view->input, + list->view->abs_x, + list->view->abs_y); + pixman_region32_intersect_rect (&temp, &view->input, + view->abs_x, view->abs_y, + ViewWidth (view), + ViewHeight (view)); + pixman_region32_union (&total_input, &temp, &total_input); + + /* Translate it back. */ + pixman_region32_translate (&list->view->input, + -list->view->abs_x, + -list->view->abs_y); + } + } + + if (ViewHaveTransform (view)) + XRenderSetPictureTransform (compositor.display, picture, + &identity_transform); + + next_1: + list = list->next; + } + while (list != subcompositor->inferiors); + + complete: + + if (IsGarbaged (subcompositor)) + { + /* The opaque region changed, so run any callbacks. */ + if (subcompositor->opaque_change) + { + /* Translate this to appear in the "virtual" coordinate + space. */ + pixman_region32_translate (&total_opaque, -min_x, -min_y); + + subcompositor->opaque_change (subcompositor, + subcompositor->opaque_change_data, + &total_opaque); + } + + pixman_region32_fini (&total_opaque); + + /* The input region changed, so run any callbacks. */ + if (subcompositor->input_change) + { + /* Translate this to appear in the "virtual" coordinate + space. */ + pixman_region32_translate (&total_input, -min_x, -min_y); + + subcompositor->input_change (subcompositor, + subcompositor->input_change_data, + &total_input); + } + + pixman_region32_fini (&total_input); + } + + pixman_region32_fini (&temp); + pixman_region32_fini (&update_region); + + /* The update has completed, so the compositor is no longer + garbaged. */ + subcompositor->state &= ~SubcompositorIsGarbaged; + subcompositor->state &= ~SubcompositorIsOpaqueDirty; + subcompositor->state &= ~SubcompositorIsInputDirty; +} + +void +SubcompositorExpose (Subcompositor *subcompositor, XEvent *event) +{ + List *list; + View *view; + int x, y, width, height, nboxes, min_x, min_y, tx, ty; + pixman_box32_t extents, *boxes; + int op, i; + pixman_region32_t temp; + Picture picture; + XTransform transform; + + /* Graphics exposures are not yet handled. */ + if (event->type == GraphicsExpose) + return; + + /* No target? No update. */ + if (subcompositor->target == None) + return; + + x = event->xexpose.x + subcompositor->min_x; + y = event->xexpose.y + subcompositor->min_y; + width = event->xexpose.width; + height = event->xexpose.height; + + min_x = subcompositor->min_x; + min_y = subcompositor->min_y; + tx = subcompositor->tx; + ty = subcompositor->ty; + + extents.x1 = x; + extents.y1 = y; + extents.x2 = x + width; + extents.y2 = y + height; + + view = NULL; + + /* Draw every subsurface overlapping the exposure region from the + subcompositor onto the target. Most importantly, do NOT update + the bounds of the target, in case the exposure is in response to + a resize. */ + + list = subcompositor->inferiors; + + do + { + if (!list->view) + goto next; + + if (IsViewUnmapped (list->view)) + { + list = list->view->inferior; + goto next; + } + + if (!list->view->buffer) + goto next; + + /* If the first mapped view contains everything, draw it with + PictOpSrc. */ + if (!view && ViewContainsExtents (list->view, &extents)) + op = PictOpSrc; + else + { + /* Otherwise, fill the region with transparency for the + first update, and then use PictOpOver. */ + + if (!view) + FillBoxesWithTransparency (subcompositor, + &extents, 1); + + op = PictOpOver; + } + + view = list->view; + + /* Now, get the intersection of the rectangle with the view + bounds. */ + pixman_region32_init_rect (&temp, x, y, width, height); + pixman_region32_intersect_rect (&temp, &temp, view->abs_x, + view->abs_y, ViewWidth (view), + ViewHeight (view)); + + /* Composite the contents according to OP. */ + picture = XLPictureFromBuffer (view->buffer); + boxes = pixman_region32_rectangles (&temp, &nboxes); + + if (ViewHaveTransform (view)) + { + /* Set up transforms if necessary. */ + transform = ViewGetTransform (view); + + XRenderSetPictureTransform (compositor.display, picture, + &transform); + } + + for (i = 0; i < nboxes; ++i) + XRenderComposite (compositor.display, op, picture, + None, subcompositor->target, + /* src-x. */ + BoxStartX (boxes[i]) - view->abs_x, + /* src-y. */ + BoxStartY (boxes[i]) - view->abs_y, + /* mask-x, mask-y. */ + 0, 0, + /* dst-x. */ + BoxStartX (boxes[i]) - min_x + tx, + /* dst-y. */ + BoxStartY (boxes[i]) - min_y + ty, + /* width, height. */ + BoxWidth (boxes[i]), BoxHeight (boxes[i])); + + /* Undo transforms that were applied. */ + if (ViewHaveTransform (view)) + XRenderSetPictureTransform (compositor.display, picture, + &identity_transform); + + /* Free the scratch region used to compute the intersection. */ + pixman_region32_fini (&temp); + + next: + /* Move onto the next view. */ + list = list->next; + } + while (list != subcompositor->inferiors); +} + +void +SubcompositorGarbage (Subcompositor *subcompositor) +{ + SetGarbaged (subcompositor); +} + +void +SubcompositorSetProjectiveTransform (Subcompositor *subcompositor, + int tx, int ty) +{ + subcompositor->tx = tx; + subcompositor->ty = ty; +} + +void +SubcompositorFree (Subcompositor *subcompositor) +{ + /* It isn't valid to call this function with children attached. */ + XLAssert (subcompositor->children->next + == subcompositor->children); + XLAssert (subcompositor->inferiors->next + == subcompositor->inferiors); + + XLFree (subcompositor->children); + XLFree (subcompositor->inferiors); + + XLFree (subcompositor); +} + +View * +SubcompositorLookupView (Subcompositor *subcompositor, int x, int y, + int *view_x, int *view_y) +{ + List *list; + int temp_x, temp_y; + pixman_box32_t box; + + x += subcompositor->min_x; + y += subcompositor->min_y; + + for (list = subcompositor->inferiors->last; + list != subcompositor->inferiors; + list = list->last) + { + if (!list->view) + continue; + + if (IsViewUnmapped (list->view)) + { + list = list->view->inferior; + continue; + } + + if (!list->view->buffer) + continue; + + temp_x = x - list->view->abs_x; + temp_y = y - list->view->abs_y; + + /* If the coordinates don't fit in the view bounds, skip the + view. This test is the equivalent to intersecting the view's + input region with the bounds of the view. */ + if (temp_x < 0 || temp_y < 0 + || temp_x >= ViewWidth (list->view) + || temp_y >= ViewHeight (list->view)) + continue; + + /* Now see if the input region contains the given + coordinates. If it does, return the view. */ + if (pixman_region32_contains_point (&list->view->input, x, y, + &box)) + { + *view_x = list->view->abs_x - subcompositor->min_x; + *view_y = list->view->abs_y - subcompositor->min_y; + + return list->view; + } + } + + return NULL; +} + +void * +ViewGetData (View *view) +{ + return view->data; +} + +void +ViewSetData (View *view, void *data) +{ + view->data = data; +} + +void +ViewTranslate (View *view, int x, int y, int *x_out, int *y_out) +{ + if (view->subcompositor) + { + /* X and Y are assumed to be in the "virtual" coordinate + space. */ + x += view->subcompositor->min_x; + y += view->subcompositor->min_y; + } + + *x_out = x - view->abs_x; + *y_out = y - view->abs_y; +} + +View * +ViewGetParent (View *view) +{ + return view->parent; +} + +void +SubcompositorInit (void) +{ + identity_transform.matrix[0][0] = 1; + identity_transform.matrix[1][1] = 1; + identity_transform.matrix[2][2] = 1; +} + +int +SubcompositorWidth (Subcompositor *subcompositor) +{ + return subcompositor->max_x - subcompositor->min_x + 1; +} + +int +SubcompositorHeight (Subcompositor *subcompositor) +{ + return subcompositor->max_y - subcompositor->min_y + 1; +} + +void +SubcompositorFreeze (Subcompositor *subcompositor) +{ + SetFrozen (subcompositor); +} + +void +SubcompositorUnfreeze (Subcompositor *subcompositor) +{ + subcompositor->state &= ~SubcompositorIsFrozen; +} + +#endif diff --git a/subsurface.c b/subsurface.c new file mode 100644 index 0000000..54f66e4 --- /dev/null +++ b/subsurface.c @@ -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 . */ + +#include +#include + +#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); + } +} diff --git a/surface.c b/surface.c new file mode 100644 index 0000000..e8b0a8c --- /dev/null +++ b/surface.c @@ -0,0 +1,1313 @@ +/* Wayland compositor running on top of an X server. + +Copyright (C) 2022 to various contributors. + +This file is part of 12to11. + +12to11 is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +12to11 is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with 12to11. If not, see . */ + +#include +#include +#include + +#include + +#include "compositor.h" + +/* List of all currently existing surfaces. */ +Surface all_surfaces; + +static DestroyCallback * +AddDestroyCallbackAfter (DestroyCallback *after) +{ + DestroyCallback *callback; + + callback = XLCalloc (1, sizeof *callback); + + callback->next = after->next; + callback->last = after; + + after->next->last = callback; + after->next = callback; + + return callback; +} + +static void +UnlinkDestroyCallback (DestroyCallback *callback) +{ + callback->last->next = callback->next; + callback->next->last = callback->last; + + callback->last = callback; + callback->next = callback; +} + +static UnmapCallback * +AddUnmapCallbackAfter (UnmapCallback *after) +{ + UnmapCallback *callback; + + callback = XLCalloc (1, sizeof *callback); + + callback->next = after->next; + callback->last = after; + + after->next->last = callback; + after->next = callback; + + return callback; +} + +static void +UnlinkUnmapCallback (UnmapCallback *callback) +{ + callback->last->next = callback->next; + callback->next->last = callback->last; + + callback->last = callback; + callback->next = callback; +} + +static CommitCallback * +AddCommitCallbackAfter (CommitCallback *after) +{ + CommitCallback *callback; + + callback = XLSafeMalloc (sizeof *callback); + + if (!callback) + return callback; + + callback->next = after->next; + callback->last = after; + + after->next->last = callback; + after->next = callback; + + return callback; +} + +static void +UnlinkCommitCallback (CommitCallback *callback) +{ + callback->last->next = callback->next; + callback->next->last = callback->last; + + callback->last = callback; + callback->next = callback; +} + +static void +RunCommitCallbacks (Surface *surface) +{ + CommitCallback *callback; + + /* first is a sentinel node. */ + callback = surface->commit_callbacks.next; + + while (callback != &surface->commit_callbacks) + { + callback->commit (surface, callback->data); + callback = callback->next; + } +} + +static void +RunUnmapCallbacks (Surface *surface) +{ + UnmapCallback *callback, *last; + + /* first is a sentinel node. */ + callback = surface->unmap_callbacks.next; + + while (callback != &surface->unmap_callbacks) + { + last = callback; + callback = callback->next; + + last->unmap (last->data); + } +} + +static void +FreeCommitCallbacks (CommitCallback *first) +{ + CommitCallback *callback, *last; + + /* first is a sentinel node. */ + callback = first->next; + + while (callback != first) + { + last = callback; + callback = callback->next; + + XLFree (last); + } +} + +static void +FreeUnmapCallbacks (UnmapCallback *first) +{ + UnmapCallback *callback, *last; + + /* first is a sentinel node. */ + callback = first->next; + + while (callback != first) + { + last = callback; + callback = callback->next; + + XLFree (last); + } +} + +static void +FreeDestroyCallbacks (DestroyCallback *first) +{ + DestroyCallback *callback, *last; + + callback = first->next; + + while (callback != first) + { + last = callback; + callback = callback->next; + + last->destroy_func (last->data); + XLFree (last); + } +} + +static FrameCallback * +AddCallbackAfter (FrameCallback *after) +{ + FrameCallback *callback; + + callback = XLSafeMalloc (sizeof *callback); + + if (!callback) + return callback; + + callback->next = after->next; + callback->last = after; + + after->next->last = callback; + after->next = callback; + + return callback; +} + +static void +UnlinkCallbacks (FrameCallback *start, FrameCallback *end) +{ + /* First, make the list skip past END. */ + start->last->next = end->next; + end->next->last = start->last; + + /* Then, unlink the list. */ + start->last = end; + end->next = start; +} + +static void +RelinkCallbacksAfter (FrameCallback *start, FrameCallback *end, + FrameCallback *dest) +{ + end->next = dest->next; + start->last = dest; + + dest->next->last = end; + dest->next = start; +} + +static void +HandleCallbackResourceDestroy (struct wl_resource *resource) +{ + FrameCallback *callback; + + callback = wl_resource_get_user_data (resource); + UnlinkCallbacks (callback, callback); + XLFree (callback); +} + +static void +FreeFrameCallbacks (FrameCallback *start) +{ + FrameCallback *callback, *last; + + callback = start->next; + + while (callback != start) + { + last = callback; + callback = callback->next; + + /* This will unlink last from its surroundings and free it. */ + wl_resource_destroy (last->resource); + } +} + +static void +RunFrameCallbacks (FrameCallback *start, uint32_t time) +{ + FrameCallback *callback, *last; + + callback = start->next; + + while (callback != start) + { + last = callback; + callback = callback->next; + + wl_callback_send_done (last->resource, time); + /* This will unlink last from its surroundings and free it. */ + wl_resource_destroy (last->resource); + } +} + +static void +AttachBuffer (State *state, ExtBuffer *buffer) +{ + if (state->buffer) + XLDereferenceBuffer (state->buffer); + + state->buffer = buffer; + XLRetainBuffer (buffer); +} + +static void +ClearBuffer (State *state) +{ + if (!state->buffer) + return; + + XLDereferenceBuffer (state->buffer); + state->buffer = NULL; +} + +static void +DestroySurface (struct wl_client *client, + struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static void +Attach (struct wl_client *client, struct wl_resource *resource, + struct wl_resource *buffer_resource, int32_t x, int32_t y) +{ + Surface *surface; + ExtBuffer *buffer; + + if (x != 0 && y != 0 + && wl_resource_get_version (resource) >= 5) + { + wl_resource_post_error (resource, WL_SURFACE_ERROR_INVALID_OFFSET, + "invalid offsets given to wl_surface_attach"); + return; + } + + surface = wl_resource_get_user_data (resource); + + if (buffer_resource) + { + buffer = wl_resource_get_user_data (buffer_resource); + AttachBuffer (&surface->pending_state, buffer); + } + else + ClearBuffer (&surface->pending_state); + + surface->pending_state.x = x; + surface->pending_state.y = y; + + surface->pending_state.pending |= PendingBuffer; + surface->pending_state.pending |= PendingAttachments; +} + +static void +Offset (struct wl_client *client, struct wl_resource *resource, + int32_t x, int32_t y) +{ + Surface *surface; + + surface = wl_resource_get_user_data (resource); + + surface->pending_state.x = x; + surface->pending_state.y = y; + + surface->pending_state.pending |= PendingAttachments; +} + +static void +Damage (struct wl_client *client, struct wl_resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) +{ + Surface *surface; + + surface = wl_resource_get_user_data (resource); + + /* Prevent integer overflow during later processing, since some + clients really set the damage region to INT_MAX. */ + + pixman_region32_union_rect (&surface->pending_state.surface, + &surface->pending_state.surface, + x, y, MIN (65535, width), + MIN (65535, height)); + + surface->pending_state.pending |= PendingSurfaceDamage; +} + +static void +Frame (struct wl_client *client, struct wl_resource *resource, + uint32_t callback_id) +{ + struct wl_resource *callback_resource; + FrameCallback *callback; + Surface *surface; + + surface = wl_resource_get_user_data (resource); + callback = AddCallbackAfter (&surface->pending_state.frame_callbacks); + + if (!callback) + { + wl_client_post_no_memory (client); + + return; + } + + callback_resource = wl_resource_create (client, &wl_callback_interface, + 1, callback_id); + + if (!callback_resource) + { + wl_client_post_no_memory (client); + UnlinkCallbacks (callback, callback); + XLFree (callback); + + return; + } + + wl_resource_set_implementation (callback_resource, NULL, + callback, HandleCallbackResourceDestroy); + + callback->resource = callback_resource; + surface->pending_state.pending |= PendingFrameCallbacks; +} + +static void +SetOpaqueRegion (struct wl_client *client, struct wl_resource *resource, + struct wl_resource *region_resource) +{ + Surface *surface; + pixman_region32_t *region; + + surface = wl_resource_get_user_data (resource); + + if (region_resource) + { + region = wl_resource_get_user_data (region_resource); + + /* Some ugly clients give the region ridiculous dimensions like + 0, 0, INT_MAX, INT_MAX, which causes overflows later on. So + intersect it with the largest possible dimensions of a + view. */ + pixman_region32_intersect_rect (&surface->pending_state.opaque, + region, 0, 0, 65535, 65535); + } + else + pixman_region32_clear (&surface->pending_state.opaque); + + surface->pending_state.pending |= PendingOpaqueRegion; +} + +static void +SetInputRegion (struct wl_client *client, struct wl_resource *resource, + struct wl_resource *region_resource) +{ + Surface *surface; + pixman_region32_t *region; + + surface = wl_resource_get_user_data (resource); + + if (region_resource) + { + region = wl_resource_get_user_data (region_resource); + + /* Some ugly clients give the region ridiculous dimensions like + 0, 0, INT_MAX, INT_MAX, which causes overflows later on. So + intersect it with the largest possible dimensions of a + view. */ + pixman_region32_intersect_rect (&surface->pending_state.input, + region, 0, 0, 65535, 65535); + } + else + { + pixman_region32_clear (&surface->pending_state.input); + pixman_region32_union_rect (&surface->pending_state.input, + &surface->pending_state.input, + 0, 0, 65535, 65535); + } + + surface->pending_state.pending |= PendingInputRegion; +} + +void +XLDefaultCommit (Surface *surface) +{ + /* Nothing has to be done here yet. */ +} + +static void +ApplyBuffer (Surface *surface) +{ + if (surface->current_state.buffer) + ViewAttachBuffer (surface->view, surface->current_state.buffer); + else + ViewDetach (surface->view); +} + +/* Return the effective scale. + + The effective scale is a value by which to scale down the contents + of a surface on display. */ + +static int +GetEffectiveScale (int scale) +{ + /* A "scale" is how many times to scale _down_ a surface, not up. + Negative values mean to scale the surface up instead of down. */ + scale = scale - global_scale_factor; + + return scale; +} + +static void +ApplyScale (Surface *surface) +{ + int scale, effective; + + scale = surface->current_state.buffer_scale; + effective = GetEffectiveScale (scale); + + ViewSetScale (surface->view, effective); +} + +static void +ApplyOpaqueRegion (Surface *surface) +{ + pixman_region32_t temp; + + /* These regions, along with the global damage, must be multipled by + the global scale factor. */ + if (global_scale_factor == 1) + ViewSetOpaque (surface->view, + &surface->current_state.opaque); + else + { + pixman_region32_init (&temp); + XLScaleRegion (&temp, &surface->current_state.opaque, + global_scale_factor, global_scale_factor); + ViewSetOpaque (surface->view, &temp); + pixman_region32_fini (&temp); + } +} + +static void +ApplyInputRegion (Surface *surface) +{ + pixman_region32_t temp; + + /* These regions, along with the global damage, must be multipled by + the global scale factor. */ + if (global_scale_factor == 1) + ViewSetInput (surface->view, + &surface->current_state.input); + else + { + pixman_region32_init (&temp); + XLScaleRegion (&temp, &surface->current_state.input, + global_scale_factor, global_scale_factor); + ViewSetInput (surface->view, &temp); + pixman_region32_fini (&temp); + } +} + +static void +HandleScaleChanged (void *data, int new_scale) +{ + Surface *surface; + Subcompositor *subcompositor; + + surface = data; + + /* First, reapply various regions that depend on the surface + scale. */ + ApplyInputRegion (surface); + ApplyOpaqueRegion (surface); + ApplyScale (surface); + + /* Next, call any role-specific hooks. */ + if (surface->role && surface->role->funcs.rescale) + surface->role->funcs.rescale (surface, surface->role); + + /* Then, redisplay the view if a subcompositor is already + attached. */ + subcompositor = ViewGetSubcompositor (surface->view); + + if (subcompositor) + { + /* When updating stuff out-of-band, a subframe must be started + around the update. */ + + if (surface->role && surface->role->funcs.subframe + && surface->role->funcs.subframe (surface, surface->role)) + { + SubcompositorUpdate (subcompositor); + + if (surface->role && surface->role->funcs.end_subframe) + surface->role->funcs.end_subframe (surface, surface->role); + } + } +} + +static void +ApplyDamage (Surface *surface) +{ + pixman_region32_t temp; + int scale; + + scale = GetEffectiveScale (surface->current_state.buffer_scale); + + /* N.B. that this must come after the scale is applied. */ + + if (scale) + { + pixman_region32_init (&temp); + + if (scale > 0) + XLScaleRegion (&temp, &surface->current_state.damage, + 1.0 / (scale + 1), 1.0 / (scale + 1)); + else + XLScaleRegion (&temp, &surface->current_state.damage, + abs (scale) + 1, abs (scale) + 1); + + ViewDamage (surface->view, &temp); + + pixman_region32_fini (&temp); + } + else + ViewDamage (surface->view, + &surface->current_state.damage); +} + +static void +ApplySurfaceDamage (Surface *surface) +{ + pixman_region32_t temp; + + /* These regions, along with the global damage, must be multipled by + the global scale factor. */ + + if (global_scale_factor == 1) + ViewDamage (surface->view, + &surface->current_state.surface); + else + { + pixman_region32_init (&temp); + XLScaleRegion (&temp, &surface->current_state.surface, + global_scale_factor, global_scale_factor); + ViewDamage (surface->view, &temp); + pixman_region32_fini (&temp); + } +} + +static void +SavePendingState (Surface *surface) +{ + FrameCallback *start, *end; + + /* Save pending state to cached state. Release any buffer + previously in the cached state. */ + + if (surface->pending_state.pending & PendingBuffer) + { + if (surface->cached_state.buffer + && (surface->pending_state.buffer + != surface->cached_state.buffer) + /* If the cached buffer has already been applied, releasing + it is a mistake! */ + && (surface->cached_state.buffer + != surface->current_state.buffer)) + { + if (surface->role) + surface->role->funcs.release_buffer (surface, surface->role, + surface->cached_state.buffer); + else + XLReleaseBuffer (surface->cached_state.buffer); + } + + if (surface->pending_state.buffer) + { + AttachBuffer (&surface->cached_state, + surface->pending_state.buffer); + ClearBuffer (&surface->pending_state); + } + else + ClearBuffer (&surface->cached_state); + } + + if (surface->pending_state.pending & PendingInputRegion) + pixman_region32_copy (&surface->cached_state.input, + &surface->pending_state.input); + + if (surface->pending_state.pending & PendingOpaqueRegion) + pixman_region32_copy (&surface->cached_state.opaque, + &surface->pending_state.opaque); + + if (surface->pending_state.pending & PendingBufferScale) + surface->cached_state.buffer_scale + = surface->pending_state.buffer_scale; + + if (surface->pending_state.pending & PendingAttachments) + { + surface->cached_state.x = surface->pending_state.x; + surface->cached_state.y = surface->pending_state.y; + } + + if (surface->pending_state.pending & PendingDamage) + { + pixman_region32_union (&surface->cached_state.damage, + &surface->cached_state.damage, + &surface->pending_state.damage); + pixman_region32_clear (&surface->pending_state.damage); + } + + if (surface->pending_state.pending & PendingSurfaceDamage) + { + pixman_region32_union (&surface->cached_state.surface, + &surface->cached_state.surface, + &surface->pending_state.surface); + pixman_region32_clear (&surface->pending_state.surface); + } + + if (surface->pending_state.pending & PendingFrameCallbacks + && (surface->pending_state.frame_callbacks.next + != &surface->pending_state.frame_callbacks)) + { + start = surface->pending_state.frame_callbacks.next; + end = surface->pending_state.frame_callbacks.last; + + UnlinkCallbacks (start, end); + RelinkCallbacksAfter (start, end, + &surface->cached_state.frame_callbacks); + } + + surface->cached_state.pending |= surface->pending_state.pending; + surface->pending_state.pending = PendingNone; +} + +static void +InternalCommit (Surface *surface, State *pending) +{ + FrameCallback *start, *end; + + if (pending->pending & PendingBuffer) + { + if ((surface->current_state.buffer != pending->buffer) + && surface->current_state.buffer) + { + if (surface->role) + surface->role->funcs.release_buffer (surface, surface->role, + surface->current_state.buffer); + else + XLReleaseBuffer (surface->current_state.buffer); + } + + if (pending->buffer) + { + AttachBuffer (&surface->current_state, + pending->buffer); + ApplyBuffer (surface); + ClearBuffer (pending); + } + else + { + ClearBuffer (&surface->current_state); + ApplyBuffer (surface); + ClearBuffer (pending); + } + } + + if (pending->pending & PendingInputRegion) + { + pixman_region32_copy (&surface->current_state.input, + &pending->input); + ApplyInputRegion (surface); + } + + if (pending->pending & PendingOpaqueRegion) + { + pixman_region32_copy (&surface->current_state.opaque, + &pending->opaque); + ApplyOpaqueRegion (surface); + } + + if (pending->pending & PendingBufferScale) + { + surface->current_state.buffer_scale = pending->buffer_scale; + ApplyScale (surface); + } + + if (pending->pending & PendingAttachments) + { + surface->current_state.x = pending->x; + surface->current_state.y = pending->y; + } + + if (pending->pending & PendingDamage) + { + pixman_region32_copy (&surface->current_state.damage, + &pending->damage); + pixman_region32_clear (&pending->damage); + + ApplyDamage (surface); + } + + if (pending->pending & PendingSurfaceDamage) + { + pixman_region32_copy (&surface->current_state.surface, + &pending->surface); + pixman_region32_clear (&pending->surface); + + ApplySurfaceDamage (surface); + } + + if (pending->pending & PendingFrameCallbacks) + { + /* Insert the pending frame callbacks in front of the current + ones. */ + + if (pending->frame_callbacks.next != &pending->frame_callbacks) + { + start = pending->frame_callbacks.next; + end = pending->frame_callbacks.last; + + UnlinkCallbacks (start, end); + RelinkCallbacksAfter (start, end, + &surface->current_state.frame_callbacks); + } + } + + RunCommitCallbacks (surface); + + if (surface->subsurfaces) + /* Pending surface stacking actions are stored on the parent so + they run in the right order. */ + XLSubsurfaceHandleParentCommit (surface); + + if (!surface->role) + { + XLDefaultCommit (surface); + pending->pending = PendingNone; + + return; + } + + surface->role->funcs.commit (surface, surface->role); + pending->pending = PendingNone; +} + +static void +Commit (struct wl_client *client, struct wl_resource *resource) +{ + Surface *surface; + + surface = wl_resource_get_user_data (resource); + + if (surface->role && surface->role->funcs.early_commit + /* The role chose to postpone the commit for a later time. */ + && !surface->role->funcs.early_commit (surface, surface->role)) + { + /* So save the state for the role to commit later. */ + SavePendingState (surface); + return; + } + + InternalCommit (surface, &surface->pending_state); +} + +static void +SetBufferTransform (struct wl_client *client, struct wl_resource *resource, + int32_t transform) +{ + if (transform != WL_OUTPUT_TRANSFORM_NORMAL) + wl_resource_post_error (resource, WL_DISPLAY_ERROR_IMPLEMENTATION, + "this compositor does not support buffer transforms"); +} + +static void +SetBufferScale (struct wl_client *client, struct wl_resource *resource, + int32_t scale) +{ + Surface *surface; + + if (scale <= 0) + { + wl_resource_post_error (resource, WL_SURFACE_ERROR_INVALID_SCALE, + "invalid scale: %d", scale); + return; + } + + surface = wl_resource_get_user_data (resource); + + surface->pending_state.buffer_scale = scale; + surface->pending_state.pending |= PendingBufferScale; +} + +static void +DamageBuffer (struct wl_client *client, struct wl_resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) +{ + Surface *surface; + + surface = wl_resource_get_user_data (resource); + + /* Prevent integer overflow during later processing, since some + clients really set the damage region to INT_MAX. */ + + pixman_region32_union_rect (&surface->pending_state.damage, + &surface->pending_state.damage, + x, y, MIN (65535, width), + MIN (65535, height)); + + surface->pending_state.pending |= PendingDamage; +} + +static const struct wl_surface_interface wl_surface_impl = + { + .destroy = DestroySurface, + .attach = Attach, + .damage = Damage, + .frame = Frame, + .set_opaque_region = SetOpaqueRegion, + .set_input_region = SetInputRegion, + .commit = Commit, + .set_buffer_transform = SetBufferTransform, + .set_buffer_scale = SetBufferScale, + .damage_buffer = DamageBuffer, + .offset = Offset, + }; + +static void +InitState (State *state) +{ + pixman_region32_init (&state->damage); + pixman_region32_init (&state->opaque); + pixman_region32_init (&state->surface); + + /* The initial state of the input region is always infinite. */ + pixman_region32_init_rect (&state->input, 0, 0, + 65535, 65535); + + state->pending = PendingNone; + state->buffer = NULL; + state->buffer_scale = 1; + + /* Initialize the sentinel node. */ + state->frame_callbacks.next = &state->frame_callbacks; + state->frame_callbacks.last = &state->frame_callbacks; + state->frame_callbacks.resource = NULL; +} + +static void +FinalizeState (State *state) +{ + pixman_region32_fini (&state->damage); + pixman_region32_fini (&state->opaque); + pixman_region32_fini (&state->surface); + pixman_region32_fini (&state->input); + + if (state->buffer) + XLDereferenceBuffer (state->buffer); + state->buffer = NULL; + + /* Destroy any callbacks that might be remaining. */ + FreeFrameCallbacks (&state->frame_callbacks); +} + +static void +NotifySubsurfaceDestroyed (void *data) +{ + Surface *surface; + + surface = data; + + if (surface->role) + XLSubsurfaceParentDestroyed (surface->role); +} + +static void +HandleSurfaceDestroy (struct wl_resource *resource) +{ + Surface *surface; + int i; + + surface = wl_resource_get_user_data (resource); + + if (surface->role) + XLSurfaceReleaseRole (surface, surface->role); + + /* Keep surface->resource around until the role is released; some + code (such as dnd.c) assumes that surface->resource will always + be available in unmap callbacks. */ + surface->resource = NULL; + + /* First, free all subsurfaces. */ + XLListFree (surface->subsurfaces, + NotifySubsurfaceDestroyed); + + /* Then release all client data. */ + for (i = 0; i < MaxClientData; ++i) + { + if (surface->client_data[i]) + surface->free_client_data[i] (surface->client_data[i]); + XLFree (surface->client_data[i]); + + surface->client_data[i] = NULL; + } + + /* Release the output region. */ + pixman_region32_fini (&surface->output_region); + + /* Next, free the views. */ + ViewFree (surface->view); + ViewFree (surface->under); + + /* Then, unlink the surface from the list of all surfaces. */ + surface->next->last = surface->last; + surface->last->next = surface->next; + + /* Free outputs. */ + XLFree (surface->outputs); + + /* Free the window scaling factor callback. */ + XLRemoveScaleChangeCallback (surface->scale_callback_key); + + FinalizeState (&surface->pending_state); + FinalizeState (&surface->current_state); + FinalizeState (&surface->cached_state); + FreeCommitCallbacks (&surface->commit_callbacks); + FreeUnmapCallbacks (&surface->unmap_callbacks); + FreeDestroyCallbacks (&surface->destroy_callbacks); + XLFree (surface); +} + +void +XLCreateSurface (struct wl_client *client, + struct wl_resource *resource, + uint32_t id) +{ + Surface *surface; + + surface = XLSafeMalloc (sizeof *surface); + + if (!surface) + { + wl_resource_post_no_memory (resource); + return; + } + + memset (surface, 0, sizeof *surface); + surface->resource + = wl_resource_create (client, &wl_surface_interface, + wl_resource_get_version (resource), + id); + + if (!surface->resource) + { + wl_resource_post_no_memory (resource); + XLFree (surface); + return; + } + + wl_resource_set_implementation (surface->resource, &wl_surface_impl, + surface, HandleSurfaceDestroy); + + surface->role = NULL; + surface->view = MakeView (); + surface->under = MakeView (); + surface->subsurfaces = NULL; + + /* Make it so that seat.c can associate the surface with the + view. */ + ViewSetData (surface->view, surface); + + /* Initialize the sentinel node for the commit callback list. */ + surface->commit_callbacks.last = &surface->commit_callbacks; + surface->commit_callbacks.next = &surface->commit_callbacks; + surface->commit_callbacks.commit = NULL; + surface->commit_callbacks.data = NULL; + + /* And the sentinel node for the unmap callback list. */ + surface->unmap_callbacks.last = &surface->unmap_callbacks; + surface->unmap_callbacks.next = &surface->unmap_callbacks; + surface->unmap_callbacks.unmap = NULL; + surface->unmap_callbacks.data = NULL; + + /* And the sentinel node for the destroy callback list. */ + surface->destroy_callbacks.last = &surface->destroy_callbacks; + surface->destroy_callbacks.next = &surface->destroy_callbacks; + surface->destroy_callbacks.destroy_func = NULL; + surface->destroy_callbacks.data = NULL; + + InitState (&surface->pending_state); + InitState (&surface->current_state); + InitState (&surface->cached_state); + + /* Now the default input has been initialized, so apply it to the + view. */ + ApplyInputRegion (surface); + + /* Likewise for the scale. */ + ApplyScale (surface); + + /* Initially, allow surfaces to accept any kind of role. */ + surface->role_type = AnythingType; + + /* Initialize the output region. */ + pixman_region32_init (&surface->output_region); + + /* Link the surface onto the list of all surfaces. */ + surface->next = all_surfaces.next; + surface->last = &all_surfaces; + all_surfaces.next->last = surface; + all_surfaces.next = surface; + + /* Also add the scale change callback. */ + surface->scale_callback_key + = XLAddScaleChangeCallback (surface, HandleScaleChanged); + + /* Clear surface output coordinates. */ + surface->output_x = INT_MIN; + surface->output_y = INT_MIN; +} + +void +XLInitSurfaces (void) +{ + all_surfaces.next = &all_surfaces; + all_surfaces.last = &all_surfaces; +} + + +/* Role management: XDG shells, wl_shells, et cetera. */ + +Bool +XLSurfaceAttachRole (Surface *surface, Role *role) +{ + if (surface->role) + return False; + + if (!role->funcs.setup (surface, role)) + return False; + + surface->role = role; + + return True; +} + +void +XLSurfaceReleaseRole (Surface *surface, Role *role) +{ + role->funcs.teardown (surface, role); + + if (surface->resource) + /* Now that the surface is unmapped, leave every output it + previously entered. */ + XLClearOutputs (surface); + + surface->role = NULL; + surface->output_x = INT_MIN; + surface->output_y = INT_MIN; + RunUnmapCallbacks (surface); +} + +void +XLStateAttachBuffer (State *state, ExtBuffer *buffer) +{ + AttachBuffer (state, buffer); +} + +void +XLStateDetachBuffer (State *state) +{ + ClearBuffer (state); +} + + +/* Various other functions exported for roles. */ + +void +XLSurfaceRunFrameCallbacks (Surface *surface, struct timespec time) +{ + uint32_t ms_time; + XLList *list; + + ms_time = time.tv_sec * 1000 + time.tv_nsec / 1000000; + + RunFrameCallbacks (&surface->current_state.frame_callbacks, + ms_time); + + /* Run frame callbacks for each attached subsurface as well. */ + for (list = surface->subsurfaces; list; list = list->next) + XLSurfaceRunFrameCallbacks (list->data, time); +} + +CommitCallback * +XLSurfaceRunAtCommit (Surface *surface, + void (*commit_func) (Surface *, void *), + void *data) +{ + CommitCallback *callback; + + callback = AddCommitCallbackAfter (&surface->commit_callbacks); + callback->commit = commit_func; + callback->data = data; + + return callback; +} + +void +XLSurfaceCancelCommitCallback (CommitCallback *callback) +{ + UnlinkCommitCallback (callback); + + XLFree (callback); +} + +UnmapCallback * +XLSurfaceRunAtUnmap (Surface *surface, + void (*unmap_func) (void *), + void *data) +{ + UnmapCallback *callback; + + callback = AddUnmapCallbackAfter (&surface->unmap_callbacks); + callback->unmap = unmap_func; + callback->data = data; + + return callback; +} + +void +XLSurfaceCancelUnmapCallback (UnmapCallback *callback) +{ + UnlinkUnmapCallback (callback); + + XLFree (callback); +} + +void +XLCommitSurface (Surface *surface, Bool use_pending) +{ + InternalCommit (surface, (use_pending + ? &surface->pending_state + : &surface->cached_state)); +} + +DestroyCallback * +XLSurfaceRunOnFree (Surface *surface, void (*destroy_func) (void *), + void *data) +{ + DestroyCallback *callback; + + callback = AddDestroyCallbackAfter (&surface->destroy_callbacks); + callback->destroy_func = destroy_func; + callback->data = data; + + return callback; +} + +void +XLSurfaceCancelRunOnFree (DestroyCallback *callback) +{ + UnlinkDestroyCallback (callback); + + XLFree (callback); +} + +void * +XLSurfaceGetClientData (Surface *surface, ClientDataType type, + size_t size, void (*free_func) (void *)) +{ + if (surface->client_data[type]) + return surface->client_data[type]; + + surface->client_data[type] = XLCalloc (1, size); + surface->free_client_data[type] = free_func; + + return surface->client_data[type]; +} + +Window +XLWindowFromSurface (Surface *surface) +{ + if (!surface->role + || !surface->role->funcs.get_window (surface, + surface->role)) + return None; + + return surface->role->funcs.get_window (surface, + surface->role); +} + +Bool +XLSurfaceGetResizeDimensions (Surface *surface, int *width, int *height) +{ + if (!surface->role + || !surface->role->funcs.get_resize_dimensions) + return False; + + surface->role->funcs.get_resize_dimensions (surface, surface->role, + width, height); + return True; +} + +void +XLSurfacePostResize (Surface *surface, int west_motion, int north_motion, + int new_width, int new_height) +{ + if (!surface->role + || !surface->role->funcs.post_resize) + return; + + surface->role->funcs.post_resize (surface, surface->role, + west_motion, north_motion, + new_width, new_height); + return; +} + +void +XLSurfaceMoveBy (Surface *surface, int west, int north) +{ + if (!surface->role + || !surface->role->funcs.move_by) + return; + + surface->role->funcs.move_by (surface, surface->role, + west, north); +} diff --git a/svn-commit.tmp b/svn-commit.tmp new file mode 100644 index 0000000..501cd2d --- /dev/null +++ b/svn-commit.tmp @@ -0,0 +1,4 @@ + +--This line, and those below, will be ignored-- + +A /home/oldosfan/12to11 diff --git a/timer.c b/timer.c new file mode 100644 index 0000000..877d08e --- /dev/null +++ b/timer.c @@ -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 . */ + +/* Some of this file was taken from timespec-add.c and timespec-sub.c, + part of gnulib, written by Paul Eggert . */ + +#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; +} diff --git a/xdata.c b/xdata.c new file mode 100644 index 0000000..4ddfa1e --- /dev/null +++ b/xdata.c @@ -0,0 +1,1782 @@ +/* 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 . */ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "compositor.h" + +#include + +/* X11 data source for Wayland clients. */ + +typedef struct _ReadTargetsData ReadTargetsData; +typedef struct _TargetMapping TargetMapping; +typedef struct _TransferInfo TransferInfo; +typedef struct _ConversionTransferInfo ConversionTransferInfo; +typedef struct _WriteInfo WriteInfo; +typedef struct _ConversionWriteInfo ConversionWriteInfo; +typedef struct _DataConversion DataConversion; + +struct _ReadTargetsData +{ + /* Array of atoms read from the selection. */ + Atom *atoms; + + /* Number of atoms read. */ + int n_atoms; +}; + +struct _TargetMapping +{ + /* The atom of the X target. */ + Atom atom; + + /* The name of the Wayland MIME type. */ + const char *mime_type; + + /* Optional translation function. */ + void (*translation_func) (Time, Atom, Atom, int); +}; + +struct _DataConversion +{ + /* The MIME type of the Wayland offer. */ + const char *mime_type; + + /* The atom describing the type of the property data. */ + Atom type; + + /* The atom of the X target. */ + Atom atom; + + /* An alternative GetClipboardCallback, if any. */ + GetDataFunc (*clipboard_callback) (WriteTransfer *, Atom, Atom *); +}; + +enum + { + NeedNewChunk = 1, + NeedDelayedFinish = (1 << 2), + }; + +struct _TransferInfo +{ + /* The file descriptor being written to. -1 if it was closed. */ + int fd; + + /* Some flags. */ + int flags; + + /* The currently buffered chunk. */ + unsigned char *chunk; + + /* The size of the currently buffered chunk, the number of bytes + into the chunk that have been written, and the number of bytes in + the property after the currently buffered chunk. */ + ptrdiff_t chunk_size, bytes_into, bytes_after; + + /* Any active file descriptor write callback. */ + WriteFd *write_callback; +}; + +struct _ConversionTransferInfo +{ + /* The file descriptor being written to. -1 if it was closed. */ + int fd; + + /* Some flags. */ + int flags; + + /* Conversion buffer. */ + char *buffer, *position; + + /* The bytes remaining in the conversion buffer. */ + size_t buffer_size; + + /* The output buffer. */ + char output_buffer[BUFSIZ]; + + /* And the amount of data used in the output buffer. */ + size_t outsize; + + /* The data format conversion context. */ + iconv_t cd; + + /* Any active file descriptor write callback. */ + WriteFd *write_callback; +}; + +enum + { + IsDragAndDrop = (1 << 16), + }; + +struct _WriteInfo +{ + /* The file descriptor being read from. */ + int fd; + + /* Some flags. */ + int flags; + + /* Any active file descriptor read callback. */ + ReadFd *read_callback; +}; + +enum + { + ReachedEndOfFile = 1, + }; + +struct _ConversionWriteInfo +{ + /* The file descriptor being read from. -1 if it was closed. */ + int fd; + + /* Some flags. */ + int flags; + + /* Any active file descriptor read callback. */ + ReadFd *read_callback; + + /* Input buffer for iconv. */ + char inbuf[BUFSIZ]; + + /* Number of bytes into the input buffer that have been read. */ + size_t inread; + + /* Pointer to the current position in the input buffer. Used by + iconv. */ + char *inptr; + + /* Iconv context for this conversion. */ + iconv_t cd; +}; + +/* Base event code of the Xfixes extension. */ +static int fixes_event_base; + +/* Map of targets that can be transferred from X to Wayland clients + and vice versa. */ +static TargetMapping direct_transfer[] = + { + { XA_STRING, "text/plain;charset=iso-9889-1" }, + { 0, "text/plain;charset=utf-8" }, + { XA_STRING, "text/plain;charset=utf-8" }, + /* These mappings are automatically generated. */ + DirectTransferMappings + }; + +/* Map of Wayland offer types to X atoms and data conversion + functions. */ +static DataConversion data_conversions[] = + { + { "text/plain;charset=utf-8", 0, 0, NULL }, + { "text/plain;charset=utf-8", 0, 0, NULL }, + }; + +/* The time of the last X selection change. */ +static Time last_x_selection_change; + +/* The time ownership was last asserted over CLIPBOARD, and the last + time any client did that. */ +static Time last_clipboard_time, last_clipboard_change; + +/* The currently supported selection targets. */ +static Atom *x_selection_targets; + +/* The number of targets in that array. */ +static int num_x_selection_targets; + +/* Data source currently being used to provide the X clipboard. */ +static DataSource *selection_data_source; + +/* Data source currently being used to provide the X drag-and-drop + selection. */ +static DataSource *drag_data_source; + +#ifdef DEBUG + +static void __attribute__ ((__format__ (gnu_printf, 1, 2))) +DebugPrint (const char *format, ...) +{ + va_list ap; + + va_start (ap, format); + vfprintf (stderr, format, ap); + va_end (ap); +} + +#else +#define DebugPrint(fmt, ...) ((void) 0) +#endif + +static void +Accept (struct wl_client *client, struct wl_resource *resource, + uint32_t serial, const char *mime_type) +{ + /* Nothing has to be done here yet. */ +} + +static Bool +HasSelectionTarget (Atom atom) +{ + int i; + + for (i = 0; i < num_x_selection_targets; ++i) + { + if (x_selection_targets[i] == atom) + return True; + } + + return False; +} + +static TargetMapping * +FindTranslationForMimeType (const char *mime_type) +{ + int i; + + for (i = 0; i < ArrayElements (direct_transfer); ++i) + { + if (!strcmp (direct_transfer[i].mime_type, mime_type) + && HasSelectionTarget (direct_transfer[i].atom)) + return &direct_transfer[i]; + } + + return NULL; +} + +static void +FinishTransfer (TransferInfo *info) +{ + if (info->write_callback) + XLRemoveWriteFd (info->write_callback); + + if (info->fd != -1) + /* Close the file descriptor, letting the client know that the + transfer completed. */ + close (info->fd); + + /* Finally, free the info structure. */ + XLFree (info); +} + +static void +MaybeFinishDelayedTransfer (ReadTransfer *transfer, + TransferInfo *info) +{ + if (info->flags & NeedDelayedFinish) + { + DebugPrint ("Completing a delayed transfer.\n"); + FinishTransfer (info); + CompleteDelayedTransfer (transfer); + } +} + +static void +NoticeTransferWritable (int fd, void *data) +{ + ReadTransfer *transfer; + TransferInfo *info; + long quantum; + unsigned char *chunk; + ptrdiff_t chunk_size, bytes_after; + ssize_t written; + + DebugPrint ("File descriptor %d became writable\n", fd); + + transfer = data; + info = GetTransferData (transfer); + + /* Start by reading at most this many bytes from the property. + TODO: take into account sizeof (long) == 8. */ + quantum = SelectionQuantum () / 4 * 4; + + if (!info->chunk) + { + read_chunk: + info->flags &= ~NeedNewChunk; + chunk = ReadChunk (transfer, quantum / 4, + &chunk_size, &bytes_after); + DebugPrint ("Reading a piece of the property of size %ld\n", + quantum); + + /* If chunk is NULL, close fd. The failure callback will also be + run soon. */ + if (!chunk) + { + DebugPrint ("Read failed\n"); + if (info->fd != -1) + close (info->fd); + + info->fd = -1; + + MaybeFinishDelayedTransfer (transfer, info); + return; + } + + /* Set info->chunk to the chunk and the chunk size. */ + info->chunk = chunk; + info->chunk_size = chunk_size; + info->bytes_after = bytes_after; + info->bytes_into = 0; + + DebugPrint ("Read actually got: %td, with %td after\n", + chunk_size, bytes_after); + } + + DebugPrint ("Writing %td bytes of chunk at offset %td\n", + info->chunk_size - info->bytes_into, + info->bytes_into); + + /* Try to write the chunk into the fd. */ + written = write (fd, info->chunk + info->bytes_into, + info->chunk_size - info->bytes_into); + + DebugPrint ("%zd bytes were really written; offset is now %td\n", + written, info->bytes_into + written); + + if (written < 0) + { + DebugPrint ("Some bytes could not be written: %s\n", + strerror (errno)); + + /* Write failed with EAGAIN. This might cause us to spin. */ + if (errno == EAGAIN) + return; + + /* Write failed with EPIPE. */ + if (errno == EPIPE) + { + /* EPIPE happened; set the fd to -1, skip the chunk if it + was not completely read, and return. */ + if (!info->bytes_after) + SkipChunk (transfer); + + close (info->fd); + info->fd = -1; + XFree (info->chunk); + info->chunk = NULL; + + DebugPrint ("EPIPE recieved while reading; cancelling transfer\n"); + + MaybeFinishDelayedTransfer (transfer, info); + return; + } + + perror ("write"); + exit (1); + } + + /* See how much was written. */ + info->bytes_into += written; + + if (info->bytes_into == info->chunk_size) + { + DebugPrint ("Chunk of %td completely written; bytes left " + "in property: %td\n", info->chunk_size, + info->bytes_after); + + /* The entire read part of the chunk has been written; read a + new chunk, or cancel the write callback if the chunk was + completely read. */ + + XFree (info->chunk); + info->chunk = NULL; + + if (info->bytes_after) + /* There are still bytes in the property waiting to be + read. */ + goto read_chunk; + else + { + /* The property has been completely read. If a new chunk + already exists, read and try to write it now. Otherwise, + cancel the write callback and wait for either a new + property to be set, or for DirectFinishCallback to be + called. */ + + if (info->flags & NeedNewChunk) + goto read_chunk; + else + { + DebugPrint ("Removing write callback\n"); + + XLRemoveWriteFd (info->write_callback); + info->write_callback = NULL; + MaybeFinishDelayedTransfer (transfer, info); + } + } + } +} + +static void +DirectReadCallback (ReadTransfer *transfer, Atom type, int format, + ptrdiff_t size) +{ + TransferInfo *info; + + info = GetTransferData (transfer); + + /* This is the start of a chunk. If the fd was closed, simply skip + the chunk. */ + if (info->fd == -1) + { + SkipChunk (transfer); + + DebugPrint ("DirectReadCallback skipped a chunk due to the" + " fd being closed\n"); + return; + } + + if (info->write_callback) + { + /* Make sure two chunks aren't read at the same time. */ + XLAssert (!(info->flags & NeedNewChunk)); + + DebugPrint ("DirectReadCallback received a new chunk, but the" + " current chunk is still being read from\n"); + + info->flags |= NeedNewChunk; + return; + } + + /* Ask for a notification to be sent once the file descriptor + becomes writable. */ + XLAssert (!info->write_callback); + + DebugPrint ("DirectReadCallback is starting the write callback\n"); + + info->write_callback = XLAddWriteFd (info->fd, transfer, + NoticeTransferWritable); +} + +static Bool +DirectFinishCallback (ReadTransfer *transfer, Bool success) +{ + TransferInfo *info; + + /* The read completed. Clean up any memory and write callbacks that + might have been allocated or installed. */ + + info = GetTransferData (transfer); + + if (info->chunk) + { + /* The write callback should still exist, since this means the + data has not yet been fully written. */ + XLAssert (info->write_callback != NULL); + + DebugPrint ("The transfer finished, but the chunk was not yet" + " completely written; the finish is being delayed.\n"); + info->flags |= NeedDelayedFinish; + + return False; + } + else + DebugPrint ("The transfer finished %s\n", + success ? "successfully" : "with failure"); + + FinishTransfer (info); + return True; +} + +static void +MakeFdNonblocking (int fd) +{ + int rc; + + rc = fcntl (fd, F_GETFL); + + if (rc < 0) + { + DebugPrint ("Failed to make selection file descriptor" + " %d non-blocking. Writing to it might hang.\n", + fd); + return; + } + + rc = fcntl (fd, F_SETFL, rc | O_NONBLOCK); + + if (rc < 0) + { + DebugPrint ("Failed to make selection file descriptor" + " %d non-blocking. Writing to it might hang.\n", + fd); + return; + } +} + +static void +PostReceiveDirect (Time time, Atom selection, Atom target, int fd) +{ + TransferInfo *info; + + info = XLCalloc (1, sizeof *info); + info->fd = fd; + + /* Try to make the file description nonblocking. Clients seem to + behave fine this way. */ + MakeFdNonblocking (fd); + + DebugPrint ("Converting selection at %lu for fd %d\n", time, fd); + + ConvertSelectionFuncs (selection, target, time, + info, NULL, DirectReadCallback, + DirectFinishCallback); +} + +/* Forward declaration. */ + +static void PostReceiveConversion (Time, Atom, Atom, int); + +static void +Receive (struct wl_client *client, struct wl_resource *resource, + const char *mime_type, int fd) +{ + Time time; + TargetMapping *translation; + + /* Cast to intptr_t to silence warnings when the pointer type is + larger than long. */ + time = (Time) (intptr_t) wl_resource_get_user_data (resource); + + /* Find which selection target corresponds to MIME_TYPE. */ + translation = FindTranslationForMimeType (mime_type); + + if (translation) + { + if (!translation->translation_func) + /* If a corresponding target exists, ask to receive it. */ + PostReceiveDirect (time, CLIPBOARD, translation->atom, fd); + else + /* Otherwise, use the translation function. */ + translation->translation_func (time, CLIPBOARD, + translation->atom, fd); + } + else + close (fd); +} + +static void +Destroy (struct wl_client *client, struct wl_resource *resource) +{ + wl_resource_destroy (resource); +} + +static void +Finish (struct wl_client *client, struct wl_resource *resource) +{ + /* TODO... */ +} + +static void +SetActions (struct wl_client *client, struct wl_resource *resource, + uint32_t dnd_actions, uint32_t preferred_action) +{ + /* TODO... */ +} + +static const struct wl_data_offer_interface wl_data_offer_impl = + { + .accept = Accept, + .receive = Receive, + .destroy = Destroy, + .finish = Finish, + .set_actions = SetActions, + }; + +static struct wl_resource * +CreateOffer (struct wl_client *client, Time time) +{ + struct wl_resource *resource; + + resource = wl_resource_create (client, &wl_data_offer_interface, + 3, 0); + + if (!resource) + /* If allocating the resource failed, return NULL. */ + return NULL; + + /* Otherwise, set the user_data to the time of the selection + change. */ + wl_resource_set_implementation (resource, &wl_data_offer_impl, + (void *) time, NULL); + return resource; +} + +static void +SendOffers (struct wl_resource *resource, Time time) +{ + int i, j; + + if (time < last_x_selection_change) + /* This offer is out of date. */ + return; + + for (i = 0; i < num_x_selection_targets; ++i) + { + /* Offer each type corresponding to this target. */ + + for (j = 0; j < ArrayElements (direct_transfer); ++j) + { + if (direct_transfer[j].atom == x_selection_targets[i]) + /* If it exists, offer it to the client. TODO: handle + duplicates. */ + wl_data_offer_send_offer (resource, + direct_transfer[j].mime_type); + } + } +} + +static void +HandleNewSelection (Time time, Atom *targets, int ntargets) +{ + CreateOfferFuncs funcs; + + /* Ignore outdated selection changes. */ + if (time < last_x_selection_change) + { + /* We are responsible for deallocating targets. */ + XLFree (targets); + return; + } + + /* Note that our free function explicitly ignores pointers to NULL, + so this is safe. */ + XLFree (x_selection_targets); + x_selection_targets = targets; + num_x_selection_targets = ntargets; + + last_x_selection_change = time; + + /* Add the right functions and set them as the foreign selection + handler at TIME. */ + + funcs.create_offer = CreateOffer; + funcs.send_offers = SendOffers; + + XLSetForeignSelection (time, funcs); +} + +static void +TargetsReadCallback (ReadTransfer *transfer, Atom type, int format, + ptrdiff_t size) +{ + Atom *atoms; + ptrdiff_t n_atoms, old, i; + ReadTargetsData *data; + + if (type != XA_ATOM || format != 32) + { + SkipChunk (transfer); + return; + } + + /* Since format is 32, size must be a multiple of sizeof (long). */ + atoms = (Atom *) ReadChunk (transfer, size / sizeof (long), &size, + NULL); + + /* Reading the property data failed. The finish function will be + run with success set to False soon. */ + if (!atoms) + return; + + data = GetTransferData (transfer); + n_atoms = size / sizeof (long); + old = data->n_atoms; + + /* Copy the atoms to data->atoms. */ + + if (IntAddWrapv (n_atoms, data->n_atoms, + &data->n_atoms)) + { + /* Overflow. Something is definitely wrong with the selection + data. */ + data->n_atoms = 0; + return; + } + + data->atoms = XLRealloc (data->atoms, + data->n_atoms * sizeof *atoms); + for (i = 0; i < n_atoms; ++i) + data->atoms[old + i] = atoms[i]; + + /* Use XFree, since this is Xlib-allocated memory. */ + XFree (atoms); +} + +static Bool +TargetsFinishCallback (ReadTransfer *transfer, Bool success) +{ + ReadTargetsData *data; + + data = GetTransferData (transfer); + + if (success) + DebugPrint ("Received targets from CLIPBOARD\n"); + else + DebugPrint ("Failed to obtain targets from CLIPBOARD\n"); + + if (success) + HandleNewSelection (GetTransferTime (transfer), + data->atoms, data->n_atoms); + else + /* HandleNewSelection keeps data->atoms around for a while. */ + XLFree (data->atoms); + + XLFree (data); + return True; +} + +/* Notice that the owner of CLIPBOARD has changed at TIME. Try to get + a list of selection targets from it, and if successful, make the + Wayland data source current. */ + +static void +NoticeClipboardChanged (Time time) +{ + ReadTargetsData *data; + + data = XLCalloc (1, sizeof *data); + + ConvertSelectionFuncs (CLIPBOARD, TARGETS, time, data, + NULL, TargetsReadCallback, + TargetsFinishCallback); +} + +static void +NoticeClipboardCleared (Time time) +{ + /* Ignore outdated events. */ + if (time < last_x_selection_change) + return; + + last_x_selection_change = time; + XLClearForeignSelection (time); +} + +static void +HandleSelectionNotify (XFixesSelectionNotifyEvent *event) +{ + if (event->owner == selection_transfer_window) + /* Ignore events sent when we get selection ownership. */ + return; + + if (event->selection == CLIPBOARD + && event->selection_timestamp > last_clipboard_change) + /* This time is used to keep track of whether or not things like + disowning the selection were successful. */ + last_clipboard_change = event->selection_timestamp; + + if (event->owner != None + && event->selection == CLIPBOARD) + NoticeClipboardChanged (event->timestamp); + else + NoticeClipboardCleared (event->timestamp); +} + +Bool +XLHandleOneXEventForXData (XEvent *event) +{ + if (event->type == fixes_event_base + XFixesSelectionNotify) + { + HandleSelectionNotify ((XFixesSelectionNotifyEvent *) event); + + return True; + } + + return False; +} + +static void +SelectSelectionInput (Atom selection) +{ + int mask; + + /* If SELECTION already exists, announce it as well. Use + CurrentTime (even though the ICCCM says this is a bad idea); any + XFixeSeslectionNotify event that arrives later will clear up our + (bad) view of the selection change time. */ + + if (selection == CLIPBOARD + && XGetSelectionOwner (compositor.display, CLIPBOARD) != None) + NoticeClipboardChanged (CurrentTime); + + mask = XFixesSetSelectionOwnerNotifyMask; + mask |= XFixesSelectionWindowDestroyNotifyMask; + mask |= XFixesSelectionClientCloseNotifyMask; + + XFixesSelectSelectionInput (compositor.display, + selection_transfer_window, + selection, mask); +} + +static DataConversion * +GetDataConversion (Atom target) +{ + int i; + + for (i = 0; i < ArrayElements (data_conversions); ++i) + { + if (data_conversions[i].atom == target) + return &data_conversions[i]; + } + + return NULL; +} + +static const char * +MimeTypeFromTarget (Atom target) +{ + DataConversion *conversion; + static char *string; + + /* TODO: replace XGetAtomName with something more efficient. */ + if (string) + XFree (string); + string = NULL; + + if (!XLDataSourceHasAtomTarget (selection_data_source, target)) + { + /* If the data source does not itself provide the specified + target, then a conversion function is in use. */ + conversion = GetDataConversion (target); + + if (!conversion) + /* There must be a data conversion here. */ + abort (); + + DebugPrint ("Converting X type %lu to MIME type %s...\n", + conversion->type, conversion->mime_type); + + return conversion->mime_type; + } + + string = XGetAtomName (compositor.display, target); + return string; +} + +static Atom +TypeFromTarget (Atom target) +{ + DataConversion *conversion; + + if (!XLDataSourceHasAtomTarget (selection_data_source, target)) + { + /* If the data source does not itself provide the specified + target, then a conversion function is in use. Use the type + specified in the conversion table entry. */ + conversion = GetDataConversion (target); + + if (!conversion) + /* There must be a data conversion here. */ + abort (); + + return conversion->type; + } + + /* Otherwise, assume the data type is the same as the target. This + is mostly intended to handle text/uri-list and similar + formats. */ + return target; +} + +static void +NoticeTransferReadable (int fd, void *data) +{ + WriteTransfer *transfer; + WriteInfo *info; + + transfer = data; + + /* Retrieve the info and make sure it hasn't been freed. */ + info = GetWriteTransferData (transfer); + XLAssert (info != NULL); + + DebugPrint ("Fd %d is now readable...\n", fd); + + /* Now cancel the read callback, and switch from waiting for the + client to send something to waiting for the requestor to read the + data. */ + XLRemoveReadFd (info->read_callback); + info->read_callback = NULL; + + /* And tell the selection code to start reading. */ + StartReading (transfer); +} + +static ReadStatus +ClipboardReadFunc (WriteTransfer *transfer, unsigned char *buffer, + ptrdiff_t buffer_size, ptrdiff_t *nbytes) +{ + WriteInfo *info; + ssize_t size; + + /* Retrieve the info and make sure it hasn't been freed. */ + info = GetWriteTransferData (transfer); + + if (buffer_size == -1) + { + DebugPrint ("ClipboardReadFunc called to free data for timeout\n"); + + if (info) + { + if (info->read_callback) + { + /* Cancel any read callback in progress. */ + XLRemoveReadFd (info->read_callback); + info->read_callback = NULL; + } + + close (info->fd); + XLFree (info); + SetWriteTransferData (transfer, NULL); + } + + return EndOfFile; + } + + XLAssert (info != NULL); + + /* By the time this function is entered, the polling callback should + be NULL. */ + XLAssert (info->read_callback == NULL); + + DebugPrint ("ClipboardReadFunc called to read %td bytes\n", + buffer_size); + + /* Try to read buffer_size bytes into buffer. */ + size = read (info->fd, buffer, buffer_size); + + /* If EOF, return that, free info, and close the pipe. */ + + if (!size) + { + DebugPrint ("EOF; completing transfer\n"); + + if (info->read_callback) + { + /* Cancel any read callback in progress. */ + XLRemoveReadFd (info->read_callback); + info->read_callback = NULL; + } + + close (info->fd); + XLFree (info); + SetWriteTransferData (transfer, NULL); + + *nbytes = 0; + return EndOfFile; + } + + /* If an error occured, see what it was. */ + + if (size == -1) + { + DebugPrint ("read failed with: %s\n", strerror (errno)); + + if (errno == EAGAIN) + { + /* Start the read callback again. This might make us + spin. */ + *nbytes = 0; + + info->read_callback = XLAddReadFd (info->fd, transfer, + NoticeTransferReadable); + return ReadOk; + } + + perror ("write"); + exit (1); + } + + DebugPrint ("Read %zd bytes, starting the read callback again\n", + size); + + /* Otherwise, set nbytes to the number of bytes read, and start the + write callback again. */ + *nbytes = size; + info->read_callback = XLAddReadFd (info->fd, transfer, + NoticeTransferReadable); + return ReadOk; +} + +static GetDataFunc +GetClipboardCallback (WriteTransfer *transfer, Atom target, + Atom *type) +{ + WriteInfo *info; + int pipe[2], rc; + DataConversion *conversion; + + /* If the selection data source is destroyed, the selection will be + disowned. */ + XLAssert (selection_data_source != NULL); + + if (!XLDataSourceHasAtomTarget (selection_data_source, target)) + { + /* If the data source does not itself provide the specified + target, then a conversion function is in use. Call the + clipboard callback specified in the conversion table entry, + if it exists. */ + conversion = GetDataConversion (target); + + if (!conversion) + /* There must be a data conversion here. */ + abort (); + + if (conversion->clipboard_callback) + return conversion->clipboard_callback (transfer, target, + type); + + /* Otherwise, use the default callback. */ + DebugPrint ("Conversion to type %lu specified with default callback\n", + target); + } + + DebugPrint ("Entered GetClipboardCallback; target is %lu\n", + target); + + /* First, create a non-blocking pipe. We will give the write end to + the client. */ + rc = pipe2 (pipe, O_NONBLOCK); + + if (rc) + /* Creating the pipe failed. */ + return NULL; + + DebugPrint ("Created pipe (%d, %d)\n", pipe[0], pipe[1]); + + /* Send the write end of the pipe to the source. */ + wl_data_source_send_send (XLResourceFromDataSource (selection_data_source), + MimeTypeFromTarget (target), pipe[1]); + close (pipe[1]); + + /* And set the prop type appropriately. */ + *type = TypeFromTarget (target); + + /* Create the transfer info structure. */ + info = XLMalloc (sizeof *info); + info->fd = pipe[0]; + info->flags = 0; + + DebugPrint ("Adding the read callback\n"); + + /* Wait for info to become readable. */ + info->read_callback = XLAddReadFd (info->fd, transfer, + NoticeTransferReadable); + + /* Set info as the transfer data. */ + SetWriteTransferData (transfer, info); + + return ClipboardReadFunc; +} + +/* Conversions between UTF-8 and Latin-1. */ + + +static void +NoticeConversionTransferReadable (int fd, void *data) +{ + WriteTransfer *transfer; + ConversionWriteInfo *info; + size_t nbytes; + + transfer = data; + + /* Retrieve the info and make sure it hasn't been freed. */ + info = GetWriteTransferData (transfer); + XLAssert (info != NULL); + + DebugPrint ("Fd %d is now readable...\n", fd); + + /* Now try to fill the conversion buffer. */ + nbytes = read (info->fd, info->inbuf + info->inread, + BUFSIZ - info->inread); + + if (nbytes <= 0) + { + /* EOF was reached or an error occured. Flag the transfer as + having reached the EOF. */ + info->flags |= ReachedEndOfFile; + DebugPrint ("EOF read from %d\n", fd); + + /* Cancel any read callback in progress. */ + XLRemoveReadFd (info->read_callback); + info->read_callback = NULL; + + /* Signal that the selection code should start reading. */ + StartReading (transfer); + } + else + { + /* Add the number of bytes read to the number of bytes in the + buffer that are filled up. */ + info->inread += nbytes; + + DebugPrint ("Read %tu bytes\n", info->inread); + + /* If the buffer is full, begin converting from it. */ + if (info->inread == BUFSIZ) + { + DebugPrint ("Buffer is now full\n"); + + /* Cancel any read callback in progress. */ + XLRemoveReadFd (info->read_callback); + info->read_callback = NULL; + + /* Signal that the selection code should start reading. */ + StartReading (transfer); + } + } +} + +static ReadStatus +ConversionReadFunc (WriteTransfer *transfer, unsigned char *buffer, + ptrdiff_t buffer_size, ptrdiff_t *nbytes) +{ + ConversionWriteInfo *info; + size_t nconv, outsize; + + /* Retrieve the info and make sure it hasn't been freed. */ + info = GetWriteTransferData (transfer); + + DebugPrint ("ClipboardReadFunc called to read %zd bytes\n", + buffer_size); + + if (buffer_size == -1) + { + DebugPrint ("ConversionReadFunc called to free data for timeout\n"); + + if (info) + { + if (info->read_callback) + { + /* Cancel any read callback in progress. */ + XLRemoveReadFd (info->read_callback); + info->read_callback = NULL; + } + + close (info->fd); + iconv_close (info->cd); + XLFree (info); + + SetWriteTransferData (transfer, NULL); + } + + return False; + } + + XLAssert (info != NULL); + + /* By the time this function is entered, the polling callback should + be NULL. */ + XLAssert (info->read_callback == NULL); + + /* Convert the appropriate amount of bytes into the output + buffer. */ + outsize = buffer_size; + nconv = iconv (info->cd, &info->inptr, &info->inread, + (char **) &buffer, &outsize); + + DebugPrint ("iconv returned: %tu\n", nconv); + + if (nconv == (size_t) -1 && errno != EINVAL) + { + if (errno == E2BIG) + { + DebugPrint ("iconv needs a bigger buffer\n"); + *nbytes = buffer_size - outsize; + + if (*nbytes < 1) + { + DebugPrint ("iconv failed with a buffer as large as the" + "max-request-size!\n"); + + goto eof; + } + + memmove (info->inbuf, info->inptr, info->inread); + info->inptr = info->inbuf; + return NeedBiggerBuffer; + } + + DebugPrint ("iconv failed with: %s; bytes written: %tu\n", + strerror (errno), buffer_size - outsize); + + eof: + if (info->read_callback) + { + /* Cancel any read callback in progress. */ + XLRemoveReadFd (info->read_callback); + info->read_callback = NULL; + } + + /* The conversion failed/finished; return EOF. */ + close (info->fd); + iconv_close (info->cd); + XLFree (info); + + SetWriteTransferData (transfer, NULL); + *nbytes = buffer_size - outsize; + return EndOfFile; + } + + /* Otherwise, move the data that has still not been read back to the + start. */ + memmove (info->inbuf, info->inptr, info->inread); + info->inptr = info->inbuf; + DebugPrint ("iconv wrote: %tu\n", buffer_size - outsize); + + if (info->flags & ReachedEndOfFile) + goto eof; + + /* Restore the read callback, and return the number of bytes + read. */ + *nbytes = buffer_size - outsize; + info->read_callback = XLAddReadFd (info->fd, transfer, + NoticeConversionTransferReadable); + + return ReadOk; +} + +static GetDataFunc +GetConversionCallback (WriteTransfer *transfer, Atom target, + Atom *type) +{ + ConversionWriteInfo *info; + int pipe[2], rc; + iconv_t cd; + + DebugPrint ("Performing data conversion of UTF-8 string to %lu\n", + target); + + cd = iconv_open ("ISO-8859-1", "UTF-8"); + + if (cd == (iconv_t) -1) + /* The conversion context couldn't be initialized, so fail this + transfer. */ + return NULL; + + /* First, create a non-blocking pipe. We will give the write end to + the client. */ + rc = pipe2 (pipe, O_NONBLOCK); + + if (rc) + return NULL; + + /* Then, send the write end of the pipe to the data source. */ + wl_data_source_send_send (XLResourceFromDataSource (selection_data_source), + "text/plain;charset=utf-8", pipe[1]); + close (pipe[1]); + + /* Following that, set the property type correctly. */ + *type = XA_STRING; + + /* Create the transfer info and buffer. */ + info = XLMalloc (sizeof *info); + info->fd = pipe[0]; + info->flags = 0; + info->inptr = info->inbuf; + info->inread = 0; + info->cd = cd; + + DebugPrint ("Adding the read callback\n"); + + /* Wait for info to become readable. */ + info->read_callback = XLAddReadFd (info->fd, transfer, + NoticeConversionTransferReadable); + + /* Set info as the transfer data. */ + SetWriteTransferData (transfer, info); + + return ConversionReadFunc; +} + +static void +ConversionReadCallback (ReadTransfer *transfer, Atom type, int format, + ptrdiff_t size) +{ + ConversionTransferInfo *info; + unsigned char *data; + + /* Read all the data from the chunk now. */ + + data = ReadChunk (transfer, (format == 32 ? size / sizeof (long) + : (size + 3) / 4), + &size, NULL); + + if (!data) + return; + + info = GetTransferData (transfer); + + /* We buffer up all the data from the selection and convert it + later. */ + + info->buffer = XLRealloc (info->buffer, info->buffer_size + size); + memcpy (info->buffer + info->buffer_size, data, size); + info->buffer_size += size; +} + +static void +FinishConversionTransfer (ReadTransfer *transfer, ConversionTransferInfo *info) +{ + DebugPrint ("Completing conversion transfer...\n"); + + XLFree (info->buffer); + iconv_close (info->cd); + + if (info->write_callback) + XLRemoveWriteFd (info->write_callback); + + if (info->fd != -1) + close (info->fd); + + XLFree (info); + + /* Complete the transfer itself. */ + CompleteDelayedTransfer (transfer); +} + +static void +NoticeConversionTransferWritable (int fd, void *data) +{ + ConversionTransferInfo *info; + ssize_t written; + size_t outsize, nconv, start; + char *outbuf; + + /* The pipe is now writable. First, try to write data from the + buffer to the file descriptor. */ + + info = GetTransferData (data); + + /* Skip the call to write if there is nothing to do. */ + + if (info->outsize) + { + written = write (info->fd, info->output_buffer, + info->outsize); + + if (written == -1) + { + DebugPrint ("write: %s\n", strerror (errno)); + + if (errno == EAGAIN) + /* Writing the data failed, so return. This might cause us to + spin. */ + return; + + if (errno != EPIPE) + { + perror ("write"); + exit (1); + } + + /* The other end of the pipe was closed by the client. Stop + this transfer. */ + FinishConversionTransfer (data, info); + return; + } + + /* Writing the data was successful. Move the written part back to + the start. */ + memmove (info->output_buffer, info->output_buffer + written, + info->outsize - written); + info->outsize -= written; + } + + /* Now, run iconv if there is still data in the input buffer. */ + if (info->buffer_size) + { + outbuf = info->output_buffer + info->outsize; + start = outsize = BUFSIZ - info->outsize; + nconv = iconv (info->cd, &info->position, &info->buffer_size, + &outbuf, &outsize); + + if (nconv == (size_t) -1 && errno != EINVAL) + { + /* iconv failed for various reasons. */ + + if ((errno == E2BIG && outsize == BUFSIZ) + || errno != E2BIG) + { + /* BUFSIZ is either too small to hold the output contents + (which shouldn't happen), or some other error + occured. Punt. */ + FinishConversionTransfer (data, info); + return; + } + } + + /* Increase the output buffer size by the amount that was was + written. */ + info->outsize += start - outsize; + DebugPrint ("Output buffer is now %tu bytes full\n", info->outsize); + } + else if (!info->outsize) + /* Everything has been written; finish the transfer. */ + FinishConversionTransfer (data, info); +} + +static Bool +ConversionFinishCallback (ReadTransfer *transfer, Bool success) +{ + ConversionTransferInfo *info; + + DebugPrint ("ConversionFinishCallback called; converting data in chunks.\n"); + + /* The conversion is complete. Start the conversion and write the + correct amount of data into the pipe. */ + + info = GetTransferData (transfer); + info->position = info->buffer; + + info->write_callback = XLAddWriteFd (info->fd, transfer, + NoticeConversionTransferWritable); + return False; +} + +static void +PostReceiveConversion (Time time, Atom selection, Atom target, int fd) +{ + ConversionTransferInfo *info; + iconv_t cd; + + cd = iconv_open ("UTF-8", "ISO-8859-1"); + + if (cd == (iconv_t) -1) + { + /* The conversion context couldn't be created. Close the file + descriptor and fail. */ + close (fd); + return; + } + + info = XLCalloc (1, sizeof *info); + info->fd = fd; + info->cd = cd; + + /* Try to make the file description nonblocking. Clients seem to + behave fine this way. */ + MakeFdNonblocking (fd); + + DebugPrint ("Converting selection to UTF-8 at %lu for fd %d\n", time, fd); + ConvertSelectionFuncs (selection, target, time, info, NULL, + ConversionReadCallback, ConversionFinishCallback); +} + +/* Drag-and-drop support functions. */ + + +static const char * +DragMimeTypeFromTarget (Atom target) +{ + static char *string; + + /* TODO: replace XGetAtomName with something more efficient. */ + if (string) + XFree (string); + + string = XGetAtomName (compositor.display, target); + return string; +} + +static Atom +DragTypeFromTarget (Atom target) +{ + /* Assume the data type is the same as the target. This is mostly + intended to handle text/uri-list and similar formats. */ + return target; +} + +static GetDataFunc +GetDragCallback (WriteTransfer *transfer, Atom target, + Atom *type) +{ + WriteInfo *info; + int pipe[2], rc; + + /* If the drag and drop data source is destroyed, the selection will + be disowned. */ + XLAssert (drag_data_source != NULL); + + DebugPrint ("Entered GetClipboardCallback; target is %lu\n", + target); + + /* First, create a non-blocking pipe. We will give the write end to + the client. */ + rc = pipe2 (pipe, O_NONBLOCK); + + if (rc) + /* Creating the pipe failed. */ + return NULL; + + DebugPrint ("Created pipe (%d, %d)\n", pipe[0], pipe[1]); + + /* Send the write end of the pipe to the source. */ + wl_data_source_send_send (XLResourceFromDataSource (drag_data_source), + DragMimeTypeFromTarget (target), pipe[1]); + close (pipe[1]); + + /* And set the prop type appropriately. */ + *type = DragTypeFromTarget (target); + + /* Create the transfer info structure. */ + info = XLMalloc (sizeof *info); + info->fd = pipe[0]; + info->flags = IsDragAndDrop; + + DebugPrint ("Adding the read callback\n"); + + /* Wait for info to become readable. */ + info->read_callback = XLAddReadFd (info->fd, transfer, + NoticeTransferReadable); + + /* Set info as the transfer data. */ + SetWriteTransferData (transfer, info); + + return ClipboardReadFunc; +} + +Bool +XLOwnDragSelection (Time time, DataSource *source) +{ + Atom *targets; + int ntargets; + Bool rc; + + DebugPrint ("Trying to own XdndSelection\n"); + + /* Copy targets from the data source. */ + ntargets = XLDataSourceTargetCount (source); + targets = XLMalloc (sizeof *targets * ntargets); + XLDataSourceGetTargets (source, targets); + + /* Own the selection. */ + drag_data_source = source; + + rc = OwnSelection (time, XdndSelection, GetDragCallback, + targets, ntargets); + XLFree (targets); + + return rc; +} + + + +void +XLNoteSourceDestroyed (DataSource *source) +{ + if (source == selection_data_source) + { + DebugPrint ("Disowning CLIPBOARD at %lu (vs. last change %lu)\n" + "This is being done in response to the source being destroyed.\n", + last_clipboard_time, last_x_selection_change); + + /* Disown the selection. */ + DisownSelection (CLIPBOARD); + } + + if (source == drag_data_source) + { + DebugPrint ("Disowning XdndSelection\nThis is being done in response" + "to the data source being destroyed.\n"); + + /* Disown the selection. */ + DisownSelection (XdndSelection); + } +} + +static Bool +FindTargetInArray (Atom *targets, int ntargets, Atom atom) +{ + int i; + + for (i = 0; i < ntargets; ++i) + { + if (targets[i] == atom) + return True; + } + + return False; +} + +Bool +XLNoteLocalSelection (Seat *seat, DataSource *source) +{ + Time time; + Atom *targets; + int ntargets, i, n_data_conversions; + Bool rc; + + if (source == NULL) + { + DebugPrint ("Disowning CLIPBOARD at %lu (vs. last change %lu)\n", + last_clipboard_time, last_x_selection_change); + + /* Disown the selection. */ + DisownSelection (CLIPBOARD); + selection_data_source = NULL; + + /* Return whether or not the selection was actually + disowned. */ + return last_clipboard_time >= last_x_selection_change; + } + + time = XLSeatGetLastUserTime (seat); + + DebugPrint ("Acquiring ownership of CLIPBOARD at %lu\n", time); + + if (!time) + /* Nothing has yet happened on the seat. */ + return False; + + if (time < last_clipboard_time + || time < last_clipboard_change) + /* TIME is out of date. */ + return False; + + DebugPrint ("Setting callback function for CLIPBOARD\n"); + + /* Since the local selection is now set, free foreign selection + data. */ + XLFree (x_selection_targets); + num_x_selection_targets = 0; + x_selection_targets = NULL; + + /* Otherwise, set the clipboard change time to TIME. */ + last_clipboard_time = time; + last_clipboard_change = time; + + /* And copy the targets from the data source. */ + ntargets = XLDataSourceTargetCount (source); + n_data_conversions = ArrayElements (data_conversions); + targets = XLMalloc (sizeof *targets * (ntargets + n_data_conversions)); + XLDataSourceGetTargets (source, targets); + + /* Now, look to see what standard X targets the client doesn't + offer, and add them to the targets array. + + Most functioning Wayland clients will also offer the typical X + selection targets such as STRING and UTF8_STRING in addition to + MIME types; when they do, they perform the data conversion + conversion better than us. */ + + for (i = 0; i < ArrayElements (data_conversions); ++i) + { + /* If the source provides MIME typed-data for a format we know + how to convert, announce the associated selection conversion + targets. */ + + if (XLDataSourceHasTarget (source, + data_conversions[i].mime_type) + && !FindTargetInArray (targets, ntargets, + data_conversions[i].type)) + { + DebugPrint ("Client doesn't provide standard X conversion" + " target for %s; adding it\n", + data_conversions[i].mime_type); + + targets[ntargets++] = data_conversions[i].type; + } + } + + /* And own the selection. */ + selection_data_source = source; + + rc = OwnSelection (time, CLIPBOARD, GetClipboardCallback, + targets, ntargets); + XLFree (targets); + + return rc; +} + +void +XLInitXData (void) +{ + sigset_t set; + int rc, fixes_error_base, major, minor; + + rc = XFixesQueryExtension (compositor.display, + &fixes_event_base, + &fixes_error_base); + + if (!rc) + { + fprintf (stderr, "The X server does not support the " + "XFixes protocol extension\n"); + exit (1); + } + + rc = XFixesQueryVersion (compositor.display, + &major, &minor); + + if (!rc || major < 1) + { + fprintf (stderr, "The X server does not support the " + "right version of the XFixes protocol extension\n"); + exit (1); + } + + SelectSelectionInput (CLIPBOARD); + + /* Initialize atoms used in the direct transfer table. */ + direct_transfer[1].atom = UTF8_STRING; + direct_transfer[2].translation_func = PostReceiveConversion; + DirectTransferInitializer (direct_transfer, 3); + + /* And those used in the data conversions table. */ + data_conversions[0].atom = UTF8_STRING; + data_conversions[0].type = UTF8_STRING; + data_conversions[1].atom = XA_STRING; + data_conversions[1].type = XA_STRING; + data_conversions[1].clipboard_callback = GetConversionCallback; + + /* Ignore SIGPIPE, since we could be writing to a pipe whose reading + end was closed by the client. */ + sigemptyset (&set); + sigaddset (&set, SIGPIPE); + + if (pthread_sigmask (SIG_BLOCK, &set, NULL)) + { + perror ("pthread_sigmask"); + exit (1); + } +} + +void +XLReceiveDataFromSelection (Time time, Atom selection, Atom target, + int fd) +{ + PostReceiveDirect (time, selection, target, fd); +} diff --git a/xdg-shell.xml b/xdg-shell.xml new file mode 100644 index 0000000..8c9804f --- /dev/null +++ b/xdg-shell.xml @@ -0,0 +1,1313 @@ + + + + + Copyright © 2008-2013 Kristian Høgsberg + Copyright © 2013 Rafael Antognolli + Copyright © 2013 Jasper St. Pierre + Copyright © 2010-2013 Intel Corporation + Copyright © 2015-2017 Samsung Electronics Co., Ltd + Copyright © 2015-2017 Red Hat Inc. + + 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. + + + + + The xdg_wm_base interface is exposed as a global object enabling clients + to turn their wl_surfaces into windows in a desktop environment. It + defines the basic functionality needed for clients and the compositor to + create windows that can be dragged, resized, maximized, etc, as well as + creating transient windows such as popup menus. + + + + + + + + + + + + + + Destroy this xdg_wm_base object. + + Destroying a bound xdg_wm_base object while there are surfaces + still alive created by this xdg_wm_base object instance is illegal + and will result in a protocol error. + + + + + + Create a positioner object. A positioner object is used to position + surfaces relative to some parent surface. See the interface description + and xdg_surface.get_popup for details. + + + + + + + This creates an xdg_surface for the given surface. While xdg_surface + itself is not a role, the corresponding surface may only be assigned + a role extending xdg_surface, such as xdg_toplevel or xdg_popup. It is + illegal to create an xdg_surface for a wl_surface which already has an + assigned role and this will result in a protocol error. + + This creates an xdg_surface for the given surface. An xdg_surface is + used as basis to define a role to a given surface, such as xdg_toplevel + or xdg_popup. It also manages functionality shared between xdg_surface + based surface roles. + + See the documentation of xdg_surface for more details about what an + xdg_surface is and how it is used. + + + + + + + + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. See xdg_wm_base.ping. + + + + + + + The ping event asks the client if it's still alive. Pass the + serial specified in the event back to the compositor by sending + a "pong" request back with the specified serial. See xdg_wm_base.pong. + + Compositors can use this to determine if the client is still + alive. It's unspecified what will happen if the client doesn't + respond to the ping request, or in what timeframe. Clients should + try to respond in a reasonable amount of time. + + A compositor is free to ping in any way it wants, but a client must + always respond to any xdg_wm_base object it created. + + + + + + + + The xdg_positioner provides a collection of rules for the placement of a + child surface relative to a parent surface. Rules can be defined to ensure + the child surface remains within the visible area's borders, and to + specify how the child surface changes its position, such as sliding along + an axis, or flipping around a rectangle. These positioner-created rules are + constrained by the requirement that a child surface must intersect with or + be at least partially adjacent to its parent surface. + + See the various requests for details about possible rules. + + At the time of the request, the compositor makes a copy of the rules + specified by the xdg_positioner. Thus, after the request is complete the + xdg_positioner object can be destroyed or reused; further changes to the + object will have no effect on previous usages. + + For an xdg_positioner object to be considered complete, it must have a + non-zero size set by set_size, and a non-zero anchor rectangle set by + set_anchor_rect. Passing an incomplete xdg_positioner object when + positioning a surface raises an error. + + + + + + + + + Notify the compositor that the xdg_positioner will no longer be used. + + + + + + Set the size of the surface that is to be positioned with the positioner + object. The size is in surface-local coordinates and corresponds to the + window geometry. See xdg_surface.set_window_geometry. + + If a zero or negative size is set the invalid_input error is raised. + + + + + + + + Specify the anchor rectangle within the parent surface that the child + surface will be placed relative to. The rectangle is relative to the + window geometry as defined by xdg_surface.set_window_geometry of the + parent surface. + + When the xdg_positioner object is used to position a child surface, the + anchor rectangle may not extend outside the window geometry of the + positioned child's parent surface. + + If a negative size is set the invalid_input error is raised. + + + + + + + + + + + + + + + + + + + + + + Defines the anchor point for the anchor rectangle. The specified anchor + is used derive an anchor point that the child surface will be + positioned relative to. If a corner anchor is set (e.g. 'top_left' or + 'bottom_right'), the anchor point will be at the specified corner; + otherwise, the derived anchor point will be centered on the specified + edge, or in the center of the anchor rectangle if no edge is specified. + + + + + + + + + + + + + + + + + + + Defines in what direction a surface should be positioned, relative to + the anchor point of the parent surface. If a corner gravity is + specified (e.g. 'bottom_right' or 'top_left'), then the child surface + will be placed towards the specified gravity; otherwise, the child + surface will be centered over the anchor point on any axis that had no + gravity specified. + + + + + + + The constraint adjustment value define ways the compositor will adjust + the position of the surface, if the unadjusted position would result + in the surface being partly constrained. + + Whether a surface is considered 'constrained' is left to the compositor + to determine. For example, the surface may be partly outside the + compositor's defined 'work area', thus necessitating the child surface's + position be adjusted until it is entirely inside the work area. + + The adjustments can be combined, according to a defined precedence: 1) + Flip, 2) Slide, 3) Resize. + + + + Don't alter the surface position even if it is constrained on some + axis, for example partially outside the edge of an output. + + + + + Slide the surface along the x axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the x axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + x axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Slide the surface along the y axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the y axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + y axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Invert the anchor and gravity on the x axis if the surface is + constrained on the x axis. For example, if the left edge of the + surface is constrained, the gravity is 'left' and the anchor is + 'left', change the gravity to 'right' and the anchor to 'right'. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_x adjustment will be the one before the + adjustment. + + + + + Invert the anchor and gravity on the y axis if the surface is + constrained on the y axis. For example, if the bottom edge of the + surface is constrained, the gravity is 'bottom' and the anchor is + 'bottom', change the gravity to 'top' and the anchor to 'top'. + + The adjusted position is calculated given the original anchor + rectangle and offset, but with the new flipped anchor and gravity + values. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_y adjustment will be the one before the + adjustment. + + + + + Resize the surface horizontally so that it is completely + unconstrained. + + + + + Resize the surface vertically so that it is completely unconstrained. + + + + + + + Specify how the window should be positioned if the originally intended + position caused the surface to be constrained, meaning at least + partially outside positioning boundaries set by the compositor. The + adjustment is set by constructing a bitmask describing the adjustment to + be made when the surface is constrained on that axis. + + If no bit for one axis is set, the compositor will assume that the child + surface should not change its position on that axis when constrained. + + If more than one bit for one axis is set, the order of how adjustments + are applied is specified in the corresponding adjustment descriptions. + + The default adjustment is none. + + + + + + + Specify the surface position offset relative to the position of the + anchor on the anchor rectangle and the anchor on the surface. For + example if the anchor of the anchor rectangle is at (x, y), the surface + has the gravity bottom|right, and the offset is (ox, oy), the calculated + surface position will be (x + ox, y + oy). The offset position of the + surface is the one used for constraint testing. See + set_constraint_adjustment. + + An example use case is placing a popup menu on top of a user interface + element, while aligning the user interface element of the parent surface + with some user interface element placed somewhere in the popup surface. + + + + + + + + + + When set reactive, the surface is reconstrained if the conditions used + for constraining changed, e.g. the parent window moved. + + If the conditions changed and the popup was reconstrained, an + xdg_popup.configure event is sent with updated geometry, followed by an + xdg_surface.configure event. + + + + + + Set the parent window geometry the compositor should use when + positioning the popup. The compositor may use this information to + determine the future state the popup should be constrained using. If + this doesn't match the dimension of the parent the popup is eventually + positioned against, the behavior is undefined. + + The arguments are given in the surface-local coordinate space. + + + + + + + + Set the serial of an xdg_surface.configure event this positioner will be + used in response to. The compositor may use this information together + with set_parent_size to determine what future state the popup should be + constrained using. + + + + + + + + An interface that may be implemented by a wl_surface, for + implementations that provide a desktop-style user interface. + + It provides a base set of functionality required to construct user + interface elements requiring management by the compositor, such as + toplevel windows, menus, etc. The types of functionality are split into + xdg_surface roles. + + Creating an xdg_surface does not set the role for a wl_surface. In order + to map an xdg_surface, the client must create a role-specific object + using, e.g., get_toplevel, get_popup. The wl_surface for any given + xdg_surface can have at most one role, and may not be assigned any role + not based on xdg_surface. + + A role must be assigned before any other requests are made to the + xdg_surface object. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_surface state to take effect. + + Creating an xdg_surface from a wl_surface which has a buffer attached or + committed is a client error, and any attempts by a client to attach or + manipulate a buffer prior to the first xdg_surface.configure call must + also be treated as errors. + + After creating a role-specific object and setting it up, the client must + perform an initial commit without any buffer attached. The compositor + will reply with an xdg_surface.configure event. The client must + acknowledge it and is then allowed to attach a buffer to map the surface. + + Mapping an xdg_surface-based role surface is defined as making it + possible for the surface to be shown by the compositor. Note that + a mapped surface is not guaranteed to be visible once it is mapped. + + For an xdg_surface to be mapped by the compositor, the following + conditions must be met: + (1) the client has assigned an xdg_surface-based role to the surface + (2) the client has set and committed the xdg_surface state and the + role-dependent state to the surface + (3) the client has committed a buffer to the surface + + A newly-unmapped surface is considered to have met condition (1) out + of the 3 required conditions for mapping a surface if its role surface + has not been destroyed, i.e. the client must perform the initial commit + again before attaching a buffer. + + + + + + + + + + + Destroy the xdg_surface object. An xdg_surface must only be destroyed + after its role object has been destroyed. + + + + + + This creates an xdg_toplevel object for the given xdg_surface and gives + the associated wl_surface the xdg_toplevel role. + + See the documentation of xdg_toplevel for more details about what an + xdg_toplevel is and how it is used. + + + + + + + This creates an xdg_popup object for the given xdg_surface and gives + the associated wl_surface the xdg_popup role. + + If null is passed as a parent, a parent surface must be specified using + some other protocol, before committing the initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + + + The window geometry of a surface is its "visible bounds" from the + user's perspective. Client-side decorations often have invisible + portions like drop-shadows which should be ignored for the + purposes of aligning, placing and constraining windows. + + The window geometry is double buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + When maintaining a position, the compositor should treat the (x, y) + coordinate of the window geometry as the top left corner of the window. + A client changing the (x, y) window geometry coordinate should in + general not alter the position of the window. + + Once the window geometry of the surface is set, it is not possible to + unset it, and it will remain the same until set_window_geometry is + called again, even if a new subsurface or buffer is attached. + + If never set, the value is the full bounds of the surface, + including any subsurfaces. This updates dynamically on every + commit. This unset is meant for extremely simple clients. + + The arguments are given in the surface-local coordinate space of + the wl_surface associated with this xdg_surface. + + The width and height must be greater than zero. Setting an invalid size + will raise an error. When applied, the effective window geometry will be + the set window geometry clamped to the bounding rectangle of the + combined geometry of the surface of the xdg_surface and the associated + subsurfaces. + + + + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + For instance, for toplevel surfaces the compositor might use this + information to move a surface to the top left only when the client has + drawn itself for the maximized or fullscreen state. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + The configure event marks the end of a configure sequence. A configure + sequence is a set of one or more events configuring the state of the + xdg_surface, including the final xdg_surface.configure event. + + Where applicable, xdg_surface surface roles will during a configure + sequence extend this event as a latched state sent as events before the + xdg_surface.configure event. Such events should be considered to make up + a set of atomically applied configuration states, where the + xdg_surface.configure commits the accumulated state. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + If the client receives multiple configure events before it can respond + to one, it is free to discard all but the last event it received. + + + + + + + + + This interface defines an xdg_surface role which allows a surface to, + among other things, set window-like properties such as maximize, + fullscreen, and minimize, set application-specific metadata like title and + id, and well as trigger user interactive operations such as interactive + resize and move. + + Unmapping an xdg_toplevel means that the surface cannot be shown + by the compositor until it is explicitly mapped again. + All active operations (e.g., move, resize) are canceled and all + attributes (e.g. title, state, stacking, ...) are discarded for + an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + the state it had right after xdg_surface.get_toplevel. The client + can re-map the toplevel by perfoming a commit without any buffer + attached, waiting for a configure event and handling it as usual (see + xdg_surface description). + + Attaching a null buffer to a toplevel unmaps the surface. + + + + + This request destroys the role surface and unmaps the surface; + see "Unmapping" behavior in interface section for details. + + + + + + + + + + Set the "parent" of this surface. This surface should be stacked + above the parent surface and all other ancestor surfaces. + + Parent surfaces should be set on dialogs, toolboxes, or other + "auxiliary" surfaces, so that the parent is raised when the dialog + is raised. + + Setting a null parent for a child surface unsets its parent. Setting + a null parent for a surface which currently has no parent is a no-op. + + Only mapped surfaces can have child surfaces. Setting a parent which + is not mapped is equivalent to setting a null parent. If a surface + becomes unmapped, its children's parent is set to the parent of + the now-unmapped surface. If the now-unmapped surface has no parent, + its children's parent is unset. If the now-unmapped surface becomes + mapped again, its parent-child relationship is not restored. + + + + + + + Set a short title for the surface. + + This string may be used to identify the surface in a task bar, + window list, or other user interface elements provided by the + compositor. + + The string must be encoded in UTF-8. + + + + + + + Set an application identifier for the surface. + + The app ID identifies the general class of applications to which + the surface belongs. The compositor can use this to group multiple + surfaces together, or to determine how to launch a new application. + + For D-Bus activatable applications, the app ID is used as the D-Bus + service name. + + The compositor shell will try to group application surfaces together + by their app ID. As a best practice, it is suggested to select app + ID's that match the basename of the application's .desktop file. + For example, "org.freedesktop.FooViewer" where the .desktop file is + "org.freedesktop.FooViewer.desktop". + + Like other properties, a set_app_id request can be sent after the + xdg_toplevel has been mapped to update the property. + + See the desktop-entry specification [0] for more details on + application identifiers and how they relate to well-known D-Bus + names and .desktop files. + + [0] http://standards.freedesktop.org/desktop-entry-spec/ + + + + + + + Clients implementing client-side decorations might want to show + a context menu when right-clicking on the decorations, giving the + user a menu that they can use to maximize or minimize the window. + + This request asks the compositor to pop up such a window menu at + the given position, relative to the local surface coordinates of + the parent surface. There are no guarantees as to what menu items + the window menu contains. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. + + + + + + + + + + Start an interactive, user-driven move of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive move (touch, + pointer, etc). + + The server may ignore move requests depending on the state of + the surface (e.g. fullscreen or maximized), or if the passed serial + is no longer valid. + + If triggered, the surface will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the move. It is up to the + compositor to visually indicate that the move is taking place, such as + updating a pointer cursor, during the move. There is no guarantee + that the device focus will return when the move is completed. + + + + + + + + These values are used to indicate which edge of a surface + is being dragged in a resize operation. + + + + + + + + + + + + + + + Start a user-driven, interactive resize of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive resize (touch, + pointer, etc). + + The server may ignore resize requests depending on the state of + the surface (e.g. fullscreen or maximized). + + If triggered, the client will receive configure events with the + "resize" state enum value and the expected sizes. See the "resize" + enum value for more details about what is required. The client + must also acknowledge configure events using "ack_configure". After + the resize is completed, the client will receive another "configure" + event without the resize state. + + If triggered, the surface also will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the resize. It is up to the + compositor to visually indicate that the resize is taking place, + such as updating a pointer cursor, during the resize. There is no + guarantee that the device focus will return when the resize is + completed. + + The edges parameter specifies how the surface should be resized, and + is one of the values of the resize_edge enum. Values not matching + a variant of the enum will cause a protocol error. The compositor + may use this information to update the surface position for example + when dragging the top left corner. The compositor may also use + this information to adapt its behavior, e.g. choose an appropriate + cursor image. + + + + + + + + + The different state values used on the surface. This is designed for + state values like maximized, fullscreen. It is paired with the + configure event to ensure that both the client and the compositor + setting the state can be synchronized. + + States set in this way are double-buffered. They will get applied on + the next commit. + + + + The surface is maximized. The window geometry specified in the configure + event must be obeyed by the client. + + The client should draw without shadow or other + decoration outside of the window geometry. + + + + + The surface is fullscreen. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. For + a surface to cover the whole fullscreened area, the geometry + dimensions must be obeyed by the client. For more details, see + xdg_toplevel.set_fullscreen. + + + + + The surface is being resized. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. + Clients that have aspect ratio or cell sizing configuration can use + a smaller size, however. + + + + + Client window decorations should be painted as if the window is + active. Do not assume this means that the window actually has + keyboard or pointer focus. + + + + + The window is currently in a tiled layout and the left edge is + considered to be adjacent to another part of the tiling grid. + + + + + The window is currently in a tiled layout and the right edge is + considered to be adjacent to another part of the tiling grid. + + + + + The window is currently in a tiled layout and the top edge is + considered to be adjacent to another part of the tiling grid. + + + + + The window is currently in a tiled layout and the bottom edge is + considered to be adjacent to another part of the tiling grid. + + + + + + + Set a maximum size for the window. + + The client can specify a maximum size so that the compositor does + not try to configure the window beyond this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered. They will get applied + on the next commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the maximum + size. The compositor may decide to ignore the values set by the + client and request a larger size. + + If never set, or a value of zero in the request, means that the + client has no expected maximum size in the given dimension. + As a result, a client wishing to reset the maximum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a maximum size to be smaller than the minimum size of + a surface is illegal and will result in a protocol error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width and height will result in a + protocol error. + + + + + + + + Set a minimum size for the window. + + The client can specify a minimum size so that the compositor does + not try to configure the window below this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered. They will get applied + on the next commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the minimum + size. The compositor may decide to ignore the values set by the + client and request a smaller size. + + If never set, or a value of zero in the request, means that the + client has no expected minimum size in the given dimension. + As a result, a client wishing to reset the minimum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a minimum size to be larger than the maximum size of + a surface is illegal and will result in a protocol error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width and height will result in a + protocol error. + + + + + + + + Maximize the surface. + + After requesting that the surface should be maximized, the compositor + will respond by emitting a configure event. Whether this configure + actually sets the window maximized is subject to compositor policies. + The client must then update its content, drawing in the configured + state. The client must also acknowledge the configure when committing + the new content (see ack_configure). + + It is up to the compositor to decide how and where to maximize the + surface, for example which output and what region of the screen should + be used. + + If the surface was already maximized, the compositor will still emit + a configure event with the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. + + + + + + Unmaximize the surface. + + After requesting that the surface should be unmaximized, the compositor + will respond by emitting a configure event. Whether this actually + un-maximizes the window is subject to compositor policies. + If available and applicable, the compositor will include the window + geometry dimensions the window had prior to being maximized in the + configure event. The client must then update its content, drawing it in + the configured state. The client must also acknowledge the configure + when committing the new content (see ack_configure). + + It is up to the compositor to position the surface after it was + unmaximized; usually the position the surface had before maximizing, if + applicable. + + If the surface was already not maximized, the compositor will still + emit a configure event without the "maximized" state. + + If the surface is in a fullscreen state, this request has no direct + effect. It may alter the state the surface is returned to when + unmaximized unless overridden by the compositor. + + + + + + Make the surface fullscreen. + + After requesting that the surface should be fullscreened, the + compositor will respond by emitting a configure event. Whether the + client is actually put into a fullscreen state is subject to compositor + policies. The client must also acknowledge the configure when + committing the new content (see ack_configure). + + The output passed by the request indicates the client's preference as + to which display it should be set fullscreen on. If this value is NULL, + it's up to the compositor to choose which display will be used to map + this surface. + + If the surface doesn't cover the whole output, the compositor will + position the surface in the center of the output and compensate with + with border fill covering the rest of the output. The content of the + border fill is undefined, but should be assumed to be in some way that + attempts to blend into the surrounding area (e.g. solid black). + + If the fullscreened surface is not opaque, the compositor must make + sure that other screen content not part of the same surface tree (made + up of subsurfaces, popups or similarly coupled surfaces) are not + visible below the fullscreened surface. + + + + + + + Make the surface no longer fullscreen. + + After requesting that the surface should be unfullscreened, the + compositor will respond by emitting a configure event. + Whether this actually removes the fullscreen state of the client is + subject to compositor policies. + + Making a surface unfullscreen sets states for the surface based on the following: + * the state(s) it may have had before becoming fullscreen + * any state(s) decided by the compositor + * any state(s) requested by the client while the surface was fullscreen + + The compositor may include the previous window geometry dimensions in + the configure event, if applicable. + + The client must also acknowledge the configure when committing the new + content (see ack_configure). + + + + + + Request that the compositor minimize your surface. There is no + way to know if the surface is currently minimized, nor is there + any way to unset minimization on this surface. + + If you are looking to throttle redrawing when minimized, please + instead use the wl_surface.frame event for this, as this will + also work with live previews on windows in Alt-Tab, Expose or + similar compositor features. + + + + + + This configure event asks the client to resize its toplevel surface or + to change its state. The configured state should not be applied + immediately. See xdg_surface.configure for details. + + The width and height arguments specify a hint to the window + about how its surface should be resized in window geometry + coordinates. See set_window_geometry. + + If the width or height arguments are zero, it means the client + should decide its own window dimension. This may happen when the + compositor needs to configure the state of the surface but doesn't + have any information about any previous or expected dimension. + + The states listed in the event specify how the width/height + arguments should be interpreted, and possibly how it should be + drawn. + + Clients must send an ack_configure in response to this event. See + xdg_surface.configure and xdg_surface.ack_configure for details. + + + + + + + + + The close event is sent by the compositor when the user + wants the surface to be closed. This should be equivalent to + the user clicking the close button in client-side decorations, + if your application has any. + + This is only a request that the user intends to close the + window. The client may choose to ignore this request, or show + a dialog to ask the user to save their data, etc. + + + + + + + + The configure_bounds event may be sent prior to a xdg_toplevel.configure + event to communicate the bounds a window geometry size is recommended + to constrain to. + + The passed width and height are in surface coordinate space. If width + and height are 0, it means bounds is unknown and equivalent to as if no + configure_bounds event was ever sent for this surface. + + The bounds can for example correspond to the size of a monitor excluding + any panels or other shell components, so that a surface isn't created in + a way that it cannot fit. + + The bounds may change at any point, and in such a case, a new + xdg_toplevel.configure_bounds will be sent, followed by + xdg_toplevel.configure and xdg_surface.configure. + + + + + + + + + + + + + + + + + This event advertises the capabilities supported by the compositor. If + a capability isn't supported, clients should hide or disable the UI + elements that expose this functionality. For instance, if the + compositor doesn't advertise support for minimized toplevels, a button + triggering the set_minimized request should not be displayed. + + The compositor will ignore requests it doesn't support. For instance, + a compositor which doesn't advertise support for minimized will ignore + set_minimized requests. + + Compositors must send this event once before the first + xdg_surface.configure event. When the capabilities change, compositors + must send this event again and then send an xdg_surface.configure + event. + + The configured state should not be applied immediately. See + xdg_surface.configure for details. + + The capabilities are sent as an array of 32-bit unsigned integers in + native endianness. + + + + + + + + A popup surface is a short-lived, temporary surface. It can be used to + implement for example menus, popovers, tooltips and other similar user + interface concepts. + + A popup can be made to take an explicit grab. See xdg_popup.grab for + details. + + When the popup is dismissed, a popup_done event will be sent out, and at + the same time the surface will be unmapped. See the xdg_popup.popup_done + event for details. + + Explicitly destroying the xdg_popup object will also dismiss the popup and + unmap the surface. Clients that want to dismiss the popup when another + surface of their own is clicked should dismiss the popup using the destroy + request. + + A newly created xdg_popup will be stacked on top of all previously created + xdg_popup surfaces associated with the same xdg_toplevel. + + The parent of an xdg_popup must be mapped (see the xdg_surface + description) before the xdg_popup itself. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_popup state to take effect. + + + + + + + + + This destroys the popup. Explicitly destroying the xdg_popup + object will also dismiss the popup, and unmap the surface. + + If this xdg_popup is not the "topmost" popup, a protocol error + will be sent. + + + + + + This request makes the created popup take an explicit grab. An explicit + grab will be dismissed when the user dismisses the popup, or when the + client destroys the xdg_popup. This can be done by the user clicking + outside the surface, using the keyboard, or even locking the screen + through closing the lid or a timeout. + + If the compositor denies the grab, the popup will be immediately + dismissed. + + This request must be used in response to some sort of user action like a + button press, key press, or touch down event. The serial number of the + event should be passed as 'serial'. + + The parent of a grabbing popup must either be an xdg_toplevel surface or + another xdg_popup with an explicit grab. If the parent is another + xdg_popup it means that the popups are nested, with this popup now being + the topmost popup. + + Nested popups must be destroyed in the reverse order they were created + in, e.g. the only popup you are allowed to destroy at all times is the + topmost one. + + When compositors choose to dismiss a popup, they may dismiss every + nested grabbing popup as well. When a compositor dismisses popups, it + will follow the same dismissing order as required from the client. + + If the topmost grabbing popup is destroyed, the grab will be returned to + the parent of the popup, if that parent previously had an explicit grab. + + If the parent is a grabbing popup which has already been dismissed, this + popup will be immediately dismissed. If the parent is a popup that did + not take an explicit grab, an error will be raised. + + During a popup grab, the client owning the grab will receive pointer + and touch events for all their surfaces as normal (similar to an + "owner-events" grab in X11 parlance), while the top most grabbing popup + will always have keyboard focus. + + + + + + + + This event asks the popup surface to configure itself given the + configuration. The configured state should not be applied immediately. + See xdg_surface.configure for details. + + The x and y arguments represent the position the popup was placed at + given the xdg_positioner rule, relative to the upper left corner of the + window geometry of the parent surface. + + For version 2 or older, the configure event for an xdg_popup is only + ever sent once for the initial configuration. Starting with version 3, + it may be sent again if the popup is setup with an xdg_positioner with + set_reactive requested, or in response to xdg_popup.reposition requests. + + + + + + + + + + The popup_done event is sent out when a popup is dismissed by the + compositor. The client should destroy the xdg_popup object at this + point. + + + + + + + + Reposition an already-mapped popup. The popup will be placed given the + details in the passed xdg_positioner object, and a + xdg_popup.repositioned followed by xdg_popup.configure and + xdg_surface.configure will be emitted in response. Any parameters set + by the previous positioner will be discarded. + + The passed token will be sent in the corresponding + xdg_popup.repositioned event. The new popup position will not take + effect until the corresponding configure event is acknowledged by the + client. See xdg_popup.repositioned for details. The token itself is + opaque, and has no other special meaning. + + If multiple reposition requests are sent, the compositor may skip all + but the last one. + + If the popup is repositioned in response to a configure event for its + parent, the client should send an xdg_positioner.set_parent_configure + and possibly an xdg_positioner.set_parent_size request to allow the + compositor to properly constrain the popup. + + If the popup is repositioned together with a parent that is being + resized, but not in response to a configure event, the client should + send an xdg_positioner.set_parent_size request. + + + + + + + + The repositioned event is sent as part of a popup configuration + sequence, together with xdg_popup.configure and lastly + xdg_surface.configure to notify the completion of a reposition request. + + The repositioned event is to notify about the completion of a + xdg_popup.reposition request. The token argument is the token passed + in the xdg_popup.reposition request. + + Immediately after this event is emitted, xdg_popup.configure and + xdg_surface.configure will be sent with the updated size and position, + as well as a new configure serial. + + The client should optionally update the content of the popup, but must + acknowledge the new popup configuration for the new position to take + effect. See xdg_surface.ack_configure for details. + + + + + + diff --git a/xdg_popup.c b/xdg_popup.c new file mode 100644 index 0000000..be1a411 --- /dev/null +++ b/xdg_popup.c @@ -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 . */ + +#include + +#include "compositor.h" +#include "xdg-shell.h" + +#include + +#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; +} diff --git a/xdg_surface.c b/xdg_surface.c new file mode 100644 index 0000000..b149558 --- /dev/null +++ b/xdg_surface.c @@ -0,0 +1,1769 @@ +/* Wayland compositor running on top of an X server. + +Copyright (C) 2022 to various contributors. + +This file is part of 12to11. + +12to11 is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +12to11 is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with 12to11. If not, see . */ + +#include + +#include +#include + +#include "compositor.h" +#include "xdg-shell.h" + +#include + +#define XdgRoleFromRole(role) ((XdgRole *) (role)) + +enum + { + StatePendingFrameCallback = 1, + StateLateFrame = (1 << 1), + StatePendingWindowGeometry = (1 << 2), + StateWaitingForAckConfigure = (1 << 3), + StateLateFrameAcked = (1 << 4), + }; + +typedef struct _XdgRole XdgRole; +typedef struct _XdgState XdgState; +typedef struct _ReleaseLaterRecord ReleaseLaterRecord; +typedef struct _ReconstrainCallback ReconstrainCallback; + +/* Association between XIDs and surfaces. */ + +static XLAssocTable *surfaces; + +/* The default border color of a window. Not actually used for + anything other than preventing BadMatch errors during window + creation. */ + +unsigned long border_pixel; + +struct _ReconstrainCallback +{ + /* Function called when a configure event is received. */ + void (*configure) (void *, XEvent *); + + /* Function called when we are certain a frame moved or resized. */ + void (*resized) (void *); + + /* Data the functions is called with. */ + void *data; + + /* The next and last callbacks in this list. */ + ReconstrainCallback *next, *last; +}; + +struct _XdgState +{ + int window_geometry_x; + int window_geometry_y; + int window_geometry_width; + int window_geometry_height; +}; + +struct _XdgRole +{ + /* The role object. */ + Role role; + + /* Number of references to this role. Used when the client + terminates and the Wayland library destroys objects out of + order. */ + int refcount; + + /* The window backing this role. */ + Window window; + + /* The picture backing this role. */ + Picture picture; + + /* The subcompositor backing this role. */ + Subcompositor *subcompositor; + + /* The implementation of this role. */ + XdgRoleImplementation *impl; + + /* Various role state. */ + int state; + + /* Queue of buffers to release later (when the X server is done with + them). */ + ReleaseLaterRecord *release_records; + + /* The frame clock. */ + FrameClock *clock; + + /* The pending xdg_surface state. */ + XdgState pending_state; + + /* The current xdg_surface state. */ + XdgState current_state; + + /* Configure event serial. */ + uint32_t conf_serial; + + /* The current bounds of the subcompositor. */ + int min_x, min_y, max_x, max_y; + + /* The bounds width and bounds height of the subcompositor. */ + int bounds_width, bounds_height; + + /* The pending root window position of the subcompositor. */ + int pending_root_x, pending_root_y; + + /* How many synthetic (in the case of toplevels) ConfigureNotify + events to wait for before ignoring those coordinates. */ + int pending_synth_configure; + + /* The type of the attached role. */ + XdgRoleImplementationType type; + + /* The input region of the attached subsurface. */ + pixman_region32_t input_region; + + /* List of callbacks run upon a ConfigureNotify event. */ + ReconstrainCallback reconstrain_callbacks; + + /* The number of desynchronous children of this toplevel. */ + int n_desync_children; +}; + +struct _ReleaseLaterRecord +{ + /* A monotonically (overflow aside) increasing identifier. */ + uint64_t id; + + /* Key for the free func. */ + void *free_func_key; + + /* The buffer that should be released upon receiving this + message. */ + ExtBuffer *buffer; + + /* The next and last records. */ + ReleaseLaterRecord *next, *last; +}; + +/* Event base of the XShape extension. */ +int shape_base; + +static void +RemoveRecord (ReleaseLaterRecord *record) +{ + /* Removing the sentinel record is invalid. */ + XLAssert (record->buffer != NULL); + + /* First, make the rest of the list skip RECORD. */ + record->last->next = record->next; + record->next->last = record->last; + + /* Next, remove the buffer listener. */ + XLBufferCancelRunOnFree (record->buffer, + record->free_func_key); + + /* Finally, free RECORD. */ + XLFree (record); +} + +static void +DeleteRecord (ReleaseLaterRecord *record) +{ + /* Removing the sentinel record is invalid. */ + XLAssert (record->buffer != NULL); + + /* First, make the rest of the list skip RECORD. */ + record->last->next = record->next; + record->next->last = record->last; + + /* Finally, free RECORD. */ + XLFree (record); +} + +static void +FreeRecords (ReleaseLaterRecord *records) +{ + ReleaseLaterRecord *last, *tem; + + tem = records->next; + + while (tem != records) + { + last = tem; + tem = tem->next; + + /* Release the buffer now. */ + XLReleaseBuffer (last->buffer); + + /* And cancel the destroy listener. */ + XLBufferCancelRunOnFree (last->buffer, + last->free_func_key); + + /* Before freeing the record itself. */ + XLFree (last); + } + + XLFree (records); +} + +static ReleaseLaterRecord * +AddRecordAfter (ReleaseLaterRecord *start) +{ + ReleaseLaterRecord *record; + + record = XLMalloc (sizeof *record); + record->next = start->next; + record->last = start; + + start->next->last = record; + start->next = record; + + return record; +} + +static ReconstrainCallback * +AddCallbackAfter (ReconstrainCallback *start) +{ + ReconstrainCallback *callback; + + callback = XLMalloc (sizeof *callback); + callback->next = start->next; + callback->last = start; + + start->next->last = callback; + start->next = callback; + + return callback; +} + +static void +UnlinkReconstrainCallback (ReconstrainCallback *callback) +{ + callback->last->next = callback->next; + callback->next->last = callback->last; + + XLFree (callback); +} + +static void +RunReconstrainCallbacksForXEvent (XdgRole *role, XEvent *event) +{ + ReconstrainCallback *callback; + + callback = role->reconstrain_callbacks.next; + + while (callback != &role->reconstrain_callbacks) + { + callback->configure (callback->data, event); + callback = callback->next; + } +} + +static void +RunReconstrainCallbacks (XdgRole *role) +{ + ReconstrainCallback *callback; + + callback = role->reconstrain_callbacks.next; + + while (callback != &role->reconstrain_callbacks) + { + callback->resized (callback->data); + callback = callback->next; + } +} + +static void +FreeReconstrainCallbacks (XdgRole *role) +{ + ReconstrainCallback *callback, *last; + + callback = role->reconstrain_callbacks.next; + + while (callback != &role->reconstrain_callbacks) + { + last = callback; + callback = callback->next; + + XLFree (last); + } +} + +static void +ReleaseLaterExtBufferFunc (ExtBuffer *buffer, void *data) +{ + DeleteRecord (data); +} + +static void +RunFrameCallbacks (Surface *surface, XdgRole *role) +{ + struct timespec time; + + /* Surface can be NULL for various reasons, especially events + arriving after the shell surface is detached. */ + if (!surface) + return; + + clock_gettime (CLOCK_MONOTONIC, &time); + XLSurfaceRunFrameCallbacks (surface, time); +} + +static void +RunFrameCallbacksConditionally (XdgRole *role) +{ + if (role->release_records->last == role->release_records + && role->role.surface) + RunFrameCallbacks (role->role.surface, role); + else if (role->role.surface) + /* weston-simple-shm seems to assume that a frame callback can + only arrive after all buffers have been released. */ + role->state |= StatePendingFrameCallback; +} + +static void +HandleReleaseLaterMessage (XdgRole *role, uint64_t id) +{ + ReleaseLaterRecord *record; + Surface *surface; + + record = role->release_records->last; + + if (record == role->release_records) + return; + + /* Since the list of release records is a (circular) queue, ID will + either be the last element or invalid as the buffer has been + destroyed. */ + + if (record->id == id) + { + XLReleaseBuffer (record->buffer); + RemoveRecord (record); + } + + surface = role->role.surface; + + /* Run frame callbacks now, if no more buffers are waiting to be + released. */ + if (surface && role->state & StatePendingFrameCallback + && role->release_records->next == role->release_records) + { + RunFrameCallbacks (surface, role); + + role->state &= ~StatePendingFrameCallback; + } +} + +/* It shouldn't be possible for billions of frames to pile up without + a response from the X server, so relying on wraparound to handle id + overflow should be fine. */ + +static void +ReleaseLater (XdgRole *role, ExtBuffer *buffer) +{ + ReleaseLaterRecord *record; + XEvent event; + static uint64_t id; + + /* Send a message to the X server; once it is received, release the + given buffer. This is necessary because the connection to the X + server does not behave synchronously, and receiving the message + tells us that the X server has finished processing all requests + that access the buffer. */ + + record = AddRecordAfter (role->release_records); + record->id = ++id; + record->buffer = buffer; + record->free_func_key + = XLBufferRunOnFree (buffer, ReleaseLaterExtBufferFunc, + record); + + memset (&event, 0, sizeof event); + + event.xclient.type = ClientMessage; + event.xclient.window = role->window; + event.xclient.message_type = _XL_BUFFER_RELEASE; + event.xclient.format = 32; + + event.xclient.data.l[0] = id >> 31 >> 1; + event.xclient.data.l[1] = id & 0xffffffff; + + XSendEvent (compositor.display, role->window, False, + NoEventMask, &event); +} + +Bool +XLHandleXEventForXdgSurfaces (XEvent *event) +{ + XdgRole *role; + uint64_t id, low, high; + Window window; + + if (event->type == ClientMessage + && event->xclient.message_type == _XL_BUFFER_RELEASE) + { + role = XLLookUpAssoc (surfaces, event->xclient.window); + + if (role) + { + /* These values are masked because Xlib sign-extends 32-bit + values to fit potentially 64-bit long. */ + low = event->xclient.data.l[0] & 0xffffffff; + high = event->xclient.data.l[1] & 0xffffffff; + + id = (low << 32) | high; + HandleReleaseLaterMessage (role, id); + } + + return True; + } + + if (event->type == ClientMessage + && ((event->xclient.message_type == _NET_WM_FRAME_DRAWN + || event->xclient.message_type == _NET_WM_FRAME_TIMINGS) + || (event->xclient.message_type == WM_PROTOCOLS + && event->xclient.data.l[0] == _NET_WM_SYNC_REQUEST))) + { + role = XLLookUpAssoc (surfaces, event->xclient.window); + + if (role) + { + XLFrameClockHandleFrameEvent (role->clock, event); + return True; + } + + return False; + } + + if (event->type == Expose) + { + role = XLLookUpAssoc (surfaces, event->xexpose.window); + + if (role) + { + /* If resizing is in progress with WM synchronization, don't + handle exposure events, since that causes updates outside + a frame. */ + if (!XLFrameClockNeedConfigure (role->clock)) + SubcompositorExpose (role->subcompositor, event); + + return True; + } + + return False; + } + + window = XLGetGEWindowForSeats (event); + + if (window != None) + { + role = XLLookUpAssoc (surfaces, window); + + if (role && role->role.surface) + { + XLDispatchGEForSeats (event, role->role.surface, + role->subcompositor); + return True; + } + + return False; + } + + return False; +} + +static void +Destroy (struct wl_client *client, struct wl_resource *resource) +{ + XdgRole *role; + + role = wl_resource_get_user_data (resource); + + if (role->impl) + { + wl_resource_post_error (resource, XDG_WM_BASE_ERROR_ROLE, + "trying to destroy xdg surface with role"); + return; + } + + wl_resource_destroy (resource); +} + +static void +GetToplevel (struct wl_client *client, struct wl_resource *resource, + uint32_t id) +{ + XdgRole *role; + + role = wl_resource_get_user_data (resource); + + if (!role->role.surface) + /* This object is inert. */ + return; + + if (role->type == TypePopup) + { + wl_resource_post_error (resource, XDG_WM_BASE_ERROR_ROLE, + "surface was previously a popup"); + return; + } + + role->type = TypeToplevel; + + XLGetXdgToplevel (client, resource, id); +} + +static void +GetPopup (struct wl_client *client, struct wl_resource *resource, + uint32_t id, struct wl_resource *parent_resource, + struct wl_resource *positioner_resource) +{ + XdgRole *role; + + role = wl_resource_get_user_data (resource); + + if (!role->role.surface) + /* This object is inert. */ + return; + + if (role->type == TypeToplevel) + { + wl_resource_post_error (resource, XDG_WM_BASE_ERROR_ROLE, + "surface was previously a toplevel"); + return; + } + + role->type = TypePopup; + + XLGetXdgPopup (client, resource, id, parent_resource, + positioner_resource); +} + +static void +SetWindowGeometry (struct wl_client *client, struct wl_resource *resource, + int32_t x, int32_t y, int32_t width, int32_t height) +{ + XdgRole *role; + + role = wl_resource_get_user_data (resource); + + if (x == role->current_state.window_geometry_x + && y == role->pending_state.window_geometry_y + && width == role->pending_state.window_geometry_width + && height == role->pending_state.window_geometry_height) + return; + + role->state |= StatePendingWindowGeometry; + + role->pending_state.window_geometry_x = x; + role->pending_state.window_geometry_y = y; + role->pending_state.window_geometry_width = width; + role->pending_state.window_geometry_height = height; +} + +static void +AckConfigure (struct wl_client *client, struct wl_resource *resource, + uint32_t serial) +{ + XdgRole *xdg_role; + + xdg_role = wl_resource_get_user_data (resource); + + if (!xdg_role->role.surface) + return; + + if (serial == xdg_role->conf_serial) + { + xdg_role->state &= ~StateWaitingForAckConfigure; + + /* Garbage the subcompositor too, since contents could be + exposed due to changes in bounds. */ + SubcompositorGarbage (xdg_role->subcompositor); + + /* Also run frame callbacks if the frame clock is frozen. */ + if (XLFrameClockIsFrozen (xdg_role->clock) + && xdg_role->role.surface) + RunFrameCallbacksConditionally (xdg_role); + } + + if (xdg_role->impl) + xdg_role->impl->funcs.ack_configure (&xdg_role->role, + xdg_role->impl, + serial); +} + +static const struct xdg_surface_interface xdg_surface_impl = + { + .get_toplevel = GetToplevel, + .get_popup = GetPopup, + .destroy = Destroy, + .set_window_geometry = SetWindowGeometry, + .ack_configure = AckConfigure, + }; + +static void +Unfreeze (XdgRole *role) +{ + XLFrameClockUnfreeze (role->clock); +} + +static void +Commit (Surface *surface, Role *role) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + + if (!xdg_role->impl) + return; + + if (xdg_role->state & StatePendingWindowGeometry) + { + xdg_role->current_state.window_geometry_x + = xdg_role->pending_state.window_geometry_x; + xdg_role->current_state.window_geometry_y + = xdg_role->pending_state.window_geometry_y; + xdg_role->current_state.window_geometry_width + = xdg_role->pending_state.window_geometry_width; + xdg_role->current_state.window_geometry_height + = xdg_role->pending_state.window_geometry_height; + } + + xdg_role->impl->funcs.commit (role, surface, + xdg_role->impl); + + if (xdg_role->state & StatePendingWindowGeometry) + { + if (xdg_role->impl->funcs.handle_geometry_change) + xdg_role->impl->funcs.handle_geometry_change (role, + xdg_role->impl); + + xdg_role->state &= ~StatePendingWindowGeometry; + } + + /* A frame is already in progress, so instead say that an urgent + update is needed immediately after the frame completes. In any + case, don't run frame callbacks upon buffer release anymore. */ + if (XLFrameClockFrameInProgress (xdg_role->clock)) + { + if (XLFrameClockCanBatch (xdg_role->clock)) + /* But if we can squeeze the frame inside the vertical + blanking period, go ahead. */ + goto just_draw_then_return; + + xdg_role->state |= StateLateFrame; + xdg_role->state &= ~StatePendingFrameCallback; + + if (xdg_role->state & StateWaitingForAckConfigure) + xdg_role->state &= ~StateLateFrameAcked; + else + xdg_role->state |= StateLateFrameAcked; + + return; + } + + /* If the frame clock is frozen but we are no longer waiting for the + configure event to be acknowledged by the client, unfreeze the + frame clock. */ + if (!(xdg_role->state & StateWaitingForAckConfigure)) + Unfreeze (xdg_role); + + XLFrameClockStartFrame (xdg_role->clock, False); + SubcompositorUpdate (xdg_role->subcompositor); + + /* Also run role "commit inside frame" hook. */ + if (xdg_role->impl && xdg_role->impl->funcs.commit_inside_frame) + xdg_role->impl->funcs.commit_inside_frame (role, xdg_role->impl); + XLFrameClockEndFrame (xdg_role->clock); + + /* Clients which commit when a configure event that has not yet been + acked still expect frame callbacks to be called; however, frame + callbacks are not provided by the frame clock while it is frozen. + + If that happens, just run the frame callback immediately. */ + if (XLFrameClockIsFrozen (xdg_role->clock)) + RunFrameCallbacksConditionally (xdg_role); + + return; + + just_draw_then_return: + SubcompositorUpdate (xdg_role->subcompositor); +} + +static Bool +Setup (Surface *surface, Role *role) +{ + XdgRole *xdg_role; + + /* Set role->surface here, since this is where the refcounting is + done as well. */ + role->surface = surface; + + /* Prevent the surface from ever holding another kind of role. */ + surface->role_type = XdgType; + + xdg_role = XdgRoleFromRole (role); + ViewSetSubcompositor (surface->view, + xdg_role->subcompositor); + ViewSetSubcompositor (surface->under, + xdg_role->subcompositor); + + /* Make sure the under view ends up beneath surface->view. */ + SubcompositorInsert (xdg_role->subcompositor, + surface->under); + SubcompositorInsert (xdg_role->subcompositor, + surface->view); + + /* Count the number of desynchronous children attached to this + surface, directly or indirectly. This number is then updated as + surfaces are attached, made desynchronous and/or removed in + NoteChildSynced and NoteDesyncChild. */ + XLUpdateDesynchronousChildren (surface, &xdg_role->n_desync_children); + + /* If there are desynchronous children, enable frame refresh + prediction in the frame clock, which batches subframes from + multiple subsurfaces together if they arrive in time. */ + if (xdg_role->n_desync_children) + XLFrameClockSetPredictRefresh (xdg_role->clock); + + /* Retain the backing data. */ + xdg_role->refcount++; + + return True; +} + +static void +ReleaseBacking (XdgRole *role) +{ + if (--role->refcount) + return; + + /* Sync, and then release all buffers pending release. The sync is + necessary because the X server does not perform operations + immediately after the Xlib function is called. */ + XSync (compositor.display, False); + FreeRecords (role->release_records); + + /* Now release the reference to any toplevel implementation that + might be attached. */ + if (role->impl) + XLXdgRoleDetachImplementation (&role->role, role->impl); + + /* Release all allocated resources. */ + XRenderFreePicture (compositor.display, role->picture); + XDestroyWindow (compositor.display, role->window); + + /* And the association. */ + XLDeleteAssoc (surfaces, role->window); + + /* There shouldn't be any children of the subcompositor at this + point. */ + SubcompositorFree (role->subcompositor); + + /* The frame clock is no longer useful. */ + XLFreeFrameClock (role->clock); + + /* Free the input region. */ + pixman_region32_fini (&role->input_region); + + /* Free reconstrain callbacks. */ + FreeReconstrainCallbacks (role); + + /* And since there are no C level references to the role anymore, it + can be freed. */ + XLFree (role); +} + +static void +Teardown (Surface *surface, Role *role) +{ + XdgRole *xdg_role; + + /* Clear role->surface here, since this is where the refcounting is + done as well. */ + role->surface = NULL; + + xdg_role = XdgRoleFromRole (role); + + /* Unparent the surface's views as well. */ + ViewUnparent (surface->view); + ViewUnparent (surface->under); + + /* Detach the surface's views from the subcompositor. */ + ViewSetSubcompositor (surface->view, NULL); + ViewSetSubcompositor (surface->under, NULL); + + /* Release the backing data. */ + ReleaseBacking (xdg_role); +} + +static void +ReleaseBuffer (Surface *surface, Role *role, ExtBuffer *buffer) +{ + ReleaseLater (XdgRoleFromRole (role), buffer); +} + +static Bool +Subframe (Surface *surface, Role *role) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + + /* If the frame clock is frozen, return False. */ + if (XLFrameClockIsFrozen (xdg_role->clock)) + { + /* However, run frame callbacks. */ + RunFrameCallbacksConditionally (xdg_role); + return False; + } + + /* If a frame is already in progress, return False. Then, require a + late frame. */ + if (XLFrameClockFrameInProgress (xdg_role->clock)) + { + if (XLFrameClockCanBatch (xdg_role->clock)) + /* But if we can squeeze the frame inside the vertical + blanking period, go ahead. */ + return True; + + xdg_role->state |= StateLateFrame; + xdg_role->state &= ~StatePendingFrameCallback; + + if (xdg_role->state & StateWaitingForAckConfigure) + xdg_role->state &= ~StateLateFrameAcked; + + return False; + } + + /* I guess subsurface updates don't count as urgent frames? */ + XLFrameClockStartFrame (xdg_role->clock, False); + return True; +} + +static void +EndSubframe (Surface *surface, Role *role) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + XLFrameClockEndFrame (xdg_role->clock); +} + +static Window +GetWindow (Surface *surface, Role *role) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + return xdg_role->window; +} + +static void +HandleResourceDestroy (struct wl_resource *resource) +{ + XdgRole *role; + + role = wl_resource_get_user_data (resource); + role->role.resource = NULL; + + /* Release the backing data. */ + ReleaseBacking (role); +} + +static void +AfterFrame (FrameClock *clock, void *data) +{ + XdgRole *role; + + role = data; + + if (role->state & StateLateFrame) + { + role->state &= ~StateLateFrame; + + if (role->state & StateLateFrameAcked) + XLFrameClockUnfreeze (role->clock); + + /* Since we are running late, make the compositor draw the frame + now. */ + XLFrameClockStartFrame (clock, True); + SubcompositorUpdate (role->subcompositor); + + /* Also run role "commit inside frame" hook. */ + if (role->impl + && role->impl->funcs.commit_inside_frame) + role->impl->funcs.commit_inside_frame (&role->role, + role->impl); + XLFrameClockEndFrame (clock); + + /* See the comment in Commit about frame callbacks being + attached while the frame clock is frozen. */ + if (XLFrameClockIsFrozen (role->clock) + && role->role.surface) + RunFrameCallbacksConditionally (role); + + return; + } + + /* If all pending frames have been drawn, run frame callbacks. + Unless some buffers have not yet been released, in which case the + callbacks will be run when they are. */ + + RunFrameCallbacksConditionally (role); +} + +static void +OpaqueRegionChanged (Subcompositor *subcompositor, + void *client_data, + pixman_region32_t *opaque_region) +{ + XdgRole *role; + long *data; + int nrects, i; + pixman_box32_t *boxes; + + boxes = pixman_region32_rectangles (opaque_region, &nrects); + role = client_data; + + if (nrects < 64) + data = alloca (4 * sizeof *data * nrects); + else + data = XLMalloc (4 * sizeof *data * nrects); + + for (i = 0; i < nrects; ++i) + { + data[i * 4 + 0] = BoxStartX (boxes[i]); + data[i * 4 + 1] = BoxStartY (boxes[i]); + data[i * 4 + 2] = BoxWidth (boxes[i]); + data[i * 4 + 3] = BoxHeight (boxes[i]); + } + + XChangeProperty (compositor.display, role->window, + _NET_WM_OPAQUE_REGION, XA_CARDINAL, + 32, PropModeReplace, + (unsigned char *) data, nrects * 4); + + if (nrects >= 64) + XLFree (data); +} + +static void +InputRegionChanged (Subcompositor *subcompositor, + void *data, + pixman_region32_t *input_region) +{ + XdgRole *role; + int nrects, i; + pixman_box32_t *boxes; + XRectangle *rects; + + role = data; + boxes = pixman_region32_rectangles (input_region, &nrects); + + /* If the number of rectangles is small (<= 256), allocate them on + the stack. Otherwise, use the heap instead. */ + + if (nrects < 256) + rects = alloca (sizeof *rects * nrects); + else + rects = XLMalloc (sizeof *rects * nrects); + + /* Convert the boxes into proper XRectangles and make them the input + region of the window. */ + + for (i = 0; i < nrects; ++i) + { + rects[i].x = BoxStartX (boxes[i]); + rects[i].y = BoxStartY (boxes[i]); + rects[i].width = BoxWidth (boxes[i]); + rects[i].height = BoxHeight (boxes[i]); + } + + XShapeCombineRectangles (compositor.display, + role->window, ShapeInput, + 0, 0, rects, nrects, + /* pixman uses the same region + representation as the X server, which is + YXBanded. */ + ShapeSet, YXBanded); + + if (nrects >= 256) + XLFree (rects); + + /* Also save the input region for future use. */ + pixman_region32_copy (&role->input_region, input_region); +} + +static void +NoteConfigure (XdgRole *role, XEvent *event) +{ + if (role->pending_synth_configure) + role->pending_synth_configure--; + + /* Update the surface that the surface is inside. */ + if (role->role.surface) + XLUpdateSurfaceOutputs (role->role.surface, + event->xconfigure.x + role->min_x, + event->xconfigure.y + role->min_y, + -1, -1); + + RunReconstrainCallbacksForXEvent (role, event); +} + +static void +CurrentRootPosition (XdgRole *role, int *root_x, int *root_y) +{ + Window child_return; + + if (role->pending_synth_configure) + { + *root_x = role->pending_root_x; + *root_y = role->pending_root_y; + + return; + } + + /* TODO: handle root position changing in quick succession and avoid + call to XTranslateCoordinates. */ + XTranslateCoordinates (compositor.display, role->window, + DefaultRootWindow (compositor.display), + 0, 0, root_x, root_y, &child_return); +} + +static void +NoteBounds (void *data, int min_x, int min_y, + int max_x, int max_y) +{ + XdgRole *role; + int bounds_width, bounds_height, root_x, root_y; + Bool run_reconstrain_callbacks; + + role = data; + run_reconstrain_callbacks = False; + + if (XLFrameClockIsFrozen (role->clock)) + /* We are waiting for the acknowledgement of a configure event. + Don't resize the window until it's acknowledged. */ + return; + + /* Avoid resizing the window should its actual size not have + changed. */ + + bounds_width = max_x - min_x + 1; + bounds_height = max_y - min_y + 1; + + if (role->bounds_width != bounds_width + || role->bounds_height != bounds_height) + { + if (role->impl->funcs.note_window_pre_resize) + role->impl->funcs.note_window_pre_resize (&role->role, + role->impl, + bounds_width, + bounds_height); + + XResizeWindow (compositor.display, role->window, + bounds_width, bounds_height); + run_reconstrain_callbacks = True; + + if (role->impl->funcs.note_window_resized) + role->impl->funcs.note_window_resized (&role->role, + role->impl, + bounds_width, + bounds_height); + } + + /* Now, make sure the window stays at the same position relative to + the origin of the view. */ + + if (min_x != role->min_x || min_y != role->min_y) + { + /* Move the window by the opposite of the amount the min_x and + min_y changed. */ + + CurrentRootPosition (role, &root_x, &root_y); + XMoveWindow (compositor.display, role->window, + root_x + min_x + role->min_x, + root_y + min_y + role->min_y); + + /* Set pending root window positions. These positions will be + used until the movement really happens, to avoid outdated + positions being used after the minimum positions change in + quick succession. */ + role->pending_root_x = root_x + min_x + role->min_x; + role->pending_root_y = root_y + min_y + role->min_y; + role->pending_synth_configure++; + } + + /* Finally, record the current bounds. */ + + role->min_x = min_x; + role->max_x = max_x; + role->min_y = min_y; + role->max_y = max_y; + + role->bounds_width = bounds_width; + role->bounds_height = bounds_height; + + /* Tell the role implementation about the change in window size. */ + + if (role->impl && role->impl->funcs.note_size) + role->impl->funcs.note_size (&role->role, role->impl, + max_x - min_x + 1, + max_y - min_y + 1); + + /* Run reconstrain callbacks if a resize happened. */ + if (run_reconstrain_callbacks) + RunReconstrainCallbacks (role); +} + +static void +ResizeForMap (XdgRole *role) +{ + int min_x, min_y, max_x, max_y; + + SubcompositorBounds (role->subcompositor, &min_x, + &min_y, &max_x, &max_y); + NoteBounds (role, min_x, min_y, max_x, max_y); +} + +static void +GetResizeDimensions (Surface *surface, Role *role, int *x_out, + int *y_out) +{ + XLXdgRoleGetCurrentGeometry (role, NULL, NULL, x_out, y_out); + + *x_out *= global_scale_factor; + *y_out *= global_scale_factor; +} + +static void +PostResize (Surface *surface, Role *role, int west_motion, + int north_motion, int new_width, int new_height) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + + if (!xdg_role->impl || !xdg_role->impl->funcs.post_resize) + return; + + xdg_role->impl->funcs.post_resize (role, xdg_role->impl, + west_motion, north_motion, + new_width, new_height); +} + +static void +MoveBy (Surface *surface, Role *role, int west, int north) +{ + XLXdgRoleMoveBy (role, west, north); +} + +static void +Rescale (Surface *surface, Role *role) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + + /* The window geometry actually applied to the X window (in the form + of frame extents, etc) heavily depends on the output scale. */ + + if (xdg_role->impl->funcs.handle_geometry_change) + xdg_role->impl->funcs.handle_geometry_change (role, xdg_role->impl); +} + +static void +NoteChildSynced (Surface *surface, Role *role) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + + if (xdg_role->n_desync_children) + xdg_role->n_desync_children--; + + if (!xdg_role->n_desync_children) + XLFrameClockDisablePredictRefresh (xdg_role->clock); +} + +static void +NoteDesyncChild (Surface *surface, Role *role) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + + xdg_role->n_desync_children++; + XLFrameClockSetPredictRefresh (xdg_role->clock); +} + +static void +WriteRedirectProperty (XdgRole *role) +{ + unsigned long bypass_compositor; + + bypass_compositor = 2; + XChangeProperty (compositor.display, role->window, + _NET_WM_BYPASS_COMPOSITOR, XA_CARDINAL, + 32, PropModeReplace, + (unsigned char *) &bypass_compositor, 1); +} + +void +XLGetXdgSurface (struct wl_client *client, struct wl_resource *resource, + uint32_t id, struct wl_resource *surface_resource) +{ + XdgRole *role; + XSetWindowAttributes attrs; + XRenderPictureAttributes picture_attrs; + unsigned int flags; + Surface *surface; + + surface = wl_resource_get_user_data (surface_resource); + + if (surface->role || (surface->role_type != AnythingType + && surface->role_type != XdgType)) + { + /* A role already exists on that surface. */ + wl_resource_post_error (resource, XDG_WM_BASE_ERROR_ROLE, + "surface already has attached role"); + return; + } + + /* TODO: post more errors if the surface is not in a valid + state. */ + + role = XLSafeMalloc (sizeof *role); + + if (!role) + { + wl_client_post_no_memory (client); + return; + } + + memset (role, 0, sizeof *role); + + role->release_records + = XLSafeMalloc (sizeof *role->release_records); + + if (!role->release_records) + { + XLFree (role); + wl_client_post_no_memory (client); + + return; + } + + role->role.resource = wl_resource_create (client, &xdg_surface_interface, + wl_resource_get_version (resource), + id); + + if (!role->role.resource) + { + XLFree (role->release_records); + XLFree (role); + wl_client_post_no_memory (client); + + return; + } + + wl_resource_set_implementation (role->role.resource, &xdg_surface_impl, + role, HandleResourceDestroy); + + /* Add a reference to this role struct since a wl_resource now + refers to it. */ + role->refcount++; + + role->role.funcs.commit = Commit; + role->role.funcs.teardown = Teardown; + role->role.funcs.setup = Setup; + role->role.funcs.release_buffer = ReleaseBuffer; + role->role.funcs.subframe = Subframe; + role->role.funcs.end_subframe = EndSubframe; + role->role.funcs.get_window = GetWindow; + role->role.funcs.get_resize_dimensions = GetResizeDimensions; + role->role.funcs.post_resize = PostResize; + role->role.funcs.move_by = MoveBy; + role->role.funcs.rescale = Rescale; + role->role.funcs.note_desync_child = NoteDesyncChild; + role->role.funcs.note_child_synced = NoteChildSynced; + + attrs.colormap = compositor.colormap; + attrs.border_pixel = border_pixel; + attrs.event_mask = (ExposureMask | StructureNotifyMask + | PropertyChangeMask); + attrs.cursor = InitDefaultCursor (); + flags = (CWColormap | CWBorderPixel | CWEventMask + | CWCursor); + + /* Sentinel node. */ + role->release_records->next = role->release_records; + role->release_records->last = role->release_records; + role->release_records->buffer = NULL; + + role->window = XCreateWindow (compositor.display, + DefaultRootWindow (compositor.display), + 0, 0, 20, 20, 0, compositor.n_planes, + InputOutput, compositor.visual, flags, + &attrs); + role->picture = XRenderCreatePicture (compositor.display, + role->window, + /* TODO: get this from the + visual instead. */ + compositor.argb_format, + 0, &picture_attrs); + role->subcompositor = MakeSubcompositor (); + role->clock = XLMakeFrameClockForWindow (role->window); + + SubcompositorSetTarget (role->subcompositor, role->picture); + SubcompositorSetInputCallback (role->subcompositor, + InputRegionChanged, role); + SubcompositorSetOpaqueCallback (role->subcompositor, + OpaqueRegionChanged, role); + SubcompositorSetBoundsCallback (role->subcompositor, + NoteBounds, role); + XLSelectStandardEvents (role->window); + XLMakeAssoc (surfaces, role->window, role); + + /* Tell the compositing manager to never un-redirect this window. + If it does, frame synchronization will not work. */ + WriteRedirectProperty (role); + + /* Initialize frame callbacks. */ + XLFrameClockAfterFrame (role->clock, AfterFrame, role); + + if (!XLSurfaceAttachRole (surface, &role->role)) + abort (); + + /* Initialize the input region. */ + pixman_region32_init (&role->input_region); + + /* Initialize the sentinel node of the reconstrain callbacks + list. */ + role->reconstrain_callbacks.next = &role->reconstrain_callbacks; + role->reconstrain_callbacks.last = &role->reconstrain_callbacks; +} + +Window +XLWindowFromXdgRole (Role *role) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + return xdg_role->window; +} + +Subcompositor * +XLSubcompositorFromXdgRole (Role *role) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + return xdg_role->subcompositor; +} + +void +XLXdgRoleAttachImplementation (Role *role, XdgRoleImplementation *impl) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + + XLAssert (!xdg_role->impl && role->surface); + impl->funcs.attach (role, impl); + + xdg_role->impl = impl; +} + +void +XLXdgRoleDetachImplementation (Role *role, XdgRoleImplementation *impl) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + + XLAssert (xdg_role->impl == impl); + impl->funcs.detach (role, impl); + + xdg_role->impl = NULL; +} + +void +XLXdgRoleSendConfigure (Role *role, uint32_t serial) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + xdg_role->conf_serial = serial; + xdg_role->state |= StateWaitingForAckConfigure; + + xdg_surface_send_configure (role->resource, serial); +} + +void +XLXdgRoleCalcNewWindowSize (Role *role, int width, int height, + int *new_width, int *new_height) +{ + XdgRole *xdg_role; + int temp, temp1, geometry_width, geometry_height; + int current_width, current_height, min_x, min_y, max_x, max_y; + + xdg_role = XdgRoleFromRole (role); + + if (!xdg_role->current_state.window_geometry_width) + { + *new_width = width; + *new_height = height; + + return; + } + + SubcompositorBounds (xdg_role->subcompositor, + &min_x, &min_y, &max_x, &max_y); + + /* Adjust the current_width and current_height by the global scale + factor. */ + current_width = (max_x - min_x + 1) / global_scale_factor; + current_height = (max_y - min_y + 1) / global_scale_factor; + + XLXdgRoleGetCurrentGeometry (role, NULL, NULL, &geometry_width, + &geometry_height); + + /* Now, temp and temp1 become the difference between the current + window geometry and the size of the surface (incl. subsurfaces) + in both axes. */ + + temp = current_width - geometry_width; + temp1 = current_height - geometry_height; + + *new_width = width - temp; + *new_height = height - temp1; + +#ifdef DEBUG_GEOMETRY_CALCULATION + fprintf (stderr, + "Configure event width, height: %d %d\n" + "Generated width, height: %d %d\n", + width, height, *new_width, *new_height); +#endif +} + +int +XLXdgRoleGetWidth (Role *role) +{ + XdgRole *xdg_role; + int x, y, x1, y1; + + xdg_role = XdgRoleFromRole (role); + + SubcompositorBounds (xdg_role->subcompositor, + &x, &y, &x1, &y1); + + return x1 - x + 1; +} + +int +XLXdgRoleGetHeight (Role *role) +{ + XdgRole *xdg_role; + int x, y, x1, y1; + + xdg_role = XdgRoleFromRole (role); + + SubcompositorBounds (xdg_role->subcompositor, + &x, &y, &x1, &y1); + + return y1 - y + 1; +} + +void +XLXdgRoleSetBoundsSize (Role *role, int bounds_width, int bounds_height) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + xdg_role->bounds_width = bounds_width; + xdg_role->bounds_height = bounds_height; +} + +void +XLXdgRoleGetCurrentGeometry (Role *role, int *x_return, int *y_return, + int *width, int *height) +{ + XdgRole *xdg_role; + int x, y, x1, y1, min_x, max_x, min_y, max_y; + + xdg_role = XdgRoleFromRole (role); + + SubcompositorBounds (xdg_role->subcompositor, + &min_x, &min_y, &max_x, &max_y); + + if (!xdg_role->current_state.window_geometry_width) + { + if (x_return) + *x_return = min_x; + if (y_return) + *y_return = min_y; + + if (width) + *width = max_x - min_x + 1; + if (height) + *height = max_y - min_y + 1; + + return; + } + + x = xdg_role->current_state.window_geometry_x; + y = xdg_role->current_state.window_geometry_y; + x1 = (xdg_role->current_state.window_geometry_x + + xdg_role->current_state.window_geometry_width - 1); + y1 = (xdg_role->current_state.window_geometry_y + + xdg_role->current_state.window_geometry_height - 1); + + x1 = MIN (x1, max_x); + y1 = MIN (y1, max_y); + x = MAX (min_x, x); + y = MAX (min_y, y); + + if (x_return) + *x_return = x; + if (y_return) + *y_return = y; + + if (width) + *width = x1 - x + 1; + if (height) + *height = y1 - y + 1; +} + +void +XLXdgRoleNoteConfigure (Role *role, XEvent *event) +{ + NoteConfigure (XdgRoleFromRole (role), event); +} + +void +XLRetainXdgRole (Role *role) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + xdg_role->refcount++; +} + +void +XLReleaseXdgRole (Role *role) +{ + ReleaseBacking (XdgRoleFromRole (role)); +} + +void +XLXdgRoleCurrentRootPosition (Role *role, int *root_x, int *root_y) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + CurrentRootPosition (xdg_role, root_x, root_y); +} + +XdgRoleImplementationType +XLTypeOfXdgRole (Role *role) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + return xdg_role->type; +} + +XdgRoleImplementation * +XLImplementationOfXdgRole (Role *role) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + return xdg_role->impl; +} + +Bool +XLXdgRoleInputRegionContains (Role *role, int x, int y) +{ + XdgRole *xdg_role; + pixman_box32_t dummy_box; + + xdg_role = XdgRoleFromRole (role); + return pixman_region32_contains_point (&xdg_role->input_region, + x, y, &dummy_box); +} + +void +XLXdgRoleResizeForMap (Role *role) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + ResizeForMap (xdg_role); +} + +void * +XLXdgRoleRunOnReconstrain (Role *role, void (*configure_func) (void *, + XEvent *), + void (*resize_func) (void *), void *data) +{ + ReconstrainCallback *callback; + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + + callback = AddCallbackAfter (&xdg_role->reconstrain_callbacks); + callback->configure = configure_func; + callback->resized = resize_func; + callback->data = data; + + return callback; +} + +void +XLXdgRoleCancelReconstrainCallback (void *key) +{ + ReconstrainCallback *callback; + + callback = key; + UnlinkReconstrainCallback (callback); +} + +void +XLXdgRoleReconstrain (Role *role, XEvent *event) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + RunReconstrainCallbacksForXEvent (xdg_role, event); +} + +void +XLXdgRoleMoveBy (Role *role, int west, int north) +{ + int root_x, root_y; + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + + /* Move the window by the opposite of west and north. */ + + CurrentRootPosition (xdg_role, &root_x, &root_y); + XMoveWindow (compositor.display, xdg_role->window, + root_x - west, root_y - north); + + /* Set pending root window positions. These positions will be + used until the movement really happens, to avoid outdated + positions being used after the minimum positions change in + quick succession. */ + xdg_role->pending_root_x = root_x - west; + xdg_role->pending_root_y = root_y - north; + xdg_role->pending_synth_configure++; +} + +FrameClock * +XLXdgRoleGetFrameClock (Role *role) +{ + XdgRole *xdg_role; + + xdg_role = XdgRoleFromRole (role); + return xdg_role->clock; +} + +void +XLInitXdgSurfaces (void) +{ + XColor alloc; + int shape_minor, shape_major, shape_error; + + surfaces = XLCreateAssocTable (2048); + + alloc.red = 0; + alloc.green = 65535; + alloc.blue = 0; + + if (!XAllocColor (compositor.display, compositor.colormap, + &alloc)) + { + fprintf (stderr, "Failed to allocate green pixel\n"); + exit (1); + } + + border_pixel = alloc.pixel; + + /* Now initialize the nonrectangular window shape extension. We + need a version that supports input shapes, which means 1.1 or + later. */ + + if (!XShapeQueryExtension (compositor.display, &shape_base, + &shape_error)) + { + fprintf (stderr, "The Nonrectangular Window Shape extension is not" + " present on the X server\n"); + exit (1); + } + + if (!XShapeQueryVersion (compositor.display, + &shape_major, &shape_minor)) + { + fprintf (stderr, "A supported version of the Nonrectangular Window" + " Shape extension is not present on the X server\n"); + exit (1); + } + + if (shape_major < 1 || (shape_major == 1 && shape_minor < 1)) + { + fprintf (stderr, "The version of the Nonrectangular Window Shape" + " extension is too old\n"); + exit (1); + } +} + +XdgRoleImplementation * +XLLookUpXdgToplevel (Window window) +{ + XdgRole *role; + + role = XLLookUpAssoc (surfaces, window); + + if (!role) + return NULL; + + if (role->type != TypeToplevel) + return NULL; + + return role->impl; +} + +XdgRoleImplementation * +XLLookUpXdgPopup (Window window) +{ + XdgRole *role; + + role = XLLookUpAssoc (surfaces, window); + + if (!role) + return NULL; + + if (role->type != TypePopup) + return NULL; + + return role->impl; +} diff --git a/xdg_toplevel.c b/xdg_toplevel.c new file mode 100644 index 0000000..d85f2e9 --- /dev/null +++ b/xdg_toplevel.c @@ -0,0 +1,1867 @@ +/* Wayland compositor running on top of an X server. + +Copyright (C) 2022 to various contributors. + +This file is part of 12to11. + +12to11 is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +12to11 is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with 12to11. If not, see . */ + +#include +#include +#include + +#include + +#include "compositor.h" +#include "xdg-shell.h" + +#define ToplevelFromRoleImpl(impl) ((XdgToplevel *) (impl)) + +typedef struct _XdgToplevel XdgToplevel; +typedef struct _ToplevelState ToplevelState; +typedef struct _PropMotifWmHints PropMotifWmHints; +typedef struct _XdgUnmapCallback XdgUnmapCallback; + +typedef enum _How How; + +enum + { + StateIsMapped = 1, + StateMissingState = (1 << 1), + StatePendingMaxSize = (1 << 2), + StatePendingMinSize = (1 << 3), + StatePendingAckMovement = (1 << 4), + }; + +enum + { + SupportsWindowMenu = 1, + SupportsMaximize = (1 << 2), + SupportsFullscreen = (1 << 3), + SupportsMinimize = (1 << 4), + }; + +enum + { + MwmHintsDecorations = (1L << 1), + MwmDecorAll = (1L << 0), + }; + +enum _How + { + Remove = 0, + Add = 1, + Toggle = 2, + }; + +struct _XdgUnmapCallback +{ + /* Function run when the toplevel is unmapped or detached. */ + void (*unmap) (void *); + + /* Data for that function. */ + void *data; + + /* Next and last callbacks in this list. */ + XdgUnmapCallback *next, *last; +}; + +struct _PropMotifWmHints +{ + unsigned long flags; + unsigned long functions; + unsigned long decorations; + long input_mode; + unsigned long status; +}; + +struct _ToplevelState +{ + /* The surface is maximized. The window geometry specified in the + configure event must be obeyed by the client. + + The client should draw without shadow or other decoration outside + of the window geometry. */ + Bool maximized : 1; + + /* The surface is fullscreen. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond + it. For a surface to cover the whole fullscreened area, the + geometry dimensions must be obeyed by the client. For more + details, see xdg_toplevel.set_fullscreen. */ + Bool fullscreen : 1; + + /* Client window decorations should be painted as if the window is + active. Do not assume this means that the window actually has + keyboard or pointer focus. */ + Bool activated : 1; +}; + +struct _XdgToplevel +{ + /* The parent role implementation. */ + XdgRoleImplementation impl; + + /* The role associated with this toplevel. */ + Role *role; + + /* The wl_resource associated with this toplevel. */ + struct wl_resource *resource; + + /* The number of references to this toplevel. */ + int refcount; + + /* Some state associated with this toplevel. */ + int state; + + /* The serial of the last configure sent. */ + uint32_t conf_serial; + + /* Whether or not we are waiting for a reply to a configure + event. */ + Bool conf_reply; + + /* Array of states. */ + struct wl_array states; + + /* The current width and height of this toplevel as received in the + last ConfigureNotify event. */ + int width, height; + + /* The Motif window manager hints associated with this toplevel. */ + PropMotifWmHints motif; + + /* The current window manager state. */ + ToplevelState toplevel_state; + + /* All resize callbacks currently posted. */ + XLList *resize_callbacks; + + /* Minimum size of this toplevel. */ + int min_width, min_height; + + /* Maximim size of this toplevel. */ + int max_width, max_height; + + /* Pending values. */ + int pending_max_width, pending_max_height; + int pending_min_height, pending_min_width; + + /* How much to move upon the next ack_configure. Used to resize a + window westwards or northwards. */ + int ack_west, ack_north; + + /* X Windows size hints. */ + XSizeHints size_hints; + + /* List of callbacks run upon unmapping. The callbacks are then + deleted. */ + XdgUnmapCallback unmap_callbacks; + + /* The parent toplevel. */ + XdgToplevel *transient_for; + + /* The unmap callback for the parent toplevel. */ + XdgUnmapCallback *parent_callback; + + /* Various geometries before a given state change. + + width00/height00 mean the size when the toplevel was neither + maximized nor fullscreen. + + width01/height01 mean the size when the toplevel was not + maximized but not fullscreen. + + width10/height10 mean the size when the toplevel was fullscreen + but not maximized. + + width11/height11 mean the size when the toplevel was + maximized both maximized and fullscreen. + + These values are used to guess how the state changed when + handling the ConfigureNotify event preceeding the PropertyNotify + event for _NET_WM_STATE. */ + int width01, height01, width10, height10; + int width00, height00, width11, height11; + + /* Mask of what this toplevel is allowed to do. It is first set + based on _NET_SUPPORTED upon toplevel creation, and then + _NET_WM_ALLOWED_ACTIONS. */ + int supported; +}; + +/* iconv context used to convert between UTF-8 and Latin-1. */ +static iconv_t latin_1_cd; + +/* Whether or not to work around state changes being desynchronized + with configure events. */ +static Bool apply_state_workaround; + +static XdgUnmapCallback * +RunOnUnmap (XdgToplevel *toplevel, void (*unmap) (void *), + void *data) +{ + XdgUnmapCallback *callback; + + XLAssert (toplevel->state & StateIsMapped + && toplevel->role); + + callback = XLMalloc (sizeof *callback); + callback->next = toplevel->unmap_callbacks.next; + callback->last = &toplevel->unmap_callbacks; + toplevel->unmap_callbacks.next->last = callback; + toplevel->unmap_callbacks.next = callback; + + callback->data = data; + callback->unmap = unmap; + + return callback; +} + +static void +CancelUnmapCallback (XdgUnmapCallback *callback) +{ + callback->next->last = callback->last; + callback->last->next = callback->next; + + XLFree (callback); +} + +static void +RunUnmapCallbacks (XdgToplevel *toplevel) +{ + XdgUnmapCallback *first, *last; + + first = toplevel->unmap_callbacks.next; + + while (first != &toplevel->unmap_callbacks) + { + last = first; + first = first->next; + + last->unmap (last->data); + XLFree (last); + } + + /* Re-initialize the sentinel node for the list of unmap + callbacks. */ + toplevel->unmap_callbacks.next = &toplevel->unmap_callbacks; + toplevel->unmap_callbacks.last = &toplevel->unmap_callbacks; +} + +static void +WriteHints (XdgToplevel *toplevel) +{ + XChangeProperty (compositor.display, + XLWindowFromXdgRole (toplevel->role), + _MOTIF_WM_HINTS, _MOTIF_WM_HINTS, 32, + PropModeReplace, + (unsigned char *) &toplevel->motif, 5); +} + +static void +SetDecorated (XdgToplevel *toplevel, Bool decorated) +{ + toplevel->motif.flags |= MwmHintsDecorations; + + if (decorated) + toplevel->motif.decorations = MwmDecorAll; + else + toplevel->motif.decorations = 0; + + if (toplevel->role) + WriteHints (toplevel); +} + +static void +DestroyBacking (XdgToplevel *toplevel) +{ + if (--toplevel->refcount) + return; + + if (toplevel->parent_callback) + CancelUnmapCallback (toplevel->parent_callback); + + XLListFree (toplevel->resize_callbacks, + XLSeatCancelResizeCallback); + + wl_array_release (&toplevel->states); + XLFree (toplevel); +} + +static void +AddState (XdgToplevel *toplevel, uint32_t state) +{ + uint32_t *data; + + data = wl_array_add (&toplevel->states, sizeof *data); + *data = state; +} + +static void +SendConfigure (XdgToplevel *toplevel, unsigned int width, + unsigned int height) +{ + uint32_t serial; + + serial = wl_display_next_serial (compositor.wl_display); + xdg_toplevel_send_configure (toplevel->resource, width, height, + &toplevel->states); + + XLXdgRoleSendConfigure (toplevel->role, serial); + + toplevel->conf_reply = True; + toplevel->conf_serial = serial; +} + +static void +WriteStates (XdgToplevel *toplevel) +{ + toplevel->states.size = 0; + + if (toplevel->toplevel_state.maximized) + AddState (toplevel, XDG_TOPLEVEL_STATE_MAXIMIZED); + + if (toplevel->toplevel_state.fullscreen) + AddState (toplevel, XDG_TOPLEVEL_STATE_FULLSCREEN); + + if (toplevel->toplevel_state.activated) + AddState (toplevel, XDG_TOPLEVEL_STATE_ACTIVATED); + + if (toplevel->resize_callbacks) + AddState (toplevel, XDG_TOPLEVEL_STATE_RESIZING); +} + +static void +SendStates (XdgToplevel *toplevel) +{ + int width, height; + + WriteStates (toplevel); + + /* Adjust the width and height we're sending by the window + geometry. */ + if (toplevel->state & StateMissingState) + XLXdgRoleGetCurrentGeometry (toplevel->role, NULL, NULL, + &width, &height); + else + XLXdgRoleCalcNewWindowSize (toplevel->role, + toplevel->width / global_scale_factor, + toplevel->height / global_scale_factor, + &width, &height); + + SendConfigure (toplevel, width, height); + + /* Mark the state has having been calculated if some state + transition has occured. */ + if (toplevel->toplevel_state.fullscreen + || toplevel->toplevel_state.maximized) + toplevel->state &= ~StateMissingState; +} + +static void +RecordStateSize (XdgToplevel *toplevel) +{ + Bool a, b; + int width, height; + + /* Record the last known size of a toplevel before its state is + changed. That way, we can send xdg_toplevel::configure with the + right state, should the window manager send ConfigureNotify + before changing the state. */ + + a = toplevel->toplevel_state.maximized; + b = toplevel->toplevel_state.fullscreen; + + if (XLWmSupportsHint (_GTK_FRAME_EXTENTS)) + { + /* Note that if _GTK_FRAME_EXTENTS is supported, the window + manager will elect to send us the old window geometry instead + upon minimization. */ + XLXdgRoleGetCurrentGeometry (toplevel->role, NULL, NULL, + &width, &height); + width *= global_scale_factor; + height *= global_scale_factor; + } + else + { + width = toplevel->width; + height = toplevel->height; + } + + if (!a && !b) /* 00 */ + { + toplevel->width00 = width; + toplevel->height00 = height; + } + + if (!a && b) /* 10 */ + { + toplevel->width10 = width; + toplevel->height10 = height; + } + + if (a && !b) /* 01 */ + { + toplevel->width01 = width; + toplevel->height01 = height; + } + + if (a && b) /* 11 */ + { + toplevel->width11 = width; + toplevel->height11 = height; + } +} + +static void +HandleWmStateChange (XdgToplevel *toplevel) +{ + unsigned long actual_size; + unsigned long bytes_remaining; + int rc, actual_format, i; + Atom actual_type, *states; + unsigned char *tmp_data; + Window window; + ToplevelState *state, old; + + tmp_data = NULL; + window = XLWindowFromXdgRole (toplevel->role); + state = &toplevel->toplevel_state; + + rc = XGetWindowProperty (compositor.display, window, + _NET_WM_STATE, 0, 65536, + False, XA_ATOM, &actual_type, + &actual_format, &actual_size, + &bytes_remaining, &tmp_data); + + if (rc != Success + || actual_type != XA_ATOM || actual_format != 32 + || bytes_remaining) + goto empty_states; + + XLAssert (tmp_data != NULL); + + states = (Atom *) tmp_data; + + /* First, reset relevant states. */ + + memcpy (&old, state, sizeof *state); + state->maximized = False; + state->fullscreen = False; + state->activated = False; + + /* Then loop through and enable any states that are set. */ + + for (i = 0; i < actual_size; ++i) + { + if (states[i] == _NET_WM_STATE_FULLSCREEN) + state->fullscreen = True; + + if (states[i] == _NET_WM_STATE_FOCUSED) + state->activated = True; + + if (states[i] == _NET_WM_STATE_MAXIMIZED_HORZ + || states[i] == _NET_WM_STATE_MAXIMIZED_VERT) + state->maximized = True; + } + + if (memcmp (&old, &state, sizeof *state)) + /* Finally, send states if they changed. */ + SendStates (toplevel); + + /* And free the atoms. */ + + if (tmp_data) + XFree (tmp_data); + + return; + + empty_states: + + /* Retrieving the EWMH state failed. Clear all states. */ + + state->maximized = False; + state->fullscreen = False; + state->activated = False; + + if (tmp_data) + XFree (tmp_data); + + SendStates (toplevel); +} + +static void +SendWmCapabilities (XdgToplevel *toplevel) +{ + struct wl_array array; + uint32_t *data; + + wl_array_init (&array); + + if (toplevel->supported & SupportsWindowMenu) + { + data = wl_array_add (&array, sizeof *data); + *data = XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU; + } + + if (toplevel->supported & SupportsMinimize) + { + data = wl_array_add (&array, sizeof *data); + *data = XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE; + } + + if (toplevel->supported & SupportsMaximize) + { + data = wl_array_add (&array, sizeof *data); + *data = XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE; + } + + if (toplevel->supported & SupportsFullscreen) + { + data = wl_array_add (&array, sizeof *data); + *data = XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN; + } + + xdg_toplevel_send_wm_capabilities (toplevel->resource, &array); + wl_array_release (&array); +} + +static void +HandleAllowedActionsChange (XdgToplevel *toplevel) +{ + unsigned long actual_size; + unsigned long bytes_remaining; + int rc, actual_format, i; + Atom actual_type, *states; + unsigned char *tmp_data; + Window window; + int old; + + tmp_data = NULL; + window = XLWindowFromXdgRole (toplevel->role); + + rc = XGetWindowProperty (compositor.display, window, + _NET_WM_ALLOWED_ACTIONS, 0, 65536, + False, XA_ATOM, &actual_type, + &actual_format, &actual_size, + &bytes_remaining, &tmp_data); + + if (rc != Success + || actual_type != XA_ATOM || actual_format != 32 + || bytes_remaining) + goto empty_states; + + XLAssert (tmp_data != NULL); + + states = (Atom *) tmp_data; + + /* First, reset the actions that we will change. */ + + old = toplevel->supported; + toplevel->supported &= ~SupportsMaximize; + toplevel->supported &= ~SupportsMinimize; + toplevel->supported &= ~SupportsFullscreen; + + /* Then loop through and enable any states that are set. */ + + for (i = 0; i < actual_size; ++i) + { + if (states[i] == _NET_WM_ACTION_FULLSCREEN) + toplevel->supported |= SupportsFullscreen; + + if (states[i] == _NET_WM_ACTION_MAXIMIZE_HORZ + || states[i] == _NET_WM_ACTION_MAXIMIZE_VERT) + toplevel->supported |= SupportsMaximize; + + if (states[i] == _NET_WM_ACTION_MINIMIZE) + toplevel->supported |= SupportsMinimize; + } + + if (toplevel->supported != old) + /* Finally, send states if they changed. */ + SendStates (toplevel); + + /* And free the atoms. */ + + if (tmp_data) + XFree (tmp_data); + + return; + + empty_states: + + /* Retrieving the action list failed. Ignore this PropertyNotify, + but free the data if it was set. */ + + if (tmp_data) + XFree (tmp_data); +} + +static void +ApplyGtkFrameExtents (XdgToplevel *toplevel, int x, int y, + int x2, int y2) +{ + long cardinals[4]; + Window window; + + cardinals[0] = x; + cardinals[1] = x2; + cardinals[2] = y; + cardinals[3] = y2; + + window = XLWindowFromXdgRole (toplevel->role); + + XChangeProperty (compositor.display, window, + _GTK_FRAME_EXTENTS, XA_CARDINAL, + 32, PropModeReplace, + (unsigned char *) cardinals, 4); +} + +static void +HandleWindowGeometryChange (XdgToplevel *toplevel) +{ + XSizeHints *hints; + int width, height, dx, dy, x, y; + Subcompositor *subcompositor; + View *view; + + if (!toplevel->role || !toplevel->role->surface) + return; + + view = toplevel->role->surface->view; + subcompositor = ViewGetSubcompositor (view); + + XLXdgRoleGetCurrentGeometry (toplevel->role, &x, &y, + &width, &height); + + width *= global_scale_factor; + height *= global_scale_factor; + x *= global_scale_factor; + y *= global_scale_factor; + + dx = SubcompositorWidth (subcompositor) - width; + dy = SubcompositorHeight (subcompositor) - height; + + ApplyGtkFrameExtents (toplevel, x, y, dx - x, dy - y); + + hints = &toplevel->size_hints; + + hints->flags |= PMinSize | PSize; + + /* Initially, specify PSize. After the first MapNotify, also + specify PPosition so that subsurfaces won't move the window. */ + + hints->min_width = toplevel->min_width * global_scale_factor + dx; + hints->min_height = toplevel->min_height * global_scale_factor + dy; + + if (toplevel->max_width) + { + hints->max_width = toplevel->max_width * global_scale_factor + dx; + hints->max_height = toplevel->max_height * global_scale_factor + dy; + hints->flags |= PMaxSize; + } + else + hints->flags &= ~PMaxSize; + + /* If a global scale factor is set, also set the resize increment to + the scale factor. */ + + if (global_scale_factor != 1) + { + hints->width_inc = global_scale_factor; + hints->height_inc = global_scale_factor; + hints->flags |= PResizeInc; + } + else + hints->flags &= ~PResizeInc; + + XSetWMNormalHints (compositor.display, + XLWindowFromXdgRole (toplevel->role), + hints); +} + +static void +Attach (Role *role, XdgRoleImplementation *impl) +{ + Atom protocols[2]; + XdgToplevel *toplevel; + int nproto; + XWMHints wmhints; + Window window; + + toplevel = ToplevelFromRoleImpl (impl); + toplevel->refcount++; + toplevel->role = role; + + nproto = 0; + window = XLWindowFromXdgRole (role); + + protocols[nproto++] = WM_DELETE_WINDOW; + + if (XLFrameClockSyncSupported ()) + protocols[nproto++] = _NET_WM_SYNC_REQUEST; + + XSetWMProtocols (compositor.display, + window, protocols, nproto); + + WriteHints (toplevel); + + /* This tells the window manager not to override size choices made + by the client. */ + toplevel->size_hints.flags |= PSize; + + /* Apply the surface's window geometry. */ + HandleWindowGeometryChange (toplevel); + + /* First, initialize toplevel->supported, should the resource be new + enough. */ + toplevel->supported = 0; + + if (wl_resource_get_version (toplevel->resource) >= 5) + { + /* Assume iconification is always supported, until we get + _NET_WM_ALLOWED_ACTIONS. */ + toplevel->supported |= SupportsMinimize; + + /* Then, populate toplevel->supported based on + _NET_SUPPORTED. */ + if (XLWmSupportsHint (_NET_WM_STATE_FULLSCREEN)) + toplevel->supported |= SupportsFullscreen; + + if (XLWmSupportsHint (_NET_WM_STATE_MAXIMIZED_HORZ) + || XLWmSupportsHint (_NET_WM_STATE_MAXIMIZED_VERT)) + toplevel->supported |= SupportsMaximize; + + if (XLWmSupportsHint (_GTK_SHOW_WINDOW_MENU)) + toplevel->supported |= SupportsWindowMenu; + + /* Finally, send the initial capabilities to the client. */ + SendWmCapabilities (toplevel); + } + + /* Set the input hint, without placing WM_TAKE_FOCUS in + WM_PROTOCOLS. This asks the window manager to manage our focus + state. */ + wmhints.flags = InputHint; + wmhints.input = True; + + XSetWMHints (compositor.display, window, &wmhints); + + /* Write the XdndAware property. */ + XLDndWriteAwarenessProperty (window); +} + +/* Forward declaration. */ + +static void Unmap (XdgToplevel *); + +static void +Detach (Role *role, XdgRoleImplementation *impl) +{ + XdgToplevel *toplevel; + + toplevel = ToplevelFromRoleImpl (impl); + + /* First, unmap the toplevel. */ + if (toplevel->state & StateIsMapped) + Unmap (toplevel); + + /* Next, undo everything that we changed on the window. */ + toplevel->role = NULL; + + XSetWMProtocols (compositor.display, + XLWindowFromXdgRole (role), + NULL, 0); + + DestroyBacking (toplevel); +} + +/* Forward declaration. */ + +static void UpdateParent (XdgToplevel *, XdgToplevel *); + +static void +Unmap (XdgToplevel *toplevel) +{ + Window window; + + toplevel->state &= ~StateIsMapped; + + window = XLWindowFromXdgRole (toplevel->role); + + XUnmapWindow (compositor.display, window); + + /* Unmapping an xdg_toplevel means that the surface cannot be shown + by the compositor until it is explicitly mapped again. All active + operations (e.g., move, resize) are canceled and all attributes + (e.g. title, state, stacking, ...) are discarded for an + xdg_toplevel surface when it is unmapped. The xdg_toplevel + returns to the state it had right after xdg_surface.get_toplevel. + The client can re-map the toplevel by perfoming a commit without + any buffer attached, waiting for a configure event and handling + it as usual (see xdg_surface description). */ + + /* Clear all the state. */ + + toplevel->state = 0; + toplevel->conf_reply = False; + toplevel->conf_serial = 0; + toplevel->states.size = 0; + toplevel->width = 0; + toplevel->height = 0; + toplevel->min_width = 0; + toplevel->min_height = 0; + + memset (&toplevel->state, 0, sizeof toplevel->states); + + XLListFree (toplevel->resize_callbacks, + XLSeatCancelResizeCallback); + toplevel->resize_callbacks = NULL; + + memset (&toplevel->size_hints, 0, sizeof toplevel->size_hints); + XSetWMNormalHints (compositor.display, window, + &toplevel->size_hints); + + /* Clear the parent. */ + UpdateParent (toplevel, NULL); + + /* Run unmap callbacks. */ + RunUnmapCallbacks (toplevel); +} + +static void +Map (XdgToplevel *toplevel) +{ + /* We can't guarantee that the toplevel contents will be preserved + at this point. */ + SubcompositorGarbage (XLSubcompositorFromXdgRole (toplevel->role)); + + toplevel->state |= StateIsMapped | StateMissingState; + + /* Update the width and height from the xdg_surface bounds. */ + toplevel->width = XLXdgRoleGetWidth (toplevel->role); + toplevel->height = XLXdgRoleGetHeight (toplevel->role); + + /* Resize the window to those bounds beforehand as well. */ + XLXdgRoleResizeForMap (toplevel->role); + + /* Now, map the window. */ + XMapWindow (compositor.display, + XLWindowFromXdgRole (toplevel->role)); +} + +static void +AckConfigure (Role *role, XdgRoleImplementation *impl, uint32_t serial) +{ + XdgToplevel *toplevel; + + toplevel = ToplevelFromRoleImpl (impl); + + if (serial == toplevel->conf_serial) + toplevel->conf_reply = False; +} + +static void +Commit (Role *role, Surface *surface, XdgRoleImplementation *impl) +{ + XdgToplevel *toplevel; + + toplevel = ToplevelFromRoleImpl (impl); + + /* Apply any pending min or max size. */ + + if (toplevel->state & StatePendingMinSize) + { + toplevel->min_width = toplevel->pending_min_width; + toplevel->min_height = toplevel->pending_min_height; + } + + if (toplevel->state & StatePendingMaxSize) + { + toplevel->max_width = toplevel->pending_max_width; + toplevel->max_height = toplevel->pending_max_height; + } + + if (toplevel->state & (StatePendingMaxSize | StatePendingMinSize)) + { + HandleWindowGeometryChange (toplevel); + + toplevel->state &= ~StatePendingMaxSize; + toplevel->state &= ~StatePendingMinSize; + } + + if (!surface->current_state.buffer) + { + /* No buffer was attached, unmap the window and send an empty + configure event. */ + if (toplevel->state & StateIsMapped) + Unmap (toplevel); + + SendConfigure (toplevel, 0, 0); + } + else if (!toplevel->conf_reply) + { + /* Configure reply received, so map the toplevel. */ + if (!(toplevel->state & StateIsMapped)) + Map (toplevel); + } +} + +static void +CommitInsideFrame (Role *role, XdgRoleImplementation *impl) +{ + XdgToplevel *toplevel; + + toplevel = ToplevelFromRoleImpl (impl); + + if (!toplevel->conf_reply + && toplevel->state & StatePendingAckMovement) + { + XLXdgRoleMoveBy (role, toplevel->ack_west, + toplevel->ack_north); + + toplevel->ack_west = 0; + toplevel->ack_north = 0; + toplevel->state &= ~StatePendingAckMovement; + } +} + +static Bool +RestoreStateTo (XdgToplevel *toplevel, int width, int height) +{ + if (width == toplevel->width11 && height == toplevel->height11) + return False; + + if (width == toplevel->width00 && height == toplevel->height00) + { + /* Neither fullscreen nor maximized. Clear both flags. */ + toplevel->toplevel_state.fullscreen = False; + toplevel->toplevel_state.maximized = False; + + return True; + } + + if (width == toplevel->width10 && height == toplevel->height10) + { + if (width == toplevel->width01 && height == toplevel->height01) + /* Ambiguous, punt. */ + return False; + + /* Fullscreen, not maximized. Clear any maximized flag that was + set. */ + toplevel->toplevel_state.maximized = False; + return True; + } + + if (width == toplevel->width01 && height == toplevel->height01) + { + if (width == toplevel->width01 && height == toplevel->height01) + /* Ambiguous, punt. */ + return False; + + /* Maximized, but not fullscreen. Clear any fullscreen flag + that was set. */ + toplevel->toplevel_state.fullscreen = False; + return True; + } + + return False; +} + +static Bool +HandleConfigureEvent (XdgToplevel *toplevel, XEvent *event) +{ + int width, height; + + if (event->xconfigure.send_event) + /* Handle only synthetic events, since that's what the + window manager sends upon movement. */ + XLXdgRoleNoteConfigure (toplevel->role, event); + else + XLXdgRoleReconstrain (toplevel->role, event); + + if (event->xconfigure.width == toplevel->width + && event->xconfigure.height == toplevel->height) + return True; + + /* Try to guess if the window state was restored to some earlier + value, and set it now, to avoid race conditions when some clients + continue trying to stay maximized or fullscreen. */ + + if (apply_state_workaround + && RestoreStateTo (toplevel, event->xconfigure.width, + event->xconfigure.height)) + WriteStates (toplevel); + + XLXdgRoleCalcNewWindowSize (toplevel->role, + ConfigureWidth (event), + ConfigureHeight (event), + &width, &height); + + SendConfigure (toplevel, width, height); + + /* Set toplevel->width and toplevel->height correctly. */ + toplevel->width = event->xconfigure.width; + toplevel->height = event->xconfigure.height; + + /* Also set the bounds width and height to avoid resizing + the window. */ + XLXdgRoleSetBoundsSize (toplevel->role, + toplevel->width, + toplevel->height); + + RecordStateSize (toplevel); + + return True; +} + +static Bool +WindowResizedPredicate (Display *display, XEvent *event, XPointer data) +{ + Role *role; + XdgToplevel *toplevel; + Window target_window; + + toplevel = (XdgToplevel *) data; + role = toplevel->role; + target_window = XLWindowFromXdgRole (role); + + if (event->type == ConfigureNotify + && event->xconfigure.window == target_window) + /* Extract the event from the event queue. */ + return True; + + return False; +} + +static int +IfEvent (XEvent *event_return, Bool (*predicate) (Display *, + XEvent *, + XPointer), + XPointer arg, struct timespec timeout) +{ + struct timespec current_time, target; + int fd; + fd_set fds; + + fd = ConnectionNumber (compositor.display); + current_time = CurrentTimespec (); + target = TimespecAdd (current_time, timeout); + + /* Check if an event is already in the queue. If it is, avoid + syncing. */ + if (XCheckIfEvent (compositor.display, event_return, + predicate, arg)) + return 0; + + while (true) + { + /* Get events into the queue. */ + XSync (compositor.display, False); + + /* Look for an event again. */ + if (XCheckIfEvent (compositor.display, event_return, + predicate, arg)) + return 0; + + /* Calculate the timeout. */ + current_time = CurrentTimespec (); + timeout = TimespecSub (target, current_time); + + /* If not, wait for some input to show up on the X connection, + or for the timeout to elapse. */ + FD_ZERO (&fds); + FD_SET (fd, &fds); + + /* If this fails due to an IO error, XSync will call the IO + error handler. */ + pselect (fd + 1, &fds, NULL, NULL, &timeout, NULL); + + /* Timeout elapsed. */ + current_time = CurrentTimespec (); + if (TimespecCmp (target, current_time) < 0) + return 1; + } +} + +static void +NoteSize (Role *role, XdgRoleImplementation *impl, + int width, int height) +{ + XdgToplevel *toplevel; + + toplevel = ToplevelFromRoleImpl (impl); + + toplevel->width = width; + toplevel->height = height; +} + +static void +NoteWindowPreResize (Role *role, XdgRoleImplementation *impl, + int width, int height) +{ + int gwidth, gheight, dx, dy, x, y; + XdgToplevel *toplevel; + + toplevel = ToplevelFromRoleImpl (impl); + + if (!toplevel->role || !toplevel->role->surface) + return; + + /* Set the GTK frame immediately before a resize. This prevents the + window manager from constraining us by the old values. */ + + XLXdgRoleGetCurrentGeometry (toplevel->role, &x, &y, + &gwidth, &gheight); + + dx = width - gwidth * global_scale_factor; + dy = height - gheight * global_scale_factor; + x *= global_scale_factor; + y *= global_scale_factor; + + ApplyGtkFrameExtents (toplevel, x, y, dx - x, dy - y); +} + +static void +NoteWindowResized (Role *role, XdgRoleImplementation *impl, + int width, int height) +{ + XEvent event; + int rc; + XdgToplevel *toplevel; + + toplevel = ToplevelFromRoleImpl (impl); + + /* The window resized. Don't allow ConfigureNotify events to pile + up and mess up our view of what the window dimensions are by + waiting for the next ConfigureNotify event. */ + + XFlush (compositor.display); + + rc = IfEvent (&event, WindowResizedPredicate, (XPointer) impl, + /* Wait at most 0.5 ms in case the window system doesn't + send a reply. */ + MakeTimespec (0, 500000000)); + + if (!rc) + { + /* Make these values right. It can happen that the window + manager doesn't respect the width and height (the main + culprit seems to be height) chosen by us. */ + toplevel->width = event.xconfigure.width; + toplevel->height = event.xconfigure.height; + + if (event.xconfigure.send_event) + XLXdgRoleNoteConfigure (toplevel->role, &event); + + RecordStateSize (toplevel); + } +} + +static void +PostResize (Role *role, XdgRoleImplementation *impl, int west_motion, + int north_motion, int new_width, int new_height) +{ + XdgToplevel *toplevel; + + toplevel = ToplevelFromRoleImpl (impl); + + if (new_width < toplevel->min_width) + { + new_width = toplevel->min_width; + + /* FIXME: this computation is not correct, just "good + enough". */ + west_motion = 0; + } + + if (new_height < toplevel->min_height) + { + new_height = toplevel->min_height; + north_motion = 0; + } + + SendConfigure (toplevel, new_width, new_height); + + toplevel->ack_west += west_motion; + toplevel->ack_north += north_motion; + toplevel->state |= StatePendingAckMovement; +} + +static void +HandleGeometryChange (Role *role, XdgRoleImplementation *impl) +{ + XdgToplevel *toplevel; + + toplevel = ToplevelFromRoleImpl (impl); + HandleWindowGeometryChange (toplevel); +} + +static void +HandleResourceDestroy (struct wl_resource *resource) +{ + XdgToplevel *toplevel; + + toplevel = wl_resource_get_user_data (resource); + toplevel->resource = NULL; + + DestroyBacking (toplevel); +} + +static void +Destroy (struct wl_client *client, + struct wl_resource *resource) +{ + XdgToplevel *toplevel; + + toplevel = wl_resource_get_user_data (resource); + + if (toplevel->role) + XLXdgRoleDetachImplementation (toplevel->role, + &toplevel->impl); + + wl_resource_destroy (resource); +} + +static void +HandleParentUnmapped (void *data) +{ + XdgToplevel *child, *new_parent; + + child = data; + new_parent = child->transient_for->transient_for; + + /* Clear child->transient_for etc so UpdateParent doesn't delete the + callback twice. */ + child->transient_for = NULL; + child->parent_callback = NULL; + + /* If parent is child itself, then it might not be mapped. */ + if (new_parent && !(new_parent->state & StateIsMapped)) + new_parent = NULL; + + /* Set the new parent of child. */ + UpdateParent (child, new_parent); +} + +static void +UpdateWmTransientForProperty (XdgToplevel *child) +{ + Window window, parent; + + window = XLWindowFromXdgRole (child->role); + + if (child->transient_for) + parent = XLWindowFromXdgRole (child->transient_for->role); + + if (!child->transient_for) + XDeleteProperty (compositor.display, window, + WM_TRANSIENT_FOR); + else + XChangeProperty (compositor.display, window, + WM_TRANSIENT_FOR, XA_WINDOW, + 32, PropModeReplace, + (unsigned char *) &parent, 1); +} + +static void +UpdateParent (XdgToplevel *child, XdgToplevel *parent) +{ + if (parent == child->transient_for) + return; + + if (child->transient_for) + { + CancelUnmapCallback (child->parent_callback); + + child->transient_for = NULL; + child->parent_callback = NULL; + } + + if (parent) + { + child->transient_for = parent; + child->parent_callback + = RunOnUnmap (parent, HandleParentUnmapped, child); + } + + UpdateWmTransientForProperty (child); +} + +static void +SetParent (struct wl_client *client, struct wl_resource *resource, + struct wl_resource *parent_resource) +{ + XdgToplevel *child, *parent; + + child = wl_resource_get_user_data (resource); + + if (!child->role) + return; + + if (parent_resource) + parent = wl_resource_get_user_data (parent_resource); + else + parent = NULL; + + if (parent && !(parent->state & StateIsMapped)) + UpdateParent (child, NULL); + else + UpdateParent (child, parent); +} + +static void +SetNetWmName (XdgToplevel *toplevel, const char *title) +{ + size_t length; + + length = strlen (title); + + /* length shouldn't be allowed to exceed the max-request-size of the + display. */ + if (length > SelectionQuantum ()) + length = SelectionQuantum (); + + /* Change the toplevel window's _NET_WM_NAME property. */ + XChangeProperty (compositor.display, + XLWindowFromXdgRole (toplevel->role), + _NET_WM_NAME, UTF8_STRING, 8, PropModeReplace, + (unsigned char *) title, length); +} + +static void +ConvertWmName (XdgToplevel *toplevel, const char *title) +{ + iconv_t cd; + char *outbuf, *inbuf; + char *outptr, *inptr; + size_t outbytesleft, inbytesleft; + + /* Try to convert TITLE from UTF-8 to Latin-1, which is what X + wants. */ + cd = latin_1_cd; + + if (cd == (iconv_t) -1) + /* The conversion could not take place for any number of + reasons. */ + return; + + /* Latin-1 is generally smaller than UTF-8. */ + outbytesleft = strlen (title); + inbytesleft = outbytesleft; + outbuf = XLMalloc (outbytesleft); + inbuf = (char *) title; + inptr = inbuf; + outptr = outbuf; + + /* latin_1_cd might already have been used. Reset the iconv + state. */ + iconv (cd, NULL, NULL, &outptr, &outbytesleft); + + /* Restore outptr and outbytesleft to their old values. */ + outptr = outbuf; + outbytesleft = inbytesleft; + + /* No error checking is necessary when performing conversions from + UTF-8 to Latin-1. */ + iconv (cd, &inptr, &inbytesleft, &outptr, &outbytesleft); + + /* Write the converted string. */ + XChangeProperty (compositor.display, + XLWindowFromXdgRole (toplevel->role), + WM_NAME, XA_STRING, 8, PropModeReplace, + (unsigned char *) outbuf, + /* Limit the size of the title to the amount of + data that can be transferred to the X + server. */ + MIN (SelectionQuantum (), outptr - outbuf)); + + /* Free the output buffer. */ + XLFree (outbuf); +} + +static void +SetTitle (struct wl_client *client, struct wl_resource *resource, + const char *title) +{ + XdgToplevel *toplevel; + + toplevel = wl_resource_get_user_data (resource); + + if (!toplevel->role) + return; + + SetNetWmName (toplevel, title); + + /* Also set WM_NAME, in addition for _NET_WM_NAME, for the benefit + of old pagers and window managers. */ + ConvertWmName (toplevel, title); +} + +static void +SetAppId (struct wl_client *client, struct wl_resource *resource, + const char *app_id) +{ + XClassHint class_hints; + XdgToplevel *toplevel; + + toplevel = wl_resource_get_user_data (resource); + + if (!toplevel->role) + return; + + class_hints.res_name = (char *) app_id; + class_hints.res_class = (char *) app_id; + + XSetClassHint (compositor.display, + XLWindowFromXdgRole (toplevel->role), + &class_hints); +} + +static void +ShowWindowMenu (struct wl_client *client, struct wl_resource *resource, + struct wl_resource *seat_resource, uint32_t serial, int32_t x, + int32_t y) +{ + int root_x, root_y; + Seat *seat; + XdgToplevel *toplevel; + + toplevel = wl_resource_get_user_data (resource); + + if (!toplevel->role) + return; + + seat = wl_resource_get_user_data (seat_resource); + + if (XLSeatIsInert (seat)) + return; + + XLXdgRoleCurrentRootPosition (toplevel->role, &root_x, &root_y); + + XLSeatShowWindowMenu (seat, toplevel->role->surface, + root_x + x, root_y + y); +} + +static void +Move (struct wl_client *client, struct wl_resource *resource, + struct wl_resource *seat_resource, uint32_t serial) +{ + XdgToplevel *toplevel; + Seat *seat; + + seat = wl_resource_get_user_data (seat_resource); + toplevel = wl_resource_get_user_data (resource); + + if (!toplevel->role || !toplevel->role->surface) + return; + + XLMoveToplevel (seat, toplevel->role->surface, serial); +} + +static void +HandleResizeDone (void *key, void *data) +{ + XdgToplevel *toplevel; + + toplevel = data; + toplevel->resize_callbacks + = XLListRemove (toplevel->resize_callbacks, key); + + if (!toplevel->resize_callbacks) + SendStates (toplevel); +} + +static void +Resize (struct wl_client *client, struct wl_resource *resource, + struct wl_resource *seat_resource, uint32_t serial, + uint32_t edges) +{ + XdgToplevel *toplevel; + Seat *seat; + Bool ok; + void *callback_key; + + if (edges > XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT) + { + wl_resource_post_error (resource, + XDG_TOPLEVEL_ERROR_INVALID_RESIZE_EDGE, + "not a resize edge"); + return; + } + + seat = wl_resource_get_user_data (seat_resource); + toplevel = wl_resource_get_user_data (resource); + + if (!toplevel->role || !toplevel->role->surface) + return; + + ok = XLResizeToplevel (seat, toplevel->role->surface, + serial, edges); + + if (!ok) + return; + + /* Now set up the special resizing state. */ + callback_key = XLSeatRunAfterResize (seat, HandleResizeDone, + toplevel); + toplevel->resize_callbacks + = XLListPrepend (toplevel->resize_callbacks, + callback_key); + + /* And send it to the client. */ + SendStates (toplevel); +} + +static void +SetMaxSize (struct wl_client *client, struct wl_resource *resource, + int32_t width, int32_t height) +{ + XdgToplevel *toplevel; + + toplevel = wl_resource_get_user_data (resource); + + if (width < 0 || height < 0) + { + wl_resource_post_error (resource, WL_SURFACE_ERROR_INVALID_SIZE, + "invalid max size %d %d", width, height); + return; + } + + toplevel->pending_max_width = width; + toplevel->pending_max_height = height; + + if (toplevel->max_height != height + || toplevel->max_width != width) + toplevel->state |= StatePendingMaxSize; +} + +static void +SetMinSize (struct wl_client *client, struct wl_resource *resource, + int32_t width, int32_t height) +{ + XdgToplevel *toplevel; + + toplevel = wl_resource_get_user_data (resource); + + if (width < 0 || height < 0) + { + wl_resource_post_error (resource, WL_SURFACE_ERROR_INVALID_SIZE, + "invalid min size %d %d", width, height); + return; + } + + toplevel->pending_min_width = width; + toplevel->pending_min_height = height; + + if (toplevel->min_width != width + || toplevel->min_height != height) + toplevel->state |= StatePendingMinSize; +} + +static void +SetWmState (XdgToplevel *toplevel, Atom what, Atom what1, How how) +{ + XEvent event; + + if (!toplevel->role) + return; + + memset (&event, 0, sizeof event); + + event.xclient.type = ClientMessage; + event.xclient.window = XLWindowFromXdgRole (toplevel->role); + event.xclient.message_type = _NET_WM_STATE; + event.xclient.format = 32; + + event.xclient.data.l[0] = how; + event.xclient.data.l[1] = what; + event.xclient.data.l[2] = what1; + event.xclient.data.l[3] = 1; + + XSendEvent (compositor.display, + DefaultRootWindow (compositor.display), + False, + SubstructureRedirectMask | SubstructureNotifyMask, + &event); +} + +static void +SetMaximized (struct wl_client *client, struct wl_resource *resource) +{ + XdgToplevel *toplevel; + + toplevel = wl_resource_get_user_data (resource); + SetWmState (toplevel, _NET_WM_STATE_MAXIMIZED_HORZ, + _NET_WM_STATE_MAXIMIZED_VERT, Add); +} + +static void +UnsetMaximized (struct wl_client *client, struct wl_resource *resource) +{ + XdgToplevel *toplevel; + + toplevel = wl_resource_get_user_data (resource); + SetWmState (toplevel, _NET_WM_STATE_MAXIMIZED_HORZ, + _NET_WM_STATE_MAXIMIZED_VERT, Remove); +} + +static void +SetFullscreen (struct wl_client *client, struct wl_resource *resource, + struct wl_resource *output_resource) +{ + XdgToplevel *toplevel; + + /* Maybe also move the toplevel to the output? */ + + toplevel = wl_resource_get_user_data (resource); + SetWmState (toplevel, _NET_WM_STATE_FULLSCREEN, None, Add); +} + +static void +UnsetFullscreen (struct wl_client *client, struct wl_resource *resource) +{ + XdgToplevel *toplevel; + + toplevel = wl_resource_get_user_data (resource); + SetWmState (toplevel, _NET_WM_STATE_FULLSCREEN, None, Remove); +} + +static void +SetMinimized (struct wl_client *client, struct wl_resource *resource) +{ + XdgToplevel *toplevel; + + toplevel = wl_resource_get_user_data (resource); + + /* N.B. that this is very easy for us, since Wayland "conveniently" + provides no way for the client to determine the iconification + state of toplevels, or to deiconify them. */ + + if (!toplevel->role) + return; + + XIconifyWindow (compositor.display, + XLWindowFromXdgRole (toplevel->role), + DefaultScreen (compositor.display)); +} + +static const struct xdg_toplevel_interface xdg_toplevel_impl = + { + .destroy = Destroy, + .set_parent = SetParent, + .set_title = SetTitle, + .set_app_id = SetAppId, + .show_window_menu = ShowWindowMenu, + .move = Move, + .resize = Resize, + .set_max_size = SetMaxSize, + .set_min_size = SetMinSize, + .set_maximized = SetMaximized, + .unset_maximized = UnsetMaximized, + .set_fullscreen = SetFullscreen, + .unset_fullscreen = UnsetFullscreen, + .set_minimized = SetMinimized, + }; + +void +XLGetXdgToplevel (struct wl_client *client, struct wl_resource *resource, + uint32_t id) +{ + XdgToplevel *toplevel; + Role *role; + + toplevel = XLSafeMalloc (sizeof *toplevel); + role = wl_resource_get_user_data (resource); + + if (!toplevel) + { + wl_client_post_no_memory (client); + return; + } + + memset (toplevel, 0, sizeof *toplevel); + + toplevel->resource + = wl_resource_create (client, &xdg_toplevel_interface, + wl_resource_get_version (resource), + id); + + if (!toplevel->resource) + { + XLFree (toplevel); + wl_client_post_no_memory (client); + + return; + } + + toplevel->impl.funcs.attach = Attach; + toplevel->impl.funcs.commit = Commit; + toplevel->impl.funcs.detach = Detach; + + toplevel->impl.funcs.ack_configure = AckConfigure; + toplevel->impl.funcs.note_size = NoteSize; + toplevel->impl.funcs.note_window_resized = NoteWindowResized; + toplevel->impl.funcs.note_window_pre_resize = NoteWindowPreResize; + toplevel->impl.funcs.handle_geometry_change = HandleGeometryChange; + toplevel->impl.funcs.post_resize = PostResize; + toplevel->impl.funcs.commit_inside_frame = CommitInsideFrame; + + /* Set up the sentinel node for the list of unmap callbacks. */ + toplevel->unmap_callbacks.next = &toplevel->unmap_callbacks; + toplevel->unmap_callbacks.last = &toplevel->unmap_callbacks; + + wl_array_init (&toplevel->states); + + wl_resource_set_implementation (toplevel->resource, &xdg_toplevel_impl, + toplevel, HandleResourceDestroy); + toplevel->refcount++; + + /* Wayland surfaces are by default undecorated. Removing + decorations will (or rather ought to) also cause the window + manager to empty the frame window's input region, which allows + the surface-specified input region to work correctly. */ + SetDecorated (toplevel, False); + + XLXdgRoleAttachImplementation (role, &toplevel->impl); +} + +Bool +XLHandleXEventForXdgToplevels (XEvent *event) +{ + XdgToplevel *toplevel; + XdgRoleImplementation *impl; + + if (event->type == ClientMessage) + { + impl = XLLookUpXdgToplevel (event->xclient.window); + + if (!impl) + return False; + + toplevel = ToplevelFromRoleImpl (impl); + + if (event->xclient.message_type == WM_PROTOCOLS) + { + if (event->xclient.data.l[0] == WM_DELETE_WINDOW + && toplevel->resource) + { + xdg_toplevel_send_close (toplevel->resource); + + return True; + } + + return False; + } + + return (toplevel->role->surface + ? XLDndFilterClientMessage (toplevel->role->surface, + event) + : False); + } + + if (event->type == MapNotify) + { + /* Always pass through MapNotify events. */ + + impl = XLLookUpXdgToplevel (event->xclient.window); + + if (!impl) + return False; + + toplevel = ToplevelFromRoleImpl (impl); + + if (toplevel) + { + toplevel->size_hints.flags |= PPosition; + + XSetWMNormalHints (compositor.display, + event->xmap.window, + &toplevel->size_hints); + } + + return False; + } + + if (event->type == ConfigureNotify) + { + impl = XLLookUpXdgToplevel (event->xclient.window); + + if (!impl) + return False; + + toplevel = ToplevelFromRoleImpl (impl); + + if (toplevel && toplevel->role + && toplevel->role->surface + && toplevel->state & StateIsMapped) + return HandleConfigureEvent (toplevel, event); + + return False; + } + + if (event->type == PropertyNotify) + { + if (event->xproperty.atom == _NET_WM_STATE) + { + impl = XLLookUpXdgToplevel (event->xclient.window); + + if (!impl) + return False; + + toplevel = ToplevelFromRoleImpl (impl); + + if (toplevel && toplevel->role + && toplevel->role->surface) + HandleWmStateChange (toplevel); + + return True; + } + + if (event->xproperty.atom == _NET_WM_ALLOWED_ACTIONS) + { + impl = XLLookUpXdgToplevel (event->xclient.window); + + if (!impl) + return False; + + toplevel = ToplevelFromRoleImpl (impl); + + if (toplevel && toplevel->role + && toplevel->role->surface + && (wl_resource_get_version (toplevel->resource) >= 5)) + HandleAllowedActionsChange (toplevel); + + return True; + } + + return False; + } + + return False; +} + +void +XLInitXdgToplevels (void) +{ + latin_1_cd = iconv_open ("ISO-8859-1", "UTF-8"); + apply_state_workaround = (getenv ("APPLY_STATE_WORKAROUND") != NULL); +} + +Bool +XLIsXdgToplevel (Window window) +{ + return XLLookUpXdgToplevel (window) != NULL; +} diff --git a/xdg_wm.c b/xdg_wm.c new file mode 100644 index 0000000..6e3cb0e --- /dev/null +++ b/xdg_wm.c @@ -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 . */ + +#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); +} diff --git a/xerror.c b/xerror.c new file mode 100644 index 0000000..596d8e9 --- /dev/null +++ b/xerror.c @@ -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 . */ + +#include +#include + +#include "compositor.h" + +#include + +/* 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); +} diff --git a/xsettings.c b/xsettings.c new file mode 100644 index 0000000..7864993 --- /dev/null +++ b/xsettings.c @@ -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 . */ + +#include + +#include +#include + +#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); +}