mirror of
https://github.com/brl/mutter.git
synced 2024-12-02 12:50:53 -05:00
2fb3bdf774
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>
937 lines
26 KiB
C
937 lines
26 KiB
C
/*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#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
|
|
{
|
|
unsigned int width;
|
|
unsigned int height;
|
|
int x;
|
|
int y;
|
|
} Zone;
|
|
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC (Zone, g_free)
|
|
|
|
typedef enum _Capabilities
|
|
{
|
|
CAPABILITY_NONE = 0,
|
|
CAPABILITY_KEYBOARD = 1,
|
|
CAPABILITY_POINTER = 2,
|
|
CAPABILITY_TOUCH = 4,
|
|
} Capabilities;
|
|
|
|
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;
|
|
|
|
static void
|
|
ping_mutter (InputCaptureSession *session)
|
|
{
|
|
GDBusProxy *proxy = G_DBUS_PROXY (session->proxy);
|
|
GError *error = NULL;
|
|
|
|
if (!g_dbus_connection_call_sync (g_dbus_proxy_get_connection (proxy),
|
|
"org.gnome.Mutter.InputCapture",
|
|
g_dbus_proxy_get_object_path (proxy),
|
|
"org.freedesktop.DBus.Peer",
|
|
"Ping",
|
|
NULL,
|
|
NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1,
|
|
NULL, &error))
|
|
g_error ("Failed to ping D-Bus peer: %s", error->message);
|
|
}
|
|
|
|
static void
|
|
write_state (InputCaptureSession *session,
|
|
const char *state)
|
|
{
|
|
ping_mutter (session);
|
|
fprintf (stdout, "%s\n", state);
|
|
fflush (stdout);
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
GMainLoop *loop;
|
|
const char *expected_state;
|
|
} WaitData;
|
|
|
|
static void
|
|
on_line_read (GObject *source_object,
|
|
GAsyncResult *res,
|
|
gpointer user_data)
|
|
{
|
|
WaitData *data = user_data;
|
|
g_autofree char *line = NULL;
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
line =
|
|
g_data_input_stream_read_line_finish (G_DATA_INPUT_STREAM (source_object),
|
|
res, NULL, &error);
|
|
if (error)
|
|
g_error ("Failed to read line from test client: %s", error->message);
|
|
if (!line)
|
|
g_error ("Unexpected EOF");
|
|
|
|
g_assert_cmpstr (data->expected_state, ==, line);
|
|
|
|
g_main_loop_quit (data->loop);
|
|
}
|
|
|
|
static void
|
|
wait_for_state (InputCaptureSession *session,
|
|
const char *expected_state)
|
|
{
|
|
WaitData data;
|
|
|
|
data.loop = g_main_loop_new (NULL, FALSE);
|
|
data.expected_state = expected_state;
|
|
|
|
g_data_input_stream_read_line_async (stdin_reader,
|
|
G_PRIORITY_DEFAULT,
|
|
NULL,
|
|
on_line_read,
|
|
&data);
|
|
|
|
g_main_loop_run (data.loop);
|
|
g_main_loop_unref (data.loop);
|
|
ping_mutter (session);
|
|
}
|
|
|
|
static InputCapture *
|
|
input_capture_new (void)
|
|
{
|
|
InputCapture *input_capture;
|
|
GError *error = NULL;
|
|
|
|
input_capture = g_new0 (InputCapture, 1);
|
|
input_capture->proxy = meta_dbus_input_capture_proxy_new_for_bus_sync (
|
|
G_BUS_TYPE_SESSION,
|
|
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
|
|
"org.gnome.Mutter.InputCapture",
|
|
"/org/gnome/Mutter/InputCapture",
|
|
NULL,
|
|
&error);
|
|
if (!input_capture->proxy)
|
|
g_error ("Failed to acquire proxy: %s", error->message);
|
|
|
|
return input_capture;
|
|
}
|
|
|
|
static InputCaptureSession *
|
|
input_capture_create_session (InputCapture *input_capture)
|
|
{
|
|
GError *error = NULL;
|
|
InputCaptureSession *session;
|
|
g_autofree char *session_path = NULL;
|
|
|
|
if (!meta_dbus_input_capture_call_create_session_sync (input_capture->proxy,
|
|
CAPABILITY_POINTER,
|
|
&session_path,
|
|
NULL,
|
|
&error))
|
|
g_error ("Failed to create input capture session: %s", error->message);
|
|
|
|
session = g_new0 (InputCaptureSession, 1);
|
|
session->proxy = meta_dbus_input_capture_session_proxy_new_for_bus_sync (
|
|
G_BUS_TYPE_SESSION,
|
|
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
|
|
"org.gnome.Mutter.InputCapture",
|
|
session_path,
|
|
NULL, &error);
|
|
if (!session->proxy)
|
|
g_error ("Failed to acquire proxy: %s", error->message);
|
|
|
|
return session;
|
|
}
|
|
|
|
static void
|
|
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);
|
|
|
|
g_object_unref (session->proxy);
|
|
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)
|
|
{
|
|
GError *error = NULL;
|
|
g_autoptr (GVariant) zones_variant = NULL;
|
|
GVariantIter iter;
|
|
GList *zones = NULL;
|
|
unsigned int width, height;
|
|
int x, y;
|
|
|
|
if (!meta_dbus_input_capture_session_call_get_zones_sync (session->proxy,
|
|
&session->serial,
|
|
&zones_variant,
|
|
NULL, &error))
|
|
g_error ("Failed to get zones: %s", error->message);
|
|
|
|
g_variant_iter_init (&iter, zones_variant);
|
|
while (g_variant_iter_next (&iter, "(uuii)", &width, &height, &x, &y))
|
|
{
|
|
Zone *zone;
|
|
|
|
zone = g_new0 (Zone, 1);
|
|
*zone = (Zone) {
|
|
.width = width,
|
|
.height = height,
|
|
.x = x,
|
|
.y = y,
|
|
};
|
|
zones = g_list_append (zones, zone);
|
|
}
|
|
|
|
return zones;
|
|
}
|
|
|
|
static unsigned int
|
|
input_capture_session_add_barrier (InputCaptureSession *session,
|
|
int x1,
|
|
int y1,
|
|
int x2,
|
|
int y2)
|
|
{
|
|
g_autoptr (GError) error = NULL;
|
|
unsigned int barrier_id;
|
|
|
|
if (!meta_dbus_input_capture_session_call_add_barrier_sync (
|
|
session->proxy,
|
|
session->serial,
|
|
g_variant_new ("(iiii)", x1, y1, x2, y2),
|
|
&barrier_id,
|
|
NULL,
|
|
&error))
|
|
{
|
|
g_warning ("Failed to add barrier: %s", error->message);
|
|
return 0;
|
|
}
|
|
|
|
return barrier_id;
|
|
}
|
|
|
|
static void
|
|
input_capture_session_clear_barriers (InputCaptureSession *session)
|
|
{
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
if (!meta_dbus_input_capture_session_call_clear_barriers_sync (
|
|
session->proxy, NULL, &error))
|
|
g_warning ("Failed to clear barriers: %s", error->message);
|
|
}
|
|
|
|
static void
|
|
input_capture_session_enable (InputCaptureSession *session)
|
|
{
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
if (!meta_dbus_input_capture_session_call_enable_sync (session->proxy,
|
|
NULL, &error))
|
|
g_warning ("Failed to enable session: %s", error->message);
|
|
}
|
|
|
|
static void
|
|
input_capture_session_disable (InputCaptureSession *session)
|
|
{
|
|
g_autoptr (GError) error = NULL;
|
|
|
|
if (!meta_dbus_input_capture_session_call_disable_sync (session->proxy,
|
|
NULL, &error))
|
|
g_warning ("Failed to disable session: %s", error->message);
|
|
}
|
|
|
|
static void
|
|
input_capture_session_release (InputCaptureSession *session,
|
|
double x,
|
|
double y)
|
|
{
|
|
g_autoptr (GError) error = NULL;
|
|
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));
|
|
|
|
if (!meta_dbus_input_capture_session_call_release_sync (session->proxy,
|
|
g_variant_builder_end (&options_builder),
|
|
NULL, &error))
|
|
g_warning ("Failed to release pointer: %s", error->message);
|
|
}
|
|
|
|
static void
|
|
test_sanity (void)
|
|
{
|
|
InputCapture *input_capture;
|
|
InputCaptureSession *session;
|
|
|
|
input_capture = input_capture_new ();
|
|
session = input_capture_create_session (input_capture);
|
|
|
|
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
|
|
"*org.freedesktop.DBus.Error.Failed: Session not enabled*");
|
|
input_capture_session_disable (session);
|
|
g_test_assert_expected_messages ();
|
|
|
|
input_capture_session_enable (session);
|
|
|
|
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
|
|
"*org.freedesktop.DBus.Error.Failed: Already enabled*");
|
|
input_capture_session_enable (session);
|
|
g_test_assert_expected_messages ();
|
|
|
|
input_capture_session_disable (session);
|
|
|
|
g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
|
|
"*org.freedesktop.DBus.Error.Failed: Session not enabled*");
|
|
input_capture_session_disable (session);
|
|
g_test_assert_expected_messages ();
|
|
|
|
input_capture_session_close (session);
|
|
}
|
|
|
|
static void
|
|
on_zones_changed (MetaDBusInputCaptureSession *proxy,
|
|
int *zones_changed_count)
|
|
{
|
|
*zones_changed_count += 1;
|
|
}
|
|
|
|
static void
|
|
assert_zones (GList *zones,
|
|
const Zone *expected_zones,
|
|
int n_expected_zones)
|
|
{
|
|
GList *l;
|
|
int i;
|
|
|
|
g_assert_cmpuint (g_list_length (zones), ==, n_expected_zones);
|
|
|
|
for (l = zones, i = 0; l; l = l->next, i++)
|
|
{
|
|
Zone *zone = l->data;
|
|
|
|
g_assert_cmpint (zone->width, ==, expected_zones[i].width);
|
|
g_assert_cmpint (zone->height, ==, expected_zones[i].height);
|
|
g_assert_cmpint (zone->x, ==, expected_zones[i].x);
|
|
g_assert_cmpint (zone->y, ==, expected_zones[i].y);
|
|
}
|
|
}
|
|
|
|
static void
|
|
test_zones (void)
|
|
{
|
|
InputCapture *input_capture;
|
|
InputCaptureSession *session;
|
|
static const Zone expected_zones1[] = {
|
|
{ .width = 800, .height = 600, .x = 0, .y = 0 },
|
|
{ .width = 1024, .height = 768, .x = 800, .y = 0 },
|
|
};
|
|
static const Zone expected_zones2[] = {
|
|
{ .width = 1024, .height = 768, .x = 0, .y = 0 },
|
|
};
|
|
GList *zones;
|
|
int zones_changed_count = 0;
|
|
unsigned int serial;
|
|
|
|
input_capture = input_capture_new ();
|
|
session = input_capture_create_session (input_capture);
|
|
|
|
g_signal_connect (session->proxy, "zones-changed",
|
|
G_CALLBACK (on_zones_changed),
|
|
&zones_changed_count);
|
|
|
|
zones = input_capture_session_get_zones (session);
|
|
assert_zones (zones, expected_zones1, G_N_ELEMENTS (expected_zones1));
|
|
g_clear_list (&zones, g_free);
|
|
|
|
write_state (session, "1");
|
|
|
|
while (zones_changed_count == 0)
|
|
g_main_context_iteration (NULL, TRUE);
|
|
|
|
serial = session->serial;
|
|
g_clear_list (&zones, g_free);
|
|
|
|
zones = input_capture_session_get_zones (session);
|
|
g_assert_cmpuint (session->serial, >, serial);
|
|
assert_zones (zones, expected_zones2, G_N_ELEMENTS (expected_zones2));
|
|
g_clear_list (&zones, g_free);
|
|
|
|
input_capture_session_close (session);
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
unsigned int activated_barrier_id;
|
|
double activated_x;
|
|
double activated_y;
|
|
unsigned int activated_serial;
|
|
} BarriersTestData;
|
|
|
|
static void
|
|
on_activated (MetaDBusInputCaptureSession *proxy,
|
|
unsigned int barrier_id,
|
|
unsigned int serial,
|
|
GVariant *cursor_position,
|
|
BarriersTestData *data)
|
|
{
|
|
g_assert_cmpuint (data->activated_barrier_id, ==, 0);
|
|
|
|
data->activated_barrier_id = barrier_id;
|
|
data->activated_serial = serial;
|
|
g_variant_get (cursor_position, "(dd)",
|
|
&data->activated_x, &data->activated_y);
|
|
}
|
|
|
|
static void
|
|
test_barriers (void)
|
|
{
|
|
InputCapture *input_capture;
|
|
InputCaptureSession *session;
|
|
g_autolist (Zone) zones = NULL;
|
|
unsigned int barrier1, barrier2;
|
|
BarriersTestData data = {};
|
|
unsigned int prev_activated_serial;
|
|
|
|
input_capture = input_capture_new ();
|
|
session = input_capture_create_session (input_capture);
|
|
|
|
zones = input_capture_session_get_zones (session);
|
|
|
|
/*
|
|
* +-------------+--------------+
|
|
* || | |
|
|
* ||<--B#1 | |
|
|
* || | B#2 |
|
|
* +-------------+ | |
|
|
* | V |
|
|
* +==============+
|
|
*/
|
|
barrier1 = input_capture_session_add_barrier (session, 0, 0, 0, 600);
|
|
barrier2 = input_capture_session_add_barrier (session, 800, 768, 1824, 768);
|
|
|
|
g_assert_cmpuint (barrier1, !=, 0);
|
|
g_assert_cmpuint (barrier2, !=, 0);
|
|
g_assert_cmpuint (barrier1, !=, barrier2);
|
|
|
|
g_signal_connect (session->proxy, "activated",
|
|
G_CALLBACK (on_activated), &data);
|
|
|
|
input_capture_session_enable (session);
|
|
|
|
write_state (session, "1");
|
|
|
|
while (data.activated_barrier_id == 0)
|
|
g_main_context_iteration (NULL, TRUE);
|
|
|
|
g_assert_cmpuint (data.activated_serial, !=, 0);
|
|
g_assert_cmpuint (data.activated_barrier_id, ==, barrier1);
|
|
g_assert_cmpfloat_with_epsilon (data.activated_x, 0.0, DBL_EPSILON);
|
|
g_assert_cmpfloat_with_epsilon (data.activated_y, 15.0, DBL_EPSILON);
|
|
|
|
wait_for_state (session, "1");
|
|
|
|
input_capture_session_release (session, 200, 150);
|
|
|
|
write_state (session, "2");
|
|
|
|
prev_activated_serial = data.activated_serial;
|
|
|
|
data = (BarriersTestData) {};
|
|
while (data.activated_barrier_id == 0)
|
|
g_main_context_iteration (NULL, TRUE);
|
|
g_assert_cmpuint (data.activated_serial, !=, 0);
|
|
g_assert_cmpuint (data.activated_serial, !=, prev_activated_serial);
|
|
g_assert_cmpuint (data.activated_barrier_id, ==, barrier2);
|
|
g_assert_cmpfloat_with_epsilon (data.activated_x, 1000.0, DBL_EPSILON);
|
|
g_assert_cmpfloat_with_epsilon (data.activated_y, 768.0, DBL_EPSILON);
|
|
|
|
input_capture_session_release (session, 1200, 700);
|
|
write_state (session, "3");
|
|
|
|
input_capture_session_close (session);
|
|
}
|
|
|
|
static void
|
|
test_clear_barriers (void)
|
|
{
|
|
InputCapture *input_capture;
|
|
InputCaptureSession *session;
|
|
g_autolist (Zone) zones = NULL;
|
|
BarriersTestData data = {};
|
|
|
|
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);
|
|
|
|
g_signal_connect (session->proxy, "activated",
|
|
G_CALLBACK (on_activated), &data);
|
|
|
|
input_capture_session_enable (session);
|
|
|
|
write_state (session, "1");
|
|
|
|
while (data.activated_barrier_id == 0)
|
|
g_main_context_iteration (NULL, TRUE);
|
|
|
|
input_capture_session_clear_barriers (session);
|
|
write_state (session, "2");
|
|
wait_for_state (session, "1");
|
|
|
|
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;
|
|
void (* func) (void);
|
|
} test_cases[] = {
|
|
{ "sanity", test_sanity, },
|
|
{ "zones", test_zones, },
|
|
{ "barriers", test_barriers, },
|
|
{ "clear-barriers", test_clear_barriers, },
|
|
{ "cancel-keybinding", test_cancel_keybinding, },
|
|
{ "events", test_events, },
|
|
};
|
|
|
|
static void
|
|
print_to_stderr (const char *text)
|
|
{
|
|
fputs (text, stderr);
|
|
fflush (stderr);
|
|
}
|
|
|
|
int
|
|
main (int argc,
|
|
char **argv)
|
|
{
|
|
const char *test_case;
|
|
int i;
|
|
|
|
g_assert_cmpint (argc, ==, 2);
|
|
|
|
test_case = argv[1];
|
|
|
|
g_set_print_handler (print_to_stderr);
|
|
g_test_init (&argc, &argv, NULL);
|
|
|
|
for (i = 0; i < G_N_ELEMENTS (test_cases); i++)
|
|
{
|
|
if (g_strcmp0 (test_cases[i].name, test_case) == 0)
|
|
{
|
|
g_autoptr (GInputStream) stdin_stream = NULL;
|
|
|
|
stdin_stream = g_unix_input_stream_new (fileno (stdin), FALSE);
|
|
stdin_reader = g_data_input_stream_new (stdin_stream);
|
|
|
|
test_cases[i].func ();
|
|
|
|
g_clear_object (&stdin_reader);
|
|
g_clear_object (&stdin_stream);
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
}
|
|
|
|
g_warning ("Invalid test case '%s'", test_case);
|
|
return EXIT_FAILURE;
|
|
}
|