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,