diff --git a/src/meson.build b/src/meson.build
index 8f8a6571d..47ee8f570 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -625,6 +625,8 @@ if have_wayland
'wayland/meta-wayland-inhibit-shortcuts-dialog.c',
'wayland/meta-wayland-inhibit-shortcuts-dialog.h',
'wayland/meta-wayland-inhibit-shortcuts.h',
+ 'wayland/meta-wayland-input.c',
+ 'wayland/meta-wayland-input.h',
'wayland/meta-wayland-input-device.c',
'wayland/meta-wayland-input-device.h',
'wayland/meta-wayland-keyboard.c',
diff --git a/src/wayland/meta-wayland-input.c b/src/wayland/meta-wayland-input.c
new file mode 100644
index 000000000..8115c489d
--- /dev/null
+++ b/src/wayland/meta-wayland-input.c
@@ -0,0 +1,326 @@
+/*
+ * Interface for Wayland events
+ *
+ * Copyright (C) 2023 Red Hat Inc.
+ *
+ * This program 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, see .
+ *
+ * Author: Carlos Garnacho
+ */
+
+#include "config.h"
+
+#include
+
+#include "wayland/meta-wayland-input.h"
+
+#include "wayland/meta-wayland-seat.h"
+#include "wayland/meta-wayland-tablet-seat.h"
+
+struct _MetaWaylandEventHandler
+{
+ const MetaWaylandEventInterface *iface;
+ MetaWaylandInput *input;
+ gpointer user_data;
+ struct wl_list link;
+};
+
+struct _MetaWaylandInput
+{
+ GObject parent_instance;
+
+ MetaWaylandSeat *seat;
+ struct wl_list event_handler_list;
+};
+
+G_DEFINE_FINAL_TYPE (MetaWaylandInput, meta_wayland_input, G_TYPE_OBJECT)
+
+static void
+meta_wayland_input_init (MetaWaylandInput *input)
+{
+ wl_list_init (&input->event_handler_list);
+}
+
+static void
+meta_wayland_input_finalize (GObject *object)
+{
+ MetaWaylandInput *input = META_WAYLAND_INPUT (object);
+ MetaWaylandEventHandler *handler, *next;
+
+ wl_list_for_each_safe (handler, next, &input->event_handler_list, link)
+ meta_wayland_input_detach_event_handler (input, handler);
+
+ G_OBJECT_CLASS (meta_wayland_input_parent_class)->finalize (object);
+}
+
+static void
+meta_wayland_input_class_init (MetaWaylandInputClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = meta_wayland_input_finalize;
+}
+
+MetaWaylandInput *
+meta_wayland_input_new (MetaWaylandSeat *seat)
+{
+ MetaWaylandInput *input;
+
+ input = g_object_new (META_TYPE_WAYLAND_INPUT, NULL);
+ input->seat = seat;
+
+ return input;
+}
+
+static void
+meta_wayland_event_handler_invalidate_focus (MetaWaylandEventHandler *handler,
+ ClutterInputDevice *device,
+ ClutterEventSequence *sequence)
+{
+ MetaWaylandSurface *surface = NULL;
+
+ if (!handler->iface->focus)
+ return;
+
+ /* Only the first handler can focus other than a NULL surface */
+ if (meta_wayland_input_is_current_handler (handler->input, handler) &&
+ handler->iface->get_focus_surface)
+ {
+ surface = handler->iface->get_focus_surface (handler,
+ device, sequence,
+ handler->user_data);
+ }
+
+ handler->iface->focus (handler,
+ device, sequence, surface,
+ handler->user_data);
+}
+
+static void
+meta_wayland_event_handler_invalidate_all_focus (MetaWaylandEventHandler *handler)
+{
+ MetaWaylandSeat *seat = handler->input->seat;
+ ClutterSeat *clutter_seat =
+ clutter_backend_get_default_seat (clutter_get_default_backend ());
+ ClutterInputDevice *device;
+ GHashTableIter iter;
+
+ /* Trigger sync of all known devices */
+ if (meta_wayland_seat_has_pointer (seat))
+ {
+ device = clutter_seat_get_pointer (clutter_seat);
+ meta_wayland_event_handler_invalidate_focus (handler, device, NULL);
+ }
+
+ if (meta_wayland_seat_has_keyboard (seat))
+ {
+ device = clutter_seat_get_keyboard (clutter_seat);
+ meta_wayland_event_handler_invalidate_focus (handler, device, NULL);
+ }
+
+ if (meta_wayland_seat_has_touch (seat))
+ meta_wayland_touch_cancel (seat->touch);
+
+ g_hash_table_iter_init (&iter, seat->tablet_seat->tablets);
+ while (g_hash_table_iter_next (&iter, (gpointer*) &device, NULL))
+ meta_wayland_event_handler_invalidate_focus (handler, device, NULL);
+
+ g_hash_table_iter_init (&iter, seat->tablet_seat->pads);
+ while (g_hash_table_iter_next (&iter, (gpointer*) &device, NULL))
+ meta_wayland_event_handler_invalidate_focus (handler, device, NULL);
+}
+
+static gboolean
+meta_wayland_event_handler_handle_event (MetaWaylandEventHandler *handler,
+ const ClutterEvent *event)
+{
+ ClutterEventType event_type = clutter_event_type (event);
+
+ switch (event_type)
+ {
+ case CLUTTER_ENTER:
+ case CLUTTER_LEAVE:
+ {
+ ClutterInputDevice *device;
+ ClutterEventSequence *sequence;
+
+ device = clutter_event_get_device (event);
+ sequence = clutter_event_get_event_sequence (event);
+ meta_wayland_event_handler_invalidate_focus (handler,
+ device, sequence);
+ }
+
+ return CLUTTER_EVENT_PROPAGATE;
+ case CLUTTER_TOUCH_BEGIN:
+ case CLUTTER_BUTTON_PRESS:
+ if (!handler->iface->press)
+ return CLUTTER_EVENT_PROPAGATE;
+ return handler->iface->press (handler, event, handler->user_data);
+
+ case CLUTTER_TOUCH_UPDATE:
+ case CLUTTER_MOTION:
+ if (!handler->iface->motion)
+ return CLUTTER_EVENT_PROPAGATE;
+ return handler->iface->motion (handler, event, handler->user_data);
+
+ case CLUTTER_TOUCH_END:
+ case CLUTTER_BUTTON_RELEASE:
+ if (!handler->iface->release)
+ return CLUTTER_EVENT_PROPAGATE;
+ return handler->iface->release (handler, event, handler->user_data);
+
+ case CLUTTER_KEY_PRESS:
+ case CLUTTER_KEY_RELEASE:
+ if (!handler->iface->key)
+ return CLUTTER_EVENT_PROPAGATE;
+ return handler->iface->key (handler, event, handler->user_data);
+
+ case CLUTTER_TOUCH_CANCEL:
+ case CLUTTER_SCROLL:
+ case CLUTTER_PROXIMITY_IN:
+ case CLUTTER_PROXIMITY_OUT:
+ case CLUTTER_TOUCHPAD_PINCH:
+ case CLUTTER_TOUCHPAD_SWIPE:
+ case CLUTTER_TOUCHPAD_HOLD:
+ case CLUTTER_PAD_BUTTON_PRESS:
+ case CLUTTER_PAD_BUTTON_RELEASE:
+ case CLUTTER_PAD_STRIP:
+ case CLUTTER_PAD_RING:
+ case CLUTTER_IM_COMMIT:
+ case CLUTTER_IM_DELETE:
+ case CLUTTER_IM_PREEDIT:
+ case CLUTTER_DEVICE_ADDED:
+ case CLUTTER_DEVICE_REMOVED:
+ case CLUTTER_NOTHING:
+ case CLUTTER_EVENT_LAST:
+ if (!handler->iface->other)
+ return CLUTTER_EVENT_PROPAGATE;
+ return handler->iface->other (handler, event, handler->user_data);
+ }
+
+ g_assert_not_reached ();
+}
+
+MetaWaylandEventHandler *
+meta_wayland_input_attach_event_handler (MetaWaylandInput *input,
+ const MetaWaylandEventInterface *iface,
+ gpointer user_data)
+{
+ MetaWaylandEventHandler *handler;
+
+ handler = g_new0 (MetaWaylandEventHandler, 1);
+ handler->iface = iface;
+ handler->input = input;
+ handler->user_data = user_data;
+ wl_list_init (&handler->link);
+ wl_list_insert (&input->event_handler_list, &handler->link);
+
+ meta_wayland_event_handler_invalidate_all_focus (handler);
+
+ return handler;
+}
+
+void
+meta_wayland_input_detach_event_handler (MetaWaylandInput *input,
+ MetaWaylandEventHandler *handler)
+{
+ gboolean handler_change = FALSE;
+
+ handler_change = meta_wayland_input_is_current_handler (input, handler);
+ wl_list_remove (&handler->link);
+
+ if (handler_change && !wl_list_empty (&input->event_handler_list))
+ {
+ MetaWaylandEventHandler *head =
+ wl_container_of (input->event_handler_list.next,
+ head, link);
+
+ meta_wayland_event_handler_invalidate_all_focus (head);
+ }
+
+ g_free (handler);
+}
+
+gboolean
+meta_wayland_input_is_current_handler (MetaWaylandInput *input,
+ MetaWaylandEventHandler *handler)
+{
+ return input->event_handler_list.next == &handler->link;
+}
+
+gboolean
+meta_wayland_input_handle_event (MetaWaylandInput *input,
+ const ClutterEvent *event)
+{
+ MetaWaylandEventHandler *handler, *next;
+ gboolean retval = CLUTTER_EVENT_PROPAGATE;
+ ClutterEventType event_type = clutter_event_type (event);
+
+ wl_list_for_each_safe (handler, next, &input->event_handler_list, link)
+ {
+ retval = meta_wayland_event_handler_handle_event (handler, event);
+ if (retval == CLUTTER_EVENT_STOP)
+ break;
+ /* Event handlers propagate focus themselves */
+ if (event_type == CLUTTER_ENTER ||
+ event_type == CLUTTER_LEAVE)
+ break;
+ }
+
+ return retval;
+}
+
+void
+meta_wayland_input_invalidate_focus (MetaWaylandInput *input,
+ ClutterInputDevice *device,
+ ClutterEventSequence *sequence)
+{
+ if (!wl_list_empty (&input->event_handler_list))
+ {
+ MetaWaylandEventHandler *head =
+ wl_container_of (input->event_handler_list.next,
+ head, link);
+
+ meta_wayland_event_handler_invalidate_focus (head, device, sequence);
+ }
+}
+
+MetaWaylandSurface *
+meta_wayland_event_handler_chain_up_get_focus_surface (MetaWaylandEventHandler *handler,
+ ClutterInputDevice *device,
+ ClutterEventSequence *sequence)
+{
+ MetaWaylandEventHandler *next;
+
+ g_assert (!wl_list_empty (&handler->link));
+
+ next = wl_container_of (handler->link.next, next, link);
+
+ return next->iface->get_focus_surface (next, device, sequence, next->user_data);
+}
+
+void
+meta_wayland_event_handler_chain_up_focus (MetaWaylandEventHandler *handler,
+ ClutterInputDevice *device,
+ ClutterEventSequence *sequence,
+ MetaWaylandSurface *surface)
+{
+ MetaWaylandEventHandler *next;
+
+ g_assert (!wl_list_empty (&handler->link));
+
+ next = wl_container_of (handler->link.next, next, link);
+
+ return next->iface->focus (next, device, sequence, surface, next->user_data);
+}
diff --git a/src/wayland/meta-wayland-input.h b/src/wayland/meta-wayland-input.h
new file mode 100644
index 000000000..5eff00435
--- /dev/null
+++ b/src/wayland/meta-wayland-input.h
@@ -0,0 +1,102 @@
+/*
+ * Interface for Wayland events
+ *
+ * Copyright (C) 2023 Red Hat Inc.
+ *
+ * This program 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 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, see .
+ *
+ * Author: Carlos Garnacho
+ */
+
+#pragma once
+
+#include
+
+#include "clutter/clutter.h"
+#include "wayland/meta-wayland-types.h"
+
+#define META_TYPE_WAYLAND_INPUT (meta_wayland_input_get_type ())
+G_DECLARE_FINAL_TYPE (MetaWaylandInput,
+ meta_wayland_input,
+ META, WAYLAND_INPUT,
+ GObject);
+
+typedef struct _MetaWaylandEventHandler MetaWaylandEventHandler;
+
+typedef struct _MetaWaylandEventInterface MetaWaylandEventInterface;
+
+struct _MetaWaylandEventInterface
+{
+ MetaWaylandSurface * (*get_focus_surface) (MetaWaylandEventHandler *handler,
+ ClutterInputDevice *device,
+ ClutterEventSequence *sequence,
+ gpointer user_data);
+
+ /* Pointer/stylus/touch */
+ void (*focus) (MetaWaylandEventHandler *handler,
+ ClutterInputDevice *device,
+ ClutterEventSequence *sequence,
+ MetaWaylandSurface *surface,
+ gpointer user_data);
+
+ gboolean (*motion) (MetaWaylandEventHandler *handler,
+ const ClutterEvent *event,
+ gpointer user_data);
+
+ gboolean (*press) (MetaWaylandEventHandler *handler,
+ const ClutterEvent *event,
+ gpointer user_data);
+
+ gboolean (*release) (MetaWaylandEventHandler *handler,
+ const ClutterEvent *event,
+ gpointer user_data);
+
+ /* Key */
+ gboolean (*key) (MetaWaylandEventHandler *handler,
+ const ClutterEvent *event,
+ gpointer user_data);
+
+ /* Other (Pads/IM/...) */
+ gboolean (*other) (MetaWaylandEventHandler *handler,
+ const ClutterEvent *event,
+ gpointer user_data);
+};
+
+MetaWaylandInput * meta_wayland_input_new (MetaWaylandSeat *seat);
+
+MetaWaylandEventHandler * meta_wayland_input_attach_event_handler (MetaWaylandInput *input,
+ const MetaWaylandEventInterface *iface,
+ gpointer user_data);
+
+gboolean meta_wayland_input_is_current_handler (MetaWaylandInput *input,
+ MetaWaylandEventHandler *handler);
+
+void meta_wayland_input_detach_event_handler (MetaWaylandInput *input,
+ MetaWaylandEventHandler *handler);
+
+gboolean meta_wayland_input_handle_event (MetaWaylandInput *input,
+ const ClutterEvent *event);
+
+void meta_wayland_input_invalidate_focus (MetaWaylandInput *input,
+ ClutterInputDevice *device,
+ ClutterEventSequence *sequence);
+
+MetaWaylandSurface * meta_wayland_event_handler_chain_up_get_focus_surface (MetaWaylandEventHandler *handler,
+ ClutterInputDevice *device,
+ ClutterEventSequence *sequence);
+
+void meta_wayland_event_handler_chain_up_focus (MetaWaylandEventHandler *handler,
+ ClutterInputDevice *device,
+ ClutterEventSequence *sequence,
+ MetaWaylandSurface *surface);