diff --git a/data/meson.build b/data/meson.build
index 59e98a1f1..eea49adda 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -18,6 +18,7 @@ portal_sources = files(
'org.freedesktop.portal.GameMode.xml',
'org.freedesktop.portal.GlobalShortcuts.xml',
'org.freedesktop.portal.Inhibit.xml',
+ 'org.freedesktop.portal.InputCapture.xml',
'org.freedesktop.portal.Location.xml',
'org.freedesktop.portal.MemoryMonitor.xml',
'org.freedesktop.portal.NetworkMonitor.xml',
@@ -49,6 +50,7 @@ portal_impl_sources = files(
'org.freedesktop.impl.portal.FileChooser.xml',
'org.freedesktop.impl.portal.GlobalShortcuts.xml',
'org.freedesktop.impl.portal.Inhibit.xml',
+ 'org.freedesktop.impl.portal.InputCapture.xml',
'org.freedesktop.impl.portal.Lockdown.xml',
'org.freedesktop.impl.portal.Notification.xml',
'org.freedesktop.impl.portal.PermissionStore.xml',
diff --git a/data/org.freedesktop.impl.portal.InputCapture.xml b/data/org.freedesktop.impl.portal.InputCapture.xml
new file mode 100644
index 000000000..e6b38b0bc
--- /dev/null
+++ b/data/org.freedesktop.impl.portal.InputCapture.xml
@@ -0,0 +1,436 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/org.freedesktop.portal.InputCapture.xml b/data/org.freedesktop.portal.InputCapture.xml
new file mode 100644
index 000000000..286bc0969
--- /dev/null
+++ b/data/org.freedesktop.portal.InputCapture.xml
@@ -0,0 +1,587 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/portal-docs.xml.in b/doc/portal-docs.xml.in
index d7a00d3c0..c32a8e0ad 100644
--- a/doc/portal-docs.xml.in
+++ b/doc/portal-docs.xml.in
@@ -105,6 +105,7 @@
+
@@ -154,6 +155,7 @@
+
diff --git a/src/input-capture.c b/src/input-capture.c
new file mode 100644
index 000000000..9945d8239
--- /dev/null
+++ b/src/input-capture.c
@@ -0,0 +1,1195 @@
+/*
+ * Copyright © 2017-2018 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ */
+
+#include "config.h"
+
+#include
+#include
+#include
+
+#include "session.h"
+#include "input-capture.h"
+#include "request.h"
+#include "xdp-dbus.h"
+#include "xdp-impl-dbus.h"
+#include "xdp-utils.h"
+
+#define VERSION_1 1 /* Makes grep easier */
+
+typedef struct _InputCapture InputCapture;
+typedef struct _InputCaptureClass InputCaptureClass;
+
+struct _InputCapture
+{
+ XdpDbusInputCaptureSkeleton parent_instance;
+};
+
+struct _InputCaptureClass
+{
+ XdpDbusInputCaptureSkeletonClass parent_class;
+};
+
+static XdpDbusImplInputCapture *impl;
+static int impl_version;
+static InputCapture *input_capture;
+
+static GQuark quark_request_session;
+
+GType input_capture_get_type (void);
+static void input_capture_iface_init (XdpDbusInputCaptureIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (InputCapture, input_capture, XDP_DBUS_TYPE_INPUT_CAPTURE_SKELETON,
+ G_IMPLEMENT_INTERFACE (XDP_DBUS_TYPE_INPUT_CAPTURE,
+ input_capture_iface_init))
+
+typedef enum _InputCaptureSessionState
+{
+ INPUT_CAPTURE_SESSION_STATE_INIT,
+ INPUT_CAPTURE_SESSION_STATE_ENABLED,
+ INPUT_CAPTURE_SESSION_STATE_ACTIVE,
+ INPUT_CAPTURE_SESSION_STATE_DISABLED,
+ INPUT_CAPTURE_SESSION_STATE_CLOSED
+} InputCaptureSessionState;
+
+typedef struct _InputCaptureSession
+{
+ Session parent;
+
+ InputCaptureSessionState state;
+} InputCaptureSession;
+
+typedef struct _InputCaptureSessionClass
+{
+ SessionClass parent_class;
+} InputCaptureSessionClass;
+
+GType input_capture_session_get_type (void);
+
+G_DEFINE_TYPE (InputCaptureSession, input_capture_session, session_get_type ())
+
+static gboolean
+is_input_capture_session (Session *session)
+{
+ return G_TYPE_CHECK_INSTANCE_TYPE (session, input_capture_session_get_type ());
+}
+
+static InputCaptureSession *
+input_capture_session_new (GVariant *options,
+ Request *request,
+ GError **error)
+{
+ Session *session;
+ GDBusInterfaceSkeleton *interface_skeleton =
+ G_DBUS_INTERFACE_SKELETON (request);
+ const char *session_token;
+ GDBusConnection *connection =
+ g_dbus_interface_skeleton_get_connection (interface_skeleton);
+ GDBusConnection *impl_connection =
+ g_dbus_proxy_get_connection (G_DBUS_PROXY (impl));
+ const char *impl_dbus_name = g_dbus_proxy_get_name (G_DBUS_PROXY (impl));
+
+ session_token = lookup_session_token (options);
+ session = g_initable_new (input_capture_session_get_type (), NULL, error,
+ "sender", request->sender,
+ "app-id", xdp_app_info_get_id (request->app_info),
+ "token", session_token,
+ "connection", connection,
+ "impl-connection", impl_connection,
+ "impl-dbus-name", impl_dbus_name,
+ NULL);
+
+ if (session)
+ g_debug ("capture input session owned by '%s' created", session->sender);
+
+ return (InputCaptureSession*)session;
+}
+
+static void
+create_session_done (GObject *source_object,
+ GAsyncResult *res,
+ gpointer data)
+{
+ g_autoptr(Request) request = data;
+ Session *session;
+ guint response = 2;
+ GVariant *results;
+ gboolean should_close_session;
+ GVariantBuilder results_builder;
+ g_autoptr(GError) error = NULL;
+ guint capabilities = 0;
+
+ REQUEST_AUTOLOCK (request);
+
+ session = g_object_get_qdata (G_OBJECT (request), quark_request_session);
+ SESSION_AUTOLOCK_UNREF (g_object_ref (session));
+ g_object_set_qdata (G_OBJECT (request), quark_request_session, NULL);
+
+ g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT);
+
+ if (!xdp_dbus_impl_input_capture_call_create_session_finish (impl,
+ &response,
+ &results,
+ res,
+ &error))
+ {
+ g_dbus_error_strip_remote_error (error);
+ g_warning ("A backend call failed: %s", error->message);
+ should_close_session = TRUE;
+ goto out;
+ }
+
+ if (request->exported && response == 0)
+ {
+ if (!session_export (session, &error))
+ {
+ g_warning ("Failed to export session: %s", error->message);
+ response = 2;
+ should_close_session = TRUE;
+ goto out;
+ }
+
+ if (!g_variant_lookup (results, "capabilities", "u", &capabilities))
+ {
+ g_warning ("Impl did not set capabilities");
+ response = 2;
+ should_close_session = TRUE;
+ goto out;
+ }
+
+ should_close_session = FALSE;
+ session_register (session);
+
+ g_variant_builder_add (&results_builder, "{sv}",
+ "capabilities", g_variant_new_uint32 (capabilities));
+ g_variant_builder_add (&results_builder, "{sv}",
+ "session_handle", g_variant_new ("o", session->id));
+ }
+ else
+ {
+ should_close_session = TRUE;
+ }
+
+out:
+ if (request->exported)
+ {
+ xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request),
+ response,
+ g_variant_builder_end (&results_builder));
+ request_unexport (request);
+ }
+ else
+ {
+ g_variant_builder_clear (&results_builder);
+ }
+
+ if (should_close_session)
+ session_close (session, FALSE);
+}
+
+static gboolean
+validate_capabilities (const char *key,
+ GVariant *value,
+ GVariant *options,
+ GError **error)
+{
+ guint32 types = g_variant_get_uint32 (value);
+
+ if ((types & ~(1 | 2 | 4 | 8)) != 0)
+ {
+ g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT,
+ "Unsupported capability: %x", types & ~(1 | 2 | 4 | 8));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static XdpOptionKey input_capture_create_session_options[] = {
+ { "capabilities", G_VARIANT_TYPE_UINT32, validate_capabilities },
+};
+
+static gboolean
+handle_create_session (XdpDbusInputCapture *object,
+ GDBusMethodInvocation *invocation,
+ const char *arg_parent_window,
+ GVariant *arg_options)
+{
+ Request *request = request_from_invocation (invocation);
+ g_autoptr(GError) error = NULL;
+ g_autoptr(XdpDbusImplRequest) impl_request = NULL;
+ Session *session;
+ GVariantBuilder options_builder;
+ GVariant *options;
+
+ REQUEST_AUTOLOCK (request);
+
+ impl_request =
+ xdp_dbus_impl_request_proxy_new_sync (g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)),
+ G_DBUS_PROXY_FLAGS_NONE,
+ g_dbus_proxy_get_name (G_DBUS_PROXY (impl)),
+ request->id,
+ NULL, &error);
+ if (!impl_request)
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ request_set_impl_request (request, impl_request);
+ request_export (request, g_dbus_method_invocation_get_connection (invocation));
+
+ session = (Session *)input_capture_session_new (arg_options, request, &error);
+ if (!session)
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT);
+ if (!xdp_filter_options (arg_options, &options_builder,
+ input_capture_create_session_options,
+ G_N_ELEMENTS (input_capture_create_session_options),
+ &error))
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+ options = g_variant_builder_end (&options_builder);
+
+ g_object_set_qdata_full (G_OBJECT (request),
+ quark_request_session,
+ g_object_ref (session),
+ g_object_unref);
+
+ xdp_dbus_impl_input_capture_call_create_session (impl,
+ request->id,
+ session->id,
+ xdp_app_info_get_id (request->app_info),
+ arg_parent_window,
+ options,
+ NULL,
+ create_session_done,
+ g_object_ref (request));
+
+ xdp_dbus_input_capture_complete_create_session (object, invocation, request->id);
+
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static void
+get_zones_done (GObject *source_object, GAsyncResult *res, gpointer data)
+{
+ g_autoptr(Request) request = data;
+ Session *session;
+ guint response = 2;
+ gboolean should_close_session;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GVariant) results = NULL;
+
+ REQUEST_AUTOLOCK (request);
+
+ session = g_object_get_qdata (G_OBJECT (request), quark_request_session);
+ SESSION_AUTOLOCK_UNREF (g_object_ref (session));
+ g_object_set_qdata (G_OBJECT (request), quark_request_session, NULL);
+
+ if (!xdp_dbus_impl_input_capture_call_get_zones_finish (impl,
+ &response,
+ &results,
+ res,
+ &error))
+ {
+ g_dbus_error_strip_remote_error (error);
+ g_warning ("A backend call failed: %s", error->message);
+ }
+
+ should_close_session = !request->exported || response != 0;
+
+ if (request->exported)
+ {
+ if (response != 0)
+ {
+ GVariantBuilder results_builder;
+
+ g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT);
+ results = g_variant_builder_end (&results_builder);
+ }
+
+ xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, results);
+ request_unexport (request);
+ }
+
+ if (should_close_session)
+ {
+ session_close (session, TRUE);
+ }
+}
+
+static XdpOptionKey input_capture_get_zones_options[] = {
+};
+
+static gboolean
+handle_get_zones (XdpDbusInputCapture *object,
+ GDBusMethodInvocation *invocation,
+ const char *arg_session_handle,
+ GVariant *arg_options)
+{
+ Request *request = request_from_invocation (invocation);
+ Session *session;
+ InputCaptureSession *input_capture_session;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(XdpDbusImplRequest) impl_request = NULL;
+ GVariantBuilder options_builder;
+ GVariant *options;
+
+ REQUEST_AUTOLOCK (request);
+
+ session = acquire_session (arg_session_handle, request);
+ if (!session)
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_ACCESS_DENIED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ SESSION_AUTOLOCK_UNREF (session);
+
+ if (!is_input_capture_session (session))
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ input_capture_session = (InputCaptureSession *)session;
+
+ switch (input_capture_session->state)
+ {
+ case INPUT_CAPTURE_SESSION_STATE_INIT:
+ case INPUT_CAPTURE_SESSION_STATE_ENABLED:
+ case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
+ case INPUT_CAPTURE_SESSION_STATE_DISABLED:
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_CLOSED:
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ impl_request =
+ xdp_dbus_impl_request_proxy_new_sync (g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)),
+ G_DBUS_PROXY_FLAGS_NONE,
+ g_dbus_proxy_get_name (G_DBUS_PROXY (impl)),
+ request->id,
+ NULL, &error);
+ if (!impl_request)
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ request_set_impl_request (request, impl_request);
+ request_export (request, g_dbus_method_invocation_get_connection (invocation));
+
+ g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT);
+ if (!xdp_filter_options (arg_options, &options_builder,
+ input_capture_get_zones_options,
+ G_N_ELEMENTS (input_capture_get_zones_options),
+ &error))
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ options = g_variant_builder_end (&options_builder);
+
+ g_object_set_qdata_full (G_OBJECT (request),
+ quark_request_session,
+ g_object_ref (session),
+ g_object_unref);
+
+ xdp_dbus_impl_input_capture_call_get_zones (impl,
+ request->id,
+ arg_session_handle,
+ xdp_app_info_get_id (request->app_info),
+ options,
+ NULL,
+ get_zones_done,
+ g_object_ref (request));
+
+ xdp_dbus_input_capture_complete_get_zones (object, invocation, request->id);
+
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static void
+set_pointer_barriers_done (GObject *source_object, GAsyncResult *res, gpointer data)
+{
+ g_autoptr(Request) request = data;
+ Session *session;
+ guint response = 2;
+ gboolean should_close_session;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GVariant) results = NULL;
+
+ REQUEST_AUTOLOCK (request);
+
+ session = g_object_get_qdata (G_OBJECT (request), quark_request_session);
+ SESSION_AUTOLOCK_UNREF (g_object_ref (session));
+ g_object_set_qdata (G_OBJECT (request), quark_request_session, NULL);
+
+ if (!xdp_dbus_impl_input_capture_call_set_pointer_barriers_finish (impl,
+ &response,
+ &results,
+ res,
+ &error))
+ {
+ g_dbus_error_strip_remote_error (error);
+ g_warning ("A backend call failed: %s", error->message);
+ }
+
+ should_close_session = !request->exported || response != 0;
+
+ if (request->exported)
+ {
+ if (response != 0)
+ {
+ GVariantBuilder results_builder;
+
+ g_variant_builder_init (&results_builder, G_VARIANT_TYPE_VARDICT);
+ results = g_variant_builder_end (&results_builder);
+ }
+
+ xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, results);
+ request_unexport (request);
+ }
+
+ if (should_close_session)
+ {
+ session_close (session, TRUE);
+ }
+}
+
+static XdpOptionKey input_capture_set_pointer_barriers_options[] = {
+};
+
+static gboolean
+handle_set_pointer_barriers (XdpDbusInputCapture *object,
+ GDBusMethodInvocation *invocation,
+ const char *arg_session_handle,
+ GVariant *arg_options,
+ GVariant *arg_barriers,
+ guint arg_zone_set)
+{
+ Request *request = request_from_invocation (invocation);
+ Session *session;
+ InputCaptureSession *input_capture_session;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(XdpDbusImplRequest) impl_request = NULL;
+ GVariantBuilder options_builder;
+ GVariant *options;
+
+ REQUEST_AUTOLOCK (request);
+
+ session = acquire_session (arg_session_handle, request);
+ if (!session)
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_ACCESS_DENIED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ SESSION_AUTOLOCK_UNREF (session);
+
+ if (!is_input_capture_session (session))
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ input_capture_session = (InputCaptureSession *)session;
+
+ switch (input_capture_session->state)
+ {
+ case INPUT_CAPTURE_SESSION_STATE_INIT:
+ case INPUT_CAPTURE_SESSION_STATE_ENABLED:
+ case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
+ case INPUT_CAPTURE_SESSION_STATE_DISABLED:
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_CLOSED:
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ impl_request =
+ xdp_dbus_impl_request_proxy_new_sync (g_dbus_proxy_get_connection (G_DBUS_PROXY (impl)),
+ G_DBUS_PROXY_FLAGS_NONE,
+ g_dbus_proxy_get_name (G_DBUS_PROXY (impl)),
+ request->id,
+ NULL, &error);
+ if (!impl_request)
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ request_set_impl_request (request, impl_request);
+ request_export (request, g_dbus_method_invocation_get_connection (invocation));
+
+ g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT);
+ if (!xdp_filter_options (arg_options, &options_builder,
+ input_capture_set_pointer_barriers_options,
+ G_N_ELEMENTS (input_capture_set_pointer_barriers_options),
+ &error))
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ options = g_variant_builder_end (&options_builder);
+
+ g_object_set_qdata_full (G_OBJECT (request),
+ quark_request_session,
+ g_object_ref (session),
+ g_object_unref);
+
+ xdp_dbus_impl_input_capture_call_set_pointer_barriers (impl,
+ request->id,
+ arg_session_handle,
+ xdp_app_info_get_id (request->app_info),
+ options,
+ g_variant_ref(arg_barriers), /* FIXME: validation? */
+ arg_zone_set, /* FIXME: validation? */
+ NULL,
+ set_pointer_barriers_done,
+ g_object_ref (request));
+
+ xdp_dbus_input_capture_complete_set_pointer_barriers (object, invocation, request->id);
+
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static XdpOptionKey input_capture_enable_options[] = {
+};
+
+static gboolean
+handle_enable (XdpDbusInputCapture *object,
+ GDBusMethodInvocation *invocation,
+ const char *arg_session_handle,
+ GVariant *arg_options)
+{
+ Call *call = call_from_invocation (invocation);
+ Session *session;
+ InputCaptureSession *input_capture_session;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(XdpDbusImplRequest) impl_request = NULL;
+ GVariantBuilder options_builder;
+
+ session = acquire_session_from_call (arg_session_handle, call);
+ if (!session)
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_ACCESS_DENIED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ SESSION_AUTOLOCK_UNREF (session);
+
+ if (!is_input_capture_session (session))
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ input_capture_session = (InputCaptureSession *)session;
+
+ switch (input_capture_session->state)
+ {
+ case INPUT_CAPTURE_SESSION_STATE_INIT:
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Not connected to EIS");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ case INPUT_CAPTURE_SESSION_STATE_ENABLED:
+ case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
+ case INPUT_CAPTURE_SESSION_STATE_DISABLED:
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_CLOSED:
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT);
+
+ if (!xdp_filter_options (arg_options, &options_builder,
+ input_capture_enable_options,
+ G_N_ELEMENTS (input_capture_enable_options),
+ &error))
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ input_capture_session->state = INPUT_CAPTURE_SESSION_STATE_ENABLED;
+
+ /* Let's be lenient and make Enable() a noop for anything but a disabled
+ * session.
+ */
+ switch (input_capture_session->state)
+ {
+ case INPUT_CAPTURE_SESSION_STATE_INIT: /* ignore, handled above */
+ g_assert_not_reached ();
+ case INPUT_CAPTURE_SESSION_STATE_ENABLED:
+ case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_DISABLED:
+ input_capture_session->state = INPUT_CAPTURE_SESSION_STATE_ENABLED;
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_CLOSED: /* ignore, handled above */
+ g_assert_not_reached ();
+ }
+
+ xdp_dbus_impl_input_capture_call_enable (impl,
+ arg_session_handle,
+ xdp_app_info_get_id (call->app_info),
+ g_variant_builder_end (&options_builder),
+ NULL,
+ NULL,
+ NULL);
+
+ xdp_dbus_input_capture_complete_enable (object, invocation);
+
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static XdpOptionKey input_capture_disable_options[] = {
+};
+
+static gboolean
+handle_disable (XdpDbusInputCapture *object,
+ GDBusMethodInvocation *invocation,
+ const char *arg_session_handle,
+ GVariant *arg_options)
+{
+ Call *call = call_from_invocation (invocation);
+ Session *session;
+ InputCaptureSession *input_capture_session;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(XdpDbusImplRequest) impl_request = NULL;
+ GVariantBuilder options_builder;
+
+ session = acquire_session_from_call (arg_session_handle, call);
+ if (!session)
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_ACCESS_DENIED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ SESSION_AUTOLOCK_UNREF (session);
+
+ if (!is_input_capture_session (session))
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ input_capture_session = (InputCaptureSession *)session;
+
+ switch (input_capture_session->state)
+ {
+ case INPUT_CAPTURE_SESSION_STATE_INIT:
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Not connected to EIS");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ case INPUT_CAPTURE_SESSION_STATE_ENABLED:
+ case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
+ case INPUT_CAPTURE_SESSION_STATE_DISABLED:
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_CLOSED:
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT);
+ if (!xdp_filter_options (arg_options, &options_builder,
+ input_capture_disable_options,
+ G_N_ELEMENTS (input_capture_disable_options),
+ &error))
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ /* We need to be lenient, a caller may call Disable() before processing a
+ * Disabled signal. So we pretend everything's ok but only
+ * update our internal state in the right transitions.
+ */
+ switch (input_capture_session->state)
+ {
+ case INPUT_CAPTURE_SESSION_STATE_INIT: /* ignore, handled above */
+ g_assert_not_reached ();
+ case INPUT_CAPTURE_SESSION_STATE_ENABLED:
+ case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
+ input_capture_session->state = INPUT_CAPTURE_SESSION_STATE_DISABLED;
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_DISABLED:
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_CLOSED: /* ignore, handled above */
+ g_assert_not_reached ();
+ }
+
+ xdp_dbus_impl_input_capture_call_disable (impl,
+ arg_session_handle,
+ xdp_app_info_get_id (call->app_info),
+ g_variant_builder_end (&options_builder),
+ NULL,
+ NULL,
+ NULL);
+
+ xdp_dbus_input_capture_complete_disable (object, invocation);
+
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static XdpOptionKey input_capture_release_options[] = {
+ { "cursor_position", (const GVariantType *)"(dd)", NULL },
+ { "activation_id", G_VARIANT_TYPE_UINT32, NULL },
+};
+
+static gboolean
+handle_release (XdpDbusInputCapture *object,
+ GDBusMethodInvocation *invocation,
+ const char *arg_session_handle,
+ GVariant *arg_options)
+{
+ Call *call = call_from_invocation (invocation);
+ Session *session;
+ InputCaptureSession *input_capture_session;
+ g_autoptr(GError) error = NULL;
+ GVariantBuilder options_builder;
+
+ session = acquire_session_from_call (arg_session_handle, call);
+ if (!session)
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_ACCESS_DENIED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ SESSION_AUTOLOCK_UNREF (session);
+
+ if (!is_input_capture_session (session))
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ input_capture_session = (InputCaptureSession *)session;
+
+ switch (input_capture_session->state)
+ {
+ case INPUT_CAPTURE_SESSION_STATE_INIT:
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Not connected to EIS");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ case INPUT_CAPTURE_SESSION_STATE_ENABLED:
+ case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
+ case INPUT_CAPTURE_SESSION_STATE_DISABLED:
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_CLOSED:
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ g_variant_builder_init (&options_builder, G_VARIANT_TYPE_VARDICT);
+ if (!xdp_filter_options (arg_options, &options_builder,
+ input_capture_release_options,
+ G_N_ELEMENTS (input_capture_release_options),
+ &error))
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ /* We need to be lenient, a caller may call Release() before processing a
+ * Deactivated/Disabled signal. So we pretend everything's ok but only
+ * update our internal state in the right transitions.
+ */
+ switch (input_capture_session->state)
+ {
+ case INPUT_CAPTURE_SESSION_STATE_INIT: /* ignore, handled above */
+ g_assert_not_reached ();
+ case INPUT_CAPTURE_SESSION_STATE_ENABLED:
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
+ input_capture_session->state = INPUT_CAPTURE_SESSION_STATE_ENABLED;
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_DISABLED:
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_CLOSED: /* ignore, handled above */
+ g_assert_not_reached ();
+ }
+
+ xdp_dbus_impl_input_capture_call_release (impl,
+ arg_session_handle,
+ xdp_app_info_get_id (call->app_info),
+ g_variant_builder_end (&options_builder),
+ NULL,
+ NULL,
+ NULL);
+
+ xdp_dbus_input_capture_complete_release (object, invocation);
+
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static gboolean
+handle_connect_to_eis (XdpDbusInputCapture *object,
+ GDBusMethodInvocation *invocation,
+ GUnixFDList *in_fd_list,
+ const char *arg_session_handle,
+ GVariant *arg_options)
+{
+ Call *call = call_from_invocation (invocation);
+ Session *session;
+ InputCaptureSession *input_capture_session;
+ g_autoptr(GUnixFDList) out_fd_list = NULL;
+ g_autoptr(GError) error = NULL;
+ GVariantBuilder empty;
+ GVariant *fd;
+
+ session = acquire_session_from_call (arg_session_handle, call);
+ if (!session)
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_ACCESS_DENIED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ SESSION_AUTOLOCK_UNREF (session);
+
+ if (!is_input_capture_session (session))
+ {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_ACCESS_DENIED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ input_capture_session = (InputCaptureSession *)session;
+
+ switch (input_capture_session->state)
+ {
+ case INPUT_CAPTURE_SESSION_STATE_INIT:
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_ENABLED:
+ case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
+ case INPUT_CAPTURE_SESSION_STATE_DISABLED:
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Already connected");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ case INPUT_CAPTURE_SESSION_STATE_CLOSED:
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_FAILED,
+ "Invalid session");
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+ }
+
+ g_variant_builder_init (&empty, G_VARIANT_TYPE_VARDICT);
+
+ if (!xdp_dbus_impl_input_capture_call_connect_to_eis_sync (impl,
+ arg_session_handle,
+ xdp_app_info_get_id (call->app_info),
+ g_variant_builder_end (&empty),
+ in_fd_list,
+ &fd,
+ &out_fd_list,
+ NULL,
+ &error))
+ {
+ g_warning ("Failed to ConnectToEIS: %s", error->message);
+ out_fd_list = g_unix_fd_list_new ();
+ }
+
+ input_capture_session->state = INPUT_CAPTURE_SESSION_STATE_DISABLED;
+
+ xdp_dbus_input_capture_complete_connect_to_eis (object, invocation, out_fd_list, fd);
+ return G_DBUS_METHOD_INVOCATION_HANDLED;
+}
+
+static void
+input_capture_iface_init (XdpDbusInputCaptureIface *iface)
+{
+ iface->handle_create_session = handle_create_session;
+ iface->handle_get_zones = handle_get_zones;
+ iface->handle_set_pointer_barriers = handle_set_pointer_barriers;
+ iface->handle_connect_to_eis = handle_connect_to_eis;
+ iface->handle_enable = handle_enable;
+ iface->handle_disable = handle_disable;
+ iface->handle_release = handle_release;
+
+}
+
+static void
+pass_signal (XdpDbusImplInputCapture *impl,
+ const char *signal_name,
+ const char *session_id,
+ GVariant *options,
+ gpointer *data)
+{
+ GDBusConnection *connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (impl));
+ g_autoptr(Session) session = lookup_session (session_id);
+
+ g_dbus_connection_emit_signal (connection,
+ session->sender,
+ "/org/freedesktop/portal/desktop",
+ "org.freedesktop.portal.InputCapture",
+ signal_name,
+ g_variant_new ("(o@a{sv})", session_id, options),
+ NULL);
+}
+
+static void
+on_disabled_cb (XdpDbusImplInputCapture *impl,
+ const char *session_id,
+ GVariant *options,
+ gpointer *data)
+{
+ g_autoptr(Session) session = lookup_session (session_id);
+ InputCaptureSession *input_capture_session;
+
+ if (!is_input_capture_session (session))
+ {
+ g_critical ("Invalid session type for signal");
+ return;
+ }
+
+ input_capture_session = (InputCaptureSession*)session;
+
+ switch (input_capture_session->state)
+ {
+ case INPUT_CAPTURE_SESSION_STATE_INIT:
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_ENABLED:
+ case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
+ pass_signal (impl, "Disabled", session_id, options, data);
+ input_capture_session->state = INPUT_CAPTURE_SESSION_STATE_DISABLED;
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_DISABLED:
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_CLOSED:
+ break;
+ }
+}
+
+static void
+on_activated_cb (XdpDbusImplInputCapture *impl,
+ const char *session_id,
+ GVariant *options,
+ gpointer *data)
+{
+ g_autoptr(Session) session = lookup_session (session_id);
+ InputCaptureSession *input_capture_session;
+
+ if (!is_input_capture_session (session))
+ {
+ g_critical ("Invalid session type for signal");
+ return;
+ }
+
+ input_capture_session = (InputCaptureSession*)session;
+
+ switch (input_capture_session->state)
+ {
+ case INPUT_CAPTURE_SESSION_STATE_INIT:
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_ENABLED:
+ pass_signal (impl, "Activated", session_id, options, data);
+ input_capture_session->state = INPUT_CAPTURE_SESSION_STATE_ACTIVE;
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
+ case INPUT_CAPTURE_SESSION_STATE_DISABLED:
+ case INPUT_CAPTURE_SESSION_STATE_CLOSED:
+ break;
+ }
+}
+
+static void
+on_deactivated_cb (XdpDbusImplInputCapture *impl,
+ const char *session_id,
+ GVariant *options,
+ gpointer *data)
+{
+ g_autoptr(Session) session = lookup_session (session_id);
+ InputCaptureSession *input_capture_session;
+
+ if (!is_input_capture_session (session))
+ {
+ g_critical ("Invalid session type for signal");
+ return;
+ }
+
+ input_capture_session = (InputCaptureSession*)session;
+
+ switch (input_capture_session->state)
+ {
+ case INPUT_CAPTURE_SESSION_STATE_INIT:
+ case INPUT_CAPTURE_SESSION_STATE_ENABLED:
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
+ pass_signal (impl, "Deactivated", session_id, options, data);
+ input_capture_session->state = INPUT_CAPTURE_SESSION_STATE_ACTIVE;
+ break;
+ case INPUT_CAPTURE_SESSION_STATE_DISABLED:
+ case INPUT_CAPTURE_SESSION_STATE_CLOSED:
+ break;
+ }
+}
+
+static void
+input_capture_init (InputCapture *input_capture)
+{
+ xdp_dbus_input_capture_set_version (XDP_DBUS_INPUT_CAPTURE (input_capture), VERSION_1);
+ unsigned int supported_capabilities;
+
+ supported_capabilities =
+ xdp_dbus_impl_input_capture_get_supported_capabilities (impl);
+ xdp_dbus_input_capture_set_supported_capabilities (XDP_DBUS_INPUT_CAPTURE (input_capture),
+ supported_capabilities);
+
+ g_signal_connect (impl, "disabled", G_CALLBACK (on_disabled_cb), input_capture);
+ g_signal_connect (impl, "activated", G_CALLBACK (on_activated_cb), input_capture);
+ g_signal_connect (impl, "deactivated", G_CALLBACK (on_deactivated_cb), input_capture);
+}
+
+static void
+input_capture_class_init (InputCaptureClass *klass)
+{
+ quark_request_session =
+ g_quark_from_static_string ("-xdp-request-capture-input-session");
+}
+
+GDBusInterfaceSkeleton *
+input_capture_create (GDBusConnection *connection,
+ const char *dbus_name)
+{
+ g_autoptr(GError) error = NULL;
+
+ impl = xdp_dbus_impl_input_capture_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ dbus_name,
+ DESKTOP_PORTAL_OBJECT_PATH,
+ NULL,
+ &error);
+ if (impl == NULL)
+ {
+ g_warning ("Failed to create capture input proxy: %s", error->message);
+ return NULL;
+ }
+
+ impl_version = xdp_dbus_impl_input_capture_get_version (impl);
+
+ g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (impl), G_MAXINT);
+
+ input_capture = g_object_new (input_capture_get_type (), NULL);
+
+ return G_DBUS_INTERFACE_SKELETON (input_capture);
+}
+
+static void
+input_capture_session_close (Session *session)
+{
+ InputCaptureSession *input_capture_session = (InputCaptureSession *)session;
+
+ input_capture_session->state = INPUT_CAPTURE_SESSION_STATE_CLOSED;
+
+ g_debug ("screen cast session owned by '%s' closed", session->sender);
+}
+
+static void
+input_capture_session_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (input_capture_session_parent_class)->finalize (object);
+}
+
+static void
+input_capture_session_init (InputCaptureSession *input_capture_session)
+{
+}
+
+static void
+input_capture_session_class_init (InputCaptureSessionClass *klass)
+{
+ GObjectClass *object_class;
+ SessionClass *session_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = input_capture_session_finalize;
+
+ session_class = (SessionClass *)klass;
+ session_class->close = input_capture_session_close;
+}
diff --git a/src/input-capture.h b/src/input-capture.h
new file mode 100644
index 000000000..7db7a7b24
--- /dev/null
+++ b/src/input-capture.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright © 2022 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see .
+ *
+ */
+
+#pragma once
+
+#include
+#include
+
+GDBusInterfaceSkeleton * input_capture_create (GDBusConnection *connection,
+ const char *dbus_name);
diff --git a/src/meson.build b/src/meson.build
index 6fabe46a2..8f4054c98 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -60,6 +60,7 @@ xdg_desktop_portal_sources = files(
'glib-backports.c',
'global-shortcuts.c',
'inhibit.c',
+ 'input-capture.c',
'memory-monitor.c',
'network-monitor.c',
'notification.c',
diff --git a/src/request.c b/src/request.c
index 12696f9e8..4216696b2 100644
--- a/src/request.c
+++ b/src/request.c
@@ -202,6 +202,10 @@ get_token (GDBusMethodInvocation *invocation)
else if (strcmp (method, "CreateMonitor") == 0)
options = g_variant_get_child_value (parameters, 1);
}
+ else if (strcmp (interface, "org.freedesktop.portal.InputCapture") == 0)
+ {
+ options = g_variant_get_child_value (parameters, 1);
+ }
else if (strcmp (interface, "org.freedesktop.portal.NetworkMonitor") == 0)
{
// no methods
diff --git a/src/xdg-desktop-portal.c b/src/xdg-desktop-portal.c
index 3e2bc2fd4..4a1ae22ed 100644
--- a/src/xdg-desktop-portal.c
+++ b/src/xdg-desktop-portal.c
@@ -43,6 +43,7 @@
#include "gamemode.h"
#include "global-shortcuts.h"
#include "inhibit.h"
+#include "input-capture.h"
#include "location.h"
#include "memory-monitor.h"
#include "network-monitor.h"
@@ -151,6 +152,16 @@ method_needs_request (GDBusMethodInvocation *invocation)
else
return TRUE;
}
+ else if (strcmp (interface, "org.freedesktop.portal.InputCapture") == 0)
+ {
+ if (strcmp (method, "ConnectToEIS") == 0 ||
+ strcmp (method, "Enable") == 0 ||
+ strcmp (method, "Disable") == 0 ||
+ strcmp (method, "Release") == 0)
+ return FALSE;
+ else
+ return TRUE;
+ }
else
{
return TRUE;
@@ -373,6 +384,11 @@ on_bus_acquired (GDBusConnection *connection,
export_portal_implementation (
connection, clipboard_create (connection, implementation->dbus_name));
#endif
+
+ implementation = find_portal_implementation ("org.freedesktop.impl.portal.InputCapture");
+ if (implementation != NULL)
+ export_portal_implementation (connection,
+ input_capture_create (connection, implementation->dbus_name));
}
static void
diff --git a/tests/portals/meson.build b/tests/portals/meson.build
index 7bed78a1b..6f6ea8b64 100644
--- a/tests/portals/meson.build
+++ b/tests/portals/meson.build
@@ -7,6 +7,7 @@ test_portals = [
'org.freedesktop.impl.portal.Email',
'org.freedesktop.impl.portal.FileChooser',
'org.freedesktop.impl.portal.Inhibit',
+ 'org.freedesktop.impl.portal.InputCapture',
'org.freedesktop.impl.portal.Lockdown',
'org.freedesktop.impl.portal.Notification',
'org.freedesktop.impl.portal.Print',
diff --git a/tests/templates/inputcapture.py b/tests/templates/inputcapture.py
new file mode 100644
index 000000000..06ea57959
--- /dev/null
+++ b/tests/templates/inputcapture.py
@@ -0,0 +1,298 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is formatted with Python Black
+
+from collections import namedtuple
+from itertools import count
+from gi.repository import GLib
+from tests.templates import Response, init_template_logger, ImplRequest, ImplSession
+
+import dbus
+import dbus.service
+import logging
+import socket
+
+BUS_NAME = "org.freedesktop.impl.portal.Test"
+MAIN_OBJ = "/org/freedesktop/portal/desktop"
+SYSTEM_BUS = False
+MAIN_IFACE = "org.freedesktop.impl.portal.InputCapture"
+VERSION = 1
+
+logger = logging.getLogger(f"templates.{__name__}")
+logger.setLevel(logging.DEBUG)
+
+serials = count()
+
+Response = namedtuple("Response", ["response", "results"])
+Barrier = namedtuple("Barrier", ["id", "position"])
+
+
+def load(mock, parameters=None):
+ logger.debug(f"Loading parameters: {parameters}")
+ # Delay before Request.response
+ mock.delay: int = parameters.get("delay", 0)
+
+ mock.supported_capabilities = parameters.get("supported_capabilities", 0xF)
+ # The actual ones we reply with in the CreateSession request
+ mock.capabilities = parameters.get("capabilities", None)
+
+ mock.default_zone = parameters.get("default-zone", [(1920, 1080, 0, 0)])
+ mock.current_zones = mock.default_zone
+ mock.current_zone_set = next(serials)
+
+ mock.disable_delay = parameters.get("disable-delay", 0)
+ mock.activated_delay = parameters.get("activated-delay", 0)
+ mock.deactivated_delay = parameters.get("deactivated-delay", 0)
+
+ mock.AddProperties(
+ MAIN_IFACE,
+ dbus.Dictionary(
+ {
+ "version": dbus.UInt32(parameters.get("version", VERSION)),
+ "SupportedCapabilities": dbus.UInt32(mock.supported_capabilities),
+ }
+ ),
+ )
+
+ mock.active_session_handles = []
+
+
+@dbus.service.method(
+ MAIN_IFACE,
+ in_signature="oossa{sv}",
+ out_signature="ua{sv}",
+)
+def CreateSession(self, handle, session_handle, app_id, parent_window, options):
+ try:
+ logger.debug(f"CreateSession({parent_window}, {options})")
+
+ assert "capabilities" in options
+
+ # Filter to the subset of supported capabilities
+ if self.capabilities is None:
+ capabilities = options["capabilities"]
+ else:
+ capabilities = self.capabilities
+
+ capabilities &= self.supported_capabilities
+ response = Response(0, {})
+
+ response.results["capabilities"] = dbus.UInt32(capabilities)
+ self.active_session_handles.append(session_handle)
+
+ logger.debug(f"CreateSession with response {response}")
+
+ return response.response, response.results
+ except Exception as e:
+ logger.critical(e)
+ return (2, {})
+
+
+@dbus.service.method(
+ MAIN_IFACE,
+ in_signature="oosa{sv}",
+ out_signature="ua{sv}",
+)
+def GetZones(self, handle, session_handle, app_id, options):
+ try:
+ logger.debug(f"GetZones({session_handle}, {options})")
+
+ assert session_handle in self.active_session_handles
+
+ response = Response(0, {})
+ response.results["zones"] = self.default_zone
+ response.results["zone_set"] = dbus.UInt32(
+ self.current_zone_set, variant_level=1
+ )
+ logger.debug(f"GetZones with response {response}")
+
+ if response.response == 0:
+ self.current_zones = response.results["zones"]
+
+ return response.response, response.results
+ except Exception as e:
+ logger.critical(e)
+ return (2, {})
+
+
+@dbus.service.method(
+ MAIN_IFACE,
+ in_signature="oosa{sv}aa{sv}u",
+ out_signature="ua{sv}",
+)
+def SetPointerBarriers(
+ self, handle, session_handle, app_id, options, barriers, zone_set
+):
+ try:
+ logger.debug(
+ f"SetPointerBarriers({session_handle}, {options}, {barriers}, {zone_set})"
+ )
+
+ assert session_handle in self.active_session_handles
+ assert zone_set == self.current_zone_set
+
+ self.current_barriers = []
+
+ failed_barriers = []
+
+ # Barrier sanity checks:
+ for b in barriers:
+ id = b["barrier_id"]
+ x1, y1, x2, y2 = b["position"]
+ if (x1 != x2 and y1 != y2) or (x1 == x2 and y1 == y2):
+ logger.debug(f"Barrier {id} is not horizontal or vertical")
+ failed_barriers.append(id)
+ continue
+
+ for z in self.current_zones:
+ w, h, x, y = z
+ if x1 < x or x1 > x + w:
+ continue
+ if y1 < y or y1 > y + h:
+ continue
+
+ # x1/y1 fit into our current zone
+ if x2 < x or x2 > x + w or y2 < y or y2 > y + h:
+ logger.debug(f"Barrier {id} spans multiple zones")
+ elif x1 == x2 and (x1 != x and x1 != x + w):
+ logger.debug(f"Barrier {id} is not on vertical edge")
+ elif y1 == y2 and (y1 != y and y1 != y + h):
+ logger.debug(f"Barrier {id} is not on horizontal edge")
+ else:
+ self.current_barriers.append(Barrier(id=id, position=b["position"]))
+ break
+
+ failed_barriers.append(id)
+ break
+ else:
+ logger.debug(f"Barrier {id} does not fit into any zone")
+ failed_barriers.append(id)
+ continue
+
+ response = Response(0, {})
+ response.results["failed_barriers"] = dbus.Array(
+ [dbus.UInt32(f) for f in failed_barriers],
+ signature="u",
+ variant_level=1,
+ )
+
+ logger.debug(f"SetPointerBarriers with response {response}")
+
+ return response.response, response.results
+ except Exception as e:
+ logger.critical(e)
+ return (2, {})
+
+
+@dbus.service.method(
+ MAIN_IFACE,
+ in_signature="osa{sv}",
+ out_signature="ua{sv}",
+)
+def Enable(self, session_handle, app_id, options):
+ try:
+ logger.debug(f"Enable({session_handle}, {options})")
+
+ assert session_handle in self.active_session_handles
+
+ # for use in the signals
+ activation_id = next(serials)
+ barrier = self.current_barriers[0]
+ pos = (barrier.position[0] + 10, barrier.position[1] + 20)
+
+ if self.disable_delay > 0:
+
+ def disable():
+ logger.debug("emitting Disabled")
+ self.EmitSignal("", "Disabled", "oa{sv}", [session_handle, {}])
+
+ GLib.timeout_add(self.disable_delay, disable)
+
+ if self.activated_delay > 0:
+
+ def activated():
+ logger.debug("emitting Activated")
+ options = {
+ "activation_id": dbus.UInt32(activation_id, variant_level=1),
+ "barrier_id": dbus.UInt32(barrier.id, variant_level=1),
+ "cursor_position": dbus.Struct(
+ pos, signature="dd", variant_level=1
+ ),
+ }
+ self.EmitSignal("", "Activated", "oa{sv}", [session_handle, options])
+
+ GLib.timeout_add(self.activated_delay, activated)
+
+ if self.deactivated_delay > 0:
+
+ def deactivated():
+ logger.debug("emitting Deactivated")
+ options = {
+ "activation_id": dbus.UInt32(activation_id, variant_level=1),
+ "cursor_position": dbus.Struct(
+ pos, signature="dd", variant_level=1
+ ),
+ }
+ self.EmitSignal("", "Deactivated", "oa{sv}", [session_handle, options])
+
+ GLib.timeout_add(self.deactivated_delay, deactivated)
+
+ except Exception as e:
+ logger.critical(e)
+ return (2, {})
+
+
+@dbus.service.method(
+ MAIN_IFACE,
+ in_signature="osa{sv}",
+ out_signature="ua{sv}",
+)
+def Disable(self, session_handle, app_id, options):
+ try:
+ logger.debug(f"Disable({session_handle}, {options})")
+
+ assert session_handle in self.active_session_handles
+ except Exception as e:
+ logger.critical(e)
+ return (2, {})
+
+
+@dbus.service.method(
+ MAIN_IFACE,
+ in_signature="osa{sv}",
+ out_signature="ua{sv}",
+)
+def Release(self, session_handle, app_id, options):
+ try:
+ logger.debug(f"Release({session_handle}, {options})")
+
+ assert session_handle in self.active_session_handles
+ except Exception as e:
+ logger.critical(e)
+ return (2, {})
+
+
+@dbus.service.method(
+ MAIN_IFACE,
+ in_signature="osa{sv}",
+ out_signature="h",
+)
+def ConnectToEIS(self, session_handle, app_id, options):
+ try:
+ logger.debug(f"ConnectToEIS({session_handle}, {options})")
+
+ assert session_handle in self.active_session_handles
+
+ sockets = socket.socketpair()
+ self.eis_socket = sockets[0]
+
+ assert self.eis_socket.send(b"HELLO") == 5
+
+ fd = sockets[1]
+
+ logger.debug(f"ConnectToEis with fd {fd.fileno()}")
+
+ return dbus.types.UnixFd(fd)
+ except Exception as e:
+ logger.critical(e)
+ return -1
diff --git a/tests/test_inputcapture.py b/tests/test_inputcapture.py
new file mode 100644
index 000000000..8ad5e83b4
--- /dev/null
+++ b/tests/test_inputcapture.py
@@ -0,0 +1,600 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is formatted with Python Black
+
+from typing import Any, Dict, List, Tuple
+from gi.repository import GLib
+
+from itertools import count
+
+import dbus
+import socket
+
+from tests import Request, PortalTest, Session
+
+counter = count()
+
+
+class TestInputCapture(PortalTest):
+ def create_session(self, capabilities=0xF):
+ """
+ Call CreateSession for the given capabilities and return the
+ (response, results) tuple.
+ """
+ inputcapture_intf = self.get_dbus_interface()
+ request = Request(self.dbus_con, inputcapture_intf)
+
+ capabilities = dbus.UInt32(capabilities, variant_level=1)
+ session_handle_token = dbus.String(f"session{next(counter)}", variant_level=1)
+
+ options = dbus.Dictionary(
+ {
+ "capabilities": capabilities,
+ "session_handle_token": session_handle_token,
+ },
+ signature="sv",
+ )
+
+ response, results = request.call(
+ "CreateSession", parent_window="", options=options
+ )
+ assert response == 0
+ assert "session_handle" in results
+ assert "capabilities" in results
+ caps = results["capabilities"]
+ # Returned capabilities must be a subset of the requested ones
+ assert caps & ~capabilities == 0
+
+ self.current_session_handle = results["session_handle"]
+
+ # Check the impl portal was called with the right args
+ method_calls = self.mock_interface.GetMethodCalls("CreateSession")
+ assert len(method_calls) > 0
+ _, args = method_calls[-1]
+ assert args[3] == "" # parent window
+ assert args[4]["capabilities"] == capabilities
+
+ return response, results
+
+ def get_zones(self):
+ """
+ Call GetZones and return the (response, results) tuple.
+ """
+ inputcapture_intf = self.get_dbus_interface()
+ request = Request(self.dbus_con, inputcapture_intf)
+ options = {}
+ response, results = request.call(
+ "GetZones", session_handle=self.current_session_handle, options=options
+ )
+ assert response == 0
+ assert "zones" in results
+ assert "zone_set" in results
+
+ self.current_zone_set = results["zone_set"]
+
+ # Check the impl portal was called with the right args
+ method_calls = self.mock_interface.GetMethodCalls("GetZones")
+ assert len(method_calls) > 0
+ _, args = method_calls[-1]
+ assert args[0] == request.handle
+ assert args[1] == self.current_session_handle
+
+ return response, results
+
+ def set_pointer_barriers(self, barriers):
+ inputcapture_intf = self.get_dbus_interface()
+ request = Request(self.dbus_con, inputcapture_intf)
+ options = {}
+ response, results = request.call(
+ "SetPointerBarriers",
+ session_handle=self.current_session_handle,
+ options=options,
+ barriers=barriers,
+ zone_set=self.current_zone_set,
+ )
+ assert response == 0
+ assert "failed_barriers" in results
+
+ # Check the impl portal was called with the right args
+ method_calls = self.mock_interface.GetMethodCalls("SetPointerBarriers")
+ assert len(method_calls) > 0
+ _, args = method_calls[-1]
+ assert args[0] == request.handle
+ assert args[1] == self.current_session_handle
+ assert args[4] == barriers
+ assert args[5] == self.current_zone_set
+
+ return response, results
+
+ def connect_to_eis(self):
+ inputcapture_intf = self.get_dbus_interface()
+ fd = inputcapture_intf.ConnectToEIS(
+ self.current_session_handle, dbus.Dictionary({}, signature="sv")
+ )
+
+ # Our dbusmock template sends HELLO
+ eis_socket = socket.fromfd(fd.take(), socket.AF_UNIX, socket.SOCK_STREAM)
+ hello = eis_socket.recv(10)
+ assert hello == b"HELLO"
+
+ method_calls = self.mock_interface.GetMethodCalls("ConnectToEIS")
+ assert len(method_calls) > 0
+ _, args = method_calls[-1]
+ assert args[0] == self.current_session_handle
+
+ return eis_socket
+
+ def enable(self):
+ inputcapture_intf = self.get_dbus_interface()
+ inputcapture_intf.Enable(
+ self.current_session_handle, dbus.Dictionary({}, signature="sv")
+ )
+
+ method_calls = self.mock_interface.GetMethodCalls("Enable")
+ assert len(method_calls) > 0
+ _, args = method_calls[-1]
+ assert args[0] == self.current_session_handle
+
+ def disable(self):
+ inputcapture_intf = self.get_dbus_interface()
+ inputcapture_intf.Disable(
+ self.current_session_handle, dbus.Dictionary({}, signature="sv")
+ )
+
+ method_calls = self.mock_interface.GetMethodCalls("Disable")
+ assert len(method_calls) > 0
+ _, args = method_calls[-1]
+ assert args[0] == self.current_session_handle
+
+ def release(self, activation_id: int, cursor_position=None):
+ options = {"activation_id": dbus.UInt32(activation_id)}
+ if cursor_position:
+ options["cursor_position"] = dbus.Struct(
+ list(cursor_position), signature="dd", variant_level=1
+ )
+
+ inputcapture_intf = self.get_dbus_interface()
+ inputcapture_intf.Release(
+ self.current_session_handle, dbus.Dictionary(options, signature="sv")
+ )
+
+ method_calls = self.mock_interface.GetMethodCalls("Release")
+ assert len(method_calls) > 0
+ _, args = method_calls[-1]
+ assert args[0] == self.current_session_handle
+ assert "activation_id" in args[2]
+ aid = args[2]["activation_id"]
+ assert aid == activation_id
+ if cursor_position:
+ assert "cursor_position" in args[2]
+ pos = args[2]["cursor_position"]
+ assert pos == cursor_position
+
+ def test_version(self):
+ self.start_impl_portal()
+ self.start_xdp()
+
+ properties_intf = self.get_dbus_interface("org.freedesktop.DBus.Properties")
+ version = properties_intf.Get("org.freedesktop.portal.InputCapture", "version")
+ EXPECTED_VERSION = 1
+ assert version == EXPECTED_VERSION
+
+ def test_supported_capabilities(self):
+ params = {
+ "supported_capabilities": 0b101, # KEYBOARD, POINTER, TOUCH
+ }
+ self.start_impl_portal(params)
+ self.start_xdp()
+
+ properties_intf = self.get_dbus_interface("org.freedesktop.DBus.Properties")
+ caps = properties_intf.Get(
+ "org.freedesktop.portal.InputCapture", "SupportedCapabilities"
+ )
+ assert caps == 0b101
+
+ def test_create_session(self):
+ self.start_impl_portal()
+ self.start_xdp()
+
+ self.create_session(capabilities=0b1) # KEYBOARD
+
+ # Check the impl portal was called with the right args
+ method_calls = self.mock_interface.GetMethodCalls("CreateSession")
+ assert len(method_calls) == 1
+ _, args = method_calls.pop(0)
+ assert args[3] == "" # parent window
+ assert args[4]["capabilities"] == 0b1
+
+ def test_create_session_limited_caps(self):
+ params = {
+ "capabilities": 0b110, # TOUCH, POINTER
+ "supported_capabilities": 0b111, # TOUCH, POINTER, KEYBOARD
+ }
+ self.start_impl_portal(params)
+ self.start_xdp()
+
+ # Request more caps than are supported
+ response, results = self.create_session(capabilities=0b111)
+ caps = results["capabilities"]
+ # Returned capabilities must the ones we set up in the params
+ assert caps == 0b110
+
+ # Check the impl portal was called with the right args
+ method_calls = self.mock_interface.GetMethodCalls("CreateSession")
+ assert len(method_calls) == 1
+ _, args = method_calls.pop(0)
+ assert args[3] == "" # parent window
+ assert args[4]["capabilities"] == 0b111
+
+ def test_get_zones(self):
+ zones = [(1024, 768, 0, 0), (640, 480, 1024, 0)]
+
+ params = {
+ "default-zone": dbus.Array(
+ [dbus.Struct(z, signature="uuii") for z in zones],
+ signature="(uuii)",
+ variant_level=1,
+ ),
+ }
+ self.start_impl_portal(params)
+ self.start_xdp()
+
+ response, results = self.create_session()
+ response, results = self.get_zones()
+ for z1, z2 in zip(results["zones"], zones):
+ assert z1 == z2
+
+ # Check the impl portal was called with the right args
+ method_calls = self.mock_interface.GetMethodCalls("CreateSession")
+ assert len(method_calls) == 1
+ method_calls = self.mock_interface.GetMethodCalls("GetZones")
+ assert len(method_calls) == 1
+
+ def test_set_pointer_barriers(self):
+ zones = [(1024, 768, 0, 0), (640, 480, 1024, 0)]
+
+ params = {
+ "default-zone": dbus.Array(
+ [dbus.Struct(z, signature="uuii") for z in zones],
+ signature="(uuii)",
+ variant_level=1,
+ ),
+ }
+ self.start_impl_portal(params)
+ self.start_xdp()
+
+ response, results = self.create_session()
+ response, results = self.get_zones()
+
+ barriers = [
+ {
+ "barrier_id": dbus.UInt32(10, variant_level=1),
+ "position": dbus.Struct(
+ [0, 0, 0, 768], signature="iiii", variant_level=1
+ ),
+ },
+ {
+ "barrier_id": dbus.UInt32(11, variant_level=1),
+ "position": dbus.Struct(
+ [0, 0, 1024, 0], signature="iiii", variant_level=1
+ ),
+ },
+ {
+ "barrier_id": dbus.UInt32(12, variant_level=1),
+ "position": dbus.Struct(
+ [1024, 0, 1024, 768], signature="iiii", variant_level=1
+ ),
+ },
+ {
+ "barrier_id": dbus.UInt32(13, variant_level=1),
+ "position": dbus.Struct(
+ [0, 768, 1024, 768], signature="iiii", variant_level=1
+ ),
+ },
+ {
+ "barrier_id": dbus.UInt32(14, variant_level=1),
+ "position": dbus.Struct(
+ [100, 768, 500, 768], signature="iiii", variant_level=1
+ ),
+ },
+ {
+ "barrier_id": dbus.UInt32(15, variant_level=1),
+ "position": dbus.Struct(
+ [1024, 0, 1024, 480], signature="iiii", variant_level=1
+ ),
+ },
+ {
+ "barrier_id": dbus.UInt32(16, variant_level=1),
+ "position": dbus.Struct(
+ [1024 + 640, 0, 1024 + 640, 480], signature="iiii", variant_level=1
+ ),
+ },
+ # invalid ones
+ {
+ "barrier_id": dbus.UInt32(20, variant_level=1),
+ "position": dbus.Struct(
+ [0, 1, 3, 4], signature="iiii", variant_level=1
+ ),
+ },
+ {
+ "barrier_id": dbus.UInt32(21, variant_level=1),
+ "position": dbus.Struct(
+ [0, 1, 1024, 1], signature="iiii", variant_level=1
+ ),
+ },
+ {
+ "barrier_id": dbus.UInt32(22, variant_level=1),
+ "position": dbus.Struct(
+ [1, 0, 1, 768], signature="iiii", variant_level=1
+ ),
+ },
+ {
+ "barrier_id": dbus.UInt32(23, variant_level=1),
+ "position": dbus.Struct(
+ [1023, 0, 1023, 768], signature="iiii", variant_level=1
+ ),
+ },
+ {
+ "barrier_id": dbus.UInt32(24, variant_level=1),
+ "position": dbus.Struct(
+ [0, 0, 1050, 0], signature="iiii", variant_level=1
+ ),
+ },
+ ]
+ response, results = self.set_pointer_barriers(barriers=barriers)
+ failed_barriers = results["failed_barriers"]
+ assert all([id >= 20 for id in failed_barriers])
+
+ for id in [b["barrier_id"] for b in barriers if b["barrier_id"] >= 20]:
+ assert id in failed_barriers
+
+ # Check the impl portal was called with the right args
+ method_calls = self.mock_interface.GetMethodCalls("CreateSession")
+ assert len(method_calls) == 1
+ method_calls = self.mock_interface.GetMethodCalls("GetZones")
+ assert len(method_calls) == 1
+ method_calls = self.mock_interface.GetMethodCalls("SetPointerBarriers")
+ assert len(method_calls) == 1
+ _, args = method_calls.pop(0)
+ assert args[4] == barriers
+ assert args[5] == self.current_zone_set
+
+ def test_connect_to_eis(self):
+ self.start_impl_portal()
+ self.start_xdp()
+
+ self.create_session()
+ self.get_zones()
+
+ # The default zone is 1920x1080
+ barriers = [
+ {
+ "barrier_id": dbus.UInt32(10, variant_level=1),
+ "position": dbus.Struct(
+ [0, 0, 1920, 0], signature="iiii", variant_level=1
+ ),
+ },
+ ]
+ self.set_pointer_barriers(barriers)
+
+ self.connect_to_eis()
+
+ def test_enable_disable(self):
+ self.start_impl_portal()
+ self.start_xdp()
+
+ self.create_session()
+ self.create_session()
+ self.get_zones()
+
+ # The default zone is 1920x1080
+ barriers = [
+ {
+ "barrier_id": dbus.UInt32(10, variant_level=1),
+ "position": dbus.Struct(
+ [0, 0, 1920, 0], signature="iiii", variant_level=1
+ ),
+ },
+ ]
+ self.set_pointer_barriers(barriers)
+ self.connect_to_eis()
+
+ # Disable before enable should be a noop
+ self.disable()
+ method_calls = self.mock_interface.GetMethodCalls("Disable")
+ assert len(method_calls) == 1
+
+ self.enable()
+ method_calls = self.mock_interface.GetMethodCalls("Enable")
+ assert len(method_calls) == 1
+
+ self.disable()
+ method_calls = self.mock_interface.GetMethodCalls("Disable")
+ assert len(method_calls) == 2
+
+ def test_disable_signal(self):
+ params = {
+ "disable-delay": 200,
+ }
+ self.start_impl_portal(params)
+ self.start_xdp()
+
+ self.create_session()
+ self.get_zones()
+ # The default zone is 1920x1080
+ barriers = [
+ {
+ "barrier_id": dbus.UInt32(10, variant_level=1),
+ "position": dbus.Struct(
+ [0, 0, 1920, 0], signature="iiii", variant_level=1
+ ),
+ },
+ ]
+ self.set_pointer_barriers(barriers)
+ self.connect_to_eis()
+
+ disabled_signal_received = False
+
+ def cb_disabled(session_handle, options):
+ nonlocal disabled_signal_received
+ disabled_signal_received = True
+ assert session_handle == session_handle
+
+ inputcapture_intf = self.get_dbus_interface()
+ inputcapture_intf.connect_to_signal("Disabled", cb_disabled)
+
+ self.enable()
+
+ mainloop = GLib.MainLoop()
+ GLib.timeout_add(500, mainloop.quit)
+ mainloop.run()
+
+ assert disabled_signal_received
+
+ def test_activated_signal(self):
+ params = {
+ "activated-delay": 200,
+ "deactivated-delay": 300,
+ }
+ self.start_impl_portal(params)
+ self.start_xdp()
+
+ self.create_session()
+ self.get_zones()
+ # The default zone is 1920x1080
+ barriers = [
+ {
+ "barrier_id": dbus.UInt32(10, variant_level=1),
+ "position": dbus.Struct(
+ [0, 0, 1920, 0], signature="iiii", variant_level=1
+ ),
+ },
+ ]
+ self.set_pointer_barriers(barriers)
+ self.connect_to_eis()
+
+ disabled_signal_received = False
+ activated_signal_received = False
+ deactivated_signal_received = False
+
+ def cb_disabled(session_handle, options):
+ nonlocal disabled_signal_received
+ disabled_signal_received = True
+
+ def cb_activated(session_handle, options):
+ nonlocal activated_signal_received
+ activated_signal_received = True
+ assert session_handle == session_handle
+ assert "activation_id" in options
+ assert "barrier_id" in options
+ assert options["barrier_id"] == 10 # template uses first barrier
+ assert "cursor_position" in options
+ assert options["cursor_position"] == (
+ 10.0,
+ 20.0,
+ ) # template uses x+10, y+20 of first barrier
+
+ def cb_deactivated(session_handle, options):
+ nonlocal deactivated_signal_received
+ deactivated_signal_received = True
+ assert session_handle == session_handle
+ assert "activation_id" in options
+ assert "cursor_position" in options
+ assert options["cursor_position"] == (
+ 10.0,
+ 20.0,
+ ) # template uses x+10, y+20 of first barrier
+
+ inputcapture_intf = self.get_dbus_interface()
+ inputcapture_intf.connect_to_signal("Activated", cb_activated)
+ inputcapture_intf.connect_to_signal("Deactivated", cb_deactivated)
+ inputcapture_intf.connect_to_signal("Disabled", cb_disabled)
+
+ self.enable()
+
+ mainloop = GLib.MainLoop()
+ GLib.timeout_add(500, mainloop.quit)
+ mainloop.run()
+
+ assert activated_signal_received
+ assert deactivated_signal_received
+ assert not disabled_signal_received
+
+ # Disabling should not trigger the signal
+ self.disable()
+
+ mainloop = GLib.MainLoop()
+ GLib.timeout_add(500, mainloop.quit)
+ mainloop.run()
+
+ assert not disabled_signal_received
+
+ def test_release(self):
+ params = {
+ "activated-delay": 200,
+ "deactivated-delay": 1000,
+ "disabled-delay": 1200,
+ }
+ self.start_impl_portal(params)
+ self.start_xdp()
+
+ self.create_session()
+ self.get_zones()
+ # The default zone is 1920x1080
+ barriers = [
+ {
+ "barrier_id": dbus.UInt32(10, variant_level=1),
+ "position": dbus.Struct(
+ [0, 0, 1920, 0], signature="iiii", variant_level=1
+ ),
+ },
+ ]
+ self.set_pointer_barriers(barriers)
+ self.connect_to_eis()
+
+ disabled_signal_received = False
+ activated_signal_received = False
+ deactivated_signal_received = False
+ activation_id = None
+
+ def cb_disabled(session_handle, options):
+ nonlocal disabled_signal_received
+ disabled_signal_received = True
+
+ def cb_activated(session_handle, options):
+ nonlocal activated_signal_received, activation_id
+ activated_signal_received = True
+ activation_id = options["activation_id"]
+
+ def cb_deactivated(session_handle, options):
+ nonlocal deactivated_signal_received
+ deactivated_signal_received = True
+
+ inputcapture_intf = self.get_dbus_interface()
+ inputcapture_intf.connect_to_signal("Disabled", cb_activated)
+ inputcapture_intf.connect_to_signal("Activated", cb_activated)
+ inputcapture_intf.connect_to_signal("Deactivated", cb_deactivated)
+
+ self.enable()
+
+ mainloop = GLib.MainLoop()
+ GLib.timeout_add(300, mainloop.quit)
+ mainloop.run()
+
+ assert activated_signal_received
+ assert activation_id is not None
+ assert not deactivated_signal_received
+ assert not disabled_signal_received
+
+ self.release(cursor_position=(10.0, 50.0), activation_id=activation_id)
+
+ # XDP should filter any signals the implementation may
+ # send after Release().
+
+ mainloop = GLib.MainLoop()
+ GLib.timeout_add(1000, mainloop.quit)
+ mainloop.run()
+
+ # Release() implies deactivated
+ assert not deactivated_signal_received
+ assert not disabled_signal_received