Remmina - The GTK+ Remote Desktop Client  v1.4.33
Remmina is a remote desktop client written in GTK+, aiming to be useful for system administrators and travellers, who need to work with lots of remote computers in front of either large monitors or tiny netbooks. Remmina supports multiple network protocols in an integrated and consistent user interface. Currently RDP, VNC, NX, XDMCP and SSH are supported.
rdp_cliprdr.c
Go to the documentation of this file.
1 /*
2  * Remmina - The GTK+ Remote Desktop Client
3  * Copyright (C) 2012-2012 Jean-Louis Dupond
4  * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
5  * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  * In addition, as a special exception, the copyright holders give
23  * permission to link the code of portions of this program with the
24  * OpenSSL library under certain conditions as described in each
25  * individual source file, and distribute linked combinations
26  * including the two.
27  * You must obey the GNU General Public License in all respects
28  * for all of the code used other than OpenSSL. * If you modify
29  * file(s) with this exception, you may extend this exception to your
30  * version of the file(s), but you are not obligated to do so. * If you
31  * do not wish to do so, delete this exception statement from your
32  * version. * If you delete this exception statement from all source
33  * files in the program, then also delete it here.
34  *
35  */
36 
37 #include "rdp_plugin.h"
38 #include "rdp_cliprdr.h"
39 #include "rdp_event.h"
40 
41 #include <freerdp/freerdp.h>
42 #include <freerdp/channels/channels.h>
43 #include <freerdp/client/cliprdr.h>
44 #include <sys/time.h>
45 
46 #define CLIPBOARD_TRANSFER_WAIT_TIME 6
47 
49 {
50  TRACE_CALL(__func__);
51  UINT32 rc;
52  gchar *name = gdk_atom_name(atom);
53  rc = 0;
54  if (g_strcmp0("UTF8_STRING", name) == 0 || g_strcmp0("text/plain;charset=utf-8", name) == 0)
55  rc = CF_UNICODETEXT;
56  if (g_strcmp0("TEXT", name) == 0 || g_strcmp0("text/plain", name) == 0)
57  rc = CF_TEXT;
58  if (g_strcmp0("text/html", name) == 0)
59  rc = CB_FORMAT_HTML;
60  if (g_strcmp0("image/png", name) == 0)
61  rc = CB_FORMAT_PNG;
62  if (g_strcmp0("image/jpeg", name) == 0)
63  rc = CB_FORMAT_JPEG;
64  if (g_strcmp0("image/bmp", name) == 0)
65  rc = CF_DIB;
66  if (g_strcmp0("text/uri-list", name) == 0)
67  rc = CB_FORMAT_TEXTURILIST;
68  g_free(name);
69  return rc;
70 }
71 
72 /* Never used? */
73 void remmina_rdp_cliprdr_get_target_types(UINT32 **formats, UINT16 *size, GdkAtom *types, int count)
74 {
75  TRACE_CALL(__func__);
76  int i;
77  *size = 1;
78  *formats = (UINT32 *)malloc(sizeof(UINT32) * (count + 1));
79 
80  *formats[0] = 0;
81  for (i = 0; i < count; i++) {
82  UINT32 format = remmina_rdp_cliprdr_get_format_from_gdkatom(types[i]);
83  if (format != 0) {
84  (*formats)[*size] = format;
85  (*size)++;
86  }
87  }
88 
89  *formats = realloc(*formats, sizeof(UINT32) * (*size));
90 }
91 
92 static UINT8 *lf2crlf(UINT8 *data, int *size)
93 {
94  TRACE_CALL(__func__);
95  UINT8 c;
96  UINT8 *outbuf;
97  UINT8 *out;
98  UINT8 *in_end;
99  UINT8 *in;
100  int out_size;
101 
102  out_size = (*size) * 2 + 1;
103  outbuf = (UINT8 *)malloc(out_size);
104  out = outbuf;
105  in = data;
106  in_end = data + (*size);
107 
108  while (in < in_end) {
109  c = *in++;
110  if (c == '\n') {
111  *out++ = '\r';
112  *out++ = '\n';
113  } else {
114  *out++ = c;
115  }
116  }
117 
118  *out++ = 0;
119  *size = out - outbuf;
120 
121  return outbuf;
122 }
123 
124 static void crlf2lf(UINT8 *data, size_t *size)
125 {
126  TRACE_CALL(__func__);
127  UINT8 c;
128  UINT8 *out;
129  UINT8 *in;
130  UINT8 *in_end;
131 
132  out = data;
133  in = data;
134  in_end = data + (*size);
135 
136  while (in < in_end) {
137  c = *in++;
138  if (c != '\r')
139  *out++ = c;
140  }
141 
142  *size = out - data;
143 }
144 
145 /* Never used? */
146 int remmina_rdp_cliprdr_server_file_contents_request(CliprdrClientContext *context, CLIPRDR_FILE_CONTENTS_REQUEST *fileContentsRequest)
147 {
148  TRACE_CALL(__func__);
149  return -1;
150 }
151 
152 /* Never used? */
153 int remmina_rdp_cliprdr_server_file_contents_response(CliprdrClientContext *context, CLIPRDR_FILE_CONTENTS_RESPONSE *fileContentsResponse)
154 {
155  TRACE_CALL(__func__);
156  return 1;
157 }
158 
160 {
161  TRACE_CALL(__func__);
163  rfContext *rfi = GET_PLUGIN_DATA(gp);
164  rfClipboard *clipboard;
165  CLIPRDR_FORMAT_LIST *pFormatList;
166  RemminaPluginRdpEvent rdp_event = { 0 };
167 
168  if (!rfi || !rfi->connected || rfi->is_reconnecting)
169  return;
170 
171  clipboard = &(rfi->clipboard);
172 
173  ui = g_new0(RemminaPluginRdpUiObject, 1);
175  ui->clipboard.clipboard = clipboard;
177  pFormatList = remmina_rdp_event_queue_ui_sync_retptr(gp, ui);
178 
180  rdp_event.clipboard_formatlist.pFormatList = pFormatList;
181  remmina_rdp_event_event_push(gp, &rdp_event);
182 }
183 
185 {
186  TRACE_CALL(__func__);
187  CLIPRDR_CAPABILITIES capabilities;
188  CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet;
189 
190  capabilities.cCapabilitiesSets = 1;
191  capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET *)&(generalCapabilitySet);
192 
193  generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL;
194  generalCapabilitySet.capabilitySetLength = 12;
195 
196  generalCapabilitySet.version = CB_CAPS_VERSION_2;
197  generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES;
198 
199  clipboard->context->ClientCapabilities(clipboard->context, &capabilities);
200 }
201 
202 static UINT remmina_rdp_cliprdr_monitor_ready(CliprdrClientContext *context, const CLIPRDR_MONITOR_READY *monitorReady)
203 {
204  TRACE_CALL(__func__);
205  rfClipboard *clipboard = (rfClipboard *)context->custom;
207 
209  gp = clipboard->rfi->protocol_widget;
211 
212  return CHANNEL_RC_OK;
213 }
214 
215 static UINT remmina_rdp_cliprdr_server_capabilities(CliprdrClientContext *context, const CLIPRDR_CAPABILITIES *capabilities)
216 {
217  TRACE_CALL(__func__);
218  return CHANNEL_RC_OK;
219 }
220 
221 
223 {
224  TRACE_CALL(__func__);
225  guint fmt;
226 
227  pthread_mutex_lock(&clipboard->srv_data_mutex);
228  if (clipboard->srv_data != NULL) {
229  fmt = clipboard->format;
230  if (fmt == CB_FORMAT_PNG || fmt == CF_DIB || fmt == CF_DIBV5 || fmt == CB_FORMAT_JPEG) {
231  g_object_unref(clipboard->srv_data);
232  } else {
233  free(clipboard->srv_data);
234  }
235  clipboard->srv_data = NULL;
236  }
237  pthread_mutex_unlock(&clipboard->srv_data_mutex);
238 }
239 
240 
241 static UINT remmina_rdp_cliprdr_server_format_list(CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST *formatList)
242 {
243  TRACE_CALL(__func__);
244 
245  /* Called when a user do a "Copy" on the server: we collect all formats
246  * the server send us and then setup the local clipboard with the appropriate
247  * targets to request server data */
248 
251  rfClipboard *clipboard;
252  CLIPRDR_FORMAT *format;
253  CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse;
254  UINT rc;
255 
256  int has_dib_level = 0;
257 
258  int i;
259 
260  clipboard = (rfClipboard *)context->custom;
261  gp = clipboard->rfi->protocol_widget;
262 
263  REMMINA_PLUGIN_DEBUG("gp=%p: Received a new ServerFormatList from server clipboard. Remmina version = %s",
264  gp, VERSION);
265 
266  GtkTargetList *list = gtk_target_list_new(NULL, 0);
267 
268  if (clipboard->srv_clip_data_wait == SCDW_BUSY_WAIT) {
269  REMMINA_PLUGIN_DEBUG("gp=%p: we already have a FormatDataRequest in progress to the server, aborting", gp);
271  }
272 
274  clipboard->server_html_format_id = 0;
275 
276  REMMINA_PLUGIN_DEBUG("gp=%p: format list from the server:", gp);
277  for (i = 0; i < formatList->numFormats; i++) {
278  format = &formatList->formats[i];
279  const char *serverFormatName = format->formatName;
280  gchar *gtkFormatName = NULL;
281  if (format->formatId == CF_UNICODETEXT) {
282  serverFormatName = "CF_UNICODETEXT";
283  gtkFormatName = "text/plain;charset=utf-8";
284  GdkAtom atom = gdk_atom_intern(gtkFormatName, TRUE);
285  gtk_target_list_add(list, atom, 0, CF_UNICODETEXT);
286  /* Add also the older UTF8_STRING format for older applications */
287  atom = gdk_atom_intern("UTF8_STRING", TRUE);
288  gtk_target_list_add(list, atom, 0, CF_UNICODETEXT);
289  } else if (format->formatId == CF_TEXT) {
290  serverFormatName = "CF_TEXT";
291  gtkFormatName = "text/plain";
292  GdkAtom atom = gdk_atom_intern(gtkFormatName, TRUE);
293  gtk_target_list_add(list, atom, 0, CF_TEXT);
294  /* Add also the older TEXT format for older applications */
295  atom = gdk_atom_intern("TEXT", TRUE);
296  gtk_target_list_add(list, atom, 0, CF_TEXT);
297  } else if (format->formatId == CF_DIB) {
298  serverFormatName = "CF_DIB";
299  if (has_dib_level < 1)
300  has_dib_level = 1;
301  } else if (format->formatId == CF_DIBV5) {
302  serverFormatName = "CF_DIBV5";
303  if (has_dib_level < 5)
304  has_dib_level = 5;
305  } else if (format->formatId == CB_FORMAT_JPEG) {
306  serverFormatName = "CB_FORMAT_JPEG";
307  gtkFormatName = "image/jpeg";
308  GdkAtom atom = gdk_atom_intern(gtkFormatName, TRUE);
309  gtk_target_list_add(list, atom, 0, CB_FORMAT_JPEG);
310  } else if (format->formatId == CB_FORMAT_PNG) {
311  serverFormatName = "CB_FORMAT_PNG";
312  gtkFormatName = "image/png";
313  GdkAtom atom = gdk_atom_intern(gtkFormatName, TRUE);
314  gtk_target_list_add(list, atom, 0, CB_FORMAT_PNG);
315  } else if (format->formatId == CB_FORMAT_HTML) {
316  serverFormatName = "CB_FORMAT_HTML";
317  gtkFormatName = "text/html";
318  GdkAtom atom = gdk_atom_intern(gtkFormatName, TRUE);
319  gtk_target_list_add(list, atom, 0, CB_FORMAT_HTML);
320  } else if (format->formatId == CB_FORMAT_TEXTURILIST) {
321  serverFormatName = "CB_FORMAT_TEXTURILIST";
322  gtkFormatName = "text/uri-list";
323  GdkAtom atom = gdk_atom_intern(gtkFormatName, TRUE);
324  gtk_target_list_add(list, atom, 0, CB_FORMAT_TEXTURILIST);
325  } else if (format->formatId == CF_LOCALE) {
326  serverFormatName = "CF_LOCALE";
327  } else if (format->formatId == CF_METAFILEPICT) {
328  serverFormatName = "CF_METAFILEPICT";
329  } else if (serverFormatName != NULL && strcmp(serverFormatName, "HTML Format") == 0) {
330  gtkFormatName = "text/html";
331  GdkAtom atom = gdk_atom_intern(gtkFormatName, TRUE);
332  gtk_target_list_add(list, atom, 0, format->formatId);
333  clipboard->server_html_format_id = format->formatId;
334  }
335  REMMINA_PLUGIN_DEBUG("the server has clipboard format %d: %s -> GTK %s", format->formatId, serverFormatName, gtkFormatName);
336  }
337 
338  /* Keep only one DIB format, if present */
339  if (has_dib_level) {
340  GdkAtom atom = gdk_atom_intern("image/bmp", TRUE);
341  if (has_dib_level == 5)
342  gtk_target_list_add(list, atom, 0, CF_DIBV5);
343  else
344  gtk_target_list_add(list, atom, 0, CF_DIB);
345  }
346 
347 
348  REMMINA_PLUGIN_DEBUG("gp=%p: sending ClientFormatListResponse to server", gp);
349  formatListResponse.msgType = CB_FORMAT_LIST_RESPONSE;
350  formatListResponse.msgFlags = CB_RESPONSE_OK; // Can be CB_RESPONSE_FAIL in case of error
351  formatListResponse.dataLen = 0;
352  rc = clipboard->context->ClientFormatListResponse(clipboard->context, &formatListResponse);
353  /* Schedule GTK event to tell GTK to change the local clipboard calling gtk_clipboard_set_with_owner
354  * via REMMINA_RDP_UI_CLIPBOARD_SET_DATA
355  * GTK will immediately fire an "owner-change" event, that we should ignore.
356  * And if we are putting text on the clipboard, mutter or other clipboard
357  * managers may try immediately request a clipboard transfer. */
358 
359  /* Ensure we have at least one target into list, or Gtk will not change owner
360  when setting clipboard owner later.
361  We put it directly in the clipboard cache (clipboard->srv_data),
362  so remmina will never ask it to the server via ClientFormatDataRequest */
363  gint n_targets;
364  GtkTargetEntry *target_table = gtk_target_table_new_from_list(list, &n_targets);
365  if (target_table)
366  gtk_target_table_free(target_table, n_targets);
367  if (n_targets == 0) {
368  REMMINA_PLUGIN_DEBUG("gp=%p adding a dummy text target (empty text) for local clipboard, because we have no interesting targets from the server. Putting it in the local clipboard cache.");
369  GdkAtom atom = gdk_atom_intern("text/plain;charset=utf-8", TRUE);
370  gtk_target_list_add(list, atom, 0, CF_UNICODETEXT);
371  pthread_mutex_lock(&clipboard->srv_data_mutex);
372  clipboard->srv_data = malloc(1);
373  ((char *)(clipboard->srv_data))[0] = 0;
374  pthread_mutex_unlock(&clipboard->srv_data_mutex);
375  clipboard->format = CF_UNICODETEXT;
376  }
377 
378 
379  ui = g_new0(RemminaPluginRdpUiObject, 1);
381  ui->clipboard.clipboard = clipboard;
383  ui->clipboard.targetlist = list;
385 
386  REMMINA_PLUGIN_DEBUG("gp=%p: processing of ServerFormatList ended, returning rc=%u to libfreerdp", gp, rc);
387  return rc;
388 }
389 
390 static UINT remmina_rdp_cliprdr_server_format_list_response(CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST_RESPONSE *formatListResponse)
391 {
392  TRACE_CALL(__func__);
393  return CHANNEL_RC_OK;
394 }
395 
396 static UINT remmina_rdp_cliprdr_server_format_data_request(CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_REQUEST *formatDataRequest)
397 {
398  TRACE_CALL(__func__);
399 
402  rfClipboard *clipboard;
403 
404  clipboard = (rfClipboard *)context->custom;
405  gp = clipboard->rfi->protocol_widget;
406 
407  ui = g_new0(RemminaPluginRdpUiObject, 1);
409  ui->clipboard.clipboard = clipboard;
411  ui->clipboard.format = formatDataRequest->requestedFormatId;
413 
414  return CHANNEL_RC_OK;
415 }
416 
417 int timeval_diff(struct timeval *start, struct timeval *end)
418 {
419  /* Returns the time elapsed from start to end, in ms */
420  int64_t ms_end = (end->tv_sec * 1000) + (end->tv_usec / 1000);
421  int64_t ms_start = (start->tv_sec * 1000) + (start->tv_usec / 1000);
422 
423  return (int)(ms_end - ms_start);
424 }
425 
426 
427 static UINT remmina_rdp_cliprdr_server_format_data_response(CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_RESPONSE *formatDataResponse)
428 {
429  TRACE_CALL(__func__);
430 
431  const UINT8 *data;
432  size_t size;
433  rfContext *rfi;
435  rfClipboard *clipboard;
436  gpointer output = NULL;
437  struct timeval now;
438  int mstrans;
439 
440  clipboard = (rfClipboard *)context->custom;
441  gp = clipboard->rfi->protocol_widget;
442  rfi = GET_PLUGIN_DATA(gp);
443 
444  data = formatDataResponse->requestedFormatData;
445  size = formatDataResponse->dataLen;
446 
447  REMMINA_PLUGIN_DEBUG("gp=%p server FormatDataResponse received: clipboard data arrived form server.", gp);
448  gettimeofday(&now, NULL);
450 
451  /* Calculate stats */
452  mstrans = timeval_diff(&(clipboard->clientformatdatarequest_tv), &now);
453  REMMINA_PLUGIN_DEBUG("gp=%p %zu bytes transferred from server in %d ms. Speed is %d bytes/sec",
454  gp, (size_t)size, mstrans, mstrans != 0 ? (int)((int64_t)size * 1000 / mstrans) : 0);
455 
456  if (size > 0) {
457  switch (rfi->clipboard.format) {
458  case CF_UNICODETEXT:
459  {
460  size = ConvertFromUnicode(CP_UTF8, 0, (WCHAR *)data, size / 2, (CHAR **)&output, 0, NULL, NULL);
461  crlf2lf(output, &size);
462  break;
463  }
464 
465  case CF_TEXT:
466  case CB_FORMAT_HTML:
467  {
468  output = (gpointer)calloc(1, size + 1);
469  if (output) {
470  memcpy(output, data, size);
471  crlf2lf(output, &size);
472  }
473  break;
474  }
475 
476  case CF_DIBV5:
477  case CF_DIB:
478  {
479  wStream *s;
480  UINT32 offset;
481  GError *perr;
482  BITMAPINFOHEADER *pbi;
483  BITMAPV5HEADER *pbi5;
484 
485  pbi = (BITMAPINFOHEADER *)data;
486 
487  // offset calculation inspired by http://downloads.poolelan.com/MSDN/MSDNLibrary6/Disk1/Samples/VC/OS/WindowsXP/GetImage/BitmapUtil.cpp
488  offset = 14 + pbi->biSize;
489  if (pbi->biClrUsed != 0)
490  offset += sizeof(RGBQUAD) * pbi->biClrUsed;
491  else if (pbi->biBitCount <= 8)
492  offset += sizeof(RGBQUAD) * (1 << pbi->biBitCount);
493  if (pbi->biSize == sizeof(BITMAPINFOHEADER)) {
494  if (pbi->biCompression == 3) // BI_BITFIELDS is 3
495  offset += 12;
496  } else if (pbi->biSize >= sizeof(BITMAPV5HEADER)) {
497  pbi5 = (BITMAPV5HEADER *)pbi;
498  if (pbi5->bV5ProfileData <= offset)
499  offset += pbi5->bV5ProfileSize;
500  }
501  s = Stream_New(NULL, 14 + size);
502  Stream_Write_UINT8(s, 'B');
503  Stream_Write_UINT8(s, 'M');
504  Stream_Write_UINT32(s, 14 + size);
505  Stream_Write_UINT32(s, 0);
506  Stream_Write_UINT32(s, offset);
507  Stream_Write(s, data, size);
508 
509  data = Stream_Buffer(s);
510  size = Stream_Length(s);
511 
512  GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
513  perr = NULL;
514  if (!gdk_pixbuf_loader_write(loader, data, size, &perr)) {
515  Stream_Free(s, TRUE);
516  g_warning("[RDP] rdp_cliprdr: gdk_pixbuf_loader_write() returned error %s\n", perr->message);
517  } else {
518  if (!gdk_pixbuf_loader_close(loader, &perr)) {
519  g_warning("[RDP] rdp_cliprdr: gdk_pixbuf_loader_close() returned error %s\n", perr->message);
520  perr = NULL;
521  }
522  Stream_Free(s, TRUE);
523  output = g_object_ref(gdk_pixbuf_loader_get_pixbuf(loader));
524  }
525  g_object_unref(loader);
526  break;
527  }
528 
529  case CB_FORMAT_PNG:
530  case CB_FORMAT_JPEG:
531  {
532  GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
533  gdk_pixbuf_loader_write(loader, data, size, NULL);
534  output = g_object_ref(gdk_pixbuf_loader_get_pixbuf(loader));
535  gdk_pixbuf_loader_close(loader, NULL);
536  g_object_unref(loader);
537  break;
538  }
539  default:
540  {
541  if (rfi->clipboard.format == clipboard->server_html_format_id) {
542  /* Converting from Microsoft HTML Clipboard Format to pure text/html
543  * https://docs.microsoft.com/en-us/windows/win32/dataxchg/html-clipboard-format */
544  int p = 0, lstart = 0;
545  size_t osize;
546  char c;
547  /* Find the 1st starting with '<' */
548  while(p < size && (c = data[p]) != 0) {
549  if (c == '<' && p == lstart) break;
550  if (c == '\n') lstart = p + 1;
551  p++;
552  }
553  if (p < size) {
554  osize = size - lstart;
555  output = (gpointer)calloc(1, osize + 1);
556  if (output) {
557  memcpy(output, data + lstart, osize);
558  ((char *)output)[osize] = 0;
559  }
560  }
561  }
562  }
563  }
564  }
565 
566  pthread_mutex_lock(&clipboard->srv_data_mutex);
567  clipboard->srv_data = output;
568  pthread_mutex_unlock(&clipboard->srv_data_mutex);
569 
570  if (output != NULL)
571  REMMINA_PLUGIN_DEBUG("gp=%p: clipboard local cache data has been loaded", gp);
572  else
573  REMMINA_PLUGIN_DEBUG("gp=%p: data from server is not valid (size=%zu format=%d), cannot load into cache", gp, size, rfi->clipboard.format);
574 
575 
576  REMMINA_PLUGIN_DEBUG("gp=%p: signalling main GTK thread that we have some clipboard data.", gp);
577 
578  pthread_mutex_lock(&clipboard->transfer_clip_mutex);
579  pthread_cond_signal(&clipboard->transfer_clip_cond);
580  if (clipboard->srv_clip_data_wait == SCDW_BUSY_WAIT) {
581  REMMINA_PLUGIN_DEBUG("gp=%p: clipboard transfer from server completed.", gp);
582  } else {
583  // Clipboard data arrived from server when we are not busy waiting on main loop
584  // Unfortunately, we must discard it
585  REMMINA_PLUGIN_DEBUG("gp=%p: clipboard transfer from server completed, but no local application is requesting it. Data is on local cache now, try to paste later.", gp);
586  }
587  clipboard->srv_clip_data_wait = SCDW_NONE;
588  pthread_mutex_unlock(&clipboard->transfer_clip_mutex);
589 
590  return CHANNEL_RC_OK;
591 }
592 
593 
594 void remmina_rdp_cliprdr_request_data(GtkClipboard *gtkClipboard, GtkSelectionData *selection_data, guint info, RemminaProtocolWidget *gp)
595 {
596  TRACE_CALL(__func__);
597 
598  /* Called by GTK when someone presses "Paste" on the client side.
599  * We ask to the server the data we need */
600 
601  CLIPRDR_FORMAT_DATA_REQUEST *pFormatDataRequest;
602  rfClipboard *clipboard;
603  rfContext *rfi = GET_PLUGIN_DATA(gp);
604  RemminaPluginRdpEvent rdp_event = { 0 };
605  struct timespec to;
606  struct timeval tv;
607  int rc;
608  time_t tlimit, tlimit1s, tnow, tstart;
609 
610  REMMINA_PLUGIN_DEBUG("gp=%p: A local application has requested remote clipboard data for remote format id %d", gp, info);
611 
612  clipboard = &(rfi->clipboard);
613  if (clipboard->srv_clip_data_wait != SCDW_NONE) {
614  g_message("[RDP] Cannot paste now, I’m already transferring clipboard data from server. Try again later\n");
615  return;
616  }
617 
618  if (clipboard->format != info || clipboard->srv_data == NULL) {
619  /* We do not have a local cached clipoard, so we have to start a remote request */
621 
622  clipboard->format = info;
623 
624  pthread_mutex_lock(&clipboard->transfer_clip_mutex);
625 
626  pFormatDataRequest = (CLIPRDR_FORMAT_DATA_REQUEST *)malloc(sizeof(CLIPRDR_FORMAT_DATA_REQUEST));
627  ZeroMemory(pFormatDataRequest, sizeof(CLIPRDR_FORMAT_DATA_REQUEST));
628  pFormatDataRequest->requestedFormatId = clipboard->format;
629 
630  clipboard->srv_clip_data_wait = SCDW_BUSY_WAIT; // Annotate that we are waiting for ServerFormatDataResponse
631 
632  REMMINA_PLUGIN_DEBUG("gp=%p Requesting clipboard data with format %d from the server via ServerFormatDataRequest", gp, clipboard->format);
634  rdp_event.clipboard_formatdatarequest.pFormatDataRequest = pFormatDataRequest;
635  remmina_rdp_event_event_push(gp, &rdp_event);
636 
637  /* Busy wait clipboard data for CLIPBOARD_TRANSFER_WAIT_TIME seconds.
638  * In the meanwhile allow GTK event loop to proceed */
639 
640  tstart = time(NULL);
641  tlimit = tstart + CLIPBOARD_TRANSFER_WAIT_TIME;
642  rc = 100000;
643  tlimit1s = tstart + 1;
644  while ((tnow = time(NULL)) < tlimit && rc != 0 && clipboard->srv_clip_data_wait == SCDW_BUSY_WAIT) {
645 
646  if (tnow >= tlimit1s) {
647  REMMINA_PLUGIN_DEBUG("gp=%p, clipboard data is still not here after %u seconds", gp, (unsigned)(tnow - tstart));
648  tlimit1s = time(NULL) + 1;
649  }
650 
651  gettimeofday(&tv, NULL);
652  to.tv_sec = tv.tv_sec;
653  to.tv_nsec = tv.tv_usec * 1000 + 5000000; // wait for data for 5ms
654  if (to.tv_nsec >= 1000000000) {
655  to.tv_nsec -= 1000000000;
656  to.tv_sec++;
657  }
658  rc = pthread_cond_timedwait(&clipboard->transfer_clip_cond, &clipboard->transfer_clip_mutex, &to);
659  if (rc == 0)
660  break;
661 
662  gtk_main_iteration_do(FALSE);
663  }
664 
665  if (rc != 0) {
666  /* Timeout, just log it and hope that data will arrive later */
667  if (clipboard->srv_clip_data_wait == SCDW_ABORTING) {
668  g_warning("[RDP] gp=%p Clipboard data wait aborted.",gp);
669  } else {
670  if (rc == ETIMEDOUT)
671  g_warning("[RDP] gp=%p Clipboard data from the server is not available in %d seconds. No data will be available to user.",
672  gp, CLIPBOARD_TRANSFER_WAIT_TIME);
673  else
674  g_warning("[RDP] gp=%p internal error: pthread_cond_timedwait() returned %d\n", gp, rc);
675  }
676  }
677  pthread_mutex_unlock(&clipboard->transfer_clip_mutex);
678 
679  }
680 
681  pthread_mutex_lock(&clipboard->srv_data_mutex);
682  if (clipboard->srv_data != NULL) {
683  REMMINA_PLUGIN_DEBUG("gp=%p pasting data to local application", gp);
684  /* We have data in cache, just paste it */
685  if (info == CB_FORMAT_PNG || info == CF_DIB || info == CF_DIBV5 || info == CB_FORMAT_JPEG) {
686  gtk_selection_data_set_pixbuf(selection_data, clipboard->srv_data);
687  } else if (info == CB_FORMAT_HTML || info == clipboard->server_html_format_id) {
688  REMMINA_PLUGIN_DEBUG("gp=%p returning %zu bytes of HTML in clipboard to requesting application", gp, strlen(clipboard->srv_data));
689  GdkAtom atom = gdk_atom_intern("text/html", TRUE);
690  gtk_selection_data_set(selection_data, atom, 8, clipboard->srv_data, strlen(clipboard->srv_data));
691  } else {
692  REMMINA_PLUGIN_DEBUG("gp=%p returning %zu bytes of text in clipboard to requesting application", gp, strlen(clipboard->srv_data));
693  gtk_selection_data_set_text(selection_data, clipboard->srv_data, -1);
694  }
695  clipboard->srv_clip_data_wait = SCDW_NONE;
696  } else {
697  REMMINA_PLUGIN_DEBUG("gp=%p cannot paste data to local application because ->srv_data is NULL", gp);
698  }
699  pthread_mutex_unlock(&clipboard->srv_data_mutex);
700 
701 }
702 
703 void remmina_rdp_cliprdr_empty_clipboard(GtkClipboard *gtkClipboard, rfClipboard *clipboard)
704 {
705  TRACE_CALL(__func__);
706  /* No need to do anything here */
707 }
708 
710 {
711  TRACE_CALL(__func__);
712 
713  GtkClipboard *gtkClipboard;
714  rfContext *rfi = GET_PLUGIN_DATA(gp);
715  GdkAtom *targets;
716  gboolean result = 0;
717  gint loccount, srvcount;
718  gint formatId, i;
719  CLIPRDR_FORMAT *formats;
720  struct retp_t {
721  CLIPRDR_FORMAT_LIST pFormatList;
722  CLIPRDR_FORMAT formats[];
723  } *retp;
724 
725  formats = NULL;
726  retp = NULL;
727  loccount = 0;
728 
729  gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
730  if (gtkClipboard)
731  result = gtk_clipboard_wait_for_targets(gtkClipboard, &targets, &loccount);
732  REMMINA_PLUGIN_DEBUG("gp=%p sending to server the following local clipboard content formats", gp);
733  if (result && loccount > 0) {
734  formats = (CLIPRDR_FORMAT *)malloc(loccount * sizeof(CLIPRDR_FORMAT));
735  srvcount = 0;
736  for (i = 0; i < loccount; i++) {
737  formatId = remmina_rdp_cliprdr_get_format_from_gdkatom(targets[i]);
738  if (formatId != 0) {
739  gchar *name = gdk_atom_name(targets[i]);
740  REMMINA_PLUGIN_DEBUG(" local clipboard format %s will be sent to remote as %d", name, formatId);
741  g_free(name);
742  formats[srvcount].formatId = formatId;
743  formats[srvcount].formatName = NULL;
744  srvcount++;
745  }
746  }
747  if (srvcount > 0) {
748  retp = (struct retp_t *)malloc(sizeof(struct retp_t) + sizeof(CLIPRDR_FORMAT) * srvcount);
749  retp->pFormatList.formats = retp->formats;
750  retp->pFormatList.numFormats = srvcount;
751  memcpy(retp->formats, formats, sizeof(CLIPRDR_FORMAT) * srvcount);
752  } else {
753  retp = (struct retp_t *)malloc(sizeof(struct retp_t));
754  retp->pFormatList.formats = NULL;
755  retp->pFormatList.numFormats = 0;
756  }
757  free(formats);
758  } else {
759  retp = (struct retp_t *)malloc(sizeof(struct retp_t) + sizeof(CLIPRDR_FORMAT));
760  retp->pFormatList.formats = NULL;
761  retp->pFormatList.numFormats = 0;
762  }
763 
764  if (result)
765  g_free(targets);
766 
767  retp->pFormatList.msgType = CB_FORMAT_LIST;
768  retp->pFormatList.msgFlags = 0;
769 
770  return (CLIPRDR_FORMAT_LIST *)retp;
771 }
772 
774 {
775  TRACE_CALL(__func__);
777 }
778 
780 {
781  TRACE_CALL(__func__);
782  GtkClipboard *gtkClipboard;
783  UINT8 *inbuf = NULL;
784  UINT8 *outbuf = NULL;
785  GdkPixbuf *image = NULL;
786  int size = 0;
787  rfContext *rfi = GET_PLUGIN_DATA(gp);
788  RemminaPluginRdpEvent rdp_event = { 0 };
789 
790  gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
791  if (gtkClipboard) {
792  switch (ui->clipboard.format) {
793  case CF_TEXT:
794  case CF_UNICODETEXT:
795  case CB_FORMAT_HTML:
796  {
797  inbuf = (UINT8 *)gtk_clipboard_wait_for_text(gtkClipboard);
798  break;
799  }
800 
801  case CB_FORMAT_PNG:
802  case CB_FORMAT_JPEG:
803  case CF_DIB:
804  case CF_DIBV5:
805  {
806  image = gtk_clipboard_wait_for_image(gtkClipboard);
807  break;
808  }
809  }
810  }
811 
812  /* No data received, send nothing */
813  if (inbuf != NULL || image != NULL) {
814  switch (ui->clipboard.format) {
815  case CF_TEXT:
816  case CB_FORMAT_HTML:
817  {
818  size = strlen((char *)inbuf);
819  outbuf = lf2crlf(inbuf, &size);
820  break;
821  }
822  case CF_UNICODETEXT:
823  {
824  size = strlen((char *)inbuf);
825  inbuf = lf2crlf(inbuf, &size);
826  size = (ConvertToUnicode(CP_UTF8, 0, (CHAR *)inbuf, -1, (WCHAR **)&outbuf, 0)) * sizeof(WCHAR);
827  g_free(inbuf);
828  break;
829  }
830  case CB_FORMAT_PNG:
831  {
832  gchar *data;
833  gsize buffersize;
834  gdk_pixbuf_save_to_buffer(image, &data, &buffersize, "png", NULL, NULL);
835  outbuf = (UINT8 *)malloc(buffersize);
836  memcpy(outbuf, data, buffersize);
837  size = buffersize;
838  g_object_unref(image);
839  break;
840  }
841  case CB_FORMAT_JPEG:
842  {
843  gchar *data;
844  gsize buffersize;
845  gdk_pixbuf_save_to_buffer(image, &data, &buffersize, "jpeg", NULL, NULL);
846  outbuf = (UINT8 *)malloc(buffersize);
847  memcpy(outbuf, data, buffersize);
848  size = buffersize;
849  g_object_unref(image);
850  break;
851  }
852  case CF_DIB:
853  case CF_DIBV5:
854  {
855  gchar *data;
856  gsize buffersize;
857  gdk_pixbuf_save_to_buffer(image, &data, &buffersize, "bmp", NULL, NULL);
858  size = buffersize - 14;
859  outbuf = (UINT8 *)malloc(size);
860  memcpy(outbuf, data + 14, size);
861  g_object_unref(image);
862  break;
863  }
864  }
865  }
866 
868  rdp_event.clipboard_formatdataresponse.data = outbuf;
869  rdp_event.clipboard_formatdataresponse.size = size;
870  remmina_rdp_event_event_push(gp, &rdp_event);
871 }
872 
874 {
875  TRACE_CALL(__func__);
876  GtkClipboard *gtkClipboard;
877  gint n_targets;
878  rfContext *rfi = GET_PLUGIN_DATA(gp);
879 
880  gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
881  if (gtkClipboard) {
882  GtkTargetEntry *targets = gtk_target_table_new_from_list(ui->clipboard.targetlist, &n_targets);
883  if (!targets) {
884  /* If no targets exists, this is an internal error, because
885  * remmina_rdp_cliprdr_server_format_list() must have produced
886  * at least one target before calling REMMINA_RDP_UI_CLIPBOARD_SET_DATA */
887  g_warning("[RDP] internal error: no targets to insert into the local clipboard");
888  }
889 
890  REMMINA_PLUGIN_DEBUG("setting clipboard with owner to me: %p", gp);
891  gtk_clipboard_set_with_owner(gtkClipboard, targets, n_targets,
892  (GtkClipboardGetFunc)remmina_rdp_cliprdr_request_data,
893  (GtkClipboardClearFunc)remmina_rdp_cliprdr_empty_clipboard, G_OBJECT(gp));
894  gtk_target_table_free(targets, n_targets);
895  }
896 }
897 
899 {
900  /* When closing a rdp connection, we should check if gp is a clipboard owner.
901  * If it’s an owner, detach it from the clipboard */
902  TRACE_CALL(__func__);
903  rfContext *rfi = GET_PLUGIN_DATA(gp);
904  GtkClipboard *gtkClipboard;
905 
906  if (!rfi || !rfi->drawing_area) return;
907 
908  gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
909  if (gtkClipboard && gtk_clipboard_get_owner(gtkClipboard) == (GObject *)gp)
910  gtk_clipboard_clear(gtkClipboard);
911 
912 }
913 
915 {
916  TRACE_CALL(__func__);
917 
918  switch (ui->clipboard.type) {
921  break;
922 
925  break;
926 
929  break;
930 
931  }
932 }
933 
935 {
936  TRACE_CALL(__func__);
937  // Future: initialize rfi->clipboard
938 }
940 {
941  TRACE_CALL(__func__);
942 
944 
945 }
946 
948 {
949  TRACE_CALL(__func__);
950  if (rfi && rfi->clipboard.srv_clip_data_wait == SCDW_BUSY_WAIT) {
951  REMMINA_PLUGIN_DEBUG("requesting clipboard data transfer from server to be ignored and busywait loop to exit");
952  /* Allow clipboard transfer from server to terminate */
953  rfi->clipboard.srv_clip_data_wait = SCDW_ABORTING;
954  usleep(100000);
955  }
956 }
957 
958 void remmina_rdp_cliprdr_init(rfContext *rfi, CliprdrClientContext *cliprdr)
959 {
960  TRACE_CALL(__func__);
961 
962  rfClipboard *clipboard;
963  clipboard = &(rfi->clipboard);
964 
965  rfi->clipboard.rfi = rfi;
966  cliprdr->custom = (void *)clipboard;
967 
968  clipboard->context = cliprdr;
969  pthread_mutex_init(&clipboard->transfer_clip_mutex, NULL);
970  pthread_cond_init(&clipboard->transfer_clip_cond, NULL);
971  clipboard->srv_clip_data_wait = SCDW_NONE;
972 
973  pthread_mutex_init(&clipboard->srv_data_mutex, NULL);
974 
975  cliprdr->MonitorReady = remmina_rdp_cliprdr_monitor_ready;
976  cliprdr->ServerCapabilities = remmina_rdp_cliprdr_server_capabilities;
977  cliprdr->ServerFormatList = remmina_rdp_cliprdr_server_format_list;
978  cliprdr->ServerFormatListResponse = remmina_rdp_cliprdr_server_format_list_response;
979  cliprdr->ServerFormatDataRequest = remmina_rdp_cliprdr_server_format_data_request;
980  cliprdr->ServerFormatDataResponse = remmina_rdp_cliprdr_server_format_data_response;
981 
982 // cliprdr->ServerFileContentsRequest = remmina_rdp_cliprdr_server_file_contents_request;
983 // cliprdr->ServerFileContentsResponse = remmina_rdp_cliprdr_server_file_contents_response;
984 }
CliprdrClientContext * context
Definition: rdp_plugin.h:135
gboolean is_reconnecting
Definition: rdp_plugin.h:345
static UINT remmina_rdp_cliprdr_server_format_list_response(CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST_RESPONSE *formatListResponse)
Definition: rdp_cliprdr.c:390
int remmina_rdp_cliprdr_server_file_contents_response(CliprdrClientContext *context, CLIPRDR_FILE_CONTENTS_RESPONSE *fileContentsResponse)
Definition: rdp_cliprdr.c:153
void remmina_rdp_cliprdr_send_client_format_list(RemminaProtocolWidget *gp)
Definition: rdp_cliprdr.c:159
static UINT8 * lf2crlf(UINT8 *data, int *size)
Definition: rdp_cliprdr.c:92
void remmina_rdp_cliprdr_init(rfContext *rfi, CliprdrClientContext *cliprdr)
Definition: rdp_cliprdr.c:958
pthread_mutex_t transfer_clip_mutex
Definition: rdp_plugin.h:142
void remmina_rdp_cliprdr_detach_owner(RemminaProtocolWidget *gp)
Definition: rdp_cliprdr.c:898
int timeval_diff(struct timeval *start, struct timeval *end)
Definition: rdp_cliprdr.c:417
pthread_mutex_t srv_data_mutex
Definition: rdp_plugin.h:146
void remmina_rdp_clipboard_abort_client_format_data_request(rfContext *rfi)
Definition: rdp_cliprdr.c:947
void * remmina_rdp_event_queue_ui_sync_retptr(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
Definition: rdp_event.c:1483
static UINT remmina_rdp_cliprdr_server_format_data_request(CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_REQUEST *formatDataRequest)
Definition: rdp_cliprdr.c:396
static void crlf2lf(UINT8 *data, size_t *size)
Definition: rdp_cliprdr.c:124
void remmina_rdp_clipboard_init(rfContext *rfi)
Definition: rdp_cliprdr.c:934
RemminaProtocolWidget * protocol_widget
Definition: rdp_plugin.h:324
rfContext * rfi
Definition: rdp_plugin.h:134
void remmina_rdp_cliprdr_request_data(GtkClipboard *gtkClipboard, GtkSelectionData *selection_data, guint info, RemminaProtocolWidget *gp)
Definition: rdp_cliprdr.c:594
gpointer srv_data
Definition: rdp_plugin.h:145
struct remmina_plugin_rdp_event::@42::@47 clipboard_formatdataresponse
void remmina_rdp_event_process_clipboard(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
Definition: rdp_cliprdr.c:914
RemminaPluginRdpEventType type
Definition: rdp_plugin.h:191
void remmina_rdp_cliprdr_set_clipboard_data(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
Definition: rdp_cliprdr.c:873
static UINT remmina_rdp_cliprdr_monitor_ready(CliprdrClientContext *context, const CLIPRDR_MONITOR_READY *monitorReady)
Definition: rdp_cliprdr.c:202
pthread_cond_t transfer_clip_cond
Definition: rdp_plugin.h:143
static void remmina_rdp_cliprdr_mt_get_format_list(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
Definition: rdp_cliprdr.c:773
GtkWidget * drawing_area
Definition: rdp_plugin.h:356
int remmina_rdp_event_queue_ui_sync_retint(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
Definition: rdp_event.c:1471
int remmina_rdp_cliprdr_server_file_contents_request(CliprdrClientContext *context, CLIPRDR_FILE_CONTENTS_REQUEST *fileContentsRequest)
Definition: rdp_cliprdr.c:146
static void remmina_rdp_cliprdr_send_client_capabilities(rfClipboard *clipboard)
Definition: rdp_cliprdr.c:184
void remmina_rdp_event_queue_ui_async(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
Definition: rdp_event.c:1465
struct remmina_plugin_rdp_event::@42::@46 clipboard_formatlist
void remmina_rdp_cliprdr_empty_clipboard(GtkClipboard *gtkClipboard, rfClipboard *clipboard)
Definition: rdp_cliprdr.c:703
CLIPRDR_FORMAT_LIST * remmina_rdp_cliprdr_get_client_format_list(RemminaProtocolWidget *gp)
Definition: rdp_cliprdr.c:709
rfClipboard clipboard
Definition: rdp_plugin.h:387
void remmina_rdp_cliprdr_cached_clipboard_free(rfClipboard *clipboard)
Definition: rdp_cliprdr.c:222
struct remmina_plugin_rdp_event::@42::@48 clipboard_formatdatarequest
void remmina_rdp_clipboard_free(rfContext *rfi)
Definition: rdp_cliprdr.c:939
gboolean connected
Definition: rdp_plugin.h:344
void remmina_rdp_cliprdr_get_target_types(UINT32 **formats, UINT16 *size, GdkAtom *types, int count)
Definition: rdp_cliprdr.c:73
static UINT remmina_rdp_cliprdr_server_capabilities(CliprdrClientContext *context, const CLIPRDR_CAPABILITIES *capabilities)
Definition: rdp_cliprdr.c:215
static UINT remmina_rdp_cliprdr_server_format_list(CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST *formatList)
Definition: rdp_cliprdr.c:241
void remmina_rdp_cliprdr_get_clipboard_data(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
Definition: rdp_cliprdr.c:779
UINT32 format
Definition: rdp_plugin.h:139
UINT32 server_html_format_id
Definition: rdp_plugin.h:148
void remmina_rdp_event_event_push(RemminaProtocolWidget *gp, const RemminaPluginRdpEvent *e)
Definition: rdp_event.c:150
static UINT remmina_rdp_cliprdr_server_format_data_response(CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_RESPONSE *formatDataResponse)
Definition: rdp_cliprdr.c:427
UINT32 remmina_rdp_cliprdr_get_format_from_gdkatom(GdkAtom atom)
Definition: rdp_cliprdr.c:48
struct timeval clientformatdatarequest_tv
Definition: rdp_plugin.h:151
enum rf_clipboard::@41 srv_clip_data_wait
RemminaPluginRdpUiType type
Definition: rdp_plugin.h:267