From 1d1983edb54fd59f7061fec616c24d37afa9cdb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20=C3=85dahl?= Date: Fri, 1 Apr 2022 15:22:17 +0200 Subject: [PATCH] input-capture: Hook up barrier adding Adding a barrier and later enabling the input capture session will create MetaBarrier instances for each added input capture barrier. The barriers are created as "sticky" which means that when a pointer hits the barrier, it'll stick to the point of entry, until it's released. The input capture session is also turned into a state machine with explicit state, to more easily track things. Part-of: --- src/backends/meta-input-capture-session.c | 448 ++++++++++++++++++++-- src/tests/input-capture-test-client.c | 193 ++++++++++ src/tests/input-capture-tests.c | 108 +++++- 3 files changed, 724 insertions(+), 25 deletions(-) diff --git a/src/backends/meta-input-capture-session.c b/src/backends/meta-input-capture-session.c index 47a8fd2aa..9a59ffda7 100644 --- a/src/backends/meta-input-capture-session.c +++ b/src/backends/meta-input-capture-session.c @@ -29,6 +29,7 @@ #include "backends/meta-monitor-manager-private.h" #include "backends/meta-logical-monitor.h" #include "backends/meta-remote-access-controller-private.h" +#include "meta/barrier.h" #include "meta/boxes.h" #include "meta/meta-backend.h" @@ -36,6 +37,8 @@ #define META_INPUT_CAPTURE_SESSION_DBUS_PATH "/org/gnome/Mutter/InputCapture/Session" +static GQuark quark_barrier_id; + enum { PROP_0, @@ -43,6 +46,25 @@ enum N_PROPS }; +typedef enum _InputCaptureState +{ + INPUT_CAPTURE_STATE_INIT, + INPUT_CAPTURE_STATE_ENABLED, + INPUT_CAPTURE_STATE_ACTIVATED, + INPUT_CAPTURE_STATE_CLOSED, +} InputCaptureState; + +typedef struct _InputCaptureBarrier +{ + int x1; + int y1; + int x2; + int y2; + + unsigned int id; + MetaBarrier *barrier; +} InputCaptureBarrier; + struct _MetaInputCaptureSession { MetaDBusInputCaptureSessionSkeleton parent; @@ -55,9 +77,11 @@ struct _MetaInputCaptureSession char *session_id; char *object_path; - gboolean enabled; + InputCaptureState state; + GHashTable *barriers; - uint32_t serial; + uint32_t zones_serial; + uint32_t activation_id; MetaInputCaptureSessionHandle *handle; }; @@ -107,31 +131,149 @@ init_remote_access_handle (MetaInputCaptureSession *session) remote_access_handle); } +static void +release_remote_access_handle (MetaInputCaptureSession *session) +{ + MetaRemoteAccessHandle *remote_access_handle = + META_REMOTE_ACCESS_HANDLE (session->handle); + + meta_remote_access_handle_notify_stopped (remote_access_handle); + g_clear_object (&session->handle); +} + +static void +on_barrier_hit (MetaBarrier *barrier, + const MetaBarrierEvent *event, + MetaInputCaptureSession *session) +{ + MetaDBusInputCaptureSession *skeleton = + META_DBUS_INPUT_CAPTURE_SESSION (session); + GVariant *cursor_position; + unsigned int barrier_id; + + switch (session->state) + { + case INPUT_CAPTURE_STATE_ACTIVATED: + return; + case INPUT_CAPTURE_STATE_ENABLED: + break; + case INPUT_CAPTURE_STATE_INIT: + case INPUT_CAPTURE_STATE_CLOSED: + g_warn_if_reached (); + return; + } + + session->state = INPUT_CAPTURE_STATE_ACTIVATED; + + barrier_id = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (barrier), + quark_barrier_id)); + cursor_position = g_variant_new ("(dd)", event->x, event->y); + + meta_dbus_input_capture_session_emit_activated (skeleton, + barrier_id, + ++session->activation_id, + cursor_position); + + init_remote_access_handle (session); +} + +static void +clear_all_barriers (MetaInputCaptureSession *session) +{ + GHashTableIter iter; + InputCaptureBarrier *input_capture_barrier; + + g_hash_table_iter_init (&iter, session->barriers); + while (g_hash_table_iter_next (&iter, NULL, + (gpointer *) &input_capture_barrier)) + g_clear_pointer (&input_capture_barrier->barrier, meta_barrier_destroy); +} + +static void +release_all_barriers (MetaInputCaptureSession *session) +{ + GHashTableIter iter; + InputCaptureBarrier *input_capture_barrier; + + g_hash_table_iter_init (&iter, session->barriers); + while (g_hash_table_iter_next (&iter, NULL, + (gpointer *) &input_capture_barrier)) + { + MetaBarrier *barrier; + + barrier = input_capture_barrier->barrier; + if (!barrier) + continue; + + meta_barrier_release (barrier, NULL); + } +} + static gboolean meta_input_capture_session_enable (MetaInputCaptureSession *session, GError **error) { - g_assert (!session->enabled); + MetaBackend *backend = + meta_dbus_session_manager_get_backend (session->session_manager); + GHashTableIter iter; + gpointer key, value; - session->enabled = TRUE; + g_warn_if_fail (session->state == INPUT_CAPTURE_STATE_INIT); - init_remote_access_handle (session); + g_hash_table_iter_init (&iter, session->barriers); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + unsigned int barrier_id = GPOINTER_TO_UINT (key); + InputCaptureBarrier *input_capture_barrier = value; + g_autoptr (MetaBarrier) barrier = NULL; + + barrier = meta_barrier_new (backend, + input_capture_barrier->x1, + input_capture_barrier->y1, + input_capture_barrier->x2, + input_capture_barrier->y2, + 0, + META_BARRIER_FLAG_STICKY, + error); + if (!barrier) + goto err; + + g_object_set_qdata (G_OBJECT (barrier), quark_barrier_id, + GUINT_TO_POINTER (barrier_id)); + g_signal_connect (barrier, "hit", G_CALLBACK (on_barrier_hit), session); + input_capture_barrier->barrier = barrier; + } + + session->state = INPUT_CAPTURE_STATE_ENABLED; return TRUE; + +err: + clear_all_barriers (session); + return FALSE; } static void meta_input_capture_session_disable (MetaInputCaptureSession *session) { - session->enabled = FALSE; + switch (session->state) + { + case INPUT_CAPTURE_STATE_INIT: + return; + case INPUT_CAPTURE_STATE_ACTIVATED: + case INPUT_CAPTURE_STATE_ENABLED: + break; + case INPUT_CAPTURE_STATE_CLOSED: + g_warn_if_reached (); + return; + } + + clear_all_barriers (session); + + session->state = INPUT_CAPTURE_STATE_INIT; if (session->handle) - { - MetaRemoteAccessHandle *remote_access_handle = - META_REMOTE_ACCESS_HANDLE (session->handle); - - meta_remote_access_handle_notify_stopped (remote_access_handle); - } + release_remote_access_handle (session); } static void @@ -142,8 +284,8 @@ meta_input_capture_session_close (MetaDbusSession *dbus_session) MetaDBusInputCaptureSession *skeleton = META_DBUS_INPUT_CAPTURE_SESSION (session); - if (session->enabled) - meta_input_capture_session_disable (session); + meta_input_capture_session_disable (session); + session->state = INPUT_CAPTURE_STATE_CLOSED; meta_dbus_session_notify_closed (META_DBUS_SESSION (session)); meta_dbus_input_capture_session_emit_closed (skeleton); @@ -160,15 +302,233 @@ check_permission (MetaInputCaptureSession *session, g_dbus_method_invocation_get_sender (invocation)) == 0; } +typedef enum +{ + LINE_ADJACENCY_ERROR, + LINE_ADJACENCY_NONE, + LINE_ADJACENCY_OVERLAP, + LINE_ADJACENCY_CONTAINED, + LINE_ADJACENCY_PARTIAL, +} LineAdjacency; + +static LineAdjacency +get_barrier_adjacency (MetaRectangle *rect, + int x1, + int y1, + int x2, + int y2, + GError **error) +{ + int x_min, x_max; + int y_min, y_max; + + x_min = MIN (x1, x2); + x_max = MAX (x1, x2); + y_min = MIN (y1, y2); + y_max = MAX (y1, y2); + + if (x1 == x2) + { + int x = x1; + + if (x < rect->x || x > rect->x + rect->width) + return LINE_ADJACENCY_NONE; + + if (y_max < rect->y || + y_min > rect->y + rect->height) + return LINE_ADJACENCY_NONE; + + if (rect->x + rect->width == x || rect->x == x) + { + if (y_max > rect->y + rect->height || + y_min < rect->y) + return LINE_ADJACENCY_PARTIAL; + else + return LINE_ADJACENCY_CONTAINED; + } + else + { + return LINE_ADJACENCY_OVERLAP; + } + } + else if (y1 == y2) + { + int y = y1; + + if (y < rect->y || y > rect->y + rect->height) + return LINE_ADJACENCY_NONE; + + if (x_max < rect->x || + x_min > rect->x + rect->width) + return LINE_ADJACENCY_NONE; + + if (rect->y + rect->height == y || rect->y == y) + { + if (x_max > rect->x + rect->width || + x_min < rect->x) + return LINE_ADJACENCY_PARTIAL; + else + return LINE_ADJACENCY_CONTAINED; + } + else + { + return LINE_ADJACENCY_OVERLAP; + } + } + + return LINE_ADJACENCY_NONE; +} + +static gboolean +check_barrier (MetaInputCaptureSession *session, + int x1, + int y1, + int x2, + int y2, + GError **error) +{ + MetaBackend *backend = + meta_dbus_session_manager_get_backend (session->session_manager); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + gboolean has_adjecent_monitor = FALSE; + GList *logical_monitors; + GList *l; + + if (x1 != x2 && y1 != y2) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Barrier coordinates not axis aligned"); + return FALSE; + } + + if (x1 == x2 && y1 == y2) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Barrier cannot be a singularity"); + return FALSE; + } + + logical_monitors = + meta_monitor_manager_get_logical_monitors (monitor_manager); + for (l = logical_monitors; l; l = l->next) + { + MetaLogicalMonitor *logical_monitor = l->data; + MetaRectangle layout; + + layout = meta_logical_monitor_get_layout (logical_monitor); + switch (get_barrier_adjacency (&layout, x1, y1, x2, y2, error)) + { + case LINE_ADJACENCY_ERROR: + return FALSE; + case LINE_ADJACENCY_NONE: + break; + case LINE_ADJACENCY_CONTAINED: + if (has_adjecent_monitor) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Adjecent to multiple monitor edges"); + return FALSE; + } + has_adjecent_monitor = TRUE; + break; + case LINE_ADJACENCY_OVERLAP: + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Line overlaps with monitor region"); + return FALSE; + case LINE_ADJACENCY_PARTIAL: + g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "Line partially with monitor region"); + return FALSE; + } + } + + return has_adjecent_monitor; +} + +static unsigned int +find_available_barrier_id (MetaInputCaptureSession *session) +{ + unsigned int id; + + for (id = 1;; id++) + { + if (!g_hash_table_contains (session->barriers, GUINT_TO_POINTER (id))) + return id; + } +} + +static void +input_capture_barrier_free (gpointer user_data) +{ + InputCaptureBarrier *input_capture_barrier = user_data; + + g_clear_pointer (&input_capture_barrier->barrier, meta_barrier_destroy); + g_free (input_capture_barrier); +} + static gboolean handle_add_barrier (MetaDBusInputCaptureSession *object, GDBusMethodInvocation *invocation, unsigned int serial, GVariant *position) { - g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, - G_DBUS_ERROR_FAILED, - "Not implemented"); + MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (object); + int x1, y1, x2, y2; + g_autoptr (GError) error = NULL; + InputCaptureBarrier *input_capture_barrier; + unsigned int barrier_id; + + 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; + } + + if (session->zones_serial != serial) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_BAD_ADDRESS, + "State out of date"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + if (session->state != INPUT_CAPTURE_STATE_INIT) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + "Session already enabled"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + g_variant_get (position, "(iiii)", &x1, &y1, &x2, &y2); + if (!check_barrier (session, x1, y1, x2, y2, &error)) + { + g_dbus_method_invocation_return_error_literal (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + error->message); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + barrier_id = find_available_barrier_id (session); + + input_capture_barrier = g_new0 (InputCaptureBarrier, 1); + *input_capture_barrier = (InputCaptureBarrier) { + .id = barrier_id, + .x1 = x1, + .y1 = y1, + .x2 = x2, + .y2 = y2, + }; + g_hash_table_insert (session->barriers, + GUINT_TO_POINTER (barrier_id), + input_capture_barrier); + + meta_dbus_input_capture_session_complete_add_barrier (object, invocation, + barrier_id); + return G_DBUS_METHOD_INVOCATION_HANDLED; } @@ -204,7 +564,7 @@ handle_get_zones (MetaDBusInputCaptureSession *object, zones_variant = g_variant_builder_end (&zones_builder); meta_dbus_input_capture_session_complete_get_zones (object, invocation, - session->serial, + session->zones_serial, zones_variant); return G_DBUS_METHOD_INVOCATION_HANDLED; } @@ -224,7 +584,7 @@ handle_enable (MetaDBusInputCaptureSession *skeleton, return G_DBUS_METHOD_INVOCATION_HANDLED; } - if (session->enabled) + if (session->state != INPUT_CAPTURE_STATE_INIT) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, @@ -261,7 +621,8 @@ handle_disable (MetaDBusInputCaptureSession *skeleton, return G_DBUS_METHOD_INVOCATION_HANDLED; } - if (!session->enabled) + if (session->state != INPUT_CAPTURE_STATE_ENABLED && + session->state != INPUT_CAPTURE_STATE_ACTIVATED) { g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, @@ -281,9 +642,40 @@ handle_release (MetaDBusInputCaptureSession *object, GDBusMethodInvocation *invocation, GVariant *position) { - g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, - G_DBUS_ERROR_ACCESS_DENIED, - "Not implemented"); + MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (object); + MetaBackend *backend = + meta_dbus_session_manager_get_backend (session->session_manager); + ClutterSeat *seat = meta_backend_get_default_seat (backend); + double x, y; + + 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; + } + + if (session->state != INPUT_CAPTURE_STATE_ACTIVATED) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_ACCESS_DENIED, + "Capture not active"); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + + release_all_barriers (session); + + session->state = INPUT_CAPTURE_STATE_ENABLED; + + g_variant_get (position, "(dd)", &x, &y); + clutter_seat_warp_pointer (seat, x, y); + + if (session->handle) + release_remote_access_handle (session); + + meta_dbus_input_capture_session_complete_release (object, invocation); + return G_DBUS_METHOD_INVOCATION_HANDLED; } @@ -315,7 +707,7 @@ on_monitors_changed (MetaMonitorManager *monitor_manager, MetaDBusInputCaptureSession *skeleton = META_DBUS_INPUT_CAPTURE_SESSION (session); - session->serial++; + session->zones_serial++; meta_input_capture_session_disable (session); meta_dbus_input_capture_session_emit_zones_changed (skeleton); } @@ -375,6 +767,8 @@ meta_input_capture_session_finalize (GObject *object) { MetaInputCaptureSession *session = META_INPUT_CAPTURE_SESSION (object); + g_clear_pointer (&session->barriers, g_hash_table_unref); + g_clear_object (&session->handle); g_free (session->peer_name); g_free (session->session_id); @@ -444,6 +838,9 @@ meta_input_capture_session_class_init (MetaInputCaptureSessionClass *klass) object_class->get_property = meta_input_capture_session_get_property; meta_dbus_session_install_properties (object_class, N_PROPS); + + quark_barrier_id = + g_quark_from_static_string ("meta-input-capture-barrier-id-quark"); } static void @@ -454,6 +851,9 @@ meta_input_capture_session_init (MetaInputCaptureSession *session) 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 * diff --git a/src/tests/input-capture-test-client.c b/src/tests/input-capture-test-client.c index 4f6bb9856..8693fb829 100644 --- a/src/tests/input-capture-test-client.c +++ b/src/tests/input-capture-test-client.c @@ -21,6 +21,7 @@ #include "config.h" #include +#include #include #include "meta-dbus-input-capture.h" @@ -54,6 +55,8 @@ typedef struct _InputCaptureSession unsigned int serial; } InputCaptureSession; +static GDataInputStream *stdin_reader; + static void ping_mutter (InputCaptureSession *session) { @@ -80,6 +83,54 @@ write_state (InputCaptureSession *session, 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) { @@ -174,6 +225,31 @@ input_capture_session_get_zones (InputCaptureSession *session) 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_enable (InputCaptureSession *session) { @@ -194,6 +270,21 @@ input_capture_session_disable (InputCaptureSession *session) 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; + GVariant *position; + + position = g_variant_new ("(dd)", x, y); + if (!meta_dbus_input_capture_session_call_release_sync (session->proxy, + position, + NULL, &error)) + g_warning ("Failed to release pointer: %s", error->message); +} + static void test_sanity (void) { @@ -296,6 +387,98 @@ test_zones (void) 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 const struct { const char *name; @@ -303,6 +486,7 @@ static const struct } test_cases[] = { { "sanity", test_sanity, }, { "zones", test_zones, }, + { "barriers", test_barriers, }, }; static void @@ -330,7 +514,16 @@ main (int argc, { 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; } } diff --git a/src/tests/input-capture-tests.c b/src/tests/input-capture-tests.c index cb319aff0..9e1eceb71 100644 --- a/src/tests/input-capture-tests.c +++ b/src/tests/input-capture-tests.c @@ -22,6 +22,7 @@ #include +#include "backends/meta-backend-private.h" #include "meta-test/meta-context-test.h" #include "tests/meta-test-utils.h" @@ -31,6 +32,7 @@ typedef struct _InputCaptureTestClient char *path; GMainLoop *main_loop; GDataInputStream *line_reader; + GDataOutputStream *line_writer; } InputCaptureTestClient; static MetaContext *test_context; @@ -45,13 +47,16 @@ input_capture_test_client_new (const char *test_case) InputCaptureTestClient *test_client; GInputStream *stdout_stream; GDataInputStream *line_reader; + GOutputStream *stdin_stream; + GDataOutputStream *line_writer; test_client_path = g_test_build_filename (G_TEST_BUILT, "src", "tests", "mutter-input-capture-test-client", NULL); - launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE); + launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE | + G_SUBPROCESS_FLAGS_STDIN_PIPE); subprocess = g_subprocess_launcher_spawn (launcher, &error, test_client_path, @@ -63,10 +68,14 @@ input_capture_test_client_new (const char *test_case) stdout_stream = g_subprocess_get_stdout_pipe (subprocess); line_reader = g_data_input_stream_new (stdout_stream); + stdin_stream = g_subprocess_get_stdin_pipe (subprocess); + line_writer = g_data_output_stream_new (stdin_stream); + test_client = g_new0 (InputCaptureTestClient, 1); test_client->subprocess = subprocess; test_client->main_loop = g_main_loop_new (NULL, FALSE); test_client->line_reader = line_reader; + test_client->line_writer = line_writer; return test_client; } @@ -118,6 +127,24 @@ input_capture_test_client_wait_for_state (InputCaptureTestClient *test_client, g_main_loop_unref (data.loop); } +static void +input_capture_test_client_write_state (InputCaptureTestClient *test_client, + const char *state) +{ + g_autoptr (GError) error = NULL; + g_autofree char *line = NULL; + + line = g_strdup_printf ("%s\n", state); + + if (!g_data_output_stream_put_string (test_client->line_writer, + line, NULL, &error)) + g_error ("Failed to write state: %s", error->message); + + if (!g_output_stream_flush (G_OUTPUT_STREAM (test_client->line_writer), + NULL, &error)) + g_error ("Failed to flush state: %s", error->message); +} + static void input_capture_test_client_finished (GObject *source_object, GAsyncResult *res, @@ -181,6 +208,83 @@ meta_test_input_capture_zones (void) input_capture_test_client_finish (test_client); } +static void +assert_pointer_position (ClutterSeat *seat, + double x, + double y) +{ + graphene_point_t pos; + + clutter_seat_query_state (seat, + clutter_seat_get_pointer (seat), + NULL, &pos, NULL); + + g_assert_cmpfloat_with_epsilon (pos.x, x, DBL_EPSILON); + g_assert_cmpfloat_with_epsilon (pos.y, y, DBL_EPSILON); +} + +static void +meta_test_input_capture_barriers (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; + InputCaptureTestClient *test_client; + + virtual_monitor1 = meta_create_test_monitor (test_context, 800, 600, 20.0); + virtual_monitor2 = meta_create_test_monitor (test_context, 1024, 768, 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); + + test_client = input_capture_test_client_new ("barriers"); + 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, 10.0); + clutter_virtual_input_device_notify_relative_motion (virtual_pointer, + g_get_monotonic_time (), + -20.0, 10.0); + clutter_virtual_input_device_notify_relative_motion (virtual_pointer, + g_get_monotonic_time (), + -20.0, 10.0); + + meta_flush_input (test_context); + meta_wait_for_paint (test_context); + + assert_pointer_position (seat, 0.0, 15.0); + + input_capture_test_client_write_state (test_client, "1"); + input_capture_test_client_wait_for_state (test_client, "2"); + + meta_flush_input (test_context); + meta_wait_for_paint (test_context); + + assert_pointer_position (seat, 200.0, 150.0); + + clutter_virtual_input_device_notify_relative_motion (virtual_pointer, + g_get_monotonic_time (), + 800.0, 300.0); + meta_flush_input (test_context); + + assert_pointer_position (seat, 1000.0, 450.0); + + clutter_virtual_input_device_notify_relative_motion (virtual_pointer, + g_get_monotonic_time (), + 0.0, 400.0); + + input_capture_test_client_wait_for_state (test_client, "3"); + assert_pointer_position (seat, 1200.0, 700.0); + + input_capture_test_client_finish (test_client); +} + static void init_tests (void) { @@ -188,6 +292,8 @@ init_tests (void) meta_test_input_capture_sanity); g_test_add_func ("/backends/native/input-capture/zones", meta_test_input_capture_zones); + g_test_add_func ("/backends/native/input-capture/barriers", + meta_test_input_capture_barriers); } int