/*
 * Copyright © 2017-2018 Red Hat, Inc
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * 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.1 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 <http://www.gnu.org/licenses/>.
 *
 */

#include "config.h"

#include <stdint.h>
#include <glib.h>
#include <gio/gunixfdlist.h>

#include "xdp-context.h"
#include "xdp-dbus.h"
#include "xdp-impl-dbus.h"
#include "xdp-portal-config.h"
#include "xdp-request.h"
#include "xdp-session.h"
#include "xdp-session-persistence.h"
#include "xdp-utils.h"

#include "input-capture.h"

typedef struct _InputCapture InputCapture;
typedef struct _InputCaptureClass InputCaptureClass;

struct _InputCapture
{
  XdpDbusInputCaptureSkeleton parent_instance;

  XdpContext *context;
  XdpDbusImplInputCapture *impl;
  int impl_version;
};

struct _InputCaptureClass
{
  XdpDbusInputCaptureSkeletonClass parent_class;
};

static GQuark quark_request_session;

GType input_capture_get_type (void)  G_GNUC_CONST;
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))

G_DEFINE_AUTOPTR_CLEANUP_FUNC (InputCapture, g_object_unref)

typedef enum _InputCaptureCapabilities
{
  INPUT_CAPTURE_CAPABILITIES_KEYBOARD =    (1 << 0),
  INPUT_CAPTURE_CAPABILITIES_POINTER =     (1 << 1),
  INPUT_CAPTURE_CAPABILITIES_TOUCHSCREEN = (1 << 2),
  INPUT_CAPTURE_CAPABILITIES_ALL =         (1 << 3) - 1,
} InputCaptureCapabilities;

typedef enum _InputCaptureSessionState
{
  INPUT_CAPTURE_SESSION_STATE_INIT,
  INPUT_CAPTURE_SESSION_STATE_STARTED,
  INPUT_CAPTURE_SESSION_STATE_ENABLED,
  INPUT_CAPTURE_SESSION_STATE_ACTIVE,
  INPUT_CAPTURE_SESSION_STATE_DISABLED,
  INPUT_CAPTURE_SESSION_STATE_CLOSED
} InputCaptureSessionState;

typedef struct _InputCaptureSession
{
  XdpSession parent;

  int impl_version;

  InputCaptureSessionState state;
  gboolean clipboard_requested;
  gboolean clipboard_enabled;

  char *restore_token;
  XdpSessionPersistenceMode persist_mode;
  GVariant *restore_data;
} InputCaptureSession;

typedef struct _InputCaptureSessionClass
{
  XdpSessionClass parent_class;
} InputCaptureSessionClass;

G_DEFINE_TYPE (InputCaptureSession, input_capture_session, xdp_session_get_type ())

static void
input_capture_session_close (XdpSession *session)
{
  InputCaptureSession *input_capture_session = INPUT_CAPTURE_SESSION (session);

  input_capture_session->state = INPUT_CAPTURE_SESSION_STATE_CLOSED;

  g_debug ("input capture session owned by '%s' closed", session->sender);
}

static void
input_capture_session_finalize (GObject *object)
{
  InputCaptureSession *input_capture_session = INPUT_CAPTURE_SESSION (object);

  g_clear_pointer (&input_capture_session->restore_token, g_free);
  g_clear_pointer (&input_capture_session->restore_data, g_variant_unref);

  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;
  XdpSessionClass *session_class;

  object_class = G_OBJECT_CLASS (klass);
  object_class->finalize = input_capture_session_finalize;

  session_class = (XdpSessionClass *)klass;
  session_class->close = input_capture_session_close;
}

static InputCaptureSession *
input_capture_session_new (InputCapture     *input_capture,
                           GVariant         *options,
                           GDBusConnection  *connection,
                           XdpAppInfo       *app_info,
                           GError          **error)
{
  const char *session_token;
  XdpSession *session;
  InputCaptureSession *input_capture_session;
  GDBusConnection *impl_connection =
    g_dbus_proxy_get_connection (G_DBUS_PROXY (input_capture->impl));
  const char *impl_dbus_name =
    g_dbus_proxy_get_name (G_DBUS_PROXY (input_capture->impl));

  session_token = lookup_session_token (options);
  session = g_initable_new (input_capture_session_get_type (), NULL, error,
                            "context", input_capture->context,
                            "sender", xdp_app_info_get_sender (app_info),
                            "app-id", xdp_app_info_get_id (app_info),
                            "token", session_token,
                            "connection", connection,
                            "impl-connection", impl_connection,
                            "impl-dbus-name", impl_dbus_name,
                            NULL);

  if (!session)
    return NULL;

  input_capture_session = (InputCaptureSession*) session;
  input_capture_session->impl_version = input_capture->impl_version;

  g_debug ("input capture session owned by '%s' created",
           xdp_app_info_get_sender (app_info));

  return input_capture_session;
}

gboolean
input_capture_session_can_request_clipboard (InputCaptureSession *session)
{
  if (session->clipboard_requested)
    return FALSE;

  if (session->impl_version < 2)
    return FALSE;

  switch (session->state)
    {
    case INPUT_CAPTURE_SESSION_STATE_INIT:
      return TRUE;
    case INPUT_CAPTURE_SESSION_STATE_STARTED:
    case INPUT_CAPTURE_SESSION_STATE_ENABLED:
    case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
    case INPUT_CAPTURE_SESSION_STATE_DISABLED:
    case INPUT_CAPTURE_SESSION_STATE_CLOSED:
      return FALSE;
    }

  g_assert_not_reached ();
}

gboolean
input_capture_session_is_clipboard_enabled (InputCaptureSession *session)
{
  return session->clipboard_enabled;
}

void
input_capture_session_clipboard_requested (InputCaptureSession *session)
{
  session->clipboard_requested = TRUE;
}

gboolean
input_capture_session_can_access_clipboard (InputCaptureSession *session)
{
  return session->clipboard_enabled &&
         session->state == INPUT_CAPTURE_SESSION_STATE_ACTIVE;
}

static gboolean
validate_capabilities (const char  *key,
                       GVariant    *value,
                       GVariant    *options,
                       gpointer     user_data,
                       GError     **error)
{
  uint32_t types = g_variant_get_uint32 (value);

  if (types == 0 || (types & ~INPUT_CAPTURE_CAPABILITIES_ALL) != 0)
    {
      g_set_error (error, XDG_DESKTOP_PORTAL_ERROR, XDG_DESKTOP_PORTAL_ERROR_INVALID_ARGUMENT,
                   "Unsupported capability: %x", types & ~INPUT_CAPTURE_CAPABILITIES_ALL);
      return FALSE;
    }

  return TRUE;
}

static void start_done (GObject      *source_object,
                        GAsyncResult *res,
                        gpointer      data);

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)
{
  InputCapture *input_capture = (InputCapture *) object;
  XdpRequest *request = xdp_request_from_invocation (invocation);
  GDBusConnection *connection =
    g_dbus_method_invocation_get_connection (invocation);
  g_autoptr(XdpDbusImplRequest) impl_request = NULL;
  g_autoptr(GError) error = NULL;
  g_auto(GVariantBuilder) options_builder =
    G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
  g_autoptr(GVariant) options = NULL;
  XdpSession *session;

  REQUEST_AUTOLOCK (request);

  impl_request = xdp_dbus_impl_request_proxy_new_sync (
    g_dbus_proxy_get_connection (G_DBUS_PROXY (input_capture->impl)),
    G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
    g_dbus_proxy_get_name (G_DBUS_PROXY (input_capture->impl)),
    request->id,
    NULL, &error);

  if (!impl_request)
    {
      g_dbus_method_invocation_return_gerror (invocation, error);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  xdp_request_set_impl_request (request, impl_request);
  xdp_request_export (request, connection);

  if (!xdp_filter_options (arg_options, &options_builder,
                           input_capture_create_session_options,
                           G_N_ELEMENTS (input_capture_create_session_options),
                           NULL, &error))
    {
      g_dbus_method_invocation_return_gerror (invocation, error);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }
  options = g_variant_ref_sink (g_variant_builder_end (&options_builder));

  session = XDP_SESSION (input_capture_session_new (input_capture,
                                                    arg_options,
                                                    connection,
                                                    request->app_info,
                                                    &error));
  if (!session)
    {
      g_dbus_method_invocation_return_gerror (invocation, error);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  g_object_set_qdata_full (G_OBJECT (request),
                           quark_request_session,
                           g_object_ref (session),
                           g_object_unref);

  g_object_set_data (G_OBJECT (request), "-xdp-input-capture", input_capture);

  if (input_capture->impl_version < 2)
    {
      xdp_dbus_impl_input_capture_call_create_session (input_capture->impl,
                                                       request->id,
                                                       session->id,
                                                       session->app_id,
                                                       arg_parent_window,
                                                       options,
                                                       NULL,
                                                       start_done,
                                                       g_object_ref (request));
    }
  else
    {
      g_auto(GVariantBuilder) empty_options_builder =
        G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
      g_autoptr(GVariant) results = NULL;

      if (!xdp_dbus_impl_input_capture_call_create_session2_sync (input_capture->impl,
                                                                  session->id,
                                                                  session->app_id,
                                                                  g_variant_builder_end (&empty_options_builder),
                                                                  &results,
                                                                  NULL,
                                                                  &error))
        {
          xdp_session_close (session, FALSE);
          g_dbus_method_invocation_return_gerror (invocation, error);
          return G_DBUS_METHOD_INVOCATION_HANDLED;
        }

      xdp_dbus_impl_input_capture_call_start (input_capture->impl,
                                              request->id,
                                              session->id,
                                              session->app_id,
                                              arg_parent_window,
                                              options,
                                              NULL,
                                              start_done,
                                              g_object_ref (request));
    }

  xdp_dbus_input_capture_complete_create_session (object, invocation, request->id);

  return G_DBUS_METHOD_INVOCATION_HANDLED;
}

static XdpOptionKey input_capture_create_session2_options[] = {
};

static gboolean
handle_create_session2 (XdpDbusInputCapture   *object,
                        GDBusMethodInvocation *invocation,
                        GVariant              *arg_options)
{
  InputCapture *input_capture = (InputCapture *) object;
  XdpAppInfo *app_info = xdp_invocation_get_app_info (invocation);
  GDBusConnection *connection =
    g_dbus_method_invocation_get_connection (invocation);
  XdpSession *session;
  g_auto(GVariantBuilder) options_builder =
    G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
  g_autoptr(GVariant) options = NULL;
  g_autoptr(GVariant) results = NULL;
  g_autoptr(GError) error = NULL;
  g_auto(GVariantBuilder) results_builder =
    G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);

  if (input_capture->impl_version < 2)
    return G_DBUS_METHOD_INVOCATION_UNHANDLED;

  if (!xdp_filter_options (arg_options, &options_builder,
                           input_capture_create_session2_options,
                           G_N_ELEMENTS (input_capture_create_session2_options),
                           NULL, &error))
    {
      g_dbus_method_invocation_return_gerror (invocation, error);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }
  options = g_variant_ref_sink (g_variant_builder_end (&options_builder));

  session = XDP_SESSION (input_capture_session_new (input_capture,
                                                    arg_options,
                                                    connection,
                                                    app_info,
                                                    &error));
  if (!session)
    {
      g_dbus_method_invocation_return_gerror (invocation, error);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  if (!xdp_dbus_impl_input_capture_call_create_session2_sync (input_capture->impl,
                                                              session->id,
                                                              session->app_id,
                                                              options,
                                                              &results,
                                                              NULL,
                                                              &error))
    {
      xdp_session_close (session, FALSE);
      g_dbus_method_invocation_return_gerror (invocation, error);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  if (!xdp_session_export (session, &error))
    {
      xdp_session_close (session, FALSE);
      g_dbus_method_invocation_return_gerror (invocation, error);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  xdp_session_register (session);

  g_variant_builder_add (&results_builder, "{sv}",
                         "session_handle", g_variant_new ("o", session->id));
  xdp_dbus_input_capture_complete_create_session2 (object,
                                                   invocation,
                                                   g_variant_builder_end (&results_builder));

  return G_DBUS_METHOD_INVOCATION_HANDLED;
}

static void
start_done (GObject      *source_object,
            GAsyncResult *res,
            gpointer      data)
{
  InputCapture *input_capture;
  XdpDbusImplInputCapture *impl = XDP_DBUS_IMPL_INPUT_CAPTURE (source_object);
  g_autoptr(XdpRequest) request = XDP_REQUEST (data);
  XdpSession *session;
  g_autoptr(GVariant) results = NULL;
  g_autoptr(GError) error = NULL;
  gboolean should_close_session = FALSE;
  uint32_t response = XDG_DESKTOP_PORTAL_RESPONSE_OTHER;
  g_auto(GVariantBuilder) results_builder =
    G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);

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

  input_capture = g_object_get_data (G_OBJECT (request), "-xdp-input-capture");

  if (input_capture->impl_version < 2)
    {
      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);
        }
    }
  else
    {
      if (!xdp_dbus_impl_input_capture_call_start_finish (impl,
                                                          &response,
                                                          &results,
                                                          res,
                                                          &error))
        {
          g_dbus_error_strip_remote_error (error);
          g_warning ("A backend call failed: %s", error->message);
        }
    }

  /* For the legacy CreateSession we have to take care of exporting it now */
  if (!session->exported)
    {
      if (!xdp_session_export (session, &error))
        {
          g_warning ("Failed to export session: %s", error->message);
          response = XDG_DESKTOP_PORTAL_RESPONSE_OTHER;
          should_close_session = TRUE;
          goto out;
        }

      xdp_session_register (session);
    }

  if (request->exported && response == XDG_DESKTOP_PORTAL_RESPONSE_SUCCESS)
    {
      InputCaptureSession *input_capture_session = INPUT_CAPTURE_SESSION (session);
      uint32_t capabilities = 0;
      gboolean clipboard_enabled = FALSE;

      input_capture_session->state = INPUT_CAPTURE_SESSION_STATE_STARTED;

      g_variant_builder_add (&results_builder, "{sv}",
                             "session_handle", g_variant_new ("o", session->id));

      if (!g_variant_lookup (results, "capabilities", "u", &capabilities))
        {
          g_warning ("Impl did not set capabilities");
          response = XDG_DESKTOP_PORTAL_RESPONSE_OTHER;
          should_close_session = TRUE;
          goto out;
        }

      g_variant_builder_add (&results_builder, "{sv}",
                            "capabilities", g_variant_new_uint32 (capabilities));

      if (g_variant_lookup (results, "clipboard_enabled", "b", &clipboard_enabled))
        {
          input_capture_session->clipboard_enabled = clipboard_enabled;

          g_variant_builder_add (&results_builder, "{sv}",
                                 "clipboard_enabled",
                                 g_variant_new ("b", clipboard_enabled));
        }

      xdp_session_persistence_replace_restore_data_with_token (XDP_SESSION (input_capture_session),
                                                               INPUT_CAPTURE_PERMISSION_TABLE,
                                                               &results,
                                                               &input_capture_session->persist_mode,
                                                               &input_capture_session->restore_token,
                                                               &input_capture_session->restore_data);
      if (input_capture_session->restore_token)
        {
          g_variant_builder_add (&results_builder, "{sv}",
                                 "restore_token",
                                 g_variant_new ("s", input_capture_session->restore_token));
        }
    }
  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));
      xdp_request_unexport (request);
    }

  if (should_close_session)
    xdp_session_close (session, FALSE);
}

static gboolean
replace_input_capture_restore_token_with_data (XdpSession *session,
                                               GVariant **in_out_options,
                                               GError **error)
{
  InputCaptureSession *input_capture_session = INPUT_CAPTURE_SESSION (session);
  g_autoptr(GVariant) options = NULL;
  XdpSessionPersistenceMode persist_mode;

  options = *in_out_options;

  if (!g_variant_lookup (options, "persist_mode", "u", &persist_mode))
    persist_mode = XDP_SESSION_PERSISTENCE_MODE_NONE;

  input_capture_session->persist_mode = persist_mode;
  xdp_session_persistence_replace_restore_token_with_data (session,
                                                           INPUT_CAPTURE_PERMISSION_TABLE,
                                                           in_out_options,
                                                           &input_capture_session->restore_token);

  return TRUE;
}

static gboolean
validate_restore_token (const char  *key,
                        GVariant    *value,
                        GVariant    *options,
                        gpointer     user_data,
                        GError     **error)
{
  const char *restore_token = g_variant_get_string (value, NULL);
  return xdp_session_persistence_validate_restore_token (restore_token, error);
}

static gboolean
validate_persist_mode (const char  *key,
                       GVariant    *value,
                       GVariant    *options,
                       gpointer     user_data,
                       GError     **error)
{
  return xdp_session_persistence_validate_persist_mode (g_variant_get_uint32 (value),
                                                        error);
}

static XdpOptionKey input_capture_start_options[] = {
  { "capabilities", G_VARIANT_TYPE_UINT32, validate_capabilities },
  { "restore_token", G_VARIANT_TYPE_STRING, validate_restore_token },
  { "persist_mode", G_VARIANT_TYPE_UINT32, validate_persist_mode },
};

static gboolean
handle_start (XdpDbusInputCapture   *object,
              GDBusMethodInvocation *invocation,
              const char            *arg_session_handle,
              const char            *arg_parent_window,
              GVariant              *arg_options)
{
  InputCapture *input_capture = (InputCapture *) object;
  XdpRequest *request = xdp_request_from_invocation (invocation);
  XdpSession *session;
  InputCaptureSession *input_capture_session;
  g_autoptr(XdpDbusImplRequest) impl_request = NULL;
  g_auto(GVariantBuilder) options_builder =
    G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
  g_autoptr(GVariant) options = NULL;
  g_autoptr(GError) error = NULL;

  if (input_capture->impl_version < 2)
    return G_DBUS_METHOD_INVOCATION_UNHANDLED;

  REQUEST_AUTOLOCK (request);

  session = xdp_session_from_request (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 = INPUT_CAPTURE_SESSION (session);

  switch (input_capture_session->state)
    {
      case INPUT_CAPTURE_SESSION_STATE_STARTED:
      case INPUT_CAPTURE_SESSION_STATE_ENABLED:
      case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
      case INPUT_CAPTURE_SESSION_STATE_DISABLED:
      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;
      case INPUT_CAPTURE_SESSION_STATE_INIT:
        break;
    }

  impl_request = xdp_dbus_impl_request_proxy_new_sync (
    g_dbus_proxy_get_connection (G_DBUS_PROXY (input_capture->impl)),
    G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
    g_dbus_proxy_get_name (G_DBUS_PROXY (input_capture->impl)),
    request->id,
    NULL, &error);
  if (!impl_request)
    {
      g_dbus_method_invocation_return_gerror (invocation, error);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  xdp_request_set_impl_request (request, impl_request);
  xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation));

  if (!xdp_filter_options (arg_options, &options_builder,
                           input_capture_start_options,
                           G_N_ELEMENTS (input_capture_start_options),
                           NULL, &error))
    {
      g_dbus_method_invocation_return_gerror (invocation, error);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  options = g_variant_ref_sink (g_variant_builder_end (&options_builder));

  /* If 'restore_token' is passed, lookup the corresponding data in the
   * permission store and / or the GHashTable with transient permissions.
   * Portal implementations do not have access to the restore token.
   */
  if (!replace_input_capture_restore_token_with_data (session, &options, &error))
    {
      g_dbus_method_invocation_return_gerror (invocation, error);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  g_object_set_qdata_full (G_OBJECT (request),
                           quark_request_session,
                           g_object_ref (session),
                           g_object_unref);

  g_object_set_data (G_OBJECT (request), "-xdp-input-capture", input_capture);

  xdp_dbus_impl_input_capture_call_start (input_capture->impl,
                                          request->id,
                                          arg_session_handle,
                                          session->app_id,
                                          arg_parent_window,
                                          options,
                                          NULL,
                                          start_done,
                                          g_object_ref (request));

  xdp_dbus_input_capture_complete_start (object, invocation, request->id);

  return G_DBUS_METHOD_INVOCATION_HANDLED;
}

static void
get_zones_done (GObject      *source_object,
                GAsyncResult *res,
                gpointer      data)
{
  XdpDbusImplInputCapture *impl = (XdpDbusImplInputCapture *) source_object;
  g_autoptr(GVariant) results = NULL;
  g_autoptr(XdpRequest) request = NULL;
  g_autoptr(GError) error = NULL;
  XdpSession *session;
  gboolean should_close_session;
  uint32_t response = XDG_DESKTOP_PORTAL_RESPONSE_OTHER;

  request = XDP_REQUEST (data);

  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 != XDG_DESKTOP_PORTAL_RESPONSE_SUCCESS;

  if (request->exported)
    {
      if (!results)
        {
          g_auto(GVariantBuilder) results_builder =
            G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);

          results = g_variant_ref_sink (g_variant_builder_end (&results_builder));
        }

      xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, results);
      xdp_request_unexport (request);
    }

  if (should_close_session)
    {
      xdp_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)
{
  InputCapture *input_capture = (InputCapture *) object;
  XdpRequest *request = xdp_request_from_invocation (invocation);
  g_autoptr(XdpDbusImplRequest) impl_request = NULL;
  InputCaptureSession *input_capture_session;
  g_autoptr(GError) error = NULL;
  g_auto(GVariantBuilder) options_builder =
    G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
  g_autoptr(GVariant) options = NULL;
  XdpSession *session;

  REQUEST_AUTOLOCK (request);

  session = xdp_session_from_request (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 = INPUT_CAPTURE_SESSION (session);

  switch (input_capture_session->state)
    {
      case INPUT_CAPTURE_SESSION_STATE_STARTED:
      case INPUT_CAPTURE_SESSION_STATE_ENABLED:
      case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
      case INPUT_CAPTURE_SESSION_STATE_DISABLED:
        break;
      case INPUT_CAPTURE_SESSION_STATE_INIT:
      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 (input_capture->impl)),
    G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
    g_dbus_proxy_get_name (G_DBUS_PROXY (input_capture->impl)),
    request->id,
    NULL, &error);

  if (!impl_request)
    {
      g_dbus_method_invocation_return_gerror (invocation, error);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  xdp_request_set_impl_request (request, impl_request);
  xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation));

  if (!xdp_filter_options (arg_options, &options_builder,
                           input_capture_get_zones_options,
                           G_N_ELEMENTS (input_capture_get_zones_options),
                           NULL, &error))
    {
      g_dbus_method_invocation_return_gerror (invocation, error);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  options = g_variant_ref_sink (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 (input_capture->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)
{
  XdpDbusImplInputCapture *impl = (XdpDbusImplInputCapture *) source_object;
  g_autoptr(GVariant) results = NULL;
  g_autoptr(XdpRequest) request = NULL;
  g_autoptr(GError) error = NULL;
  gboolean should_close_session;
  XdpSession *session;
  uint32_t response = XDG_DESKTOP_PORTAL_RESPONSE_OTHER;

  request = XDP_REQUEST (data);

  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);
      g_clear_error (&error);
    }

  should_close_session = !request->exported ||
                         response != XDG_DESKTOP_PORTAL_RESPONSE_SUCCESS;

  if (request->exported)
    {
      if (!results)
        {
          g_auto(GVariantBuilder) results_builder =
            G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);

          results = g_variant_ref_sink (g_variant_builder_end (&results_builder));
        }

      xdp_dbus_request_emit_response (XDP_DBUS_REQUEST (request), response, results);
      xdp_request_unexport (request);
    }

  if (should_close_session)
    xdp_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,
                             uint32_t               arg_zone_set)
{
  InputCapture *input_capture = (InputCapture *) object;
  XdpRequest *request = xdp_request_from_invocation (invocation);
  g_autoptr(XdpDbusImplRequest) impl_request = NULL;
  InputCaptureSession *input_capture_session;
  g_autoptr(GError) error = NULL;
  g_auto(GVariantBuilder) options_builder =
    G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
  g_autoptr(GVariant) options = NULL;
  XdpSession *session;

  REQUEST_AUTOLOCK (request);

  session = xdp_session_from_request (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 = INPUT_CAPTURE_SESSION (session);

  switch (input_capture_session->state)
    {
      case INPUT_CAPTURE_SESSION_STATE_STARTED:
      case INPUT_CAPTURE_SESSION_STATE_ENABLED:
      case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
      case INPUT_CAPTURE_SESSION_STATE_DISABLED:
        break;
      case INPUT_CAPTURE_SESSION_STATE_INIT:
      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 (input_capture->impl)),
    G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
    g_dbus_proxy_get_name (G_DBUS_PROXY (input_capture->impl)),
    request->id,
    NULL, &error);

  if (!impl_request)
    {
      g_dbus_method_invocation_return_gerror (invocation, error);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  xdp_request_set_impl_request (request, impl_request);
  xdp_request_export (request, g_dbus_method_invocation_get_connection (invocation));

  if (!xdp_filter_options (arg_options, &options_builder,
                           input_capture_set_pointer_barriers_options,
                           G_N_ELEMENTS (input_capture_set_pointer_barriers_options),
                           NULL, &error))
    {
      g_dbus_method_invocation_return_gerror (invocation, error);
      return G_DBUS_METHOD_INVOCATION_HANDLED;
    }

  options = g_variant_ref_sink (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 (input_capture->impl,
                                                         request->id,
                                                         arg_session_handle,
                                                         xdp_app_info_get_id (request->app_info),
                                                         options,
                                                         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)
{
  InputCapture *input_capture = (InputCapture *) object;
  XdpAppInfo *app_info = xdp_invocation_get_app_info (invocation);
  XdpSession *session;
  InputCaptureSession *input_capture_session;
  g_autoptr(GError) error = NULL;
  g_auto(GVariantBuilder) options_builder =
    G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);

  session = xdp_session_from_app_info (arg_session_handle, app_info);
  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 = INPUT_CAPTURE_SESSION (session);

   switch (input_capture_session->state)
     {
       case INPUT_CAPTURE_SESSION_STATE_STARTED:
         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_INIT:
       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;
     }

  if (!xdp_filter_options (arg_options, &options_builder,
                           input_capture_enable_options,
                           G_N_ELEMENTS (input_capture_enable_options),
                           NULL, &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_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_INIT:
      case INPUT_CAPTURE_SESSION_STATE_STARTED:
      case INPUT_CAPTURE_SESSION_STATE_CLOSED:
        /* ignore, handled above */
        g_assert_not_reached ();
    }

  xdp_dbus_impl_input_capture_call_enable (input_capture->impl,
                                           arg_session_handle,
                                           xdp_app_info_get_id (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)
{
  InputCapture *input_capture = (InputCapture *) object;
  XdpAppInfo *app_info = xdp_invocation_get_app_info (invocation);
  XdpSession *session;
  InputCaptureSession *input_capture_session;
  g_autoptr(GError) error = NULL;
  g_auto(GVariantBuilder) options_builder =
    G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);

  session = xdp_session_from_app_info (arg_session_handle, app_info);
  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 = INPUT_CAPTURE_SESSION (session);

   switch (input_capture_session->state)
     {
       case INPUT_CAPTURE_SESSION_STATE_STARTED:
         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_INIT:
       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;
     }

  if (!xdp_filter_options (arg_options, &options_builder,
                           input_capture_disable_options,
                           G_N_ELEMENTS (input_capture_disable_options),
                           NULL, &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_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_INIT:
      case INPUT_CAPTURE_SESSION_STATE_STARTED:
      case INPUT_CAPTURE_SESSION_STATE_CLOSED:
        /* ignore, handled above */
        g_assert_not_reached ();
    }

  xdp_dbus_impl_input_capture_call_disable (input_capture->impl,
                                            arg_session_handle,
                                            xdp_app_info_get_id (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)
{
  InputCapture *input_capture = (InputCapture *) object;
  XdpAppInfo *app_info = xdp_invocation_get_app_info (invocation);
  XdpSession *session;
  InputCaptureSession *input_capture_session;
  g_autoptr(GError) error = NULL;
  g_auto(GVariantBuilder) options_builder =
    G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);

  session = xdp_session_from_app_info (arg_session_handle, app_info);
  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 = INPUT_CAPTURE_SESSION (session);

  switch (input_capture_session->state)
    {
      case INPUT_CAPTURE_SESSION_STATE_STARTED:
        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_INIT:
      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;
    }

  if (!xdp_filter_options (arg_options, &options_builder,
                           input_capture_release_options,
                           G_N_ELEMENTS (input_capture_release_options),
                           NULL, &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_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_INIT:
      case INPUT_CAPTURE_SESSION_STATE_STARTED:
      case INPUT_CAPTURE_SESSION_STATE_CLOSED:
        /* ignore, handled above */
        g_assert_not_reached ();
    }

  xdp_dbus_impl_input_capture_call_release (input_capture->impl,
                                            arg_session_handle,
                                            xdp_app_info_get_id (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)
{
  InputCapture *input_capture = (InputCapture *) object;
  XdpAppInfo *app_info = xdp_invocation_get_app_info (invocation);
  XdpSession *session;
  InputCaptureSession *input_capture_session;
  g_autoptr(GUnixFDList) out_fd_list = NULL;
  g_autoptr(GError) error = NULL;
  g_auto(GVariantBuilder) empty =
    G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
  g_autoptr(GVariant) fd = NULL;

  session = xdp_session_from_app_info (arg_session_handle, app_info);
  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 = INPUT_CAPTURE_SESSION (session);

  switch (input_capture_session->state)
    {
      case INPUT_CAPTURE_SESSION_STATE_STARTED:
          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_INIT:
      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;
    }

  if (!xdp_dbus_impl_input_capture_call_connect_to_eis_sync (input_capture->impl,
                                                             arg_session_handle,
                                                             xdp_app_info_get_id (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_create_session2 = handle_create_session2;
  iface->handle_start = handle_start;
  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)
{
  GDBusConnection *connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (impl));
  g_autoptr(XdpSession) session = xdp_session_lookup (session_id);

  g_dbus_connection_emit_signal (connection,
                                 session->sender,
                                 DESKTOP_DBUS_PATH,
                                 INPUT_CAPTURE_DBUS_IFACE,
                                 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(XdpSession) session = xdp_session_lookup (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_STARTED:
        break;
      case INPUT_CAPTURE_SESSION_STATE_ENABLED:
      case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
        pass_signal (impl, "Disabled", session_id, options);
        input_capture_session->state = INPUT_CAPTURE_SESSION_STATE_DISABLED;
        break;
      case INPUT_CAPTURE_SESSION_STATE_DISABLED:
      case INPUT_CAPTURE_SESSION_STATE_INIT:
      case INPUT_CAPTURE_SESSION_STATE_CLOSED:
        break;
    }
}

static void
on_activated_cb (XdpDbusImplInputCapture *impl,
                 const char              *session_id,
                 GVariant                *options,
                 gpointer                 data)
{
  g_autoptr(XdpSession) session = xdp_session_lookup (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_STARTED:
        break;
      case INPUT_CAPTURE_SESSION_STATE_ENABLED:
        pass_signal (impl, "Activated", session_id, options);
        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_INIT:
      case INPUT_CAPTURE_SESSION_STATE_CLOSED:
        break;
    }
}

static void
on_deactivated_cb (XdpDbusImplInputCapture *impl,
                   const char              *session_id,
                   GVariant                *options,
                   gpointer                 data)
{
  g_autoptr(XdpSession) session = xdp_session_lookup (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_STARTED:
      case INPUT_CAPTURE_SESSION_STATE_ENABLED:
        break;
      case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
        pass_signal (impl, "Deactivated", session_id, options);
        input_capture_session->state = INPUT_CAPTURE_SESSION_STATE_ENABLED;
        break;
      case INPUT_CAPTURE_SESSION_STATE_DISABLED:
      case INPUT_CAPTURE_SESSION_STATE_INIT:
      case INPUT_CAPTURE_SESSION_STATE_CLOSED:
        break;
    }
}

static void
on_zones_changed_cb (XdpDbusImplInputCapture *impl,
                     const char              *session_id,
                     GVariant                *options,
                     gpointer                 data) 
{
  g_autoptr(XdpSession) session = xdp_session_lookup (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_STARTED:
    case INPUT_CAPTURE_SESSION_STATE_ENABLED:
    case INPUT_CAPTURE_SESSION_STATE_ACTIVE:
    case INPUT_CAPTURE_SESSION_STATE_DISABLED:
      pass_signal (impl, "ZonesChanged", session_id, options);
      break;
    case INPUT_CAPTURE_SESSION_STATE_INIT:
    case INPUT_CAPTURE_SESSION_STATE_CLOSED:
      break;
    }
}

static void
input_capture_dispose (GObject *object)
{
  InputCapture *input_capture = (InputCapture *) object;

  g_clear_object (&input_capture->impl);

  G_OBJECT_CLASS (input_capture_parent_class)->dispose (object);
}

static void
input_capture_init (InputCapture *input_capture)
{
}

static void
input_capture_class_init (InputCaptureClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->dispose = input_capture_dispose;

  quark_request_session =
    g_quark_from_static_string ("-xdp-request-capture-input-session");
}

static InputCapture *
input_capture_new (XdpContext              *context,
                   XdpDbusImplInputCapture *impl)
{
  InputCapture *input_capture;

  input_capture = g_object_new (input_capture_get_type (), NULL);
  input_capture->context = context;
  input_capture->impl = g_object_ref (impl);

  g_dbus_proxy_set_default_timeout (G_DBUS_PROXY (input_capture->impl), G_MAXINT);

  input_capture->impl_version =
    MIN (xdp_dbus_impl_input_capture_get_version (impl), 2);
  xdp_dbus_input_capture_set_version (XDP_DBUS_INPUT_CAPTURE (input_capture),
                                      input_capture->impl_version);

  g_object_bind_property (G_OBJECT (input_capture->impl), "supported-capabilities",
                          G_OBJECT (input_capture), "supported-capabilities",
                          G_BINDING_SYNC_CREATE);

  g_signal_connect_object (input_capture->impl, "disabled",
                           G_CALLBACK (on_disabled_cb),
                           input_capture,
                           G_CONNECT_DEFAULT);
  g_signal_connect_object (input_capture->impl, "activated",
                           G_CALLBACK (on_activated_cb),
                           input_capture,
                           G_CONNECT_DEFAULT);
  g_signal_connect_object (input_capture->impl, "deactivated",
                           G_CALLBACK (on_deactivated_cb),
                           input_capture,
                           G_CONNECT_DEFAULT);
  g_signal_connect_object (input_capture->impl, "zones-changed",
                           G_CALLBACK (on_zones_changed_cb),
                           input_capture,
                           G_CONNECT_DEFAULT);

  return input_capture;
}

void
init_input_capture (XdpContext *context)
{
  g_autoptr(InputCapture) input_capture = NULL;
  GDBusConnection *connection = xdp_context_get_connection (context);
  XdpPortalConfig *config = xdp_context_get_config (context);
  XdpImplConfig *impl_config;
  g_autoptr(XdpDbusImplInputCapture) impl = NULL;
  g_autoptr(GError) error = NULL;

  impl_config = xdp_portal_config_find (config, INPUT_CAPTURE_DBUS_IMPL_IFACE);
  if (impl_config == NULL)
    return;

  impl = xdp_dbus_impl_input_capture_proxy_new_sync (connection,
                                                     G_DBUS_PROXY_FLAGS_NONE,
                                                     impl_config->dbus_name,
                                                     DESKTOP_DBUS_PATH,
                                                     NULL,
                                                     &error);
  if (impl == NULL)
    {
      g_warning ("Failed to create input capture proxy: %s", error->message);
      return;
    }

  input_capture = input_capture_new (context, impl);

  xdp_context_take_and_export_portal (context,
                                      G_DBUS_INTERFACE_SKELETON (g_steal_pointer (&input_capture)),
                                      XDP_CONTEXT_EXPORT_FLAGS_NONE);
}
