diff --git a/src/meson.build b/src/meson.build
index e40d74c37..da2a9f96d 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -546,6 +546,8 @@ if have_wayland
'wayland/meta-wayland-pointer-gestures.h',
'wayland/meta-wayland-pointer-gesture-swipe.c',
'wayland/meta-wayland-pointer-gesture-swipe.h',
+ 'wayland/meta-wayland-pointer-warp.c',
+ 'wayland/meta-wayland-pointer-warp.h',
'wayland/meta-wayland-pointer.h',
'wayland/meta-wayland-popup.c',
'wayland/meta-wayland-popup.h',
@@ -813,6 +815,7 @@ if have_wayland
['linux-dmabuf', 'unstable', 'v1', ],
['pointer-constraints', 'unstable', 'v1', ],
['pointer-gestures', 'unstable', 'v1', ],
+ ['pointer-warp-v1', 'private',],
['primary-selection', 'unstable', 'v1', ],
['relative-pointer', 'unstable', 'v1', ],
['tablet', 'unstable', 'v2', ],
diff --git a/src/wayland/meta-wayland-pointer-warp.c b/src/wayland/meta-wayland-pointer-warp.c
new file mode 100644
index 000000000..fca7c3a9f
--- /dev/null
+++ b/src/wayland/meta-wayland-pointer-warp.c
@@ -0,0 +1,135 @@
+/*
+ * Wayland Support
+ *
+ * Copyright (C) 2025 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * Author: Carlos Garnacho
+ */
+
+#include "config.h"
+
+#include "meta-wayland-pointer-warp.h"
+
+#include "meta-wayland-private.h"
+#include "meta-wayland-seat.h"
+#include "meta-wayland-surface.h"
+
+#include "pointer-warp-v1-server-protocol.h"
+
+struct _MetaWaylandPointerWarp
+{
+ MetaWaylandSeat *seat;
+ struct wl_list resource_list;
+};
+
+static void
+pointer_warp_destroy (struct wl_client *client,
+ struct wl_resource *resource)
+{
+ wl_resource_destroy (resource);
+}
+
+static void
+pointer_warp_perform (struct wl_client *client,
+ struct wl_resource *resource,
+ struct wl_resource *surface_resource,
+ struct wl_resource *pointer_resource,
+ wl_fixed_t x,
+ wl_fixed_t y,
+ uint32_t serial)
+{
+ ClutterSeat *seat;
+ MetaWaylandSurface *surface = wl_resource_get_user_data (surface_resource);
+ MetaWaylandPointer *pointer = wl_resource_get_user_data (pointer_resource);
+ MetaSurfaceActor *surface_actor = meta_wayland_surface_get_actor (surface);
+ graphene_point3d_t coords =
+ GRAPHENE_POINT3D_INIT ((float) wl_fixed_to_double (x),
+ (float) wl_fixed_to_double (y),
+ 0.0);
+
+ /* Not focused and implicitly grabbed */
+ if (!meta_wayland_pointer_get_grab_info (pointer, surface, serial, TRUE,
+ NULL, NULL, NULL))
+ return;
+
+ /* Outside of actor */
+ if (!surface_actor ||
+ x < 0 || x > clutter_actor_get_width (CLUTTER_ACTOR (surface_actor)) ||
+ y < 0 || y > clutter_actor_get_height (CLUTTER_ACTOR (surface_actor)))
+ return;
+
+ clutter_actor_apply_transform_to_point (CLUTTER_ACTOR (surface_actor),
+ &coords, &coords);
+
+ seat = clutter_backend_get_default_seat (clutter_get_default_backend ());
+
+ clutter_seat_warp_pointer (seat, (int) coords.x, (int) coords.y);
+}
+
+static struct wp_pointer_warp_v1_interface pointer_warp_interface = {
+ pointer_warp_destroy,
+ pointer_warp_perform,
+};
+
+static void
+unbind_resource (struct wl_resource *resource)
+{
+ wl_list_remove (wl_resource_get_link (resource));
+}
+
+static void
+bind_pointer_warp (struct wl_client *client,
+ void *data,
+ uint32_t version,
+ uint32_t id)
+{
+ MetaWaylandPointerWarp *pointer_warp = data;
+ struct wl_resource *resource;
+
+ resource = wl_resource_create (client, &wp_pointer_warp_v1_interface,
+ MIN (version, META_WP_POINTER_WARP_VERSION),
+ id);
+ wl_resource_set_implementation (resource, &pointer_warp_interface,
+ pointer_warp, unbind_resource);
+ wl_resource_set_user_data (resource, pointer_warp);
+ wl_list_insert (&pointer_warp->resource_list,
+ wl_resource_get_link (resource));
+}
+
+MetaWaylandPointerWarp *
+meta_wayland_pointer_warp_new (MetaWaylandSeat *seat)
+{
+ MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default ();
+ MetaWaylandPointerWarp *pointer_warp;
+
+ pointer_warp = g_new0 (MetaWaylandPointerWarp, 1);
+ pointer_warp->seat = seat;
+ wl_list_init (&pointer_warp->resource_list);
+
+ wl_global_create (compositor->wayland_display,
+ &wp_pointer_warp_v1_interface,
+ META_WP_POINTER_WARP_VERSION,
+ pointer_warp, bind_pointer_warp);
+
+ return pointer_warp;
+}
+
+void
+meta_wayland_pointer_warp_destroy (MetaWaylandPointerWarp *pointer_warp)
+{
+ wl_list_remove (&pointer_warp->resource_list);
+ g_free (pointer_warp);
+}
diff --git a/src/wayland/meta-wayland-pointer-warp.h b/src/wayland/meta-wayland-pointer-warp.h
new file mode 100644
index 000000000..658638b34
--- /dev/null
+++ b/src/wayland/meta-wayland-pointer-warp.h
@@ -0,0 +1,33 @@
+/*
+ * Wayland Support
+ *
+ * Copyright (C) 2025 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ * Author: Carlos Garnacho
+ */
+
+#pragma once
+
+#include
+#include
+
+#include "wayland/meta-wayland-types.h"
+
+typedef struct _MetaWaylandPointerWarp MetaWaylandPointerWarp;
+
+MetaWaylandPointerWarp * meta_wayland_pointer_warp_new (MetaWaylandSeat *seat);
+
+void meta_wayland_pointer_warp_destroy (MetaWaylandPointerWarp *pointer_warp);
diff --git a/src/wayland/meta-wayland-pointer.c b/src/wayland/meta-wayland-pointer.c
index 9aef940cb..40b78992a 100644
--- a/src/wayland/meta-wayland-pointer.c
+++ b/src/wayland/meta-wayland-pointer.c
@@ -1289,6 +1289,32 @@ meta_wayland_pointer_can_grab_surface (MetaWaylandPointer *pointer,
pointer_can_grab_surface (pointer, surface));
}
+gboolean
+meta_wayland_pointer_get_grab_info (MetaWaylandPointer *pointer,
+ MetaWaylandSurface *surface,
+ uint32_t serial,
+ gboolean require_pressed,
+ ClutterInputDevice **device_out,
+ float *x,
+ float *y)
+{
+ if ((!require_pressed || pointer->button_count > 0) &&
+ meta_wayland_pointer_can_grab_surface (pointer, surface, serial))
+ {
+ if (device_out)
+ *device_out = pointer->device;
+
+ if (x)
+ *x = pointer->grab_x;
+ if (y)
+ *y = pointer->grab_y;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
gboolean
meta_wayland_pointer_can_popup (MetaWaylandPointer *pointer, uint32_t serial)
{
diff --git a/src/wayland/meta-wayland-pointer.h b/src/wayland/meta-wayland-pointer.h
index 1b0cdecd1..7375aad0a 100644
--- a/src/wayland/meta-wayland-pointer.h
+++ b/src/wayland/meta-wayland-pointer.h
@@ -143,6 +143,14 @@ gboolean meta_wayland_pointer_can_grab_surface (MetaWaylandPointer *pointer,
MetaWaylandSurface *surface,
uint32_t serial);
+gboolean meta_wayland_pointer_get_grab_info (MetaWaylandPointer *pointer,
+ MetaWaylandSurface *surface,
+ uint32_t serial,
+ gboolean require_pressed,
+ ClutterInputDevice **device_out,
+ float *x,
+ float *y);
+
gboolean meta_wayland_pointer_can_popup (MetaWaylandPointer *pointer,
uint32_t serial);
diff --git a/src/wayland/meta-wayland-seat.c b/src/wayland/meta-wayland-seat.c
index 782c798d0..2d05f0478 100644
--- a/src/wayland/meta-wayland-seat.c
+++ b/src/wayland/meta-wayland-seat.c
@@ -236,6 +236,7 @@ meta_wayland_seat_new (MetaWaylandCompositor *compositor,
NULL);
seat->text_input = meta_wayland_text_input_new (seat);
+ seat->pointer_warp = meta_wayland_pointer_warp_new (seat);
meta_wayland_data_device_init (&seat->data_device);
meta_wayland_data_device_primary_init (&seat->primary_data_device);
@@ -274,6 +275,7 @@ meta_wayland_seat_free (MetaWaylandSeat *seat)
g_object_unref (seat->keyboard);
g_object_unref (seat->touch);
meta_wayland_text_input_destroy (seat->text_input);
+ meta_wayland_pointer_warp_destroy (seat->pointer_warp);
g_free (seat);
}
diff --git a/src/wayland/meta-wayland-seat.h b/src/wayland/meta-wayland-seat.h
index 2e20a201c..2c545f100 100644
--- a/src/wayland/meta-wayland-seat.h
+++ b/src/wayland/meta-wayland-seat.h
@@ -30,6 +30,7 @@
#include "wayland/meta-wayland-input-device.h"
#include "wayland/meta-wayland-keyboard.h"
#include "wayland/meta-wayland-pointer.h"
+#include "wayland/meta-wayland-pointer-warp.h"
#include "wayland/meta-wayland-tablet-tool.h"
#include "wayland/meta-wayland-text-input.h"
#include "wayland/meta-wayland-touch.h"
@@ -48,6 +49,7 @@ struct _MetaWaylandSeat
MetaWaylandDataDevicePrimary primary_data_device;
MetaWaylandTextInput *text_input;
+ MetaWaylandPointerWarp *pointer_warp;
guint capabilities;
};
diff --git a/src/wayland/meta-wayland-versions.h b/src/wayland/meta-wayland-versions.h
index 666501229..bb0d9ca6e 100644
--- a/src/wayland/meta-wayland-versions.h
+++ b/src/wayland/meta-wayland-versions.h
@@ -60,5 +60,6 @@
#define META_ZWP_PRIMARY_SELECTION_V1_VERSION 1
#define META_XDG_TOPLEVEL_TAG_V1_VERSION 1
#define META_WP_CURSOR_SHAPE_VERSION 2
+#define META_WP_POINTER_WARP_VERSION 1
#endif
diff --git a/src/wayland/protocol/pointer-warp-v1.xml b/src/wayland/protocol/pointer-warp-v1.xml
new file mode 100644
index 000000000..158dad83c
--- /dev/null
+++ b/src/wayland/protocol/pointer-warp-v1.xml
@@ -0,0 +1,72 @@
+
+
+
+ Copyright © 2024 Neal Gompa
+ Copyright © 2024 Xaver Hugl
+ Copyright © 2024 Matthias Klumpp
+ Copyright © 2024 Vlad Zahorodnii
+
+ 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 global interface allows applications to request the pointer to be
+ moved to a position relative to a wl_surface.
+
+ Note that if the desired behavior is to constrain the pointer to an area
+ or lock it to a position, this protocol does not provide a reliable way
+ to do that. The pointer constraint and pointer lock protocols should be
+ used for those use cases instead.
+
+ Warning! The protocol described in this file is currently in the testing
+ phase. Backward compatible changes may be added together with the
+ corresponding interface version bump. Backward incompatible changes can
+ only be done by creating a new major version of the extension.
+
+
+
+
+ Destroy the pointer warp manager.
+
+
+
+
+
+ Request the compositor to move the pointer to a surface-local position.
+ Whether or not the compositor honors the request is implementation defined,
+ but it should
+ - honor it if the surface has pointer focus, including
+ when it has an implicit pointer grab
+ - reject it if the enter serial is incorrect
+ - reject it if the requested position is outside of the surface
+
+ Note that the enter serial is valid for any surface of the client,
+ and does not have to be from the surface the pointer is warped to.
+
+
+
+
+
+
+
+
+
+