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

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