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,