[gnome-remote-desktop] Add clipboard class for RDP



commit 5fae2a668b8403ecd0c84cb7e5f0d2ab58c66787
Author: Pascal Nowack <Pascal Nowack gmx de>
Date:   Tue Aug 11 14:12:13 2020 +0200

    Add clipboard class for RDP
    
    Add the implementation of grd-clipboard-rdp, a class that is derived
    from grd-clipboard, which implements the RDP specific clipboard parts.
    
    The communication for the clipboard in RDP consists out of several
    message types.
    Six out of them are necessary when the clipboard has been established:
    
    1. Format List
    2. Format List Response
    3. Format Data Request
    4. Format Data Response
    5. File Contents Request
    6. File Contents Response
    
    The Format List tells the other peer what kind of data (i.e. the data
    format) lies on its clipboard.
    It is sent when the clipboard of a peer changes or as part of the
    initialization sequence in order to synchronize the initial state of the
    clipboard.
    
    The Format List Response is the answer for the Format List and only has
    a meaning when the CB_RESPONSE_FAIL flag is set (rare case).
    In such case g-r-d will answer all Format Data Requests and File
    Contents Requests with the fail flag (CB_RESPONSE_FAIL) set, as the
    protocol requires it (3.1.5.2.4 Processing a Format List Response PDU).
    This state is reset when g-s advertises a new Format List.
    All types of Response messages either contain the flag CB_RESPONSE_OK
    (indicating success) or the flag CB_RESPONSE_FAIL (indicating failure).
    
    The purpose of the Format Data Request is to request the data of a
    specific (previously announced) format type.
    The Format Data Response can then either contain the CB_RESPONSE_OK
    flag in addition to the requested data, or the CB_RESPONSE_FAIL flag.
    
    The File Contents Request and Response are similar to the Format Data
    ones, with the difference that the File Contents Request and Response
    handle file data and are dependant on a previous Format Data Response
    that was sent as an answer to Format Data Request that requested the
    file list.
    
    All requests by either g-s or the remote desktop client are handled in
    the main thread as dbus handling (even _sync calls) is still handled in
    the main thread too, and calling these _sync calls from other threads
    can potentially lead g-r-d into a deadlock.
    This means that requests and responses from a remote desktop client
    push a task to the main thread to ensure that no deadlock happens.
    
    Additionally, use events (provided by WinPR) to control the state of
    such requests and responses to ensure that all messages are handled in
    their correct order and to ensure that specific actions (like
    requesting some clipboard content from the clients) are only handled
    when they are allowed to do so (no pending Format List).

 meson.build             |    2 +
 src/grd-clipboard-rdp.c | 1810 +++++++++++++++++++++++++++++++++++++++++++++++
 src/grd-clipboard-rdp.h |   37 +
 src/grd-types.h         |    1 +
 src/meson.build         |    4 +
 5 files changed, 1854 insertions(+)
---
diff --git a/meson.build b/meson.build
index 83c0d56..cdfdcad 100644
--- a/meson.build
+++ b/meson.build
@@ -11,6 +11,8 @@ gnome = import('gnome')
 
 cairo_dep = dependency('cairo')
 freerdp_dep = dependency('freerdp2', version: freerdp_req)
+freerdp_client_dep = dependency('freerdp-client2', version: freerdp_req)
+freerdp_server_dep = dependency('freerdp-server2', version: freerdp_req)
 glib_dep = dependency('glib-2.0')
 gio_dep = dependency('gio-2.0')
 gio_unix_dep = dependency('gio-unix-2.0')
diff --git a/src/grd-clipboard-rdp.c b/src/grd-clipboard-rdp.c
new file mode 100644
index 0000000..be46ae0
--- /dev/null
+++ b/src/grd-clipboard-rdp.c
@@ -0,0 +1,1810 @@
+/*
+ * Copyright (C) 2020 Pascal Nowack
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "grd-clipboard-rdp.h"
+
+#include <glib/gstdio.h>
+#include <utime.h>
+#include <winpr/clipboard.h>
+
+#include "grd-session-rdp.h"
+
+#define CLIPRDR_FILEDESCRIPTOR_SIZE (4 + 32 + 4 + 16 + 8 + 4 + 4 + 520)
+#define MAX_WAIT_TIME 4000
+#define WIN32_FILETIME_TO_UNIX_EPOCH UINT64_C (11644473600)
+
+typedef struct _ServerFormatListUpdateContext
+{
+  GrdClipboardRdp *clipboard_rdp;
+  GList *mime_type_tables;
+} ServerFormatListUpdateContext;
+
+typedef struct _ServerFormatDataRequestContext
+{
+  GrdClipboardRdp *clipboard_rdp;
+  GrdMimeType mime_type;
+  uint32_t src_format_id;
+  uint32_t dst_format_id;
+  gboolean needs_null_terminator;
+  gboolean needs_conversion;
+} ServerFormatDataRequestContext;
+
+typedef struct _FormatData
+{
+  uint8_t *data;
+  uint32_t size;
+} FormatData;
+
+struct _GrdClipboardRdp
+{
+  GrdClipboard parent;
+
+  CliprdrServerContext *cliprdr_context;
+  HANDLE stop_event;
+
+  wClipboard *system;
+  wClipboardDelegate *delegate;
+
+  GHashTable *allowed_server_formats;
+  GList *pending_server_formats;
+  gboolean server_file_contents_requests_allowed;
+  uint16_t format_list_response_msg_flags;
+
+  char *base_path;
+  GList *tracked_files;
+  GHashTable *format_data_cache;
+  GrdMimeType which_unicode_format;
+
+  HANDLE format_data_received_event;
+  CLIPRDR_FORMAT_DATA_RESPONSE *format_data_response;
+
+  HANDLE file_contents_received_event;
+  CLIPRDR_FILE_CONTENTS_RESPONSE *file_contents_response;
+
+  HANDLE completed_format_list_event;
+  HANDLE completed_format_data_request_event;
+  HANDLE format_list_received_event;
+  HANDLE format_list_response_received_event;
+  unsigned int server_format_list_update_id;
+  unsigned int server_format_data_request_id;
+  unsigned int client_format_list_response_id;
+};
+
+G_DEFINE_TYPE (GrdClipboardRdp, grd_clipboard_rdp, GRD_TYPE_CLIPBOARD);
+
+static void
+remove_tracked_files (GList *tracked_files)
+{
+  struct stat path_stat;
+  GList *l;
+
+  tracked_files = g_list_reverse (tracked_files);
+  for (l = tracked_files; l; l = l->next)
+    {
+      char *filepath = l->data;
+
+      stat (filepath, &path_stat);
+      if (S_ISREG (path_stat.st_mode))
+        unlink (filepath);
+      else
+        rmdir (filepath);
+
+      g_free (filepath);
+    }
+
+  g_list_free (tracked_files);
+}
+
+static void
+update_allowed_server_formats (GrdClipboardRdp *clipboard_rdp,
+                               gboolean         received_response)
+{
+  GList *l;
+
+  if (received_response)
+    g_debug ("[RDP.CLIPRDR] Handling format list response from client");
+
+  if (received_response &&
+      clipboard_rdp->format_list_response_msg_flags & CB_RESPONSE_OK)
+    {
+      for (l = clipboard_rdp->pending_server_formats; l; l = l->next)
+        {
+          if (GPOINTER_TO_INT (l->data) == GRD_MIME_TYPE_TEXT_URILIST)
+            clipboard_rdp->server_file_contents_requests_allowed = TRUE;
+
+          g_hash_table_add (clipboard_rdp->allowed_server_formats, l->data);
+        }
+
+      g_clear_pointer (&clipboard_rdp->pending_server_formats, g_list_free);
+
+      return;
+    }
+
+  if (received_response &&
+      !(clipboard_rdp->format_list_response_msg_flags & CB_RESPONSE_FAIL))
+    {
+      g_warning ("[RDP.CLIPRDR] Protocol violation: Client did not set response "
+                 "flag. Assuming CB_RESPONSE_FAIL");
+    }
+
+  clipboard_rdp->server_file_contents_requests_allowed = FALSE;
+  g_hash_table_remove_all (clipboard_rdp->allowed_server_formats);
+  g_clear_pointer (&clipboard_rdp->pending_server_formats, g_list_free);
+}
+
+static void
+remove_clipboard_format_data_for_mime_type (GrdClipboardRdp *clipboard_rdp,
+                                            GrdMimeType      mime_type)
+{
+  FormatData *format_data;
+
+  format_data = g_hash_table_lookup (clipboard_rdp->format_data_cache,
+                                     GUINT_TO_POINTER (mime_type));
+  if (!format_data)
+    return;
+
+  g_free (format_data->data);
+  g_free (format_data);
+
+  g_hash_table_remove (clipboard_rdp->format_data_cache,
+                       GUINT_TO_POINTER (mime_type));
+}
+
+static void
+remove_duplicated_clipboard_mime_types (GrdClipboardRdp  *clipboard_rdp,
+                                        GList           **mime_type_list)
+{
+  GrdMimeType mime_type;
+
+  /**
+   * Remove the "x-special/gnome-copied-files" mimetype, since we use the
+   * "text/uri-list" mimetype instead.
+   */
+  mime_type = GRD_MIME_TYPE_XS_GNOME_COPIED_FILES;
+  if (g_list_find (*mime_type_list, GUINT_TO_POINTER (mime_type)))
+    {
+      *mime_type_list = g_list_remove (*mime_type_list, GUINT_TO_POINTER (mime_type));
+      remove_clipboard_format_data_for_mime_type (clipboard_rdp, mime_type);
+    }
+
+  /**
+   * We can only advertise CF_UNICODETEXT as remote format once, so ignore the
+   * other local format if it exists.
+   */
+  if (g_list_find (*mime_type_list, GUINT_TO_POINTER (GRD_MIME_TYPE_TEXT_PLAIN_UTF8)) &&
+      g_list_find (*mime_type_list, GUINT_TO_POINTER (GRD_MIME_TYPE_TEXT_UTF8_STRING)))
+    {
+      mime_type = GRD_MIME_TYPE_TEXT_PLAIN_UTF8;
+      *mime_type_list = g_list_remove (*mime_type_list, GUINT_TO_POINTER (mime_type));
+      remove_clipboard_format_data_for_mime_type (clipboard_rdp, mime_type);
+    }
+}
+
+static void
+grd_clipboard_rdp_update_client_mime_type_list (GrdClipboard *clipboard,
+                                                GList        *mime_type_list)
+{
+  GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (clipboard);
+  CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
+  CLIPRDR_FORMAT_LIST format_list = {0};
+  CLIPRDR_FORMAT *cliprdr_formats;
+  GrdMimeType mime_type;
+  uint32_t n_formats;
+  uint32_t i;
+  GList *l;
+
+  if (clipboard_rdp->pending_server_formats)
+    {
+      HANDLE format_list_response_received_event;
+      HANDLE events[2];
+
+      format_list_response_received_event =
+        clipboard_rdp->format_list_response_received_event;
+
+      events[0] = clipboard_rdp->stop_event;
+      events[1] = format_list_response_received_event;
+      WaitForMultipleObjects (2, events, FALSE, MAX_WAIT_TIME);
+
+      if (WaitForSingleObject (clipboard_rdp->stop_event, 0) == WAIT_OBJECT_0)
+        {
+          g_list_free (mime_type_list);
+          return;
+        }
+
+      if (WaitForSingleObject (format_list_response_received_event, 0) == WAIT_OBJECT_0)
+        {
+          update_allowed_server_formats (clipboard_rdp, TRUE);
+
+          g_clear_handle_id (&clipboard_rdp->client_format_list_response_id,
+                             g_source_remove);
+          ResetEvent (format_list_response_received_event);
+        }
+      else
+        {
+          g_warning ("[RDP.CLIPRDR] Possible protocol violation: Client did not "
+                     "send format list response (Timeout reached)");
+
+          update_allowed_server_formats (clipboard_rdp, FALSE);
+        }
+    }
+
+  remove_duplicated_clipboard_mime_types (clipboard_rdp, &mime_type_list);
+
+  n_formats = g_list_length (mime_type_list);
+  if (!n_formats)
+    {
+      g_list_free (mime_type_list);
+      return;
+    }
+
+  cliprdr_formats = g_malloc0 (n_formats * sizeof (CLIPRDR_FORMAT));
+  for (i = 0, l = mime_type_list; i < n_formats; ++i, l = l->next)
+    {
+      mime_type = GPOINTER_TO_INT (l->data);
+      switch (mime_type)
+        {
+        case GRD_MIME_TYPE_TEXT_PLAIN:
+          cliprdr_formats[i].formatId = CF_TEXT;
+          break;
+        case GRD_MIME_TYPE_TEXT_PLAIN_UTF8:
+        case GRD_MIME_TYPE_TEXT_UTF8_STRING:
+          cliprdr_formats[i].formatId = CF_UNICODETEXT;
+          clipboard_rdp->which_unicode_format = mime_type;
+          break;
+        case GRD_MIME_TYPE_TEXT_HTML:
+          cliprdr_formats[i].formatId = CB_FORMAT_HTML;
+          cliprdr_formats[i].formatName = "HTML Format";
+          break;
+        case GRD_MIME_TYPE_IMAGE_BMP:
+          cliprdr_formats[i].formatId = CF_DIB;
+          break;
+        case GRD_MIME_TYPE_IMAGE_TIFF:
+          cliprdr_formats[i].formatId = CF_TIFF;
+          break;
+        case GRD_MIME_TYPE_IMAGE_GIF:
+          cliprdr_formats[i].formatId = CB_FORMAT_GIF;
+          break;
+        case GRD_MIME_TYPE_IMAGE_JPEG:
+          cliprdr_formats[i].formatId = CB_FORMAT_JPEG;
+          break;
+        case GRD_MIME_TYPE_IMAGE_PNG:
+          cliprdr_formats[i].formatId = CB_FORMAT_PNG;
+          break;
+        case GRD_MIME_TYPE_TEXT_URILIST:
+          /**
+           * FileGroupDescriptorW does not have a consistent format id. It is
+           * identified by its name.
+           * When the client requests the content, it MUST use the id that we
+           * told the client before when the clipboard format with the name
+           * "FileGroupDescriptorW" was advertised.
+           *
+           * See also 1.3.1.2 Clipboard Format
+           */
+          cliprdr_formats[i].formatId = CB_FORMAT_TEXTURILIST;
+          cliprdr_formats[i].formatName = "FileGroupDescriptorW";
+          clipboard_rdp->server_file_contents_requests_allowed = FALSE;
+          g_clear_pointer (&clipboard_rdp->tracked_files, remove_tracked_files);
+          break;
+        default:
+          g_assert_not_reached ();
+        }
+
+      remove_clipboard_format_data_for_mime_type (clipboard_rdp, mime_type);
+      g_hash_table_remove (clipboard_rdp->allowed_server_formats, l->data);
+
+      clipboard_rdp->pending_server_formats =
+        g_list_append (clipboard_rdp->pending_server_formats, l->data);
+    }
+
+  format_list.msgType = CB_FORMAT_LIST;
+  format_list.formats = cliprdr_formats;
+  format_list.numFormats = n_formats;
+
+  cliprdr_context->ServerFormatList (cliprdr_context, &format_list);
+
+  g_free (cliprdr_formats);
+  g_list_free (mime_type_list);
+}
+
+static CLIPRDR_FORMAT_DATA_RESPONSE *
+get_format_data_response (GrdClipboardRdp             *clipboard_rdp,
+                          CLIPRDR_FORMAT_DATA_REQUEST *format_data_request)
+{
+  CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
+  CLIPRDR_FORMAT_DATA_RESPONSE *format_data_response = NULL;
+  HANDLE format_data_received_event = clipboard_rdp->format_data_received_event;
+  HANDLE events[3];
+  uint32_t error;
+
+  ResetEvent (format_data_received_event);
+  error = cliprdr_context->ServerFormatDataRequest (cliprdr_context,
+                                                    format_data_request);
+  if (error)
+    return NULL;
+
+  events[0] = clipboard_rdp->stop_event;
+  events[1] = format_data_received_event;
+  events[2] = clipboard_rdp->format_list_received_event;
+  if (WaitForMultipleObjects (3, events, FALSE, MAX_WAIT_TIME) == WAIT_TIMEOUT)
+    {
+      g_warning ("[RDP.CLIPRDR] Possible protocol violation: Client did not "
+                 "send format data response (Timeout reached)");
+    }
+
+  if (WaitForSingleObject (format_data_received_event, 0) == WAIT_OBJECT_0)
+    {
+      format_data_response =
+        g_steal_pointer (&clipboard_rdp->format_data_response);
+    }
+
+  return format_data_response;
+}
+
+static uint8_t *
+get_remote_format_data (GrdClipboardRdp *clipboard_rdp,
+                        uint32_t         format_id,
+                        uint32_t        *size)
+{
+  CLIPRDR_FORMAT_DATA_REQUEST format_data_request = {0};
+  CLIPRDR_FORMAT_DATA_RESPONSE *format_data_response;
+  uint8_t *data;
+  gboolean response_ok;
+
+  *size = 0;
+
+  format_data_request.msgType = CB_FORMAT_DATA_REQUEST;
+  format_data_request.dataLen = 4;
+  format_data_request.requestedFormatId = format_id;
+  format_data_response = get_format_data_response (clipboard_rdp,
+                                                   &format_data_request);
+  if (!format_data_response)
+    return NULL;
+
+  data = (uint8_t *) format_data_response->requestedFormatData;
+  *size = format_data_response->dataLen;
+
+  response_ok = format_data_response->msgFlags & CB_RESPONSE_OK;
+  *size = response_ok && data ? *size : 0;
+  g_free (format_data_response);
+
+  if (!response_ok)
+    g_clear_pointer (&data, g_free);
+
+  return data;
+}
+
+static CLIPRDR_FILE_CONTENTS_RESPONSE *
+get_file_contents_response (GrdClipboardRdp               *clipboard_rdp,
+                            CLIPRDR_FILE_CONTENTS_REQUEST *file_contents_request)
+{
+  CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
+  CLIPRDR_FILE_CONTENTS_RESPONSE *file_contents_response = NULL;
+  HANDLE file_contents_received_event;
+  HANDLE events[3];
+  uint32_t error;
+
+  file_contents_received_event = clipboard_rdp->file_contents_received_event;
+
+  ResetEvent (clipboard_rdp->file_contents_received_event);
+  error = cliprdr_context->ServerFileContentsRequest (cliprdr_context,
+                                                      file_contents_request);
+  if (error)
+    return NULL;
+
+  events[0] = clipboard_rdp->stop_event;
+  events[1] = file_contents_received_event;
+  events[2] = clipboard_rdp->format_list_received_event;
+  WaitForMultipleObjects (3, events, FALSE, INFINITE);
+
+  if (WaitForSingleObject (file_contents_received_event, 0) == WAIT_OBJECT_0)
+    {
+      file_contents_response =
+        g_steal_pointer (&clipboard_rdp->file_contents_response);
+    }
+
+  return file_contents_response;
+}
+
+static gboolean
+request_remote_file_size (GrdClipboardRdp *clipboard_rdp,
+                          uint32_t         stream_id,
+                          uint32_t         list_index,
+                          uint64_t        *size)
+{
+  CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = {0};
+  CLIPRDR_FILE_CONTENTS_RESPONSE *file_contents_response;
+
+  *size = 0;
+
+  file_contents_request.msgType = CB_FILECONTENTS_REQUEST;
+  file_contents_request.dataLen = 28;
+  file_contents_request.streamId = stream_id;
+  file_contents_request.listIndex = list_index;
+  file_contents_request.dwFlags = FILECONTENTS_SIZE;
+  file_contents_request.cbRequested = 0x8;
+
+  file_contents_response = get_file_contents_response (clipboard_rdp,
+                                                       &file_contents_request);
+  if (file_contents_response)
+    {
+      if (file_contents_response->streamId != stream_id)
+        {
+          g_warning ("[RDP.CLIPRDR] The stream id of the file contents request "
+                     "and response do not match");
+        }
+
+      if (file_contents_response->msgFlags & CB_RESPONSE_OK)
+        *size = *((uint64_t *) file_contents_response->requestedData);
+
+      g_free ((uint8_t *) file_contents_response->requestedData);
+      g_free (file_contents_response);
+
+      if (file_contents_response->msgFlags & CB_RESPONSE_OK)
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+static uint8_t *
+request_remote_file_data (GrdClipboardRdp *clipboard_rdp,
+                          uint32_t         stream_id,
+                          uint32_t         list_index,
+                          uint64_t         offset,
+                          uint32_t         requested_size,
+                          uint64_t        *returned_size)
+{
+  CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = {0};
+  CLIPRDR_FILE_CONTENTS_RESPONSE *file_contents_response;
+  uint8_t *requested_data = NULL;
+
+  *returned_size = 0;
+
+  file_contents_request.msgType = CB_FILECONTENTS_REQUEST;
+  file_contents_request.dataLen = 28;
+  file_contents_request.streamId = stream_id;
+  file_contents_request.listIndex = list_index;
+  file_contents_request.dwFlags = FILECONTENTS_RANGE;
+  file_contents_request.nPositionLow = offset & 0xFFFFFFFF;
+  file_contents_request.nPositionHigh = offset >> 32 & 0xFFFFFFFF;
+  file_contents_request.cbRequested = requested_size;
+  file_contents_request.haveClipDataId = FALSE;
+
+  file_contents_response = get_file_contents_response (clipboard_rdp,
+                                                       &file_contents_request);
+  if (file_contents_response)
+    {
+      if (file_contents_response->streamId != stream_id)
+        {
+          g_warning ("[RDP.CLIPRDR] The stream id of the file contents request "
+                     "and response do not match");
+        }
+
+      if (file_contents_response->msgFlags & CB_RESPONSE_OK)
+        {
+          *returned_size = file_contents_response->cbRequested;
+          requested_data = (uint8_t *) file_contents_response->requestedData;
+        }
+
+      g_free (file_contents_response);
+    }
+
+  return requested_data;
+}
+
+static uint8_t *
+get_uri_list_from_packet_file_list (GrdClipboardRdp *clipboard_rdp,
+                                    uint8_t         *src_data,
+                                    uint32_t         src_size,
+                                    uint32_t        *dst_size)
+{
+  FILEDESCRIPTOR *files = NULL;
+  FILEDESCRIPTOR *file;
+  uint32_t n_files = 0;
+  char *filename = NULL;
+  char *escaped_name;
+  char *file_uri;
+  GArray *dst_data;
+  uint32_t i;
+
+  dst_data = g_array_new (TRUE, TRUE, sizeof (char));
+
+  cliprdr_parse_file_list (src_data, src_size, &files, &n_files);
+  for (i = 0; i < n_files; ++i)
+    {
+      file = &files[i];
+
+      ConvertFromUnicode (CP_UTF8, 0, file->cFileName, -1, &filename, 0, NULL,
+                          NULL);
+      if (strchr (filename, '\\'))
+        {
+          g_free (filename);
+          continue;
+        }
+
+      escaped_name = g_uri_escape_string (filename,
+                                          G_URI_RESERVED_CHARS_ALLOWED_IN_PATH,
+                                          TRUE);
+      file_uri = g_malloc0 (strlen ("file://") +
+                            strlen (clipboard_rdp->base_path) + 1 +
+                            strlen (escaped_name) + 2 + 1);
+      sprintf (file_uri, "file://%s/%s\r\n", clipboard_rdp->base_path,
+               escaped_name);
+      g_array_append_vals (dst_data, file_uri, strlen (file_uri));
+
+      g_free (file_uri);
+      g_free (escaped_name);
+      g_free (filename);
+    }
+
+  *dst_size = dst_data->len;
+
+  g_free (files);
+
+  return (uint8_t *) g_array_free (dst_data, FALSE);
+}
+
+static uint8_t *
+convert_client_content_for_server (GrdClipboardRdp  *clipboard_rdp,
+                                   uint8_t          *src_data,
+                                   uint32_t          src_size,
+                                   GrdMimeType       mime_type,
+                                   uint32_t          src_format_id,
+                                   GList           **tracked_files,
+                                   uint32_t         *dst_size)
+{
+  uint32_t dst_format_id;
+  uint8_t *dst_data;
+  gboolean is_null_terminated = FALSE;
+  BOOL success;
+
+  *dst_size = 0;
+
+  switch (mime_type)
+    {
+    case GRD_MIME_TYPE_TEXT_UTF8_STRING:
+      is_null_terminated = TRUE;
+      dst_format_id = ClipboardGetFormatId (clipboard_rdp->system,
+                                            "UTF8_STRING");
+      break;
+    case GRD_MIME_TYPE_TEXT_HTML:
+      is_null_terminated = TRUE;
+      src_format_id = ClipboardGetFormatId (clipboard_rdp->system,
+                                            "HTML Format");
+      dst_format_id = ClipboardGetFormatId (clipboard_rdp->system,
+                                            "text/html");
+      break;
+    case GRD_MIME_TYPE_IMAGE_BMP:
+      dst_format_id = ClipboardGetFormatId (clipboard_rdp->system,
+                                            "image/bmp");
+      break;
+    case GRD_MIME_TYPE_TEXT_URILIST:
+    case GRD_MIME_TYPE_XS_GNOME_COPIED_FILES:
+      src_format_id = ClipboardGetFormatId (clipboard_rdp->system,
+                                            "FileGroupDescriptorW");
+      dst_format_id = ClipboardGetFormatId (clipboard_rdp->system,
+                                            "text/uri-list");
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+
+  success = ClipboardSetData (clipboard_rdp->system,
+                              src_format_id, src_data, src_size);
+  if (!success)
+    {
+      g_warning ("[RDP.CLIPRDR] Converting clipboard content failed");
+      return NULL;
+    }
+
+  if (mime_type == GRD_MIME_TYPE_TEXT_URILIST ||
+      mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES)
+    {
+      dst_data = get_uri_list_from_packet_file_list (clipboard_rdp,
+                                                     src_data, src_size,
+                                                     dst_size);
+    }
+  else
+    {
+      dst_data = ClipboardGetData (clipboard_rdp->system, dst_format_id,
+                                   dst_size);
+    }
+  if (!dst_data)
+    {
+      g_warning ("[RDP.CLIPRDR] Converting clipboard content failed");
+      return NULL;
+    }
+
+  if (is_null_terminated)
+    {
+      uint8_t *null_terminator_pos = memchr (dst_data, '\0', *dst_size);
+      if (null_terminator_pos)
+        *dst_size = null_terminator_pos - dst_data;
+    }
+
+  if (mime_type == GRD_MIME_TYPE_TEXT_URILIST ||
+      mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES)
+    {
+      FILEDESCRIPTOR *files = NULL;
+      FILEDESCRIPTOR *file;
+      uint32_t n_files = 0;
+      char *filename = NULL;
+      char *escaped_name;
+      char *full_filepath;
+      uint64_t filesize, remaining_size, requesting_size, returned_size;
+      uint8_t *requested_data;
+      uint32_t stream_id = 0;
+      FILE *file_stream;
+      GrdMimeType second_mime_type;
+      FormatData *format_data;
+      GArray *data_nautilus;
+      const char *nautilus_header;
+      uint8_t *dst_data_nautilus;
+      uint32_t dst_size_nautilus;
+      uint32_t i, j;
+
+      cliprdr_parse_file_list (src_data, src_size, &files, &n_files);
+      for (i = 0; i < n_files; ++i)
+        {
+          file = &files[i];
+
+          if (!(file->dwFlags & FD_ATTRIBUTES))
+            g_warning ("[RDP.CLIPRDR] Client did not set the FD_ATTRIBUTES flag");
+
+          ConvertFromUnicode (CP_UTF8, 0, file->cFileName, -1, &filename,
+                              0, NULL, NULL);
+          for (j = 0; filename[j]; ++j)
+            {
+              if (filename[j] == '\\')
+                filename[j] = '/';
+            }
+
+          full_filepath = g_malloc0 (strlen (clipboard_rdp->base_path) + 1 +
+                                     strlen (filename) + 1);
+          sprintf (full_filepath, "%s/%s", clipboard_rdp->base_path, filename);
+
+          if (file->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+            {
+              mkdir (full_filepath, 0755);
+            }
+          else
+            {
+              if (file->dwFlags & FD_FILESIZE)
+                {
+                  filesize = ((uint64_t) file->nFileSizeHigh << 32) +
+                              file->nFileSizeLow;
+                }
+              else
+                {
+                  if (!request_remote_file_size (clipboard_rdp, stream_id++, i,
+                                                 &filesize))
+                    {
+                      g_warning ("[RDP.CLIPRDR] Failed to retrieve file size. "
+                                 "Aborting file transfer.");
+
+                      g_clear_pointer (tracked_files, remove_tracked_files);
+                      g_free (full_filepath);
+                      g_free (filename);
+                      g_free (files);
+                      g_free (src_data);
+                      g_free (dst_data);
+
+                      return NULL;
+                    }
+                }
+
+              if (!(file_stream = fopen (full_filepath, "wb+")))
+                {
+                  g_warning ("[RDP.CLIPRDR] Could not open file for writing. "
+                             "Aborting file transfer.");
+
+                  g_clear_pointer (tracked_files, remove_tracked_files);
+                  g_free (full_filepath);
+                  g_free (filename);
+                  g_free (files);
+                  g_free (src_data);
+                  g_free (dst_data);
+
+                  return NULL;
+                }
+              remaining_size = filesize;
+              while (remaining_size)
+                {
+                  requesting_size = MIN (remaining_size, 4 * 1024 * 1024);
+                  requested_data =
+                    request_remote_file_data (clipboard_rdp, stream_id++, i,
+                                              filesize - remaining_size,
+                                              requesting_size, &returned_size);
+
+                  if (!requested_data)
+                    {
+                      g_warning ("[RDP.CLIPRDR] An error occurred while "
+                                  "transferring a file. Aborting file "
+                                  "transfer.");
+
+                      fclose (file_stream);
+                      unlink (full_filepath);
+                      g_free (full_filepath);
+                      g_free (filename);
+                      g_clear_pointer (tracked_files, remove_tracked_files);
+                      g_free (src_data);
+                      g_free (dst_data);
+
+                      return NULL;
+                    }
+
+                  fwrite (requested_data, 1, returned_size, file_stream);
+
+                  g_free (requested_data);
+                  remaining_size -= returned_size;
+                }
+              fclose (file_stream);
+            }
+          if (file->dwFlags & FD_WRITESTIME)
+            {
+              uint64_t filetime, unix_time;
+              struct utimbuf times;
+
+              filetime = file->ftLastWriteTime.dwHighDateTime;
+              filetime <<= 32;
+              filetime += file->ftLastWriteTime.dwLowDateTime;
+
+              unix_time = filetime / (10 * G_USEC_PER_SEC) -
+                          WIN32_FILETIME_TO_UNIX_EPOCH;
+
+              times.actime = times.modtime = unix_time;
+
+              utime (full_filepath, &times);
+            }
+          *tracked_files = g_list_append (*tracked_files, full_filepath);
+
+          g_free (filename);
+        }
+
+      data_nautilus = g_array_new (TRUE, TRUE, sizeof (char));
+      nautilus_header = "copy";
+      g_array_append_vals (data_nautilus, nautilus_header,
+                           strlen (nautilus_header));
+      for (i = 0; i < n_files; ++i)
+        {
+          file = &files[i];
+
+          ConvertFromUnicode (CP_UTF8, 0, file->cFileName, -1, &filename,
+                              0, NULL, NULL);
+          if (strchr (filename, '\\'))
+            {
+              g_free (filename);
+              continue;
+            }
+
+          escaped_name = g_uri_escape_string (filename,
+                           G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
+          full_filepath = g_malloc0 (strlen ("\nfile://") +
+                                     strlen (clipboard_rdp->base_path) + 1 +
+                                     strlen (escaped_name) + 1);
+          sprintf (full_filepath, "\nfile://%s/%s",
+                    clipboard_rdp->base_path, escaped_name);
+          g_array_append_vals (data_nautilus, full_filepath,
+                               strlen (full_filepath));
+
+          g_free (full_filepath);
+          g_free (escaped_name);
+          g_free (filename);
+        }
+
+      dst_size_nautilus = data_nautilus->len;
+      dst_data_nautilus = (uint8_t *) g_array_free (data_nautilus, FALSE);
+
+      format_data = g_malloc0 (sizeof (FormatData));
+      if (mime_type == GRD_MIME_TYPE_TEXT_URILIST)
+        {
+          /**
+           * Also create the format data for the
+           * "x-special/gnome-copied-files" mimetype
+           */
+          second_mime_type = GRD_MIME_TYPE_XS_GNOME_COPIED_FILES;
+          format_data->size = dst_size_nautilus;
+          format_data->data = dst_data_nautilus;
+        }
+      else if (mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES)
+        {
+          /**
+           * Also create the format data for the "text/uri-list" mimetype
+           */
+          second_mime_type = GRD_MIME_TYPE_TEXT_URILIST;
+          format_data->size = *dst_size;
+          format_data->data = dst_data;
+
+          *dst_size = dst_size_nautilus;
+          dst_data = dst_data_nautilus;
+        }
+      else
+        {
+          g_assert_not_reached ();
+        }
+
+      g_hash_table_insert (clipboard_rdp->format_data_cache,
+                           GUINT_TO_POINTER (second_mime_type),
+                           format_data);
+
+      g_free (files);
+    }
+
+  if (!dst_data)
+    g_warning ("[RDP.CLIPRDR] Converting clipboard content failed");
+
+  return dst_data;
+}
+
+static uint8_t *
+grd_clipboard_rdp_request_client_content_for_mime_type (GrdClipboard     *clipboard,
+                                                        GrdMimeTypeTable *mime_type_table,
+                                                        uint32_t         *size)
+{
+  GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (clipboard);
+  GrdMimeType mime_type = mime_type_table->mime_type;
+  HANDLE completed_format_list_event = clipboard_rdp->completed_format_list_event;
+  uint32_t src_format_id;
+  uint8_t *src_data, *dst_data;
+  uint32_t src_size, dst_size;
+  GList *tracked_files = NULL;
+
+  *size = 0;
+  if (mime_type == GRD_MIME_TYPE_NONE)
+    return NULL;
+
+  if (g_hash_table_contains (clipboard_rdp->format_data_cache,
+                             GUINT_TO_POINTER (mime_type)))
+    {
+      FormatData *format_data;
+
+      format_data = g_hash_table_lookup (clipboard_rdp->format_data_cache,
+                                         GUINT_TO_POINTER (mime_type));
+      *size = format_data->size;
+
+      return g_memdup2 (format_data->data, format_data->size);
+    }
+
+  if (WaitForSingleObject (completed_format_list_event, 0) == WAIT_TIMEOUT)
+    return NULL;
+
+  src_format_id = mime_type_table->rdp.format_id;
+  src_data = get_remote_format_data (clipboard_rdp, src_format_id, &src_size);
+  if (!src_data)
+    return NULL;
+
+  dst_data = NULL;
+  dst_size = 0;
+
+  switch (mime_type)
+    {
+    case GRD_MIME_TYPE_TEXT_UTF8_STRING:
+    case GRD_MIME_TYPE_TEXT_HTML:
+    case GRD_MIME_TYPE_IMAGE_BMP:
+    case GRD_MIME_TYPE_TEXT_URILIST:
+    case GRD_MIME_TYPE_XS_GNOME_COPIED_FILES:
+      dst_data = convert_client_content_for_server (clipboard_rdp,
+                                                    src_data, src_size,
+                                                    mime_type, src_format_id,
+                                                    &tracked_files, &dst_size);
+      break;
+    default:
+      dst_data = g_steal_pointer (&src_data);
+      dst_size = src_size;
+    }
+
+  if (dst_data)
+    {
+      FormatData *format_data = g_malloc0 (sizeof (FormatData));
+
+      format_data->size = *size = dst_size;
+      format_data->data = dst_data;
+
+      if (tracked_files)
+        clipboard_rdp->tracked_files = tracked_files;
+
+      g_hash_table_insert (clipboard_rdp->format_data_cache,
+                           GUINT_TO_POINTER (mime_type),
+                           format_data);
+    }
+
+  g_free (src_data);
+
+  return g_memdup2 (dst_data, dst_size);
+}
+
+/**
+ * FreeRDP already updated our capabilites after the client told us
+ * about its capabilities, there is nothing to do here
+ */
+static uint32_t
+cliprdr_client_capabilities (CliprdrServerContext       *cliprdr_context,
+                             const CLIPRDR_CAPABILITIES *capabilities)
+{
+  return CHANNEL_RC_OK;
+}
+
+static gboolean
+update_server_format_list (gpointer user_data)
+{
+  ServerFormatListUpdateContext *update_context = user_data;
+  GrdClipboardRdp *clipboard_rdp = update_context->clipboard_rdp;
+  GrdClipboard *clipboard = GRD_CLIPBOARD (update_context->clipboard_rdp);
+  CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
+  CLIPRDR_FORMAT_LIST_RESPONSE format_list_response = {0};
+  GList *mime_type_tables = update_context->mime_type_tables;
+  GrdMimeTypeTable *mime_type_table;
+  GList *l;
+
+  for (l = mime_type_tables; l; l = l->next)
+    {
+      mime_type_table = l->data;
+
+      /* Also indirectly handles the GRD_MIME_TYPE_XS_GNOME_COPIED_FILES case */
+      if (mime_type_table->mime_type == GRD_MIME_TYPE_TEXT_URILIST)
+        {
+          clipboard_rdp->server_file_contents_requests_allowed = FALSE;
+          g_clear_pointer (&clipboard_rdp->tracked_files, remove_tracked_files);
+        }
+
+      remove_clipboard_format_data_for_mime_type (clipboard_rdp,
+                                                  mime_type_table->mime_type);
+      g_hash_table_remove (clipboard_rdp->allowed_server_formats,
+                           GUINT_TO_POINTER (mime_type_table->mime_type));
+    }
+
+  grd_clipboard_update_server_mime_type_list (clipboard, mime_type_tables);
+
+  format_list_response.msgType = CB_FORMAT_LIST_RESPONSE;
+  format_list_response.msgFlags = CB_RESPONSE_OK;
+
+  cliprdr_context->ServerFormatListResponse (cliprdr_context,
+                                             &format_list_response);
+
+  g_free (update_context);
+
+  clipboard_rdp->server_format_list_update_id = 0;
+  SetEvent (clipboard_rdp->completed_format_list_event);
+  ResetEvent (clipboard_rdp->format_list_received_event);
+
+  return G_SOURCE_REMOVE;
+}
+
+/**
+ * Client informs us that its clipboard is updated with new clipboard data
+ */
+static uint32_t
+cliprdr_client_format_list (CliprdrServerContext      *cliprdr_context,
+                            const CLIPRDR_FORMAT_LIST *format_list)
+{
+  GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom;
+  ServerFormatListUpdateContext *update_context;
+  GrdMimeTypeTable *mime_type_table = NULL;
+  GList *mime_type_tables = NULL;
+  GrdMimeType mime_type;
+  gboolean already_has_text_format = FALSE;
+  HANDLE events[2];
+  uint32_t i;
+
+  /**
+   * The text format CF_TEXT can depend on the CF_LOCALE content. In such
+   * situations, we might not get the correct content from WinPR.
+   * CF_UNICODETEXT is not affected by the CF_LOCALE content, so try to find
+   * the CF_UNICODETEXT first to ensure that we use CF_UNICODETEXT, when both
+   * formats are available and CF_TEXT is listed first.
+   */
+  for (i = 0; i < format_list->numFormats; ++i)
+    {
+      if (format_list->formats[i].formatId == CF_UNICODETEXT)
+        {
+          mime_type = GRD_MIME_TYPE_TEXT_UTF8_STRING;
+
+          mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable));
+          mime_type_table->mime_type = mime_type;
+          mime_type_table->rdp.format_id = format_list->formats[i].formatId;
+          mime_type_tables = g_list_append (mime_type_tables, mime_type_table);
+
+          already_has_text_format = TRUE;
+          g_debug ("[RDP.CLIPRDR] Force using CF_UNICODETEXT over CF_TEXT as "
+                   "external format for UTF8_STRING");
+          break;
+        }
+    }
+
+  for (i = 0; i < format_list->numFormats; ++i)
+    {
+      mime_type = GRD_MIME_TYPE_NONE;
+
+      /**
+       * FileGroupDescriptorW does not have a consistent id. The name however,
+       * is always the same.
+       *
+       * If the client uses short format names, the formatName is truncated to
+       * either 32 ASCII 8 characters or 16 UTF-16 characters.
+       */
+      if (format_list->formats[i].formatName &&
+          (strcmp (format_list->formats[i].formatName, "FileGroupDescriptorW") == 0 ||
+           strcmp (format_list->formats[i].formatName, "FileGroupDescri") == 0))
+        {
+          /**
+           * Advertise the "x-special/gnome-copied-files" format in addition to
+           * the "text/uri-list" format
+           */
+          mime_type = GRD_MIME_TYPE_XS_GNOME_COPIED_FILES;
+
+          mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable));
+          mime_type_table->mime_type = mime_type;
+          mime_type_table->rdp.format_id = format_list->formats[i].formatId;
+          mime_type_tables = g_list_append (mime_type_tables, mime_type_table);
+
+          mime_type = GRD_MIME_TYPE_TEXT_URILIST;
+        }
+      else if (format_list->formats[i].formatName &&
+               strcmp (format_list->formats[i].formatName, "HTML Format") == 0)
+        {
+          mime_type = GRD_MIME_TYPE_TEXT_HTML;
+        }
+      else
+        {
+          switch (format_list->formats[i].formatId)
+            {
+            case CF_TEXT:
+            case CF_UNICODETEXT:
+            case CF_OEMTEXT:
+              if (!already_has_text_format)
+                {
+                  mime_type = GRD_MIME_TYPE_TEXT_UTF8_STRING;
+                  already_has_text_format = TRUE;
+                  g_debug ("[RDP.CLIPRDR] Client advertised data for UTF8_STRING"
+                           " (external format: id: %i, name: %s)",
+                           format_list->formats[i].formatId,
+                           format_list->formats[i].formatName);
+                }
+              break;
+            case CF_DIB:
+              mime_type = GRD_MIME_TYPE_IMAGE_BMP;
+              break;
+            case CF_TIFF:
+              mime_type = GRD_MIME_TYPE_IMAGE_TIFF;
+              break;
+            case CB_FORMAT_GIF:
+              mime_type = GRD_MIME_TYPE_IMAGE_GIF;
+              break;
+            case CB_FORMAT_JPEG:
+              mime_type = GRD_MIME_TYPE_IMAGE_JPEG;
+              break;
+            case CB_FORMAT_PNG:
+              mime_type = GRD_MIME_TYPE_IMAGE_PNG;
+              break;
+            default:
+              g_debug ("[RDP.CLIPRDR] Client advertised unknown format: id: %i, "
+                       "name: %s", format_list->formats[i].formatId,
+                       format_list->formats[i].formatName);
+            }
+        }
+
+      if (mime_type != GRD_MIME_TYPE_NONE)
+        {
+          mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable));
+          mime_type_table->mime_type = mime_type;
+          mime_type_table->rdp.format_id = format_list->formats[i].formatId;
+          mime_type_tables = g_list_append (mime_type_tables, mime_type_table);
+        }
+    }
+
+  if (clipboard_rdp->server_format_list_update_id)
+    {
+      g_debug ("[RDP.CLIPRDR] Wrong message sequence: Got new format list "
+               "without being able to response to last update first");
+    }
+
+  events[0] = clipboard_rdp->stop_event;
+  events[1] = clipboard_rdp->completed_format_list_event;
+  WaitForMultipleObjects (2, events, FALSE, INFINITE);
+
+  if (WaitForSingleObject (clipboard_rdp->stop_event, 0) == WAIT_OBJECT_0)
+    return CHANNEL_RC_OK;
+
+  SetEvent (clipboard_rdp->format_list_received_event);
+  ResetEvent (clipboard_rdp->completed_format_list_event);
+  update_context = g_malloc0 (sizeof (ServerFormatListUpdateContext));
+  update_context->clipboard_rdp = clipboard_rdp;
+  update_context->mime_type_tables = mime_type_tables;
+
+  clipboard_rdp->server_format_list_update_id =
+    g_timeout_add (0, update_server_format_list, update_context);
+
+  return CHANNEL_RC_OK;
+}
+
+static gboolean
+handle_format_list_response (gpointer user_data)
+{
+  GrdClipboardRdp *clipboard_rdp = user_data;
+
+  update_allowed_server_formats (clipboard_rdp, TRUE);
+
+  clipboard_rdp->client_format_list_response_id = 0;
+  ResetEvent (clipboard_rdp->format_list_response_received_event);
+
+  return G_SOURCE_REMOVE;
+}
+
+/**
+ * Client sent us a response to our format list pdu
+ */
+static uint32_t
+cliprdr_client_format_list_response (CliprdrServerContext               *cliprdr_context,
+                                     const CLIPRDR_FORMAT_LIST_RESPONSE *format_list_response)
+{
+  GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom;
+  HANDLE format_list_response_received_event;
+
+  format_list_response_received_event =
+    clipboard_rdp->format_list_response_received_event;
+
+  if (WaitForSingleObject (format_list_response_received_event, 0) == WAIT_OBJECT_0)
+    {
+      g_warning ("[RDP.CLIPRDR] Wrong message sequence: Received an unexpected "
+                 "format list response. Ignoring...");
+      return CHANNEL_RC_OK;
+    }
+
+  clipboard_rdp->format_list_response_msg_flags = format_list_response->msgFlags;
+  clipboard_rdp->client_format_list_response_id =
+    g_timeout_add (0, handle_format_list_response, clipboard_rdp);
+  SetEvent (format_list_response_received_event);
+
+  return CHANNEL_RC_OK;
+}
+
+static void
+serialize_file_list (FILEDESCRIPTOR  *files,
+                     uint32_t         n_files,
+                     uint8_t        **dst_data,
+                     uint32_t        *dst_size)
+{
+  FILEDESCRIPTOR *file;
+  wStream* s = NULL;
+  uint64_t last_write_time;
+  uint32_t i, j;
+
+  if (!files || !dst_data || !dst_size)
+    return;
+
+  if (!(s = Stream_New (NULL, 4 + n_files * CLIPRDR_FILEDESCRIPTOR_SIZE)))
+    return;
+
+  Stream_Write_UINT32 (s, n_files);                    /* cItems */
+  for (i = 0; i < n_files; ++i)
+    {
+      file = &files[i];
+
+      Stream_Write_UINT32 (s, file->dwFlags);          /* flags */
+      Stream_Zero (s, 32);                             /* reserved1 */
+      Stream_Write_UINT32 (s, file->dwFileAttributes); /* fileAttributes */
+      Stream_Zero (s, 16);                             /* reserved2 */
+
+      last_write_time = file->ftLastWriteTime.dwHighDateTime;
+      last_write_time <<= 32;
+      last_write_time += file->ftLastWriteTime.dwLowDateTime;
+
+      Stream_Write_UINT64 (s, last_write_time);        /* lastWriteTime */
+      Stream_Write_UINT32 (s, file->nFileSizeHigh);    /* fileSizeHigh */
+      Stream_Write_UINT32 (s, file->nFileSizeLow);     /* fileSizeLow */
+
+      for (j = 0; j < 260; j++)                        /* cFileName */
+        Stream_Write_UINT16 (s, file->cFileName[j]);
+    }
+
+  Stream_SealLength (s);
+  Stream_GetLength (s, *dst_size);
+  Stream_GetBuffer (s, *dst_data);
+
+  Stream_Free (s, FALSE);
+}
+
+static gboolean
+request_server_format_data (gpointer user_data)
+{
+  ServerFormatDataRequestContext *request_context = user_data;
+  GrdClipboardRdp *clipboard_rdp = request_context->clipboard_rdp;
+  GrdClipboard *clipboard = GRD_CLIPBOARD (clipboard_rdp);
+  CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
+  CLIPRDR_FORMAT_DATA_RESPONSE format_data_response = {0};
+  GrdMimeType mime_type = request_context->mime_type;
+  uint32_t src_format_id = request_context->src_format_id;
+  uint32_t dst_format_id = request_context->dst_format_id;
+  uint8_t *src_data, *dst_data;
+  uint32_t src_size, dst_size;
+  BOOL success;
+
+  dst_data = src_data = NULL;
+  dst_size = src_size = 0;
+
+  if (g_hash_table_contains (clipboard_rdp->allowed_server_formats,
+                             GUINT_TO_POINTER (mime_type)))
+    {
+      src_data = grd_clipboard_request_server_content_for_mime_type (clipboard,
+                                                                     mime_type,
+                                                                     &src_size);
+    }
+  if (src_data)
+    {
+      if (request_context->needs_conversion)
+        {
+          if (request_context->needs_null_terminator)
+            {
+              char *pnull_terminator;
+
+              src_data = g_realloc (src_data, src_size + 1);
+              pnull_terminator = (char *) src_data + src_size;
+              *pnull_terminator = '\0';
+              ++src_size;
+            }
+
+          success = ClipboardSetData (clipboard_rdp->system,
+                                      src_format_id, src_data, src_size);
+          if (success)
+            {
+              dst_data = ClipboardGetData (clipboard_rdp->system,
+                                           dst_format_id, &dst_size);
+
+              if (dst_data && mime_type == GRD_MIME_TYPE_TEXT_URILIST)
+                {
+                  FILEDESCRIPTOR *files;
+                  uint32_t n_files;
+
+                  files = (FILEDESCRIPTOR *) dst_data;
+                  n_files = dst_size / sizeof (FILEDESCRIPTOR);
+
+                  dst_data = NULL;
+                  dst_size = 0;
+                  serialize_file_list (files, n_files, &dst_data, &dst_size);
+
+                  g_free (files);
+                }
+            }
+          if (!success || !dst_data)
+            g_warning ("[RDP.CLIPRDR] Converting clipboard content failed");
+        }
+      else
+        {
+          dst_data = g_steal_pointer (&src_data);
+          dst_size = src_size;
+        }
+    }
+
+  format_data_response.msgType = CB_FORMAT_DATA_RESPONSE;
+  format_data_response.msgFlags = dst_data ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
+  format_data_response.dataLen = dst_size;
+  format_data_response.requestedFormatData = dst_data;
+
+  cliprdr_context->ServerFormatDataResponse (cliprdr_context,
+                                             &format_data_response);
+
+  g_free (src_data);
+  g_free (dst_data);
+  g_free (request_context);
+
+  clipboard_rdp->server_format_data_request_id = 0;
+  SetEvent (clipboard_rdp->completed_format_data_request_event);
+
+  return G_SOURCE_REMOVE;
+}
+
+/**
+ * Client knows our format list, it requests now the data from our clipboard
+ */
+static uint32_t
+cliprdr_client_format_data_request (CliprdrServerContext              *cliprdr_context,
+                                    const CLIPRDR_FORMAT_DATA_REQUEST *format_data_request)
+{
+  GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom;
+  ServerFormatDataRequestContext *request_context;
+  CLIPRDR_FORMAT_DATA_RESPONSE format_data_response = {0};
+  GrdMimeType mime_type = GRD_MIME_TYPE_NONE;
+  uint32_t src_format_id = 0;
+  uint32_t dst_format_id;
+  gboolean needs_null_terminator = FALSE;
+  gboolean needs_conversion = FALSE;
+  HANDLE events[2];
+
+  dst_format_id = format_data_request->requestedFormatId;
+  switch (dst_format_id)
+    {
+    case CF_TEXT:
+      mime_type = GRD_MIME_TYPE_TEXT_PLAIN;
+      needs_null_terminator = TRUE;
+      needs_conversion = TRUE;
+      src_format_id = ClipboardGetFormatId (clipboard_rdp->system,
+                                            "text/plain");
+      break;
+    case CF_UNICODETEXT:
+      mime_type = clipboard_rdp->which_unicode_format;
+      needs_null_terminator = TRUE;
+      needs_conversion = TRUE;
+      src_format_id = ClipboardGetFormatId (clipboard_rdp->system,
+                                            "UTF8_STRING");
+      break;
+    case CB_FORMAT_HTML:
+      mime_type = GRD_MIME_TYPE_TEXT_HTML;
+      needs_null_terminator = TRUE;
+      needs_conversion = TRUE;
+      src_format_id = ClipboardGetFormatId (clipboard_rdp->system,
+                                            "text/html");
+      dst_format_id = ClipboardGetFormatId (clipboard_rdp->system,
+                                            "HTML Format");
+      break;
+    case CF_DIB:
+      mime_type = GRD_MIME_TYPE_IMAGE_BMP;
+      needs_conversion = TRUE;
+      src_format_id = ClipboardGetFormatId (clipboard_rdp->system,
+                                            "image/bmp");
+      break;
+    case CF_TIFF:
+      mime_type = GRD_MIME_TYPE_IMAGE_TIFF;
+      break;
+    case CB_FORMAT_GIF:
+      mime_type = GRD_MIME_TYPE_IMAGE_GIF;
+      break;
+    case CB_FORMAT_JPEG:
+      mime_type = GRD_MIME_TYPE_IMAGE_JPEG;
+      break;
+    case CB_FORMAT_PNG:
+      mime_type = GRD_MIME_TYPE_IMAGE_PNG;
+      break;
+    case CB_FORMAT_TEXTURILIST:
+      mime_type = GRD_MIME_TYPE_TEXT_URILIST;
+      needs_conversion = TRUE;
+      src_format_id = ClipboardGetFormatId (clipboard_rdp->system,
+                                            "text/uri-list");
+      dst_format_id = ClipboardGetFormatId (clipboard_rdp->system,
+                                            "FileGroupDescriptorW");
+      break;
+    }
+
+  if (clipboard_rdp->server_format_data_request_id)
+    {
+      g_debug ("[RDP.CLIPRDR] Wrong message sequence: Got new format data "
+               "request without being able to push response for last request "
+               "yet.");
+    }
+
+  events[0] = clipboard_rdp->stop_event;
+  events[1] = clipboard_rdp->completed_format_data_request_event;
+  WaitForMultipleObjects (2, events, FALSE, INFINITE);
+
+  if (WaitForSingleObject (clipboard_rdp->stop_event, 0) == WAIT_OBJECT_0)
+    return CHANNEL_RC_OK;
+
+  if (mime_type != GRD_MIME_TYPE_NONE)
+    {
+      ResetEvent (clipboard_rdp->completed_format_data_request_event);
+      request_context = g_malloc0 (sizeof (ServerFormatDataRequestContext));
+      request_context->clipboard_rdp = clipboard_rdp;
+      request_context->mime_type = mime_type;
+      request_context->src_format_id = src_format_id;
+      request_context->dst_format_id = dst_format_id;
+      request_context->needs_null_terminator = needs_null_terminator;
+      request_context->needs_conversion = needs_conversion;
+
+      clipboard_rdp->server_format_data_request_id =
+        g_timeout_add (0, request_server_format_data, request_context);
+
+      return CHANNEL_RC_OK;
+    }
+
+  format_data_response.msgType = CB_FORMAT_DATA_RESPONSE;
+  format_data_response.msgFlags = CB_RESPONSE_FAIL;
+  format_data_response.dataLen = 0;
+  format_data_response.requestedFormatData = NULL;
+
+  return cliprdr_context->ServerFormatDataResponse (cliprdr_context,
+                                                    &format_data_response);
+}
+
+/**
+ * Clients response to our data request that we sent
+ */
+static uint32_t
+cliprdr_client_format_data_response (CliprdrServerContext               *cliprdr_context,
+                                     const CLIPRDR_FORMAT_DATA_RESPONSE *format_data_response)
+{
+  GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom;
+
+  if (clipboard_rdp->format_data_response)
+    g_free ((uint8_t *) clipboard_rdp->format_data_response->requestedFormatData);
+  g_clear_pointer (&clipboard_rdp->format_data_response, g_free);
+
+  clipboard_rdp->format_data_response =
+    g_memdup2 (format_data_response,
+               sizeof (CLIPRDR_FORMAT_DATA_RESPONSE));
+  clipboard_rdp->format_data_response->requestedFormatData =
+    g_memdup2 (format_data_response->requestedFormatData,
+               format_data_response->dataLen);
+  SetEvent (clipboard_rdp->format_data_received_event);
+
+  return CHANNEL_RC_OK;
+}
+
+static uint32_t
+delegate_request_file_contents_size (CliprdrServerContext                *cliprdr_context,
+                                     const CLIPRDR_FILE_CONTENTS_REQUEST *file_contents_request)
+{
+  GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom;
+  wClipboardFileSizeRequest file_size_request = {0};
+
+  file_size_request.streamId = file_contents_request->streamId;
+  file_size_request.listIndex = file_contents_request->listIndex;
+
+  return clipboard_rdp->delegate->ClientRequestFileSize (clipboard_rdp->delegate,
+                                                         &file_size_request);
+}
+
+static uint32_t
+delegate_request_file_contents_range (CliprdrServerContext                *cliprdr_context,
+                                      const CLIPRDR_FILE_CONTENTS_REQUEST *file_contents_request)
+{
+  GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom;
+  wClipboardFileRangeRequest file_range_request = {0};
+
+  file_range_request.streamId = file_contents_request->streamId;
+  file_range_request.listIndex = file_contents_request->listIndex;
+  file_range_request.nPositionLow = file_contents_request->nPositionLow;
+  file_range_request.nPositionHigh = file_contents_request->nPositionHigh;
+  file_range_request.cbRequested = file_contents_request->cbRequested;
+
+  return clipboard_rdp->delegate->ClientRequestFileRange (clipboard_rdp->delegate,
+                                                          &file_range_request);
+}
+
+static uint32_t
+send_file_contents_response_failure (CliprdrServerContext *cliprdr_context,
+                                     uint32_t              stream_id)
+{
+  CLIPRDR_FILE_CONTENTS_RESPONSE file_contents_response = {0};
+
+  file_contents_response.msgType = CB_FILECONTENTS_RESPONSE;
+  file_contents_response.msgFlags = CB_RESPONSE_FAIL;
+  file_contents_response.streamId = stream_id;
+
+  return cliprdr_context->ServerFileContentsResponse (cliprdr_context,
+                                                      &file_contents_response);
+}
+
+/**
+ * Client requests us to send either the size of the remote file or a portion
+ * of the data in the file
+ */
+static uint32_t
+cliprdr_client_file_contents_request (CliprdrServerContext                *cliprdr_context,
+                                      const CLIPRDR_FILE_CONTENTS_REQUEST *file_contents_request)
+{
+  GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom;
+  uint32_t error = NO_ERROR;
+
+  if (!clipboard_rdp->server_file_contents_requests_allowed)
+    {
+      return send_file_contents_response_failure (cliprdr_context,
+                                                  file_contents_request->streamId);
+    }
+
+  if (file_contents_request->dwFlags & FILECONTENTS_SIZE &&
+      file_contents_request->dwFlags & FILECONTENTS_RANGE)
+    {
+      /**
+       * FILECONTENTS_SIZE and FILECONTENTS_RANGE are not allowed
+       * to be set at the same time
+       */
+      return send_file_contents_response_failure (cliprdr_context,
+                                                  file_contents_request->streamId);
+    }
+
+  if (file_contents_request->dwFlags & FILECONTENTS_SIZE)
+    {
+      error = delegate_request_file_contents_size (cliprdr_context,
+                                                   file_contents_request);
+    }
+  else if (file_contents_request->dwFlags & FILECONTENTS_RANGE)
+    {
+      error = delegate_request_file_contents_range (cliprdr_context,
+                                                    file_contents_request);
+    }
+  else
+    {
+      error = ERROR_INVALID_DATA;
+    }
+
+  if (error)
+    {
+      return send_file_contents_response_failure (cliprdr_context,
+                                                  file_contents_request->streamId);
+    }
+
+  return CHANNEL_RC_OK;
+}
+
+/**
+ * Clients response to our file contents request that we sent
+ */
+static uint32_t
+cliprdr_client_file_contents_response (CliprdrServerContext                 *cliprdr_context,
+                                       const CLIPRDR_FILE_CONTENTS_RESPONSE *file_contents_response)
+{
+  GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom;
+
+  if (clipboard_rdp->file_contents_response)
+    g_free ((uint8_t *) clipboard_rdp->file_contents_response->requestedData);
+  g_clear_pointer (&clipboard_rdp->file_contents_response, g_free);
+
+  clipboard_rdp->file_contents_response =
+    g_memdup2 (file_contents_response,
+               sizeof (CLIPRDR_FILE_CONTENTS_RESPONSE));
+  clipboard_rdp->file_contents_response->requestedData =
+    g_memdup2 (file_contents_response->requestedData,
+               file_contents_response->cbRequested);
+  SetEvent (clipboard_rdp->file_contents_received_event);
+
+  return CHANNEL_RC_OK;
+}
+
+static uint32_t
+cliprdr_file_size_success (wClipboardDelegate              *delegate,
+                           const wClipboardFileSizeRequest *file_size_request,
+                           uint64_t                         file_size)
+{
+  GrdClipboardRdp *clipboard_rdp = delegate->custom;
+  CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
+  CLIPRDR_FILE_CONTENTS_RESPONSE file_contents_response = {0};
+
+  file_contents_response.msgType = CB_FILECONTENTS_RESPONSE;
+  file_contents_response.msgFlags = CB_RESPONSE_OK;
+  file_contents_response.streamId = file_size_request->streamId;
+  file_contents_response.cbRequested = sizeof (uint64_t);
+  file_contents_response.requestedData = (uint8_t *) &file_size;
+
+  return cliprdr_context->ServerFileContentsResponse (cliprdr_context,
+                                                      &file_contents_response);
+}
+
+static uint32_t
+cliprdr_file_size_failure (wClipboardDelegate              *delegate,
+                           const wClipboardFileSizeRequest *file_size_request,
+                           uint32_t                         error)
+{
+  GrdClipboardRdp *clipboard_rdp = delegate->custom;
+
+  return send_file_contents_response_failure (clipboard_rdp->cliprdr_context,
+                                              file_size_request->streamId);
+}
+
+static uint32_t
+cliprdr_file_range_success (wClipboardDelegate               *delegate,
+                            const wClipboardFileRangeRequest *file_range_request,
+                            const uint8_t                    *data,
+                            uint32_t                          size)
+{
+  GrdClipboardRdp *clipboard_rdp = delegate->custom;
+  CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
+  CLIPRDR_FILE_CONTENTS_RESPONSE file_contents_response = {0};
+
+  file_contents_response.msgType = CB_FILECONTENTS_RESPONSE;
+  file_contents_response.msgFlags = CB_RESPONSE_OK;
+  file_contents_response.streamId = file_range_request->streamId;
+  file_contents_response.cbRequested = size;
+  file_contents_response.requestedData = data;
+
+  return cliprdr_context->ServerFileContentsResponse (cliprdr_context,
+                                                      &file_contents_response);
+}
+
+static uint32_t
+cliprdr_file_range_failure (wClipboardDelegate               *delegate,
+                            const wClipboardFileRangeRequest *file_range_request,
+                            uint32_t                          error)
+{
+  GrdClipboardRdp *clipboard_rdp = delegate->custom;
+
+  return send_file_contents_response_failure (clipboard_rdp->cliprdr_context,
+                                              file_range_request->streamId);
+}
+
+GrdClipboardRdp *
+grd_clipboard_rdp_new (GrdSessionRdp *session_rdp,
+                       HANDLE         vcm,
+                       HANDLE         stop_event)
+{
+  GrdClipboardRdp *clipboard_rdp;
+  GrdClipboard *clipboard;
+  CliprdrServerContext *cliprdr_context;
+
+  clipboard_rdp = g_object_new (GRD_TYPE_CLIPBOARD_RDP, NULL);
+  cliprdr_context = cliprdr_server_context_new (vcm);
+  if (!clipboard_rdp || !cliprdr_context)
+    {
+      g_warning ("[RDP.CLIPRDR] An error occurred while creating the RDP clipboard");
+      g_clear_pointer (&cliprdr_context, cliprdr_server_context_free);
+      g_clear_object (&clipboard_rdp);
+      return NULL;
+    }
+
+  clipboard_rdp->cliprdr_context = cliprdr_context;
+  clipboard_rdp->stop_event = stop_event;
+
+  clipboard = GRD_CLIPBOARD (clipboard_rdp);
+  grd_clipboard_initialize (clipboard, GRD_SESSION (session_rdp));
+
+  cliprdr_context->useLongFormatNames = TRUE;
+  cliprdr_context->streamFileClipEnabled = TRUE;
+  cliprdr_context->fileClipNoFilePaths = TRUE;
+  cliprdr_context->canLockClipData = FALSE;
+
+  cliprdr_context->ClientCapabilities = cliprdr_client_capabilities;
+  cliprdr_context->ClientFormatList = cliprdr_client_format_list;
+  cliprdr_context->ClientFormatListResponse = cliprdr_client_format_list_response;
+  cliprdr_context->ClientFormatDataRequest = cliprdr_client_format_data_request;
+  cliprdr_context->ClientFormatDataResponse = cliprdr_client_format_data_response;
+  cliprdr_context->ClientFileContentsRequest = cliprdr_client_file_contents_request;
+  cliprdr_context->ClientFileContentsResponse = cliprdr_client_file_contents_response;
+  cliprdr_context->custom = clipboard_rdp;
+
+  if (cliprdr_context->Start (cliprdr_context))
+    {
+      g_message ("[RDP.CLIPRDR] An error occurred while starting the RDP "
+                 "clipboard. The RDP client might not support the CLIPRDR channel");
+      g_clear_pointer (&clipboard_rdp->cliprdr_context,
+                       cliprdr_server_context_free);
+      g_clear_object (&clipboard_rdp);
+      return NULL;
+    }
+
+  return clipboard_rdp;
+}
+
+static gboolean
+clear_format_data (gpointer key,
+                   gpointer value,
+                   gpointer user_data)
+{
+  FormatData *format_data = value;
+
+  g_free (format_data->data);
+  g_free (format_data);
+
+  return TRUE;
+}
+
+static void
+grd_clipboard_rdp_dispose (GObject *object)
+{
+  GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (object);
+  GrdClipboard *clipboard = GRD_CLIPBOARD (clipboard_rdp);
+
+  if (clipboard_rdp->cliprdr_context)
+    {
+      clipboard_rdp->cliprdr_context->Stop (clipboard_rdp->cliprdr_context);
+      grd_clipboard_disable_clipboard (clipboard);
+    }
+
+  if (clipboard_rdp->file_contents_response)
+    g_free ((uint8_t *) clipboard_rdp->file_contents_response->requestedData);
+  g_clear_pointer (&clipboard_rdp->file_contents_response, g_free);
+  if (clipboard_rdp->format_data_response)
+    g_free ((uint8_t *) clipboard_rdp->format_data_response->requestedFormatData);
+  g_clear_pointer (&clipboard_rdp->format_data_response, g_free);
+
+  g_clear_pointer (&clipboard_rdp->pending_server_formats, g_list_free);
+  g_clear_pointer (&clipboard_rdp->tracked_files, remove_tracked_files);
+  g_hash_table_foreach_remove (clipboard_rdp->format_data_cache,
+                               clear_format_data,
+                               NULL);
+
+  rmdir (clipboard_rdp->base_path);
+  g_clear_pointer (&clipboard_rdp->base_path, g_free);
+  g_clear_pointer (&clipboard_rdp->format_data_cache, g_hash_table_unref);
+  g_clear_pointer (&clipboard_rdp->allowed_server_formats, g_hash_table_destroy);
+  g_clear_pointer (&clipboard_rdp->format_list_response_received_event,
+                   CloseHandle);
+  g_clear_pointer (&clipboard_rdp->format_list_received_event, CloseHandle);
+  g_clear_pointer (&clipboard_rdp->completed_format_data_request_event,
+                   CloseHandle);
+  g_clear_pointer (&clipboard_rdp->completed_format_list_event, CloseHandle);
+  g_clear_pointer (&clipboard_rdp->file_contents_received_event, CloseHandle);
+  g_clear_pointer (&clipboard_rdp->format_data_received_event, CloseHandle);
+  g_clear_pointer (&clipboard_rdp->delegate->basePath, g_free);
+  g_clear_pointer (&clipboard_rdp->system, ClipboardDestroy);
+  g_clear_pointer (&clipboard_rdp->cliprdr_context, cliprdr_server_context_free);
+
+  g_clear_handle_id (&clipboard_rdp->server_format_list_update_id, g_source_remove);
+  g_clear_handle_id (&clipboard_rdp->server_format_data_request_id, g_source_remove);
+  g_clear_handle_id (&clipboard_rdp->client_format_list_response_id, g_source_remove);
+
+  G_OBJECT_CLASS (grd_clipboard_rdp_parent_class)->dispose (object);
+}
+
+static void
+grd_clipboard_rdp_init (GrdClipboardRdp *clipboard_rdp)
+{
+  const char *prefix = "file://";
+  const char *grd_path = "/gnome-remote-desktop";
+  const char *cliprdr_template = "/cliprdr-XXXXXX";
+  g_autofree char *base_path = NULL;
+  g_autofree char *template_path = NULL;
+
+  base_path = g_strdup_printf ("%s%s", g_get_user_cache_dir (), grd_path);
+  template_path = g_strdup_printf ("%s%s", base_path, cliprdr_template);
+
+  if (g_access (base_path, F_OK))
+    {
+      if (mkdir (base_path, 0700))
+        {
+          g_error ("Failed to create clipboard cache directory %s: %s",
+                   base_path, g_strerror (errno));
+        }
+    }
+  if (!mkdtemp (template_path))
+    {
+      g_error ("Failed to create clipboard file directory %s: %s",
+               template_path, g_strerror (errno));
+    }
+
+  clipboard_rdp->system = ClipboardCreate ();
+  clipboard_rdp->delegate = ClipboardGetDelegate (clipboard_rdp->system);
+  clipboard_rdp->delegate->ClipboardFileSizeSuccess = cliprdr_file_size_success;
+  clipboard_rdp->delegate->ClipboardFileSizeFailure = cliprdr_file_size_failure;
+  clipboard_rdp->delegate->ClipboardFileRangeSuccess = cliprdr_file_range_success;
+  clipboard_rdp->delegate->ClipboardFileRangeFailure = cliprdr_file_range_failure;
+  clipboard_rdp->delegate->basePath = g_strdup_printf ("%s%s", prefix, template_path);
+  clipboard_rdp->delegate->custom = clipboard_rdp;
+
+  clipboard_rdp->format_data_received_event =
+    CreateEvent (NULL, TRUE, FALSE, NULL);
+  clipboard_rdp->file_contents_received_event =
+    CreateEvent (NULL, TRUE, FALSE, NULL);
+  clipboard_rdp->completed_format_list_event =
+    CreateEvent (NULL, TRUE, TRUE, NULL);
+  clipboard_rdp->completed_format_data_request_event =
+    CreateEvent (NULL, TRUE, TRUE, NULL);
+  clipboard_rdp->format_list_received_event =
+    CreateEvent (NULL, TRUE, FALSE, NULL);
+  clipboard_rdp->format_list_response_received_event =
+    CreateEvent (NULL, TRUE, FALSE, NULL);
+
+  clipboard_rdp->allowed_server_formats = g_hash_table_new (NULL, NULL);
+  clipboard_rdp->format_data_cache = g_hash_table_new (NULL, NULL);
+
+  clipboard_rdp->base_path = g_steal_pointer (&template_path);
+}
+
+static void
+grd_clipboard_rdp_class_init (GrdClipboardRdpClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GrdClipboardClass *clipboard_class = GRD_CLIPBOARD_CLASS (klass);
+
+  object_class->dispose = grd_clipboard_rdp_dispose;
+
+  clipboard_class->update_client_mime_type_list =
+    grd_clipboard_rdp_update_client_mime_type_list;
+  clipboard_class->request_client_content_for_mime_type =
+    grd_clipboard_rdp_request_client_content_for_mime_type;
+}
diff --git a/src/grd-clipboard-rdp.h b/src/grd-clipboard-rdp.h
new file mode 100644
index 0000000..bb37379
--- /dev/null
+++ b/src/grd-clipboard-rdp.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 Pascal Nowack
+ *
+ * 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.
+ */
+
+#ifndef GRD_CLIPBOARD_RDP_H
+#define GRD_CLIPBOARD_RDP_H
+
+#include <freerdp/server/cliprdr.h>
+
+#include "grd-clipboard.h"
+
+#define GRD_TYPE_CLIPBOARD_RDP (grd_clipboard_rdp_get_type ())
+G_DECLARE_FINAL_TYPE (GrdClipboardRdp,
+                      grd_clipboard_rdp,
+                      GRD, CLIPBOARD_RDP,
+                      GrdClipboard);
+
+GrdClipboardRdp *grd_clipboard_rdp_new (GrdSessionRdp *session_rdp,
+                                        HANDLE         vcm,
+                                        HANDLE         stop_event);
+
+#endif /* GRD_CLIPBOARD_RDP_H */
diff --git a/src/grd-types.h b/src/grd-types.h
index 448f1ab..73920ae 100644
--- a/src/grd-types.h
+++ b/src/grd-types.h
@@ -25,6 +25,7 @@
 
 typedef struct _GrdContext GrdContext;
 typedef struct _GrdClipboard GrdClipboard;
+typedef struct _GrdClipboardRdp GrdClipboardRdp;
 typedef struct _GrdRdpEventQueue GrdRdpEventQueue;
 typedef struct _GrdRdpSAMFile GrdRdpSAMFile;
 typedef struct _GrdRdpServer GrdRdpServer;
diff --git a/src/meson.build b/src/meson.build
index 756999d..0e97031 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,6 +1,8 @@
 daemon_sources = files([
   'grd-clipboard.c',
   'grd-clipboard.h',
+  'grd-clipboard-rdp.c',
+  'grd-clipboard-rdp.h',
   'grd-context.c',
   'grd-context.h',
   'grd-daemon.c',
@@ -73,6 +75,8 @@ executable('gnome-remote-desktop-daemon',
            daemon_sources,
            dependencies: [cairo_dep,
                           freerdp_dep,
+                          freerdp_client_dep,
+                          freerdp_server_dep,
                           glib_dep,
                           gio_dep,
                           gio_unix_dep,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]