From 2fb3bdf774d40c2b6566c3c623bdfe1a25f3bea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Wed, 27 Apr 2022 10:50:43 +0200 Subject: [PATCH] input-capture: Hook up capturing of events to active session This adds the actual input capturing rerouting that takes events and first hands them to the input capture session, would it be active. Events are right now not actually processed in any way, but will eventually be passed to a libei client using libeis. A key binding for allowing cancelling the capture session is added (defaults to Escape) to avoid getting stuck in case the client doesn't even terminate the session. The added test case makes sure that the pointer moves again after pressing the keybinding. Part-of: --- .../org.gnome.Mutter.InputCapture.xml | 6 + data/org.gnome.mutter.gschema.xml.in | 5 + meson.build | 3 + src/backends/meta-input-capture-private.h | 32 + src/backends/meta-input-capture-session.c | 641 +++++++++++++++++- src/backends/meta-input-capture-session.h | 6 + src/backends/meta-input-capture.c | 73 +- src/backends/meta-input-capture.h | 17 + src/core/display-private.h | 5 + src/core/display.c | 68 ++ src/core/events.c | 7 + src/core/keybindings-private.h | 4 + src/core/keybindings.c | 79 ++- src/meson.build | 1 + src/meta/prefs.h | 2 + src/tests/input-capture-test-client.c | 366 +++++++++- src/tests/input-capture-tests.c | 146 ++++ src/tests/meson.build | 3 + 18 files changed, 1445 insertions(+), 19 deletions(-) create mode 100644 src/backends/meta-input-capture-private.h diff --git a/data/dbus-interfaces/org.gnome.Mutter.InputCapture.xml b/data/dbus-interfaces/org.gnome.Mutter.InputCapture.xml index 858c03c39..fcf25bdce 100644 --- a/data/dbus-interfaces/org.gnome.Mutter.InputCapture.xml +++ b/data/dbus-interfaces/org.gnome.Mutter.InputCapture.xml @@ -49,6 +49,12 @@ + + + + + + diff --git a/data/org.gnome.mutter.gschema.xml.in b/data/org.gnome.mutter.gschema.xml.in index b87980939..5d56ae2d3 100644 --- a/data/org.gnome.mutter.gschema.xml.in +++ b/data/org.gnome.mutter.gschema.xml.in @@ -179,5 +179,10 @@ Rotates the built-in monitor configuration + + Escape']]]> + Cancel any active input capture session + + diff --git a/meson.build b/meson.build index a92ef166b..a5af1e4c5 100644 --- a/meson.build +++ b/meson.build @@ -39,6 +39,7 @@ libcanberra_req = '>= 0.26' libwacom_req = '>= 0.13' atk_req = '>= 2.5.3' harfbuzz_req = '>= 2.6' +libei_req = '>= 1.0.0' # optional version requirements udev_req = '>= 228' @@ -128,6 +129,8 @@ dbus_dep = dependency('dbus-1') colord_dep = dependency('colord', version: colord_req) lcms2_dep = dependency('lcms2', version: lcms2_req) harfbuzz_dep = dependency('harfbuzz', version: harfbuzz_req) +libeis_dep = dependency('libeis-1.0', version: libei_req) +libei_dep = dependency('libei-1.0', version: libei_req) have_wayland = get_option('wayland') # For now always require X11 support diff --git a/src/backends/meta-input-capture-private.h b/src/backends/meta-input-capture-private.h new file mode 100644 index 000000000..93a9800b6 --- /dev/null +++ b/src/backends/meta-input-capture-private.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef META_INPUT_CAPTURE_PRIVATE_H +#define META_INPUT_CAPTURE_PRIVATE_H + +#include "backends/meta-input-capture.h" + +void meta_input_capture_activate (MetaInputCapture *input_capture, + MetaInputCaptureSession *session); + +void meta_input_capture_deactivate (MetaInputCapture *input_capture, + MetaInputCaptureSession *session); + +#endif /* META_INPUT_CAPTURE_PRIVATE_H */ diff --git a/src/backends/meta-input-capture-session.c b/src/backends/meta-input-capture-session.c index 807925187..faaf11b84 100644 --- a/src/backends/meta-input-capture-session.c +++ b/src/backends/meta-input-capture-session.c @@ -22,13 +22,18 @@ #include "backends/meta-input-capture-session.h" +#include +#include #include #include "backends/meta-dbus-session-watcher.h" #include "backends/meta-dbus-session-manager.h" +#include "backends/meta-fd-source.h" +#include "backends/meta-input-capture-private.h" #include "backends/meta-monitor-manager-private.h" #include "backends/meta-logical-monitor.h" #include "backends/meta-remote-access-controller-private.h" +#include "core/meta-anonymous-file.h" #include "meta/barrier.h" #include "meta/boxes.h" #include "meta/meta-backend.h" @@ -84,6 +89,21 @@ struct _MetaInputCaptureSession uint32_t activation_id; MetaInputCaptureSessionHandle *handle; + + struct eis *eis; + struct eis_client *eis_client; + struct eis_seat *eis_seat; + struct eis_device *eis_pointer; + struct eis_device *eis_keyboard; + GSource *eis_source; + + MetaAnonymousFile *keymap_file; + + MetaViewportInfo *viewports; + + gboolean cancel_requested; + unsigned int buttons_pressed; + unsigned int keys_pressed; }; static void initable_init_iface (GInitableIface *iface); @@ -141,6 +161,258 @@ release_remote_access_handle (MetaInputCaptureSession *session) g_clear_object (&session->handle); } +static void +setup_client (MetaInputCaptureSession *session, + struct eis_client *eis_client) +{ + MetaBackend *backend = + meta_dbus_session_manager_get_backend (session->session_manager); + ClutterSeat *seat = meta_backend_get_default_seat (backend); + struct eis_seat *eis_seat; + + session->eis_client = eis_client_ref (eis_client); + + eis_client_connect (eis_client); + + eis_seat = eis_client_new_seat (eis_client, clutter_seat_get_name (seat)); + eis_seat_configure_capability (eis_seat, EIS_DEVICE_CAP_POINTER); + eis_seat_configure_capability (eis_seat, EIS_DEVICE_CAP_BUTTON); + eis_seat_configure_capability (eis_seat, EIS_DEVICE_CAP_SCROLL); + eis_seat_configure_capability (eis_seat, EIS_DEVICE_CAP_KEYBOARD); + eis_seat_add (eis_seat); + + session->eis_seat = eis_seat; +} + +static void +ensure_eis_pointer_regions (MetaInputCaptureSession *session, + struct eis_device *eis_pointer) +{ + int idx = 0; + cairo_rectangle_int_t rect; + float scale; + + if (!session->viewports) + return; + + while (meta_viewport_info_get_view_info (session->viewports, idx++, &rect, &scale)) + { + struct eis_region *r = eis_device_new_region (eis_pointer); + + eis_region_set_offset (r, rect.x, rect.y); + eis_region_set_size (r, rect.width, rect.height); + eis_region_set_physical_scale (r, scale); + eis_region_add (r); + eis_region_unref (r); + } +} + +static void +ensure_eis_pointer (MetaInputCaptureSession *session) +{ + struct eis_device *eis_pointer; + + if (session->eis_pointer) + return; + + eis_pointer = eis_seat_new_device (session->eis_seat); + eis_device_configure_name (eis_pointer, "captured relative pointer"); + eis_device_configure_capability (eis_pointer, EIS_DEVICE_CAP_POINTER); + eis_device_configure_capability (eis_pointer, EIS_DEVICE_CAP_BUTTON); + eis_device_configure_capability (eis_pointer, EIS_DEVICE_CAP_SCROLL); + ensure_eis_pointer_regions (session, eis_pointer); + eis_device_add (eis_pointer); + eis_device_resume (eis_pointer); + + session->eis_pointer = eis_pointer; +} + +static MetaAnonymousFile * +ensure_xkb_keymap_file (MetaInputCaptureSession *session, + GError **error) +{ + MetaBackend *backend = + meta_dbus_session_manager_get_backend (session->session_manager); + struct xkb_keymap *keymap; + g_autofree char *keymap_string = NULL; + size_t keymap_size; + + if (session->keymap_file) + return session->keymap_file; + + keymap = meta_backend_get_keymap (backend); + if (!keymap) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Backend has no keymap"); + return NULL; + } + + keymap_string = xkb_keymap_get_as_string (keymap, XKB_KEYMAP_FORMAT_TEXT_V1); + keymap_size = strlen (keymap_string) + 1; + + session->keymap_file = + meta_anonymous_file_new (keymap_size, (const uint8_t *) keymap_string); + + return session->keymap_file; +} + +static void +ensure_eis_keyboard (MetaInputCaptureSession *session) +{ + struct eis_device *eis_keyboard; + g_autoptr (GError) error = NULL; + struct eis_keymap *eis_keymap; + MetaAnonymousFile *keymap_file; + int keymap_fd; + size_t keymap_size; + + if (session->eis_keyboard) + return; + + keymap_file = ensure_xkb_keymap_file (session, &error); + if (!keymap_file) + { + g_warning ("Failed to create input capture keymap file: %s", + error->message); + return; + } + + eis_keyboard = eis_seat_new_device (session->eis_seat); + eis_device_configure_name (eis_keyboard, "captured keyboard"); + eis_device_configure_capability (eis_keyboard, EIS_DEVICE_CAP_KEYBOARD); + + keymap_fd = meta_anonymous_file_open_fd (keymap_file, + META_ANONYMOUS_FILE_MAPMODE_PRIVATE); + keymap_size = meta_anonymous_file_size (keymap_file); + eis_keymap = eis_device_new_keymap (eis_keyboard, + EIS_KEYMAP_TYPE_XKB, + keymap_fd, keymap_size); + eis_keymap_add (eis_keymap); + eis_keymap_unref (eis_keymap); + meta_anonymous_file_close_fd (keymap_fd); + + eis_device_add (eis_keyboard); + eis_device_resume (eis_keyboard); + + session->eis_keyboard = eis_keyboard; +} + +static void +clear_eis_pointer (MetaInputCaptureSession *session) +{ + if (!session->eis_pointer) + return; + + eis_device_remove (session->eis_pointer); + g_clear_pointer (&session->eis_pointer, eis_device_unref); +} + +static void +remove_eis_pointer (MetaInputCaptureSession *session) +{ + clear_eis_pointer (session); + + /* The pointer is removed, all its buttons are cleared */ + session->buttons_pressed = 0; +} + +static void +clear_eis_keyboard (MetaInputCaptureSession *session) +{ + if (!session->eis_keyboard) + return; + + eis_device_remove (session->eis_keyboard); + g_clear_pointer (&session->eis_keyboard, eis_device_unref); +} + +static void +remove_eis_keyboard (MetaInputCaptureSession *session) +{ + clear_eis_keyboard (session); + + /* The pointer is removed, all its buttons are cleared */ + session->keys_pressed = 0; +} + +static void +on_keymap_changed (MetaBackend *backend, + gpointer user_data) +{ + MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (user_data); + + g_clear_pointer (&session->keymap_file, meta_anonymous_file_free); + + if (session->eis_keyboard) + { + clear_eis_keyboard (session); + ensure_eis_keyboard (session); + } +} + +static void +process_eis_event (MetaInputCaptureSession *session, + struct eis_event *eis_event) +{ + struct eis_client *eis_client; + struct eis_device *eis_device; + + switch (eis_event_get_type (eis_event)) + { + case EIS_EVENT_CLIENT_CONNECT: + eis_client = eis_event_get_client (eis_event); + if (eis_client_is_sender (eis_client)) + { + g_warning ("Unexpected sender libei client '%s' connected to " + "input capture session", + eis_client_get_name (eis_client)); + eis_client_disconnect (eis_client); + return; + } + + if (session->eis_client) + { + g_warning ("Unexpected additional libei client '%s' connected to " + "input capture session", + eis_client_get_name (eis_client)); + eis_client_disconnect (eis_client); + return; + } + + setup_client (session, eis_client); + break; + + case EIS_EVENT_CLIENT_DISCONNECT: + g_clear_pointer (&session->eis_seat, eis_seat_unref); + g_clear_pointer (&session->eis_client, eis_client_unref); + break; + case EIS_EVENT_SEAT_BIND: + if (eis_event_seat_has_capability (eis_event, EIS_DEVICE_CAP_POINTER) && + eis_event_seat_has_capability (eis_event, EIS_DEVICE_CAP_BUTTON) && + eis_event_seat_has_capability (eis_event, EIS_DEVICE_CAP_SCROLL)) + ensure_eis_pointer (session); + else if (session->eis_pointer) + clear_eis_pointer (session); + + if (eis_event_seat_has_capability (eis_event, EIS_DEVICE_CAP_KEYBOARD)) + ensure_eis_keyboard (session); + else if (session->eis_keyboard) + clear_eis_keyboard (session); + break; + case EIS_EVENT_DEVICE_CLOSED: + eis_device = eis_event_get_device (eis_event); + + if (eis_device == session->eis_pointer) + remove_eis_pointer (session); + else if (eis_device == session->eis_keyboard) + remove_eis_keyboard (session); + break; + default: + break; + } +} + static void on_barrier_hit (MetaBarrier *barrier, const MetaBarrierEvent *event, @@ -148,6 +420,8 @@ on_barrier_hit (MetaBarrier *barrier, { MetaDBusInputCaptureSession *skeleton = META_DBUS_INPUT_CAPTURE_SESSION (session); + MetaInputCapture *input_capture = + META_INPUT_CAPTURE (session->session_manager); GVariant *cursor_position; unsigned int barrier_id; @@ -169,10 +443,16 @@ on_barrier_hit (MetaBarrier *barrier, quark_barrier_id)); cursor_position = g_variant_new ("(dd)", event->x, event->y); + meta_input_capture_activate (input_capture, session); + meta_dbus_input_capture_session_emit_activated (skeleton, barrier_id, ++session->activation_id, cursor_position); + if (session->eis_pointer) + eis_device_start_emulating (session->eis_pointer, session->activation_id); + if (session->eis_keyboard) + eis_device_start_emulating (session->eis_keyboard, session->activation_id); init_remote_access_handle (session); } @@ -245,6 +525,7 @@ meta_input_capture_session_enable (MetaInputCaptureSession *session, } session->state = INPUT_CAPTURE_STATE_ENABLED; + session->cancel_requested = FALSE; return TRUE; @@ -253,6 +534,26 @@ err: return FALSE; } +static void +meta_input_capture_session_deactivate (MetaInputCaptureSession *session) +{ + MetaDBusInputCaptureSession *skeleton = + META_DBUS_INPUT_CAPTURE_SESSION (session); + MetaInputCapture *input_capture = + META_INPUT_CAPTURE (session->session_manager); + + meta_input_capture_deactivate (input_capture, session); + + if (session->eis_pointer) + eis_device_stop_emulating (session->eis_pointer); + if (session->eis_keyboard) + eis_device_stop_emulating (session->eis_keyboard); + meta_dbus_input_capture_session_emit_deactivated (skeleton, + session->activation_id); + + session->state = INPUT_CAPTURE_STATE_ENABLED; +} + static void meta_input_capture_session_disable (MetaInputCaptureSession *session) { @@ -261,6 +562,8 @@ meta_input_capture_session_disable (MetaInputCaptureSession *session) case INPUT_CAPTURE_STATE_INIT: return; case INPUT_CAPTURE_STATE_ACTIVATED: + meta_input_capture_session_deactivate (session); + G_GNUC_FALLTHROUGH; case INPUT_CAPTURE_STATE_ENABLED: break; case INPUT_CAPTURE_STATE_CLOSED: @@ -270,6 +573,10 @@ meta_input_capture_session_disable (MetaInputCaptureSession *session) clear_all_barriers (session); + g_clear_pointer (&session->eis_pointer, eis_device_unref); + g_clear_pointer (&session->eis_keyboard, eis_device_unref); + g_clear_pointer (&session->eis_seat, eis_seat_unref); + session->state = INPUT_CAPTURE_STATE_INIT; if (session->handle) @@ -661,7 +968,7 @@ handle_disable (MetaDBusInputCaptureSession *skeleton, static gboolean handle_release (MetaDBusInputCaptureSession *object, GDBusMethodInvocation *invocation, - GVariant *position) + GVariant *arg_options) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (object); MetaBackend *backend = @@ -686,11 +993,10 @@ handle_release (MetaDBusInputCaptureSession *object, } release_all_barriers (session); + meta_input_capture_session_deactivate (session); - session->state = INPUT_CAPTURE_STATE_ENABLED; - - g_variant_get (position, "(dd)", &x, &y); - clutter_seat_warp_pointer (seat, x, y); + if (g_variant_lookup (arg_options, "cursor_position", "(dd)", &x, &y)) + clutter_seat_warp_pointer (seat, x, y); if (session->handle) release_remote_access_handle (session); @@ -700,6 +1006,47 @@ handle_release (MetaDBusInputCaptureSession *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } +static gboolean +handle_connect_to_eis (MetaDBusInputCaptureSession *object, + GDBusMethodInvocation *invocation, + GUnixFDList *fd_list_in) +{ + MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (object); + int fd; + g_autoptr (GUnixFDList) fd_list = NULL; + int fd_idx; + GVariant *fd_variant; + + if (!check_permission (session, invocation)) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + "Permission denied"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + fd = eis_backend_fd_add_client (session->eis); + if (fd < 0) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "Failed to create socket: %s", + g_strerror (-fd)); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + fd_list = g_unix_fd_list_new (); + fd_idx = g_unix_fd_list_append (fd_list, fd, NULL); + close (fd); + fd_variant = g_variant_new_handle (fd_idx); + + meta_dbus_input_capture_session_complete_connect_to_eis (object, + invocation, + fd_list, + fd_variant); + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + static gboolean handle_close (MetaDBusInputCaptureSession *object, GDBusMethodInvocation *invocation) @@ -721,12 +1068,30 @@ handle_close (MetaDBusInputCaptureSession *object, return G_DBUS_METHOD_INVOCATION_HANDLED; } +static void +meta_input_capture_session_set_viewports (MetaInputCaptureSession *session, + MetaViewportInfo *viewports) +{ + g_clear_object (&session->viewports); + session->viewports = g_object_ref (viewports); + + if (!session->eis_pointer) + return; + + clear_eis_pointer (session); + ensure_eis_pointer (session); +} + static void on_monitors_changed (MetaMonitorManager *monitor_manager, MetaInputCaptureSession *session) { MetaDBusInputCaptureSession *skeleton = META_DBUS_INPUT_CAPTURE_SESSION (session); + MetaViewportInfo *viewports; + + viewports = meta_monitor_manager_get_viewports (monitor_manager); + meta_input_capture_session_set_viewports (session, viewports); session->zones_serial++; meta_input_capture_session_disable (session); @@ -744,6 +1109,8 @@ meta_input_capture_session_initable_init (GInitable *initable, meta_dbus_session_manager_get_backend (session->session_manager); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); + MetaViewportInfo *viewports = + meta_monitor_manager_get_viewports (monitor_manager); session->connection = meta_dbus_session_manager_get_connection (session->session_manager); @@ -753,6 +1120,7 @@ meta_input_capture_session_initable_init (GInitable *initable, error)) return FALSE; + meta_input_capture_session_set_viewports (session, viewports); g_signal_connect_object (monitor_manager, "monitors-changed", G_CALLBACK (on_monitors_changed), session, 0); @@ -774,6 +1142,7 @@ meta_input_capture_session_init_iface (MetaDBusInputCaptureSessionIface *iface) iface->handle_enable = handle_enable; iface->handle_disable = handle_disable; iface->handle_release = handle_release; + iface->handle_connect_to_eis = handle_connect_to_eis; iface->handle_close = handle_close; iface->handle_get_zones = handle_get_zones; } @@ -795,10 +1164,107 @@ meta_input_capture_session_finalize (GObject *object) g_free (session->peer_name); g_free (session->session_id); g_free (session->object_path); + g_clear_object (&session->viewports); + g_clear_pointer (&session->keymap_file, meta_anonymous_file_free); + g_clear_pointer (&session->eis_source, g_source_destroy); + g_clear_pointer (&session->eis, eis_unref); G_OBJECT_CLASS (meta_input_capture_session_parent_class)->finalize (object); } +static void +meta_eis_log_handler (struct eis *eis, + enum eis_log_priority priority, + const char *message, + struct eis_log_context *ctx) +{ + int message_length = strlen (message); + + if (priority >= EIS_LOG_PRIORITY_ERROR) + g_critical ("EIS: %.*s", message_length, message); + else if (priority >= EIS_LOG_PRIORITY_WARNING) + g_warning ("EIS: %.*s", message_length, message); + else if (priority >= EIS_LOG_PRIORITY_INFO) + g_info ("EIS: %.*s", message_length, message); + else + meta_topic (META_DEBUG_INPUT, "EIS: %.*s", message_length, message); +} + +static gboolean +meta_eis_source_prepare (gpointer user_data) +{ + MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (user_data); + struct eis_event *eis_event; + gboolean retval; + + eis_event = eis_peek_event (session->eis); + retval = !!eis_event; + eis_event_unref (eis_event); + + return retval; +} + +static gboolean +meta_eis_source_dispatch (gpointer user_data) +{ + MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (user_data); + + eis_dispatch (session->eis); + + while (TRUE) + { + struct eis_event *eis_event; + + eis_event = eis_get_event (session->eis); + if (!eis_event) + break; + + process_eis_event (session, eis_event); + eis_event_unref (eis_event); + } + + return G_SOURCE_CONTINUE; +} + +static void +meta_input_capture_session_constructed (GObject *object) +{ + MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (object); + MetaBackend *backend = + meta_dbus_session_manager_get_backend (session->session_manager); + static unsigned int global_session_number = 0; + int fd; + GSource *source; + + session->object_path = + g_strdup_printf (META_INPUT_CAPTURE_SESSION_DBUS_PATH "/u%u", + ++global_session_number); + + session->barriers = g_hash_table_new_full (NULL, NULL, NULL, + input_capture_barrier_free); + + session->eis = eis_new (session); + eis_log_set_handler (session->eis, meta_eis_log_handler); + eis_log_set_priority (session->eis, EIS_LOG_PRIORITY_DEBUG); + eis_setup_backend_fd (session->eis); + + fd = eis_get_fd (session->eis); + source = meta_create_fd_source (fd, + "[mutter] eis", + meta_eis_source_prepare, + meta_eis_source_dispatch, + session, + NULL); + session->eis_source = source; + g_source_attach (source, NULL); + g_source_unref (source); + + g_signal_connect (backend, "keymap-changed", + G_CALLBACK (on_keymap_changed), session); + + G_OBJECT_CLASS (meta_input_capture_session_parent_class)->constructed (object); +} + static void meta_input_capture_session_set_property (GObject *object, guint prop_id, @@ -856,6 +1322,7 @@ meta_input_capture_session_class_init (MetaInputCaptureSessionClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = meta_input_capture_session_finalize; + object_class->constructed = meta_input_capture_session_constructed; object_class->set_property = meta_input_capture_session_set_property; object_class->get_property = meta_input_capture_session_get_property; @@ -868,14 +1335,6 @@ meta_input_capture_session_class_init (MetaInputCaptureSessionClass *klass) static void meta_input_capture_session_init (MetaInputCaptureSession *session) { - static unsigned int global_session_number = 0; - - session->object_path = - g_strdup_printf (META_INPUT_CAPTURE_SESSION_DBUS_PATH "/u%u", - ++global_session_number); - - session->barriers = g_hash_table_new_full (NULL, NULL, NULL, - input_capture_barrier_free); } char * @@ -884,6 +1343,162 @@ meta_input_capture_session_get_object_path (MetaInputCaptureSession *session) return session->object_path; } +static void +maybe_disable_cancelled_session (MetaInputCaptureSession *session) +{ + if (!session->cancel_requested) + return; + + if (session->keys_pressed == 0 && session->buttons_pressed == 0) + meta_input_capture_session_disable (session); +} + +static void +update_keys_pressed (MetaInputCaptureSession *session, + gboolean is_pressed) +{ + if (is_pressed) + session->keys_pressed++; + else if (session->keys_pressed > 0) + session->keys_pressed--; + else + g_warning ("Unbalanced key release"); + + maybe_disable_cancelled_session (session); +} + +static void +update_buttons_pressed (MetaInputCaptureSession *session, + gboolean is_pressed) +{ + if (is_pressed) + session->buttons_pressed++; + else if (session->buttons_pressed > 0) + session->buttons_pressed--; + else + g_warning ("Unbalanced button release"); + + maybe_disable_cancelled_session (session); +} + +gboolean +meta_input_capture_session_process_event (MetaInputCaptureSession *session, + const ClutterEvent *event) +{ + switch (event->type) + { + case CLUTTER_MOTION: + if (!session->eis_pointer) + return TRUE; + + eis_device_pointer_motion (session->eis_pointer, + event->motion.dx - event->motion.dx_constrained, + event->motion.dy - event->motion.dy_constrained); + eis_device_frame (session->eis_pointer, eis_now (session->eis)); + break; + case CLUTTER_BUTTON_PRESS: + update_buttons_pressed (session, TRUE); + + if (!session->eis_pointer) + return TRUE; + + eis_device_button_button (session->eis_pointer, + clutter_event_get_event_code (event), + true); + eis_device_frame (session->eis_pointer, eis_now (session->eis)); + break; + case CLUTTER_BUTTON_RELEASE: + update_buttons_pressed (session, FALSE); + + if (!session->eis_pointer) + return TRUE; + + eis_device_button_button (session->eis_pointer, + clutter_event_get_event_code (event), + false); + eis_device_frame (session->eis_pointer, eis_now (session->eis)); + break; + case CLUTTER_SCROLL: + { + const double factor = 10.0; + bool stop_x = false, stop_y = false; + double dx, dy; + + if (!session->eis_pointer) + return TRUE; + + if ((event->scroll.finish_flags & CLUTTER_SCROLL_FINISHED_HORIZONTAL)) + stop_x = true; + if ((event->scroll.finish_flags & CLUTTER_SCROLL_FINISHED_HORIZONTAL)) + stop_y = true; + + if (stop_x || stop_y) + eis_device_scroll_stop (session->eis_pointer, stop_x, stop_y); + + switch (clutter_event_get_scroll_direction (event)) + { + case CLUTTER_SCROLL_UP: + eis_device_scroll_discrete (session->eis_pointer, 0, -120); + break; + case CLUTTER_SCROLL_DOWN: + eis_device_scroll_discrete (session->eis_pointer, 0, 120); + break; + case CLUTTER_SCROLL_LEFT: + eis_device_scroll_discrete (session->eis_pointer, -120, 0); + break; + case CLUTTER_SCROLL_RIGHT: + eis_device_scroll_discrete (session->eis_pointer, 120, 0); + break; + case CLUTTER_SCROLL_SMOOTH: + clutter_event_get_scroll_delta (event, &dx, &dy); + eis_device_scroll_delta (session->eis_pointer, + dx * factor, + dy * factor); + break; + } + eis_device_frame (session->eis_pointer, eis_now (session->eis)); + break; + } + case CLUTTER_KEY_PRESS: + update_keys_pressed (session, TRUE); + + if (!session->eis_keyboard) + return TRUE; + + eis_device_keyboard_key (session->eis_keyboard, + clutter_event_get_event_code (event), + true); + eis_device_frame (session->eis_keyboard, eis_now (session->eis)); + break; + case CLUTTER_KEY_RELEASE: + update_keys_pressed (session, FALSE); + + if (!session->eis_keyboard) + return TRUE; + + eis_device_keyboard_key (session->eis_keyboard, + clutter_event_get_event_code (event), + false); + eis_device_frame (session->eis_keyboard, eis_now (session->eis)); + break; + default: + return FALSE; + } + + return TRUE; +} + +void +meta_input_capture_session_notify_cancelled (MetaInputCaptureSession *session) +{ + if (session->cancel_requested) + return; + + session->cancel_requested = TRUE; + + maybe_disable_cancelled_session (session); +} + static MetaInputCaptureSessionHandle * meta_input_capture_session_handle_new (MetaInputCaptureSession *session) { diff --git a/src/backends/meta-input-capture-session.h b/src/backends/meta-input-capture-session.h index 22553d1aa..8c8668ec5 100644 --- a/src/backends/meta-input-capture-session.h +++ b/src/backends/meta-input-capture-session.h @@ -24,6 +24,7 @@ #include #include "backends/meta-input-capture.h" +#include "backends/meta-viewport-info.h" #include "meta/meta-remote-access-controller.h" #define META_TYPE_INPUT_CAPTURE_SESSION (meta_input_capture_session_get_type ()) @@ -39,4 +40,9 @@ G_DECLARE_FINAL_TYPE (MetaInputCaptureSessionHandle, char *meta_input_capture_session_get_object_path (MetaInputCaptureSession *session); +gboolean meta_input_capture_session_process_event (MetaInputCaptureSession *session, + const ClutterEvent *event); + +void meta_input_capture_session_notify_cancelled (MetaInputCaptureSession *session); + #endif /* META_INPUT_CAPTURE_SESSION_H */ diff --git a/src/backends/meta-input-capture.c b/src/backends/meta-input-capture.c index 3c5b3e835..e8d15990b 100644 --- a/src/backends/meta-input-capture.c +++ b/src/backends/meta-input-capture.c @@ -20,10 +20,11 @@ #include "config.h" -#include "backends/meta-input-capture.h" +#include "backends/meta-input-capture-private.h" #include "backends/meta-input-capture-session.h" #include "backends/meta-backend-private.h" +#include "backends/meta-monitor-manager-private.h" #include "clutter/clutter.h" #include "meta-dbus-input-capture.h" @@ -31,6 +32,11 @@ #define META_INPUT_CAPTURE_DBUS_SERVICE "org.gnome.Mutter.InputCapture" #define META_INPUT_CAPTURE_DBUS_PATH "/org/gnome/Mutter/InputCapture" +enum +{ + CANCELLED, +}; + typedef enum _MetaInputCaptureCapabilities { META_INPUT_CAPTURE_CAPABILITY_NONE = 1 << 0, @@ -42,6 +48,14 @@ typedef enum _MetaInputCaptureCapabilities struct _MetaInputCapture { MetaDbusSessionManager parent; + + struct { + MetaInputCaptureEnable enable; + MetaInputCaptureDisable disable; + gpointer user_data; + } event_router; + + MetaInputCaptureSession *active_session; }; G_DEFINE_TYPE (MetaInputCapture, meta_input_capture, @@ -156,3 +170,60 @@ meta_input_capture_new (MetaBackend *backend) return input_capture; } + +void +meta_input_capture_set_event_router (MetaInputCapture *input_capture, + MetaInputCaptureEnable enable, + MetaInputCaptureDisable disable, + gpointer user_data) +{ + g_warn_if_fail (!input_capture->event_router.enable && + !input_capture->event_router.disable && + !input_capture->event_router.user_data); + + input_capture->event_router.enable = enable; + input_capture->event_router.disable = disable; + input_capture->event_router.user_data = user_data; +} + +void +meta_input_capture_activate (MetaInputCapture *input_capture, + MetaInputCaptureSession *session) +{ + g_return_if_fail (input_capture->event_router.enable); + + meta_topic (META_DEBUG_INPUT, "Activating input capturing"); + input_capture->active_session = session; + input_capture->event_router.enable (input_capture, + input_capture->event_router.user_data); +} + +void +meta_input_capture_deactivate (MetaInputCapture *input_capture, + MetaInputCaptureSession *session) +{ + g_return_if_fail (input_capture->event_router.disable); + + meta_topic (META_DEBUG_INPUT, "Deactivating input capturing"); + input_capture->event_router.disable (input_capture, + input_capture->event_router.user_data); + input_capture->active_session = NULL; +} + +void +meta_input_capture_notify_cancelled (MetaInputCapture *input_capture) +{ + g_return_if_fail (input_capture->active_session); + + meta_input_capture_session_notify_cancelled (input_capture->active_session); +} + +gboolean +meta_input_capture_process_event (MetaInputCapture *input_capture, + const ClutterEvent *event) +{ + g_return_val_if_fail (input_capture->active_session, FALSE); + + return meta_input_capture_session_process_event (input_capture->active_session, + event); +} diff --git a/src/backends/meta-input-capture.h b/src/backends/meta-input-capture.h index e229f473f..5196fcae1 100644 --- a/src/backends/meta-input-capture.h +++ b/src/backends/meta-input-capture.h @@ -22,9 +22,16 @@ #define META_INPUT_CAPTURE_H #include "backends/meta-dbus-session-manager.h" +#include "backends/meta-viewport-info.h" +#include "clutter/clutter.h" #include "meta-dbus-input-capture.h" +typedef void (* MetaInputCaptureEnable) (MetaInputCapture *input_capture, + gpointer user_data); +typedef void (* MetaInputCaptureDisable) (MetaInputCapture *input_capture, + gpointer user_data); + #define META_TYPE_INPUT_CAPTURE (meta_input_capture_get_type ()) G_DECLARE_FINAL_TYPE (MetaInputCapture, meta_input_capture, META, INPUT_CAPTURE, @@ -32,4 +39,14 @@ G_DECLARE_FINAL_TYPE (MetaInputCapture, meta_input_capture, MetaInputCapture *meta_input_capture_new (MetaBackend *backend); +void meta_input_capture_set_event_router (MetaInputCapture *input_capture, + MetaInputCaptureEnable enable, + MetaInputCaptureDisable disable, + gpointer user_data); + +void meta_input_capture_notify_cancelled (MetaInputCapture *input_capture); + +gboolean meta_input_capture_process_event (MetaInputCapture *input_capture, + const ClutterEvent *event); + #endif /* META_INPUT_CAPTURE_H */ diff --git a/src/core/display-private.h b/src/core/display-private.h index a7469a02a..e19ada740 100644 --- a/src/core/display-private.h +++ b/src/core/display-private.h @@ -348,4 +348,9 @@ void meta_display_flush_queued_window (MetaDisplay *display, MetaWindow *window, MetaQueueType queue_types); +gboolean meta_display_process_captured_input (MetaDisplay *display, + const ClutterEvent *event); + +void meta_display_cancel_input_capture (MetaDisplay *display); + #endif diff --git a/src/core/display.c b/src/core/display.c index 0e32e7483..7997ebe70 100644 --- a/src/core/display.c +++ b/src/core/display.c @@ -37,6 +37,7 @@ #include "backends/meta-backend-private.h" #include "backends/meta-cursor-sprite-xcursor.h" #include "backends/meta-cursor-tracker-private.h" +#include "backends/meta-input-capture.h" #include "backends/meta-input-device-private.h" #include "backends/meta-input-mapper-private.h" #include "backends/meta-stage-private.h" @@ -138,6 +139,8 @@ typedef struct _MetaDisplayPrivate guint queue_later_ids[META_N_QUEUE_TYPES]; GList *queue_windows[META_N_QUEUE_TYPES]; + + gboolean enable_input_capture; } MetaDisplayPrivate; G_DEFINE_TYPE_WITH_PRIVATE (MetaDisplay, meta_display, G_TYPE_OBJECT) @@ -716,6 +719,64 @@ on_monitor_privacy_screen_changed (MetaDisplay *display, : _("Privacy Screen Disabled")); } +gboolean +meta_display_process_captured_input (MetaDisplay *display, + const ClutterEvent *event) +{ + MetaDisplayPrivate *priv = meta_display_get_instance_private (display); + MetaContext *context = priv->context; + MetaBackend *backend = meta_context_get_backend (context); + MetaInputCapture *input_capture = meta_backend_get_input_capture (backend); + + if (!priv->enable_input_capture) + return FALSE; + + /* Check for the cancel key combo, but let the event flow through, so + * that meta_input_capture_process_event() can account for all press + * and release events, even the one from the key combo itself. + */ + meta_display_process_keybinding_event (display, + "cancel-input-capture", + event); + + return meta_input_capture_process_event (input_capture, event); +} + +void +meta_display_cancel_input_capture (MetaDisplay *display) +{ + MetaDisplayPrivate *priv = meta_display_get_instance_private (display); + MetaContext *context = priv->context; + MetaBackend *backend = meta_context_get_backend (context); + MetaInputCapture *input_capture = meta_backend_get_input_capture (backend); + + meta_input_capture_notify_cancelled (input_capture); +} + +static void +enable_input_capture (MetaInputCapture *input_capture, + gpointer user_data) +{ + MetaDisplay *display = META_DISPLAY (user_data); + MetaDisplayPrivate *priv = meta_display_get_instance_private (display); + + g_return_if_fail (!priv->enable_input_capture); + + priv->enable_input_capture = TRUE; +} + +static void +disable_input_capture (MetaInputCapture *input_capture, + gpointer user_data) +{ + MetaDisplay *display = META_DISPLAY (user_data); + MetaDisplayPrivate *priv = meta_display_get_instance_private (display); + + g_return_if_fail (priv->enable_input_capture); + + priv->enable_input_capture = FALSE; +} + #ifdef HAVE_X11_CLIENT static gboolean meta_display_init_x11_display (MetaDisplay *display, @@ -868,6 +929,7 @@ meta_display_new (MetaContext *context, #endif MetaMonitorManager *monitor_manager; MetaSettings *settings; + MetaInputCapture *input_capture; display = g_object_new (META_TYPE_DISPLAY, NULL); @@ -912,6 +974,12 @@ meta_display_new (MetaContext *context, display->pad_action_mapper = meta_pad_action_mapper_new (monitor_manager); + input_capture = meta_backend_get_input_capture (backend); + meta_input_capture_set_event_router (input_capture, + enable_input_capture, + disable_input_capture, + display); + settings = meta_backend_get_settings (backend); g_signal_connect (settings, "ui-scaling-factor-changed", G_CALLBACK (on_ui_scaling_factor_changed), display); diff --git a/src/core/events.c b/src/core/events.c index 7107e67c9..6a432e825 100644 --- a/src/core/events.c +++ b/src/core/events.c @@ -257,6 +257,13 @@ meta_display_handle_event (MetaDisplay *display, } } + if (meta_display_process_captured_input (display, event)) + { + bypass_clutter = TRUE; + bypass_wayland = TRUE; + goto out; + } + device = clutter_event_get_device (event); clutter_input_pointer_a11y_update (device, event); diff --git a/src/core/keybindings-private.h b/src/core/keybindings-private.h index 22919f6e2..5895c7cf1 100644 --- a/src/core/keybindings-private.h +++ b/src/core/keybindings-private.h @@ -154,4 +154,8 @@ gboolean meta_prefs_is_locate_pointer_enabled (void); void meta_x11_display_grab_keys (MetaX11Display *x11_display); void meta_x11_display_ungrab_keys (MetaX11Display *x11_display); +gboolean meta_display_process_keybinding_event (MetaDisplay *display, + const char *name, + const ClutterEvent *event); + #endif diff --git a/src/core/keybindings.c b/src/core/keybindings.c index f3594269a..59d57fc88 100644 --- a/src/core/keybindings.c +++ b/src/core/keybindings.c @@ -1914,8 +1914,13 @@ process_event (MetaDisplay *display, binding = get_keybinding (keys, &resolved_combo); - if (!binding || - (!window && binding->flags & META_KEY_BINDING_PER_WINDOW)) + if (!binding) + goto not_found; + + if (!window && binding->flags & META_KEY_BINDING_PER_WINDOW) + goto not_found; + + if (binding->flags & META_KEY_BINDING_CUSTOM_TRIGGER) goto not_found; if (binding->handler == NULL) @@ -2993,6 +2998,16 @@ handle_rotate_monitor (MetaDisplay *display, meta_monitor_manager_rotate_monitor (monitor_manager); } +static void +handle_cancel_input_capture (MetaDisplay *display, + MetaWindow *window, + const ClutterKeyEvent *event, + MetaKeyBinding *binding, + gpointer user_data) +{ + meta_display_cancel_input_capture (display); +} + static void handle_restore_shortcuts (MetaDisplay *display, MetaWindow *window, @@ -3320,6 +3335,14 @@ init_builtin_key_bindings (MetaDisplay *display) META_KEYBINDING_ACTION_ROTATE_MONITOR, handle_rotate_monitor, 0); + add_builtin_keybinding (display, + "cancel-input-capture", + mutter_keybindings, + META_KEY_BINDING_IGNORE_AUTOREPEAT | + META_KEY_BINDING_CUSTOM_TRIGGER, + META_KEYBINDING_ACTION_NONE, + handle_cancel_input_capture, 0); + #ifdef HAVE_NATIVE_BACKEND MetaContext *context = meta_display_get_context (display); MetaBackend *backend = meta_context_get_backend (context); @@ -3884,3 +3907,55 @@ meta_display_init_keys (MetaDisplay *display) g_signal_connect_swapped (backend, "keymap-layout-group-changed", G_CALLBACK (reload_keybindings), display); } + +static gboolean +process_keybinding_key_event (MetaDisplay *display, + MetaKeyHandler *handler, + const ClutterKeyEvent *event) +{ + MetaKeyBindingManager *keys = &display->key_binding_manager; + xkb_keycode_t keycode = (xkb_keycode_t) event->hardware_keycode; + MetaResolvedKeyCombo resolved_combo = { &keycode, 1 }; + MetaKeyBinding *binding; + + if (event->type == CLUTTER_KEY_RELEASE) + return FALSE; + + resolved_combo.mask = mask_from_event_params (keys, event->modifier_state); + + binding = get_keybinding (keys, &resolved_combo); + if (!binding) + return FALSE; + + if (handler != binding->handler) + return FALSE; + + g_return_val_if_fail (binding->flags & META_KEY_BINDING_CUSTOM_TRIGGER, + FALSE); + + invoke_handler (display, binding->handler, NULL, event, binding); + return TRUE; +} + +gboolean +meta_display_process_keybinding_event (MetaDisplay *display, + const char *name, + const ClutterEvent *event) +{ + MetaKeyHandler *handler; + + handler = g_hash_table_lookup (key_handlers, name); + if (!handler) + return FALSE; + + switch (event->type) + { + case CLUTTER_KEY_PRESS: + case CLUTTER_KEY_RELEASE: + return process_keybinding_key_event (display, handler, + (ClutterKeyEvent *) event); + + default: + return FALSE; + } +} diff --git a/src/meson.build b/src/meson.build index 863c78255..3fb8747e4 100644 --- a/src/meson.build +++ b/src/meson.build @@ -27,6 +27,7 @@ mutter_pkg_private_deps = [ gnome_settings_daemon_dep, json_glib_dep, xkbcommon_dep, + libeis_dep, ] if have_gnome_desktop diff --git a/src/meta/prefs.h b/src/meta/prefs.h index 4964f02ee..bd975dde5 100644 --- a/src/meta/prefs.h +++ b/src/meta/prefs.h @@ -430,6 +430,7 @@ typedef enum _MetaKeyBindingAction * @META_KEY_BINDING_NON_MASKABLE: always active * @META_KEY_BINDING_IGNORE_AUTOREPEAT: ignore autorepeat * @META_KEY_BINDING_NO_AUTO_GRAB: not grabbed automatically + * @META_KEY_BINDING_CUSTOM_TRIGGER: uses a custom keybinding action */ typedef enum { @@ -440,6 +441,7 @@ typedef enum META_KEY_BINDING_NON_MASKABLE = 1 << 3, META_KEY_BINDING_IGNORE_AUTOREPEAT = 1 << 4, META_KEY_BINDING_NO_AUTO_GRAB = 1 << 5, + META_KEY_BINDING_CUSTOM_TRIGGER = 1 << 6, } MetaKeyBindingFlags; /** diff --git a/src/tests/input-capture-test-client.c b/src/tests/input-capture-test-client.c index a71e69a8e..60536af8b 100644 --- a/src/tests/input-capture-test-client.c +++ b/src/tests/input-capture-test-client.c @@ -22,8 +22,13 @@ #include #include +#include +#include +#include #include +#include "backends/meta-fd-source.h" + #include "meta-dbus-input-capture.h" typedef struct @@ -49,10 +54,37 @@ typedef struct _InputCapture MetaDBusInputCapture *proxy; } InputCapture; +typedef struct _Event +{ + enum ei_event_type type; + struct { + double dx; + double dy; + } motion; + struct { + uint32_t button; + gboolean is_press; + } button; + struct { + uint32_t key; + gboolean is_press; + } key; +} Event; + typedef struct _InputCaptureSession { MetaDBusInputCaptureSession *proxy; unsigned int serial; + + struct ei *ei; + GSource *ei_source; + + Event *expected_events; + int n_expected_events; + int next_event; + + gboolean has_pointer; + gboolean has_keyboard; } InputCaptureSession; static GDataInputStream *stdin_reader; @@ -183,6 +215,9 @@ input_capture_session_close (InputCaptureSession *session) { GError *error = NULL; + g_clear_pointer (&session->ei, ei_unref); + g_clear_pointer (&session->ei_source, g_source_destroy); + if (!meta_dbus_input_capture_session_call_close_sync (session->proxy, NULL, &error)) g_error ("Failed to close session: %s", error->message); @@ -191,6 +226,226 @@ input_capture_session_close (InputCaptureSession *session) g_free (session); } +static void +record_event (InputCaptureSession *session, + const Event *event) +{ + const Event *expected_event; + + g_debug ("Record event #%d, with type %s", + session->next_event + 1, ei_event_type_to_string (event->type)); + g_assert_nonnull (session->expected_events); + g_assert_cmpint (session->next_event, <, session->n_expected_events); + + expected_event = &session->expected_events[session->next_event++]; + + g_assert_cmpint (expected_event->type, ==, event->type); + + switch (event->type) + { + case EI_EVENT_POINTER_MOTION: + g_assert_cmpfloat_with_epsilon (event->motion.dx, + expected_event->motion.dx, + DBL_EPSILON); + g_assert_cmpfloat_with_epsilon (event->motion.dy, + expected_event->motion.dy, + DBL_EPSILON); + break; + case EI_EVENT_BUTTON_BUTTON: + g_assert_cmpint (event->button.button, ==, expected_event->button.button); + break; + case EI_EVENT_KEYBOARD_KEY: + g_assert_cmpint (event->key.key, ==, expected_event->key.key); + break; + case EI_EVENT_FRAME: + break; + default: + break; + } +} + +static void +process_ei_event (InputCaptureSession *session, + struct ei_event *ei_event) +{ + g_debug ("Processing event %s", ei_event_type_to_string (ei_event_get_type (ei_event))); + + switch (ei_event_get_type (ei_event)) + { + case EI_EVENT_SEAT_ADDED: + { + struct ei_seat *ei_seat = ei_event_get_seat (ei_event); + + g_assert_true (ei_seat_has_capability (ei_seat, EI_DEVICE_CAP_POINTER)); + g_assert_true (ei_seat_has_capability (ei_seat, EI_DEVICE_CAP_KEYBOARD)); + g_assert_true (ei_seat_has_capability (ei_seat, EI_DEVICE_CAP_BUTTON)); + g_assert_true (ei_seat_has_capability (ei_seat, EI_DEVICE_CAP_SCROLL)); + ei_seat_bind_capabilities (ei_seat, + EI_DEVICE_CAP_POINTER, + EI_DEVICE_CAP_BUTTON, + EI_DEVICE_CAP_SCROLL, + EI_DEVICE_CAP_KEYBOARD, + NULL); + break; + } + case EI_EVENT_DEVICE_ADDED: + { + struct ei_device *ei_device = ei_event_get_device (ei_event); + + if (ei_device_has_capability (ei_device, EI_DEVICE_CAP_POINTER) && + ei_device_has_capability (ei_device, EI_DEVICE_CAP_BUTTON) && + ei_device_has_capability (ei_device, EI_DEVICE_CAP_SCROLL)) + session->has_pointer = TRUE; + if (ei_device_has_capability (ei_device, EI_DEVICE_CAP_KEYBOARD)) + session->has_keyboard = TRUE; + break; + } + case EI_EVENT_DEVICE_REMOVED: + { + struct ei_device *ei_device = ei_event_get_device (ei_event); + + if (ei_device_has_capability (ei_device, EI_DEVICE_CAP_POINTER) && + ei_device_has_capability (ei_device, EI_DEVICE_CAP_BUTTON) && + ei_device_has_capability (ei_device, EI_DEVICE_CAP_SCROLL)) + session->has_pointer = FALSE; + if (ei_device_has_capability (ei_device, EI_DEVICE_CAP_KEYBOARD)) + session->has_keyboard = FALSE; + break; + } + case EI_EVENT_POINTER_MOTION: + record_event (session, + &(Event) { + .type = EI_EVENT_POINTER_MOTION, + .motion.dx = ei_event_pointer_get_dx (ei_event), + .motion.dy = ei_event_pointer_get_dy (ei_event), + }); + break; + case EI_EVENT_BUTTON_BUTTON: + record_event (session, + &(Event) { + .type = EI_EVENT_BUTTON_BUTTON, + .button.button = ei_event_button_get_button (ei_event), + }); + break; + case EI_EVENT_KEYBOARD_KEY: + record_event (session, + &(Event) { + .type = EI_EVENT_KEYBOARD_KEY, + .key.key = ei_event_keyboard_get_key (ei_event), + }); + break; + case EI_EVENT_FRAME: + record_event (session, &(Event) { .type = EI_EVENT_FRAME }); + break; + default: + break; + } +} + +static gboolean +ei_source_prepare (gpointer user_data) +{ + InputCaptureSession *session = user_data; + struct ei_event *ei_event; + gboolean retval; + + ei_event = ei_peek_event (session->ei); + retval = !!ei_event; + ei_event_unref (ei_event); + + return retval; +} + +static gboolean +ei_source_dispatch (gpointer user_data) +{ + InputCaptureSession *session = user_data; + + ei_dispatch (session->ei); + + while (TRUE) + { + struct ei_event *ei_event; + + ei_event = ei_get_event (session->ei); + if (!ei_event) + break; + + process_ei_event (session, ei_event); + ei_event_unref (ei_event); + } + + return G_SOURCE_CONTINUE; +} + +static void +set_expected_events (InputCaptureSession *session, + Event *expected_events, + int n_expected_events) +{ + session->expected_events = expected_events; + session->n_expected_events = n_expected_events; + session->next_event = 0; +} + +static void +log_handler (struct ei *ei, + enum ei_log_priority priority, + const char *message, + struct ei_log_context *ctx) +{ + int message_length = strlen (message); + + if (priority >= EI_LOG_PRIORITY_ERROR) + g_critical ("libei: %.*s", message_length, message); + else if (priority >= EI_LOG_PRIORITY_WARNING) + g_warning ("libei: %.*s", message_length, message); + else if (priority >= EI_LOG_PRIORITY_INFO) + g_info ("libei: %.*s", message_length, message); + else + g_debug ("libei: %.*s", message_length, message); +} + +static void +input_capture_session_connect_to_eis (InputCaptureSession *session) +{ + g_autoptr (GVariant) fd_variant = NULL; + g_autoptr (GUnixFDList) fd_list = NULL; + GError *error = NULL; + int fd; + struct ei *ei; + int ret; + + if (!meta_dbus_input_capture_session_call_connect_to_eis_sync (session->proxy, + NULL, + &fd_variant, + &fd_list, + NULL, &error)) + g_error ("Failed to connect to EIS: %s", error->message); + + fd = g_unix_fd_list_get (fd_list, g_variant_get_handle (fd_variant), &error); + if (fd == -1) + g_error ("Failed to get EIS file descriptor: %s", error->message); + + ei = ei_new_receiver (session); + ei_log_set_handler (ei, log_handler); + ei_log_set_priority (ei, EI_LOG_PRIORITY_DEBUG); + + ret = ei_setup_backend_fd (ei, fd); + if (ret < 0) + g_error ("Failed to setup libei backend: %s", g_strerror (errno)); + + session->ei = ei; + session->ei_source = meta_create_fd_source (ei_get_fd (ei), + "libei", + ei_source_prepare, + ei_source_dispatch, + session, + NULL); + g_source_attach (session->ei_source, NULL); + g_source_unref (session->ei_source); +} + static GList * input_capture_session_get_zones (InputCaptureSession *session) { @@ -286,11 +541,15 @@ input_capture_session_release (InputCaptureSession *session, double y) { g_autoptr (GError) error = NULL; - GVariant *position; + GVariantBuilder options_builder; + + g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add (&options_builder, "{sv}", + "cursor_position", + g_variant_new ("(dd)", x, y)); - position = g_variant_new ("(dd)", x, y); if (!meta_dbus_input_capture_session_call_release_sync (session->proxy, - position, + g_variant_builder_end (&options_builder), NULL, &error)) g_warning ("Failed to release pointer: %s", error->message); } @@ -521,6 +780,105 @@ test_clear_barriers (void) input_capture_session_close (session); } +static void +test_cancel_keybinding (void) +{ + InputCapture *input_capture; + InputCaptureSession *session; + g_autolist (Zone) zones = NULL; + + input_capture = input_capture_new (); + session = input_capture_create_session (input_capture); + + zones = input_capture_session_get_zones (session); + input_capture_session_add_barrier (session, 0, 0, 0, 600); + input_capture_session_enable (session); + + write_state (session, "1"); + wait_for_state (session, "1"); + + input_capture_session_close (session); +} + +static void +test_events (void) +{ + InputCapture *input_capture; + InputCaptureSession *session; + g_autolist (Zone) zones = NULL; + Event expected_events[] = { + /* Move the pointer with deltas (10, 15) and (2, -5), then click */ + { + .type = EI_EVENT_POINTER_MOTION, + .motion = { .dx = -10.0, .dy = -10.0 }, + }, + { + .type = EI_EVENT_FRAME, + }, + { + .type = EI_EVENT_POINTER_MOTION, + .motion = { .dx = 2.0, .dy = -5.0 }, + }, + { + .type = EI_EVENT_FRAME, + }, + { + .type = EI_EVENT_BUTTON_BUTTON, + .button = { .button = BTN_LEFT, .is_press = TRUE }, + }, + { + .type = EI_EVENT_FRAME, + }, + { + .type = EI_EVENT_BUTTON_BUTTON, + .button = { .button = BTN_LEFT, .is_press = FALSE }, + }, + { + .type = EI_EVENT_FRAME, + }, + + /* Press, then release, KEY_A */ + { + .type = EI_EVENT_KEYBOARD_KEY, + .key = { .key = KEY_A, .is_press = TRUE }, + }, + { + .type = EI_EVENT_FRAME, + }, + { + .type = EI_EVENT_KEYBOARD_KEY, + .key = { .key = KEY_A, .is_press = FALSE }, + }, + { + .type = EI_EVENT_FRAME, + }, + }; + + input_capture = input_capture_new (); + session = input_capture_create_session (input_capture); + + input_capture_session_connect_to_eis (session); + zones = input_capture_session_get_zones (session); + input_capture_session_add_barrier (session, 0, 0, 0, 600); + + input_capture_session_enable (session); + + while (!session->has_pointer || + !session->has_keyboard) + g_main_context_iteration (NULL, TRUE); + + write_state (session, "1"); + + set_expected_events (session, + expected_events, + G_N_ELEMENTS (expected_events)); + + while (session->next_event < session->n_expected_events) + g_main_context_iteration (NULL, TRUE); + + input_capture_session_close (session); +} + static const struct { const char *name; @@ -530,6 +888,8 @@ static const struct { "zones", test_zones, }, { "barriers", test_barriers, }, { "clear-barriers", test_clear_barriers, }, + { "cancel-keybinding", test_cancel_keybinding, }, + { "events", test_events, }, }; static void diff --git a/src/tests/input-capture-tests.c b/src/tests/input-capture-tests.c index 867949565..b7bacb133 100644 --- a/src/tests/input-capture-tests.c +++ b/src/tests/input-capture-tests.c @@ -21,6 +21,7 @@ #include "config.h" #include +#include #include "backends/meta-backend-private.h" #include "meta-test/meta-context-test.h" @@ -180,6 +181,34 @@ input_capture_test_client_finish (InputCaptureTestClient *test_client) g_free (test_client); } +static void +click_button (ClutterVirtualInputDevice *virtual_pointer, + uint32_t button) +{ + clutter_virtual_input_device_notify_button (virtual_pointer, + g_get_monotonic_time (), + button, + CLUTTER_BUTTON_STATE_PRESSED); + clutter_virtual_input_device_notify_button (virtual_pointer, + g_get_monotonic_time (), + button, + CLUTTER_BUTTON_STATE_RELEASED); +} + +static void +press_key (ClutterVirtualInputDevice *virtual_keyboard, + uint32_t key) +{ + clutter_virtual_input_device_notify_key (virtual_keyboard, + g_get_monotonic_time (), + key, + CLUTTER_KEY_STATE_PRESSED); + clutter_virtual_input_device_notify_key (virtual_keyboard, + g_get_monotonic_time (), + key, + CLUTTER_KEY_STATE_RELEASED); +} + static void meta_test_input_capture_sanity (void) { @@ -326,6 +355,119 @@ meta_test_input_capture_clear_barriers (void) input_capture_test_client_finish (test_client); } +static void +meta_test_input_capture_cancel_keybinding (void) +{ + MetaBackend *backend = meta_context_get_backend (test_context); + ClutterSeat *seat = meta_backend_get_default_seat (backend); + g_autoptr (MetaVirtualMonitor) virtual_monitor = NULL; + g_autoptr (ClutterVirtualInputDevice) virtual_keyboard = NULL; + g_autoptr (ClutterVirtualInputDevice) virtual_pointer = NULL; + InputCaptureTestClient *test_client; + + virtual_monitor = meta_create_test_monitor (test_context, 800, 600, 20.0); + virtual_keyboard = clutter_seat_create_virtual_device (seat, + CLUTTER_KEYBOARD_DEVICE); + virtual_pointer = clutter_seat_create_virtual_device (seat, + CLUTTER_POINTER_DEVICE); + clutter_virtual_input_device_notify_absolute_motion (virtual_pointer, + g_get_monotonic_time (), + 10.0, 10.0); + + test_client = input_capture_test_client_new ("cancel-keybinding"); + input_capture_test_client_wait_for_state (test_client, "1"); + + clutter_virtual_input_device_notify_relative_motion (virtual_pointer, + g_get_monotonic_time (), + -20.0, 0.0); + meta_flush_input (test_context); + meta_wait_for_paint (test_context); + assert_pointer_position (seat, 0.0, 10.0); + + clutter_virtual_input_device_notify_relative_motion (virtual_pointer, + g_get_monotonic_time (), + 10.0, 10.0); + meta_flush_input (test_context); + meta_wait_for_paint (test_context); + assert_pointer_position (seat, 0.0, 10.0); + + clutter_virtual_input_device_notify_key (virtual_keyboard, + g_get_monotonic_time (), + KEY_LEFTMETA, + CLUTTER_KEY_STATE_PRESSED); + clutter_virtual_input_device_notify_key (virtual_keyboard, + g_get_monotonic_time (), + KEY_LEFTSHIFT, + CLUTTER_KEY_STATE_PRESSED); + clutter_virtual_input_device_notify_key (virtual_keyboard, + g_get_monotonic_time (), + KEY_ESC, + CLUTTER_KEY_STATE_PRESSED); + clutter_virtual_input_device_notify_key (virtual_keyboard, + g_get_monotonic_time (), + KEY_ESC, + CLUTTER_KEY_STATE_RELEASED); + clutter_virtual_input_device_notify_key (virtual_keyboard, + g_get_monotonic_time (), + KEY_LEFTSHIFT, + CLUTTER_KEY_STATE_RELEASED); + clutter_virtual_input_device_notify_key (virtual_keyboard, + g_get_monotonic_time (), + KEY_LEFTMETA, + CLUTTER_KEY_STATE_RELEASED); + + meta_flush_input (test_context); + meta_wait_for_paint (test_context); + + clutter_virtual_input_device_notify_relative_motion (virtual_pointer, + g_get_monotonic_time (), + 10.0, 10.0); + + meta_flush_input (test_context); + meta_wait_for_paint (test_context); + assert_pointer_position (seat, 10.0, 20.0); + + input_capture_test_client_write_state (test_client, "1"); + + input_capture_test_client_finish (test_client); +} + +static void +meta_test_input_capture_events (void) +{ + MetaBackend *backend = meta_context_get_backend (test_context); + ClutterSeat *seat = meta_backend_get_default_seat (backend); + g_autoptr (MetaVirtualMonitor) virtual_monitor1 = NULL; + g_autoptr (MetaVirtualMonitor) virtual_monitor2 = NULL; + g_autoptr (ClutterVirtualInputDevice) virtual_pointer = NULL; + g_autoptr (ClutterVirtualInputDevice) virtual_keyboard = NULL; + InputCaptureTestClient *test_client; + + virtual_monitor1 = meta_create_test_monitor (test_context, 800, 600, 20.0); + + virtual_pointer = clutter_seat_create_virtual_device (seat, + CLUTTER_POINTER_DEVICE); + clutter_virtual_input_device_notify_absolute_motion (virtual_pointer, + g_get_monotonic_time (), + 10.0, 10.0); + virtual_keyboard = clutter_seat_create_virtual_device (seat, + CLUTTER_KEYBOARD_DEVICE); + + test_client = input_capture_test_client_new ("events"); + input_capture_test_client_wait_for_state (test_client, "1"); + + clutter_virtual_input_device_notify_relative_motion (virtual_pointer, + g_get_monotonic_time (), + -20.0, -20.0); + clutter_virtual_input_device_notify_relative_motion (virtual_pointer, + g_get_monotonic_time (), + 2.0, -5.0); + click_button (virtual_pointer, CLUTTER_BUTTON_PRIMARY); + press_key (virtual_keyboard, KEY_A); + + input_capture_test_client_finish (test_client); +} + static void init_tests (void) { @@ -337,6 +479,10 @@ init_tests (void) meta_test_input_capture_barriers); g_test_add_func ("/backends/native/input-capture/clear-barriers", meta_test_input_capture_clear_barriers); + g_test_add_func ("/backends/native/input-capture/cancel-keybinding", + meta_test_input_capture_cancel_keybinding); + g_test_add_func ("/backends/native/input-capture/events", + meta_test_input_capture_events); } int diff --git a/src/tests/meson.build b/src/tests/meson.build index 508059678..95feb9bc6 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -311,6 +311,8 @@ if have_native_tests input_capture_client = executable('mutter-input-capture-test-client', sources: [ 'input-capture-test-client.c', + '../backends/meta-fd-source.c', + '../backends/meta-fd-source.h', dbus_input_capture_built_sources, ], include_directories: tests_includes, @@ -320,6 +322,7 @@ if have_native_tests ], dependencies: [ gio_unix_dep, + libei_dep, ], install: have_installed_tests, install_dir: mutter_installed_tests_libexecdir,