From 528f7ba8588481457990bb67fe0f05270bffd4c8 Mon Sep 17 00:00:00 2001 From: oldosfan Date: Mon, 12 Sep 2022 13:24:50 +0000 Subject: [PATCH] Import files --- 12to11.c | 168 ++ 12to11.man | 15 + Imakefile | 94 + README | 72 + alloc.c | 106 + atoms.c | 287 ++ buffer.c | 127 + compositor.c | 83 + compositor.h | 1005 +++++++ data_device.c | 1587 +++++++++++ dmabuf.c | 1393 +++++++++ dnd.c | 3111 +++++++++++++++++++++ ewmh.c | 124 + fns.c | 475 ++++ frame_clock.c | 772 +++++ icon_surface.c | 518 ++++ libraries.def | 17 + linux-dmabuf-unstable-v1.xml | 586 ++++ media_types.txt | 2974 ++++++++++++++++++++ mime0.awk | 32 + mime1.awk | 34 + mime2.awk | 31 + mime3.awk | 34 + mime4.awk | 33 + output.c | 1107 ++++++++ positioner.c | 809 ++++++ region.c | 105 + run.c | 293 ++ seat.c | 5122 ++++++++++++++++++++++++++++++++++ select.c | 1844 ++++++++++++ shm.c | 584 ++++ subcompositor.c | 2433 ++++++++++++++++ subsurface.c | 982 +++++++ surface.c | 1313 +++++++++ svn-commit.tmp | 4 + timer.c | 268 ++ xdata.c | 1782 ++++++++++++ xdg-shell.xml | 1313 +++++++++ xdg_popup.c | 866 ++++++ xdg_surface.c | 1769 ++++++++++++ xdg_toplevel.c | 1867 +++++++++++++ xdg_wm.c | 97 + xerror.c | 119 + xsettings.c | 432 +++ 44 files changed, 36787 insertions(+) create mode 100644 12to11.c create mode 100644 12to11.man create mode 100644 Imakefile create mode 100644 README create mode 100644 alloc.c create mode 100644 atoms.c create mode 100644 buffer.c create mode 100644 compositor.c create mode 100644 compositor.h create mode 100644 data_device.c create mode 100644 dmabuf.c create mode 100644 dnd.c create mode 100644 ewmh.c create mode 100644 fns.c create mode 100644 frame_clock.c create mode 100644 icon_surface.c create mode 100644 libraries.def create mode 100644 linux-dmabuf-unstable-v1.xml create mode 100644 media_types.txt create mode 100644 mime0.awk create mode 100644 mime1.awk create mode 100644 mime2.awk create mode 100644 mime3.awk create mode 100644 mime4.awk create mode 100644 output.c create mode 100644 positioner.c create mode 100644 region.c create mode 100644 run.c create mode 100644 seat.c create mode 100644 select.c create mode 100644 shm.c create mode 100644 subcompositor.c create mode 100644 subsurface.c create mode 100644 surface.c create mode 100644 svn-commit.tmp create mode 100644 timer.c create mode 100644 xdata.c create mode 100644 xdg-shell.xml create mode 100644 xdg_popup.c create mode 100644 xdg_surface.c create mode 100644 xdg_toplevel.c create mode 100644 xdg_wm.c create mode 100644 xerror.c create mode 100644 xsettings.c 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); +}