diff --git a/relative-pointer-unstable-v1.xml b/relative-pointer-unstable-v1.xml
new file mode 100644
index 0000000..ca6f81d
--- /dev/null
+++ b/relative-pointer-unstable-v1.xml
@@ -0,0 +1,136 @@
+
+
+
+
+ Copyright © 2014 Jonas Ådahl
+ Copyright © 2015 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.
+
+
+
+ This protocol specifies a set of interfaces used for making clients able to
+ receive relative pointer events not obstructed by barriers (such as the
+ monitor edge or other pointer barriers).
+
+ To start receiving relative pointer events, a client must first bind the
+ global interface "wp_relative_pointer_manager" which, if a compositor
+ supports relative pointer motion events, is exposed by the registry. After
+ having created the relative pointer manager proxy object, the client uses
+ it to create the actual relative pointer object using the
+ "get_relative_pointer" request given a wl_pointer. The relative pointer
+ motion events will then, when applicable, be transmitted via the proxy of
+ the newly created relative pointer object. See the documentation of the
+ relative pointer interface for more details.
+
+ 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.
+
+
+
+
+ A global interface used for getting the relative pointer object for a
+ given pointer.
+
+
+
+
+ Used by the client to notify the server that it will no longer use this
+ relative pointer manager object.
+
+
+
+
+
+ Create a relative pointer interface given a wl_pointer object. See the
+ wp_relative_pointer interface for more details.
+
+
+
+
+
+
+
+
+ A wp_relative_pointer object is an extension to the wl_pointer interface
+ used for emitting relative pointer events. It shares the same focus as
+ wl_pointer objects of the same seat and will only emit events when it has
+ focus.
+
+
+
+
+
+
+
+
+ Relative x/y pointer motion from the pointer of the seat associated with
+ this object.
+
+ A relative motion is in the same dimension as regular wl_pointer motion
+ events, except they do not represent an absolute position. For example,
+ moving a pointer from (x, y) to (x', y') would have the equivalent
+ relative motion (x' - x, y' - y). If a pointer motion caused the
+ absolute pointer position to be clipped by for example the edge of the
+ monitor, the relative motion is unaffected by the clipping and will
+ represent the unclipped motion.
+
+ This event also contains non-accelerated motion deltas. The
+ non-accelerated delta is, when applicable, the regular pointer motion
+ delta as it was before having applied motion acceleration and other
+ transformations such as normalization.
+
+ Note that the non-accelerated delta does not represent 'raw' events as
+ they were read from some device. Pointer motion acceleration is device-
+ and configuration-specific and non-accelerated deltas and accelerated
+ deltas may have the same value on some devices.
+
+ Relative motions are not coupled to wl_pointer.motion events, and can be
+ sent in combination with such events, but also independently. There may
+ also be scenarios where wl_pointer.motion is sent, but there is no
+ relative motion. The order of an absolute and relative motion event
+ originating from the same physical motion is not guaranteed.
+
+ If the client needs button events or focus state, it can receive them
+ from a wl_pointer object of the same seat that the wp_relative_pointer
+ object is associated with.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/relative_pointer.c b/relative_pointer.c
new file mode 100644
index 0000000..d775fae
--- /dev/null
+++ b/relative_pointer.c
@@ -0,0 +1,133 @@
+/* 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 "relative-pointer-unstable-v1.h"
+
+/* The zwp_relative_pointer_manager_v1 global. */
+static struct wl_global *relative_pointer_manager_global;
+
+static void
+DestroyRelativePointer (struct wl_client *client,
+ struct wl_resource *resource)
+{
+ wl_resource_destroy (resource);
+}
+
+static struct zwp_relative_pointer_v1_interface relative_pointer_impl =
+ {
+ .destroy = DestroyRelativePointer,
+ };
+
+static void
+HandleRelativePointerResourceDestroy (struct wl_resource *resource)
+{
+ RelativePointer *relative_pointer;
+
+ relative_pointer = wl_resource_get_user_data (resource);
+ XLSeatDestroyRelativePointer (relative_pointer);
+}
+
+
+
+static void
+Destroy (struct wl_client *client, struct wl_resource *resource)
+{
+ wl_resource_destroy (resource);
+}
+
+static void
+GetRelativePointer (struct wl_client *client, struct wl_resource *resource,
+ uint32_t id, struct wl_resource *pointer_resource)
+{
+ struct wl_resource *relative_pointer_resource;
+ Pointer *pointer;
+ RelativePointer *relative_pointer;
+ Seat *seat;
+
+ pointer = wl_resource_get_user_data (pointer_resource);
+ seat = XLPointerGetSeat (pointer);
+ relative_pointer_resource
+ = wl_resource_create (client, &zwp_relative_pointer_v1_interface,
+ wl_resource_get_version (pointer_resource),
+ id);
+
+ if (!relative_pointer_resource)
+ {
+ wl_resource_post_no_memory (pointer_resource);
+ return;
+ }
+
+ relative_pointer = XLSeatGetRelativePointer (seat,
+ relative_pointer_resource);
+ wl_resource_set_implementation (relative_pointer_resource,
+ &relative_pointer_impl, relative_pointer,
+ HandleRelativePointerResourceDestroy);
+}
+
+static const struct zwp_relative_pointer_manager_v1_interface manager_impl =
+ {
+ .destroy = Destroy,
+ .get_relative_pointer = GetRelativePointer,
+ };
+
+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_relative_pointer_manager_v1_interface,
+ version, id);
+
+ if (!resource)
+ {
+ wl_client_post_no_memory (client);
+ return;
+ }
+
+ wl_resource_set_implementation (resource, &manager_impl, NULL, NULL);
+}
+
+void
+XLInitRelativePointer (void)
+{
+ relative_pointer_manager_global
+ = wl_global_create (compositor.wl_display,
+ &zwp_relative_pointer_manager_v1_interface,
+ 1, NULL, HandleBind);
+}
+
+void
+XLRelativePointerSendRelativeMotion (struct wl_resource *resource,
+ uint64_t microsecond_time, double dx,
+ double dy)
+{
+ uint32_t time_hi, time_lo;
+
+ time_hi = microsecond_time >> 32;
+ time_lo = microsecond_time >> 32;
+
+ zwp_relative_pointer_v1_send_relative_motion (resource, time_hi, time_lo,
+ wl_fixed_from_double (dx),
+ wl_fixed_from_double (dy),
+ wl_fixed_from_double (dx),
+ wl_fixed_from_double (dy));
+}