/*
 * Copyright (C) 2023 SUSE Software Solutions Germany GmbH
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * Written by:
 *     Joan Torres <joan.torres@suse.com>
 */

#include "config.h"

#include "grd-daemon-handover.h"

#include <gio/gunixfdlist.h>
#include <glib/gi18n.h>
#include <unistd.h>

#include "grd-context.h"
#include "grd-daemon.h"
#include "grd-daemon-utils.h"
#include "grd-dbus-remote-desktop.h"
#include "grd-private.h"
#include "grd-prompt.h"
#include "grd-rdp-server.h"
#include "grd-session-rdp.h"
#include "grd-settings.h"
#include "grd-shell-dialog.h"

struct _GrdDaemonHandover
{
  GrdDaemon parent;

  GrdDBusRemoteDesktopRdpDispatcher *remote_desktop_dispatcher;
  GrdDBusRemoteDesktopRdpHandover *remote_desktop_handover;

  GDBusObjectManager *handover_object_manager;

  GrdSession *session;

  gboolean use_system_credentials;
  GrdPrompt *prompt;
  GCancellable *prompt_cancellable;

  GrdShellDialog *dialog;

  unsigned int gnome_remote_desktop_watch_name_id;
};

G_DEFINE_TYPE (GrdDaemonHandover, grd_daemon_handover, GRD_TYPE_DAEMON)

static void
on_remote_desktop_rdp_dispatcher_handover_requested (GObject      *object,
                                                     GAsyncResult *result,
                                                     gpointer      user_data);

static gboolean
grd_daemon_handover_is_ready (GrdDaemon *daemon)
{
  GrdContext *context = grd_daemon_get_context (daemon);

  if (!grd_context_get_mutter_remote_desktop_proxy (context) ||
      !grd_context_get_mutter_screen_cast_proxy (context) ||
      !GRD_DAEMON_HANDOVER (daemon)->remote_desktop_handover)
    return FALSE;

  return TRUE;
}

static void
on_take_client_finished (GObject      *object,
                         GAsyncResult *result,
                         gpointer      user_data)
{
  int fd;
  int fd_idx = -1;
  g_autoptr (GError) error = NULL;
  g_autoptr (GSocket) socket = NULL;
  g_autoptr (GVariant) fd_variant = NULL;
  g_autoptr (GUnixFDList) fd_list = NULL;
  GrdDBusRemoteDesktopRdpHandover *proxy;
  GrdDaemonHandover *daemon_handover;
  GrdRdpServer *rdp_server;
  GSocketConnection *socket_connection;

  proxy = GRD_DBUS_REMOTE_DESKTOP_RDP_HANDOVER (object);
  if (!grd_dbus_remote_desktop_rdp_handover_call_take_client_finish (
         proxy, &fd_variant, &fd_list, result, &error))
    {
      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        return;

      g_warning ("[DaemonHandover] An error occurred while calling "
                 "TakeClient: %s", error->message);
      return;
    }

  g_variant_get (fd_variant, "h", &fd_idx);
  if (!G_IS_UNIX_FD_LIST (fd_list) ||
      fd_idx < 0 || fd_idx >= g_unix_fd_list_get_length (fd_list))
    {
      g_warning ("Failed to acquire file descriptor: The fd list or fd index "
                 "is invalid");
      return;
    }

  fd = g_unix_fd_list_get (fd_list, fd_idx, &error);
  if (fd == -1)
    {
      g_warning ("[DaemonHandover] Failed to acquire file descriptor: %s",
                 error->message);
      return;
    }

  socket = g_socket_new_from_fd (fd, &error);
  if (!socket)
    {
      g_warning ("[DaemonHandover] Failed to create socket from fd: %s",
                 error->message);
      return;
    }

  daemon_handover = GRD_DAEMON_HANDOVER (user_data);
  rdp_server = grd_daemon_get_rdp_server (GRD_DAEMON (daemon_handover));
  socket_connection = g_socket_connection_factory_create_connection (socket);

  grd_rdp_server_notify_incoming (G_SOCKET_SERVICE (rdp_server),
                                  socket_connection);
}

static void
on_get_system_credentials_finished (GObject      *object,
                                    GAsyncResult *result,
                                    gpointer      user_data)
{
  GrdDBusRemoteDesktopRdpHandover *remote_desktop_handover;
  g_autoptr (GError) error = NULL;
  g_autofree char *username = NULL;
  g_autofree char *password = NULL;
  GrdDaemonHandover *daemon_handover;
  GrdContext *context;
  GrdSettings *settings;
  GCancellable *cancellable;
  const char* object_path;

  remote_desktop_handover = GRD_DBUS_REMOTE_DESKTOP_RDP_HANDOVER (object);
  if (!grd_dbus_remote_desktop_rdp_handover_call_get_system_credentials_finish (
         remote_desktop_handover,
         &username,
         &password,
         result,
         &error))
    {
      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        return;

      g_warning ("[DaemonHandover] Failed to get system credentials: %s",
                 error->message);
      return;
    }

  daemon_handover = GRD_DAEMON_HANDOVER (user_data);
  context = grd_daemon_get_context (GRD_DAEMON (daemon_handover));
  settings = grd_context_get_settings (context);

  if (!grd_settings_set_rdp_credentials (settings, username, password, &error))
    {
      g_warning ("[DaemonHanodver] Failed to overwrite credentials: %s",
                 error->message);
      return;
    }

  object_path =
    g_dbus_proxy_get_object_path (G_DBUS_PROXY (remote_desktop_handover));

  g_debug ("[DaemonHandover] At: %s, calling TakeClient", object_path);

  cancellable = grd_daemon_get_cancellable (GRD_DAEMON (daemon_handover));
  grd_dbus_remote_desktop_rdp_handover_call_take_client (
    remote_desktop_handover,
    NULL,
    cancellable,
    on_take_client_finished,
    daemon_handover);
}

static void
on_take_client_ready (GrdDBusRemoteDesktopRdpHandover *interface,
                      gboolean                         use_system_credentials,
                      GrdDaemonHandover               *daemon_handover)
{
  GCancellable *cancellable =
    grd_daemon_get_cancellable (GRD_DAEMON (daemon_handover));
  const char* object_path;

  object_path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (interface));

  g_debug ("[DaemonHandover] At: %s, received TakeClientReady signal",
           object_path);

  daemon_handover->use_system_credentials = use_system_credentials;

  if (use_system_credentials)
    {
      g_debug ("[DaemonHandover] At: %s, calling GetSystemCredentials",
                object_path);

      grd_dbus_remote_desktop_rdp_handover_call_get_system_credentials (
        interface,
        cancellable,
        on_get_system_credentials_finished,
        daemon_handover);

      return;
    }

  g_debug ("[DaemonHandover] At: %s, calling TakeClient", object_path);

  grd_dbus_remote_desktop_rdp_handover_call_take_client (
    interface,
    NULL,
    cancellable,
    on_take_client_finished,
    daemon_handover);
}

static void
on_start_handover_finished (GObject      *object,
                            GAsyncResult *result,
                            gpointer      user_data)
{
  GrdDBusRemoteDesktopRdpHandover *proxy;
  g_autofree char *certificate = NULL;
  g_autofree char *key = NULL;
  g_autoptr (GError) error = NULL;
  GrdDaemonHandover *daemon_handover;
  GrdContext *context;
  GrdSettings *settings;

  proxy = GRD_DBUS_REMOTE_DESKTOP_RDP_HANDOVER (object);
  if (!grd_dbus_remote_desktop_rdp_handover_call_start_handover_finish (
         proxy, &certificate, &key, result, &error))
    {
      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        return;

      g_warning ("[DaemonHandover] Failed to start handover: %s",
                 error->message);
      return;
    }

  daemon_handover = GRD_DAEMON_HANDOVER (user_data);
  context = grd_daemon_get_context (GRD_DAEMON (daemon_handover));
  settings = grd_context_get_settings (context);

  g_object_set (G_OBJECT (settings),
                "rdp-server-cert", certificate,
                "rdp-server-key", key,
                NULL);
}

static void
start_handover (GrdDaemonHandover *daemon_handover)
{
  GrdDaemon *daemon = GRD_DAEMON (daemon_handover);
  GrdContext *context = grd_daemon_get_context (daemon);
  GrdSettings *settings = grd_context_get_settings (context);
  GCancellable *cancellable = grd_daemon_get_cancellable (daemon);
  g_autofree char *username = NULL;
  g_autofree char *password = NULL;
  const char *object_path;

  if (!grd_settings_get_rdp_credentials (settings,
                                         &username, &password,
                                         NULL))
    g_assert_not_reached ();

  object_path = g_dbus_proxy_get_object_path (
                  G_DBUS_PROXY (daemon_handover->remote_desktop_handover));

  g_debug ("[DaemonHandover] At: %s, calling StartHandover", object_path);

  grd_dbus_remote_desktop_rdp_handover_call_start_handover (
    daemon_handover->remote_desktop_handover,
    username,
    password,
    cancellable,
    on_start_handover_finished,
    daemon_handover);
}

static void
on_session_stopped (GrdSession        *session,
                    GrdDaemonHandover *daemon_handover)
{
  if (grd_is_remote_login ())
    grd_session_manager_call_logout_sync ();

  daemon_handover->session = NULL;
}

static gboolean
show_insecure_connection_dialog (GrdDaemonHandover *daemon_handover)
{
  GCancellable *cancellable =
    grd_daemon_get_cancellable (GRD_DAEMON (daemon_handover));

  g_assert (!daemon_handover->dialog);

  daemon_handover->dialog = grd_shell_dialog_new (cancellable);
  if (!daemon_handover->dialog)
    return FALSE;

  g_signal_connect_swapped (daemon_handover->dialog, "cancelled",
                            G_CALLBACK (grd_session_stop),
                            daemon_handover->session);

  grd_shell_dialog_open (daemon_handover->dialog,
                         _("Continue With Insecure Connection?"),
                         /* Translators: Don't translate “use redirection server name:i:1”.
                          * It's a menu option, and it's the same for all languages. */
                         _("This Remote Desktop connection is insecure. "
                           "To secure this connection, enable RDSTLS Security "
                           "in your client by saving the connection settings "
                           "in your client as an RDP file and set "
                           "“use redirection server name:i:1” in it."),
                         _("Disconnect"),
                         _("Continue"));

  return TRUE;
}

static void
prompt_response_callback (GObject      *source_object,
                          GAsyncResult *async_result,
                          gpointer      user_data)
{
  GrdDaemonHandover *daemon_handover;
  g_autoptr (GError) error = NULL;
  GrdPromptResponse response;

  if (!grd_prompt_query_finish (GRD_PROMPT (source_object),
                                async_result,
                                &response,
                                &error))
    {
      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        {
          daemon_handover = GRD_DAEMON_HANDOVER (user_data);

          g_warning ("Failed to query user about session: %s", error->message);
          grd_session_stop (daemon_handover->session);
        }

      return;
    }

  daemon_handover = GRD_DAEMON_HANDOVER (user_data);

  switch (response)
    {
    case GRD_PROMPT_RESPONSE_ACCEPT:
      return;
    case GRD_PROMPT_RESPONSE_CANCEL:
      grd_session_stop (daemon_handover->session);
      return;
    }

  g_assert_not_reached ();
}

static void
show_insecure_connection_prompt (GrdDaemonHandover *daemon_handover)
{
  g_autoptr (GrdPromptDefinition) prompt_definition = NULL;

  g_assert (!daemon_handover->prompt);

  daemon_handover->prompt = g_object_new (GRD_TYPE_PROMPT, NULL);
  daemon_handover->prompt_cancellable = g_cancellable_new ();

  prompt_definition = g_new0 (GrdPromptDefinition, 1);
  prompt_definition->summary =
    g_strdup_printf (_("Continue With Insecure Connection?"));
  prompt_definition->body =
  /* Translators: Don't translate “use redirection server name:i:1”.
   * It's a menu option, and it's the same for all languages. */
    g_strdup_printf (_("This Remote Desktop connection is insecure. "
                       "To secure this connection, enable RDSTLS Security "
                       "in your client by saving the connection settings "
                       "in your client as an RDP file and set "
                       "“use redirection server name:i:1” in it."));
  prompt_definition->cancel_label = g_strdup_printf (_("Disconnect"));
  prompt_definition->accept_label = g_strdup_printf (_("Continue"));

  grd_prompt_query_async (daemon_handover->prompt,
                          prompt_definition,
                          daemon_handover->prompt_cancellable,
                          prompt_response_callback,
                          daemon_handover);
}

static void
inform_about_insecure_connection (GrdDaemonHandover *daemon_handover)
{
  gboolean could_display_dialog;

  could_display_dialog = show_insecure_connection_dialog (daemon_handover);
  if (!could_display_dialog)
    show_insecure_connection_prompt (daemon_handover);
}

static void
on_redirect_client (GrdDBusRemoteDesktopRdpHandover *interface,
                    const char                      *routing_token,
                    const char                      *username,
                    const char                      *password,
                    GrdDaemonHandover               *daemon_handover)
{
  const char *object_path =
    g_dbus_proxy_get_object_path (G_DBUS_PROXY (interface));
  GrdContext *context = grd_daemon_get_context (GRD_DAEMON (daemon_handover));
  GrdSettings *settings = grd_context_get_settings (context);
  GrdSessionRdp *session_rdp = GRD_SESSION_RDP (daemon_handover->session);
  g_autofree char *certificate = NULL;

  g_debug ("[DaemonHandover] At: %s, received RedirectClient signal",
           object_path);

  g_object_get (G_OBJECT (settings),
                "rdp-server-cert", &certificate,
                NULL);

  if (!grd_session_rdp_send_server_redirection (session_rdp, routing_token,
                                                username, password,
                                                certificate))
    grd_session_stop (daemon_handover->session);
}

static void
on_handover_is_waiting_changed (GrdDBusRemoteDesktopRdpHandover *proxy,
                                GParamSpec                      *pspec,
                                GrdDaemonHandover               *daemon_handover)
{
  gboolean handover_is_waiting;

  handover_is_waiting = 
    grd_dbus_remote_desktop_rdp_handover_get_handover_is_waiting (
      daemon_handover->remote_desktop_handover);

  if (!handover_is_waiting)
    return;

  start_handover (daemon_handover);
}

static void
on_incoming_new_connection (GrdRdpServer      *rdp_server,
                            GrdSession        *session,
                            GrdDaemonHandover *daemon_handover)
{
  GrdContext *context = grd_daemon_get_context (GRD_DAEMON (daemon_handover));
  GrdSettings *settings = grd_context_get_settings (context);

  if (daemon_handover->session)
    {
      g_signal_handlers_disconnect_by_func (daemon_handover->session,
                                            G_CALLBACK (on_session_stopped),
                                            daemon_handover);
      grd_session_stop (daemon_handover->session);
    }

  g_signal_connect (session, "stopped",
                    G_CALLBACK (on_session_stopped),
                    daemon_handover);

  daemon_handover->session = session;

  grd_settings_recreate_rdp_credentials (settings);

  if (daemon_handover->use_system_credentials && grd_is_remote_login ())
    inform_about_insecure_connection (daemon_handover);
}

static void
setup_handover (GrdDaemonHandover *daemon_handover)
{
  GrdRdpServer *rdp_server;
  gboolean handover_is_waiting;

  if (!daemon_handover->remote_desktop_handover)
    return;

  rdp_server = grd_daemon_get_rdp_server (GRD_DAEMON (daemon_handover));
  if (!rdp_server)
    return;

  g_signal_connect (daemon_handover->remote_desktop_handover, "take-client-ready",
                    G_CALLBACK (on_take_client_ready), daemon_handover);
  g_signal_connect (daemon_handover->remote_desktop_handover, "redirect-client",
                    G_CALLBACK (on_redirect_client), daemon_handover);
  g_signal_connect (daemon_handover->remote_desktop_handover, "notify::handover-is-waiting",
                    G_CALLBACK (on_handover_is_waiting_changed), daemon_handover);

  g_signal_connect (rdp_server, "incoming-new-connection",
                    G_CALLBACK (on_incoming_new_connection), daemon_handover);

  handover_is_waiting = grd_dbus_remote_desktop_rdp_handover_get_handover_is_waiting (
                          daemon_handover->remote_desktop_handover);

  if (handover_is_waiting)
    start_handover (daemon_handover);
}

static void
teardown_handover (GrdDaemonHandover *daemon_handover)
{
  GrdRdpServer *rdp_server =
    grd_daemon_get_rdp_server (GRD_DAEMON (daemon_handover));

  if (daemon_handover->remote_desktop_handover)
    {
      g_signal_handlers_disconnect_by_func (daemon_handover->remote_desktop_handover,
                                            G_CALLBACK (on_take_client_ready),
                                            daemon_handover);
      g_signal_handlers_disconnect_by_func (daemon_handover->remote_desktop_handover,
                                            G_CALLBACK (on_redirect_client),
                                            daemon_handover);
      g_signal_handlers_disconnect_by_func (daemon_handover->remote_desktop_handover,
                                            G_CALLBACK (on_handover_is_waiting_changed),
                                            daemon_handover);
    }

  if (rdp_server)
    {
      g_signal_handlers_disconnect_by_func (rdp_server,
                                            G_CALLBACK (on_incoming_new_connection),
                                            daemon_handover);
    }
}

static void
on_rdp_server_started (GrdDaemonHandover *daemon_handover)
{
  setup_handover (daemon_handover);
}

static void
on_rdp_server_stopped (GrdDaemonHandover *daemon_handover)
{
  teardown_handover (daemon_handover);
}

static void
on_handover_object_added (GDBusObjectManager *manager,
                          GDBusObject        *object,
                          GrdDaemonHandover  *daemon_handover)
{
  g_autofree char *session_id = NULL;
  g_autofree char *expected_object_path = NULL;
  const char *object_path;
  GCancellable *cancellable;

  session_id = grd_get_session_id_from_pid (getpid ());
  if (!session_id)
    {
      g_warning ("[DaemonHandover] Could not get session id");
      return;
    }

  expected_object_path = g_strdup_printf ("%s/session%s",
                                          REMOTE_DESKTOP_HANDOVERS_OBJECT_PATH,
                                          session_id);
  object_path = g_dbus_object_get_object_path (object);

  if (g_strcmp0 (object_path, expected_object_path) != 0)
    {
      g_debug ("[DaemonHandover] Ignoring handover object at: %s, "
               "expected: %s", object_path, expected_object_path);
      return;
    }

  cancellable = grd_daemon_get_cancellable (GRD_DAEMON (daemon_handover));
  grd_dbus_remote_desktop_rdp_dispatcher_call_request_handover (
    daemon_handover->remote_desktop_dispatcher,
    cancellable,
    on_remote_desktop_rdp_dispatcher_handover_requested,
    daemon_handover);
}

static void
on_handover_object_manager_acquired (GObject      *source_object,
                                     GAsyncResult *result,
                                     gpointer      user_data)
{
  g_autoptr (GDBusObjectManager) manager = NULL;
  g_autoptr (GError) error = NULL;
  GrdDaemonHandover *daemon_handover;

  manager =
    grd_dbus_remote_desktop_object_manager_client_new_for_bus_finish (result, 
                                                                      &error);
  if (!manager)
    {
      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        g_warning ("[DaemonHandover] Failed to create handover object manager: %s",
                   error->message);
      return;
    }

  daemon_handover = GRD_DAEMON_HANDOVER (user_data);
  daemon_handover->handover_object_manager = g_steal_pointer (&manager);

  g_debug ("[DaemonHandover] Watching handover objects");

  g_signal_connect (daemon_handover->handover_object_manager, "object-added",
                    G_CALLBACK (on_handover_object_added), daemon_handover);
}

static void
start_watching_handover_objects (GrdDaemonHandover *daemon_handover)
{
  GCancellable *cancellable;

  if (daemon_handover->handover_object_manager)
    return;

  cancellable = grd_daemon_get_cancellable (GRD_DAEMON (daemon_handover));

  grd_dbus_remote_desktop_object_manager_client_new_for_bus (
    G_BUS_TYPE_SYSTEM,
    G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
    REMOTE_DESKTOP_BUS_NAME,
    REMOTE_DESKTOP_HANDOVERS_OBJECT_PATH,
    cancellable,
    on_handover_object_manager_acquired,
    daemon_handover);
}

static void
stop_watching_handover_objects (GrdDaemonHandover *daemon_handover)
{
  if (!daemon_handover->handover_object_manager)
    return;

  g_signal_handlers_disconnect_by_func (daemon_handover->handover_object_manager,
                                        G_CALLBACK (on_handover_object_added),
                                        daemon_handover);

  g_clear_object (&daemon_handover->handover_object_manager);
}

static void
on_remote_desktop_rdp_handover_proxy_acquired (GObject      *object,
                                               GAsyncResult *result,
                                               gpointer      user_data)
{
  GrdDaemonHandover *daemon_handover;
  g_autoptr (GrdDBusRemoteDesktopRdpHandover) proxy = NULL;
  g_autoptr (GError) error = NULL;

  proxy =
    grd_dbus_remote_desktop_rdp_handover_proxy_new_for_bus_finish (result,
                                                                   &error);
  if (!proxy)
    {
      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        return;

      g_warning ("[DaemonHandover] Failed to create remote desktop handover "
                 "proxy: %s", error->message);
      return;
    }

  daemon_handover = GRD_DAEMON_HANDOVER (user_data);

  stop_watching_handover_objects (daemon_handover);

  daemon_handover->remote_desktop_handover = g_steal_pointer (&proxy);

  setup_handover (daemon_handover);

  grd_daemon_maybe_enable_services (GRD_DAEMON (daemon_handover));
}

static void
on_remote_desktop_rdp_dispatcher_handover_requested (GObject      *object,
                                                     GAsyncResult *result,
                                                     gpointer      user_data)
{
  GrdDBusRemoteDesktopRdpDispatcher *remote_desktop_dispatcher;
  g_autofree char *object_path = NULL;
  g_autoptr (GError) error = NULL;
  GrdDaemonHandover *daemon_handover;
  GCancellable *cancellable;
  gboolean success;

  remote_desktop_dispatcher = GRD_DBUS_REMOTE_DESKTOP_RDP_DISPATCHER (object);
  success =
    grd_dbus_remote_desktop_rdp_dispatcher_call_request_handover_finish (
      remote_desktop_dispatcher,
      &object_path,
      result,
      &error);
  if (!success)
    {
      if (g_error_matches (error, GRD_DBUS_ERROR, GRD_DBUS_ERROR_NO_HANDOVER))
        {
          daemon_handover = GRD_DAEMON_HANDOVER (user_data);
          start_watching_handover_objects (daemon_handover);
        }
      else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        {
          g_warning ("[DaemonHandover] Failed to request remote desktop "
                     "handover: %s", error->message);
        }
      return;
    }

  g_debug ("[DaemonHandover] Using: %s, from dispatcher request",
           object_path);

  daemon_handover = GRD_DAEMON_HANDOVER (user_data);
  cancellable = grd_daemon_get_cancellable (GRD_DAEMON (daemon_handover));

  grd_dbus_remote_desktop_rdp_handover_proxy_new_for_bus (
    G_BUS_TYPE_SYSTEM,
    G_DBUS_PROXY_FLAGS_NONE,
    REMOTE_DESKTOP_BUS_NAME,
    object_path,
    cancellable,
    on_remote_desktop_rdp_handover_proxy_acquired,
    daemon_handover);
}

static void
on_remote_desktop_rdp_dispatcher_proxy_acquired (GObject      *object,
                                                 GAsyncResult *result,
                                                 gpointer      user_data)
{
  g_autoptr (GrdDBusRemoteDesktopRdpDispatcher) proxy = NULL;
  g_autoptr (GError) error = NULL;
  GrdDaemonHandover *daemon_handover;
  GCancellable *cancellable;

  proxy =
    grd_dbus_remote_desktop_rdp_dispatcher_proxy_new_finish (result, &error);
  if (!proxy)
    {
      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
        return;

      g_warning ("[DaemonHandover] Failed to create remote desktop "
                 "dispatcher proxy: %s", error->message);
      return;
    }

  daemon_handover = GRD_DAEMON_HANDOVER (user_data);

  g_assert (!daemon_handover->remote_desktop_dispatcher);
  daemon_handover->remote_desktop_dispatcher = g_steal_pointer (&proxy);

  cancellable = grd_daemon_get_cancellable (GRD_DAEMON (daemon_handover));
  grd_dbus_remote_desktop_rdp_dispatcher_call_request_handover (
    daemon_handover->remote_desktop_dispatcher,
    cancellable,
    on_remote_desktop_rdp_dispatcher_handover_requested,
    daemon_handover);
}

static void
on_gnome_remote_desktop_name_appeared (GDBusConnection *connection,
                                       const char      *name,
                                       const char      *name_owner,
                                       gpointer         user_data)
{
  GrdDaemonHandover *daemon_handover = user_data;
  GCancellable *cancellable =
    grd_daemon_get_cancellable (GRD_DAEMON (daemon_handover));

  g_debug ("[DaemonHandover] %s name appeared", name);

  grd_dbus_remote_desktop_rdp_dispatcher_proxy_new (
    connection,
    G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
    REMOTE_DESKTOP_BUS_NAME,
    REMOTE_DESKTOP_DISPATCHER_OBJECT_PATH,
    cancellable,
    on_remote_desktop_rdp_dispatcher_proxy_acquired,
    daemon_handover);
}

static void
on_gnome_remote_desktop_name_vanished (GDBusConnection *connection,
                                       const char      *name,
                                       gpointer         user_data)
{
  GrdDaemonHandover *daemon_handover = user_data;

  g_warning ("[DaemonHandover] %s name vanished", name);

  teardown_handover (daemon_handover);

  g_clear_object (&daemon_handover->remote_desktop_handover);
  g_clear_object (&daemon_handover->remote_desktop_dispatcher);

  stop_watching_handover_objects (daemon_handover);

  if (grd_is_remote_login ())
    grd_session_manager_call_logout_sync ();
}

GrdDaemonHandover *
grd_daemon_handover_new (GError **error)
{
  GrdContext *context;

  context = grd_context_new (GRD_RUNTIME_MODE_HANDOVER, error);
  if (!context)
    return NULL;

  return g_object_new (GRD_TYPE_DAEMON_HANDOVER,
                       "application-id", GRD_DAEMON_HANDOVER_APPLICATION_ID,
                       "flags", G_APPLICATION_IS_SERVICE,
                       "context", context,
                       NULL);
}

static void
grd_daemon_handover_init (GrdDaemonHandover *daemon_handover)
{
}

static void
grd_daemon_handover_startup (GApplication *app)
{
  GrdDaemonHandover *daemon_handover = GRD_DAEMON_HANDOVER (app);

  grd_daemon_acquire_mutter_dbus_proxies (GRD_DAEMON (daemon_handover));

  g_signal_connect (daemon_handover, "mutter-proxy-acquired",
                    G_CALLBACK (grd_daemon_maybe_enable_services), NULL);

  daemon_handover->gnome_remote_desktop_watch_name_id =
    g_bus_watch_name (G_BUS_TYPE_SYSTEM,
                      REMOTE_DESKTOP_BUS_NAME,
                      G_BUS_NAME_WATCHER_FLAGS_NONE,
                      on_gnome_remote_desktop_name_appeared,
                      on_gnome_remote_desktop_name_vanished,
                      daemon_handover, NULL);

  g_signal_connect (daemon_handover, "rdp-server-started",
                    G_CALLBACK (on_rdp_server_started), NULL);

  g_signal_connect (daemon_handover, "rdp-server-stopped",
                    G_CALLBACK (on_rdp_server_stopped), NULL);

  G_APPLICATION_CLASS (grd_daemon_handover_parent_class)->startup (app);
}

static void
grd_daemon_handover_shutdown (GApplication *app)
{
  GrdDaemonHandover *daemon_handover = GRD_DAEMON_HANDOVER (app);
  GrdDaemon *daemon = GRD_DAEMON (daemon_handover);

  g_cancellable_cancel (grd_daemon_get_cancellable (daemon));

  g_clear_object (&daemon_handover->remote_desktop_handover);
  g_clear_object (&daemon_handover->remote_desktop_dispatcher);

  stop_watching_handover_objects (daemon_handover);

  g_clear_handle_id (&daemon_handover->gnome_remote_desktop_watch_name_id,
                     g_bus_unwatch_name);

  if (daemon_handover->prompt_cancellable)
    {
      g_cancellable_cancel (daemon_handover->prompt_cancellable);
      g_clear_object (&daemon_handover->prompt_cancellable);
    }
  g_clear_object (&daemon_handover->prompt);

  g_clear_object (&daemon_handover->dialog);

  G_APPLICATION_CLASS (grd_daemon_handover_parent_class)->shutdown (app);
}

static void
grd_daemon_handover_class_init (GrdDaemonHandoverClass *klass)
{
  GApplicationClass *g_application_class = G_APPLICATION_CLASS (klass);
  GrdDaemonClass *daemon_class = GRD_DAEMON_CLASS (klass);

  g_application_class->startup = grd_daemon_handover_startup;
  g_application_class->shutdown = grd_daemon_handover_shutdown;

  daemon_class->is_daemon_ready = grd_daemon_handover_is_ready;
}
