input-capture: Hook up capturing of events to active session

This adds the actual input capturing rerouting that takes events and
first hands them to the input capture session, would it be active.
Events are right now not actually processed in any way, but will
eventually be passed to a libei client using libeis.

A key binding for allowing cancelling the capture session is added
(defaults to <Super><Shift>Escape) to avoid getting stuck in case the client
doesn't even terminate the session.

The added test case makes sure that the pointer moves again after
pressing the keybinding.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2628>
This commit is contained in:
Jonas Ådahl 2022-04-27 10:50:43 +02:00 committed by Carlos Garnacho
parent e5a35e8802
commit 2fb3bdf774
18 changed files with 1445 additions and 19 deletions

View File

@ -49,6 +49,12 @@
<method name="Enable" />
<method name="Disable" />
<method name="ConnectToEIS">
<annotation name="org.gtk.GDBus.C.Name" value="connect_to_eis"/>
<annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
<arg type="h" name="fd" direction="out"/>
</method>
<method name="Release">
<arg type="a{sv}" name="options" direction="in" />
</method>

View File

@ -179,5 +179,10 @@
<summary>Rotates the built-in monitor configuration</summary>
</key>
<key name="cancel-input-capture" type="as">
<default><![CDATA[['<Super><Shift>Escape']]]></default>
<summary>Cancel any active input capture session</summary>
</key>
</schema>
</schemalist>

View File

@ -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

View File

@ -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 */

View File

@ -22,13 +22,18 @@
#include "backends/meta-input-capture-session.h"
#include <gio/gunixfdlist.h>
#include <libeis.h>
#include <stdint.h>
#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)
{

View File

@ -24,6 +24,7 @@
#include <glib-object.h>
#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 */

View File

@ -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);
}

View File

@ -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 */

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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;
}
}

View File

@ -27,6 +27,7 @@ mutter_pkg_private_deps = [
gnome_settings_daemon_dep,
json_glib_dep,
xkbcommon_dep,
libeis_dep,
]
if have_gnome_desktop

View File

@ -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;
/**

View File

@ -22,8 +22,13 @@
#include <glib.h>
#include <gio/gunixinputstream.h>
#include <gio/gunixfdlist.h>
#include <libei.h>
#include <linux/input.h>
#include <stdio.h>
#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

View File

@ -21,6 +21,7 @@
#include "config.h"
#include <gio/gio.h>
#include <linux/input-event-codes.h>
#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

View File

@ -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,