/* * 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" #include "wayland/meta-wayland.h" struct _MetaWaylandEventHandler { const MetaWaylandEventInterface *iface; MetaWaylandInput *input; gpointer user_data; gboolean grabbing; struct wl_list link; }; struct _MetaWaylandInput { GObject parent_instance; MetaWaylandSeat *seat; struct wl_list event_handler_list; ClutterStage *stage; ClutterGrab *grab; }; static void meta_wayland_input_sync_focus (MetaWaylandInput *input); G_DEFINE_FINAL_TYPE (MetaWaylandInput, meta_wayland_input, G_TYPE_OBJECT) static void on_stage_is_grabbed_change (MetaWaylandInput *input) { meta_wayland_input_sync_focus (input); } 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; g_signal_handlers_disconnect_by_func (input->stage, on_stage_is_grabbed_change, input); 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; MetaWaylandCompositor *compositor = seat->compositor; MetaContext *context = meta_wayland_compositor_get_context (compositor); MetaBackend *backend = meta_context_get_backend (context); input = g_object_new (META_TYPE_WAYLAND_INPUT, NULL); input->seat = seat; input->stage = CLUTTER_STAGE (meta_backend_get_stage (backend)); g_signal_connect_swapped (input->stage, "notify::is-grabbed", G_CALLBACK (on_stage_is_grabbed_change), input); return input; } static void meta_wayland_event_handler_invalidate_focus (MetaWaylandEventHandler *handler, ClutterInputDevice *device, ClutterEventSequence *sequence) { MetaWaylandInput *input = handler->input; MetaWaylandSurface *surface = NULL; if (!handler->iface->focus) return; if (handler->iface->get_focus_surface && /* Only the first handler can focus other than a NULL surface */ meta_wayland_input_is_current_handler (input, handler) && /* Stage should either be ungrabbed, or grabbed to self */ (!clutter_stage_get_grab_actor (input->stage) || (input->grab && !clutter_grab_is_revoked (input->grab)))) { 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_input_invalidate_all_focus (MetaWaylandInput *input) { MetaWaylandEventHandler *handler; MetaWaylandSeat *seat = 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); handler = wl_container_of (input->event_handler_list.next, handler, link); meta_wayland_event_handler_invalidate_focus (handler, device, NULL); } if (meta_wayland_seat_has_keyboard (seat)) { device = clutter_seat_get_keyboard (clutter_seat); handler = wl_container_of (input->event_handler_list.next, handler, link); 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)) { handler = wl_container_of (input->event_handler_list.next, handler, link); 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)) { handler = wl_container_of (input->event_handler_list.next, handler, link); 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 (); } static void meta_wayland_input_sync_focus (MetaWaylandInput *input) { MetaWaylandEventHandler *handler; g_assert (!wl_list_empty (&input->event_handler_list)); handler = wl_container_of (input->event_handler_list.next, handler, link); meta_wayland_input_invalidate_all_focus (input); } static void on_grab_revocation_change (MetaWaylandInput *input) { meta_wayland_input_sync_focus (input); } static gboolean grab_handle_event (const ClutterEvent *event, gpointer user_data) { MetaWaylandInput *input = user_data; return meta_wayland_input_handle_event (input, event); } MetaWaylandEventHandler * meta_wayland_input_attach_event_handler (MetaWaylandInput *input, const MetaWaylandEventInterface *iface, gboolean grab, gpointer user_data) { MetaWaylandEventHandler *handler; handler = g_new0 (MetaWaylandEventHandler, 1); handler->iface = iface; handler->input = input; handler->grabbing = grab; handler->user_data = user_data; wl_list_init (&handler->link); wl_list_insert (&input->event_handler_list, &handler->link); if (grab && !input->grab) { MetaWaylandCompositor *compositor = input->seat->compositor; MetaContext *context = meta_wayland_compositor_get_context (compositor); MetaBackend *backend = meta_context_get_backend (context); ClutterStage *stage = CLUTTER_STAGE (meta_backend_get_stage (backend)); input->grab = clutter_stage_grab_input_only_inactive (stage, grab_handle_event, input, NULL); clutter_grab_activate (input->grab); g_signal_connect_swapped (input->grab, "notify::revoked", G_CALLBACK (on_grab_revocation_change), input); } meta_wayland_input_invalidate_all_focus (input); return handler; } static gboolean should_be_grabbed (MetaWaylandInput *input) { MetaWaylandEventHandler *handler; gboolean grabbing = FALSE; wl_list_for_each (handler, &input->event_handler_list, link) grabbing |= handler->grabbing; return grabbing; } 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)) meta_wayland_input_invalidate_all_focus (input); if (input->grab && !should_be_grabbed (input)) { g_signal_handlers_disconnect_by_func (input->grab, on_grab_revocation_change, input); clutter_grab_dismiss (input->grab); g_clear_object (&input->grab); } 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); }