Remmina - The GTK+ Remote Desktop Client  v1.4.2
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-2020 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 void remmina_rdp_cliprdr_get_target_types(UINT32 **formats, UINT16 *size, GdkAtom *types, int count)
73 {
74  TRACE_CALL(__func__);
75  int i;
76  *size = 1;
77  *formats = (UINT32 *)malloc(sizeof(UINT32) * (count + 1));
78 
79  *formats[0] = 0;
80  for (i = 0; i < count; i++) {
81  UINT32 format = remmina_rdp_cliprdr_get_format_from_gdkatom(types[i]);
82  if (format != 0) {
83  (*formats)[*size] = format;
84  (*size)++;
85  }
86  }
87 
88  *formats = realloc(*formats, sizeof(UINT32) * (*size));
89 }
90 
91 static UINT8 *lf2crlf(UINT8 *data, int *size)
92 {
93  TRACE_CALL(__func__);
94  UINT8 c;
95  UINT8 *outbuf;
96  UINT8 *out;
97  UINT8 *in_end;
98  UINT8 *in;
99  int out_size;
100 
101  out_size = (*size) * 2 + 1;
102  outbuf = (UINT8 *)malloc(out_size);
103  out = outbuf;
104  in = data;
105  in_end = data + (*size);
106 
107  while (in < in_end) {
108  c = *in++;
109  if (c == '\n') {
110  *out++ = '\r';
111  *out++ = '\n';
112  } else {
113  *out++ = c;
114  }
115  }
116 
117  *out++ = 0;
118  *size = out - outbuf;
119 
120  return outbuf;
121 }
122 
123 static void crlf2lf(UINT8 *data, size_t *size)
124 {
125  TRACE_CALL(__func__);
126  UINT8 c;
127  UINT8 *out;
128  UINT8 *in;
129  UINT8 *in_end;
130 
131  out = data;
132  in = data;
133  in_end = data + (*size);
134 
135  while (in < in_end) {
136  c = *in++;
137  if (c != '\r')
138  *out++ = c;
139  }
140 
141  *size = out - data;
142 }
143 
144 int remmina_rdp_cliprdr_server_file_contents_request(CliprdrClientContext *context, CLIPRDR_FILE_CONTENTS_REQUEST *fileContentsRequest)
145 {
146  TRACE_CALL(__func__);
147  return -1;
148 }
149 int remmina_rdp_cliprdr_server_file_contents_response(CliprdrClientContext *context, CLIPRDR_FILE_CONTENTS_RESPONSE *fileContentsResponse)
150 {
151  TRACE_CALL(__func__);
152  return 1;
153 }
154 
156 {
157  TRACE_CALL(__func__);
159  rfContext *rfi = GET_PLUGIN_DATA(gp);
160  rfClipboard *clipboard;
161  CLIPRDR_FORMAT_LIST *pFormatList;
162  RemminaPluginRdpEvent rdp_event = { 0 };
163 
164  if (!rfi || !rfi->connected || rfi->is_reconnecting)
165  return;
166 
167  clipboard = &(rfi->clipboard);
168 
169  ui = g_new0(RemminaPluginRdpUiObject, 1);
171  ui->clipboard.clipboard = clipboard;
173  pFormatList = remmina_rdp_event_queue_ui_sync_retptr(gp, ui);
174 
176  rdp_event.clipboard_formatlist.pFormatList = pFormatList;
177  remmina_rdp_event_event_push(gp, &rdp_event);
178 }
179 
181 {
182  TRACE_CALL(__func__);
183  CLIPRDR_CAPABILITIES capabilities;
184  CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet;
185 
186  capabilities.cCapabilitiesSets = 1;
187  capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET *)&(generalCapabilitySet);
188 
189  generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL;
190  generalCapabilitySet.capabilitySetLength = 12;
191 
192  generalCapabilitySet.version = CB_CAPS_VERSION_2;
193  generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES;
194 
195  clipboard->context->ClientCapabilities(clipboard->context, &capabilities);
196 }
197 
198 
199 static UINT remmina_rdp_cliprdr_monitor_ready(CliprdrClientContext *context, const CLIPRDR_MONITOR_READY *monitorReady)
200 {
201  TRACE_CALL(__func__);
202  rfClipboard *clipboard = (rfClipboard *)context->custom;
204 
206  gp = clipboard->rfi->protocol_widget;
208 
209  return CHANNEL_RC_OK;
210 }
211 
212 static UINT remmina_rdp_cliprdr_server_capabilities(CliprdrClientContext *context, const CLIPRDR_CAPABILITIES *capabilities)
213 {
214  TRACE_CALL(__func__);
215  return CHANNEL_RC_OK;
216 }
217 
218 
219 static UINT remmina_rdp_cliprdr_server_format_list(CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST *formatList)
220 {
221  TRACE_CALL(__func__);
222 
223  /* Called when a user do a "Copy" on the server: we collect all formats
224  * the server send us and then setup the local clipboard with the appropriate
225  * targets to request server data */
226 
229  rfClipboard *clipboard;
230  CLIPRDR_FORMAT *format;
231  CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse;
232  const char *serverFormatName;
233 
234  int has_dib_level = 0;
235 
236  int i;
237 
238  clipboard = (rfClipboard *)context->custom;
239  gp = clipboard->rfi->protocol_widget;
240  GtkTargetList *list = gtk_target_list_new(NULL, 0);
241 
242  remmina_plugin_service->debug("format list from the server:");
243  for (i = 0; i < formatList->numFormats; i++) {
244  format = &formatList->formats[i];
245  serverFormatName = format->formatName;
246  if (format->formatId == CF_UNICODETEXT) {
247  serverFormatName = "CF_UNICODETEXT";
248  GdkAtom atom = gdk_atom_intern("UTF8_STRING", TRUE);
249  gtk_target_list_add(list, atom, 0, CF_UNICODETEXT);
250  } else if (format->formatId == CF_TEXT) {
251  serverFormatName = "CF_TEXT";
252  GdkAtom atom = gdk_atom_intern("TEXT", TRUE);
253  gtk_target_list_add(list, atom, 0, CF_TEXT);
254  } else if (format->formatId == CF_DIB) {
255  serverFormatName = "CF_DIB";
256  if (has_dib_level < 1)
257  has_dib_level = 1;
258  } else if (format->formatId == CF_DIBV5) {
259  serverFormatName = "CF_DIBV5";
260  if (has_dib_level < 5)
261  has_dib_level = 5;
262  } else if (format->formatId == CB_FORMAT_JPEG) {
263  serverFormatName = "CB_FORMAT_JPEG";
264  GdkAtom atom = gdk_atom_intern("image/jpeg", TRUE);
265  gtk_target_list_add(list, atom, 0, CB_FORMAT_JPEG);
266  } else if (format->formatId == CB_FORMAT_PNG) {
267  serverFormatName = "CB_FORMAT_PNG";
268  GdkAtom atom = gdk_atom_intern("image/png", TRUE);
269  gtk_target_list_add(list, atom, 0, CB_FORMAT_PNG);
270  } else if (format->formatId == CB_FORMAT_HTML) {
271  serverFormatName = "CB_FORMAT_HTML";
272  GdkAtom atom = gdk_atom_intern("text/html", TRUE);
273  gtk_target_list_add(list, atom, 0, CB_FORMAT_HTML);
274  } else if (format->formatId == CB_FORMAT_TEXTURILIST) {
275  serverFormatName = "CB_FORMAT_TEXTURILIST";
276  GdkAtom atom = gdk_atom_intern("text/uri-list", TRUE);
277  gtk_target_list_add(list, atom, 0, CB_FORMAT_TEXTURILIST);
278  } else if (format->formatId == CF_LOCALE) {
279  serverFormatName = "CF_LOCALE";
280  } else if (format->formatId == CF_METAFILEPICT) {
281  serverFormatName = "CF_METAFILEPICT";
282  }
283  remmina_plugin_service->debug("the server has clipboard format %d: %s", format->formatId, serverFormatName);
284  }
285 
286  /* Keep only one DIB format, if present */
287  if (has_dib_level) {
288  GdkAtom atom = gdk_atom_intern("image/bmp", TRUE);
289  if (has_dib_level == 5)
290  gtk_target_list_add(list, atom, 0, CF_DIBV5);
291  else
292  gtk_target_list_add(list, atom, 0, CF_DIB);
293  }
294 
295  /* Now we tell GTK to change the local keyboard calling gtk_clipboard_set_with_owner
296  * via REMMINA_RDP_UI_CLIPBOARD_SET_DATA
297  * GTK will immediately fire an "owner-change" event, that we should ignore */
298 
299  ui = g_new0(RemminaPluginRdpUiObject, 1);
301  ui->clipboard.clipboard = clipboard;
303  ui->clipboard.targetlist = list;
305 
306  /* Send FormatListResponse to server */
307 
308  formatListResponse.msgType = CB_FORMAT_LIST_RESPONSE;
309  formatListResponse.msgFlags = CB_RESPONSE_OK; // Can be CB_RESPONSE_FAIL in case of error
310  formatListResponse.dataLen = 0;
311 
312  return clipboard->context->ClientFormatListResponse(clipboard->context, &formatListResponse);
313 }
314 
315 static UINT remmina_rdp_cliprdr_server_format_list_response(CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST_RESPONSE *formatListResponse)
316 {
317  TRACE_CALL(__func__);
318  return CHANNEL_RC_OK;
319 }
320 
321 
322 static UINT remmina_rdp_cliprdr_server_format_data_request(CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_REQUEST *formatDataRequest)
323 {
324  TRACE_CALL(__func__);
325 
328  rfClipboard *clipboard;
329 
330  clipboard = (rfClipboard *)context->custom;
331  gp = clipboard->rfi->protocol_widget;
332 
333  ui = g_new0(RemminaPluginRdpUiObject, 1);
335  ui->clipboard.clipboard = clipboard;
337  ui->clipboard.format = formatDataRequest->requestedFormatId;
339 
340  return CHANNEL_RC_OK;
341 }
342 
343 static UINT remmina_rdp_cliprdr_server_format_data_response(CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_RESPONSE *formatDataResponse)
344 {
345  TRACE_CALL(__func__);
346 
347  const UINT8 *data;
348  size_t size;
349  rfContext *rfi;
351  rfClipboard *clipboard;
352  GdkPixbufLoader *pixbuf;
353  gpointer output = NULL;
354 
355  clipboard = (rfClipboard *)context->custom;
356  gp = clipboard->rfi->protocol_widget;
357  rfi = GET_PLUGIN_DATA(gp);
358 
359  data = formatDataResponse->requestedFormatData;
360  size = formatDataResponse->dataLen;
361 
362  // formatDataResponse->requestedFormatData is allocated
363  // by freerdp and freed after returning from this callback function.
364  // So we must make a copy if we need to preserve it
365 
366  if (size > 0) {
367  switch (rfi->clipboard.format) {
368  case CF_UNICODETEXT:
369  {
370  size = ConvertFromUnicode(CP_UTF8, 0, (WCHAR *)data, size / 2, (CHAR **)&output, 0, NULL, NULL);
371  crlf2lf(output, &size);
372  break;
373  }
374 
375  case CF_TEXT:
376  case CB_FORMAT_HTML:
377  {
378  output = (gpointer)calloc(1, size + 1);
379  if (output) {
380  memcpy(output, data, size);
381  crlf2lf(output, &size);
382  }
383  break;
384  }
385 
386  case CF_DIBV5:
387  case CF_DIB:
388  {
389  wStream *s;
390  UINT32 offset;
391  GError *perr;
392  BITMAPINFOHEADER *pbi;
393  BITMAPV5HEADER *pbi5;
394 
395  pbi = (BITMAPINFOHEADER *)data;
396 
397  // offset calculation inspired by http://downloads.poolelan.com/MSDN/MSDNLibrary6/Disk1/Samples/VC/OS/WindowsXP/GetImage/BitmapUtil.cpp
398  offset = 14 + pbi->biSize;
399  if (pbi->biClrUsed != 0)
400  offset += sizeof(RGBQUAD) * pbi->biClrUsed;
401  else if (pbi->biBitCount <= 8)
402  offset += sizeof(RGBQUAD) * (1 << pbi->biBitCount);
403  if (pbi->biSize == sizeof(BITMAPINFOHEADER)) {
404  if (pbi->biCompression == 3) // BI_BITFIELDS is 3
405  offset += 12;
406  } else if (pbi->biSize >= sizeof(BITMAPV5HEADER)) {
407  pbi5 = (BITMAPV5HEADER *)pbi;
408  if (pbi5->bV5ProfileData <= offset)
409  offset += pbi5->bV5ProfileSize;
410  }
411  s = Stream_New(NULL, 14 + size);
412  Stream_Write_UINT8(s, 'B');
413  Stream_Write_UINT8(s, 'M');
414  Stream_Write_UINT32(s, 14 + size);
415  Stream_Write_UINT32(s, 0);
416  Stream_Write_UINT32(s, offset);
417  Stream_Write(s, data, size);
418 
419  data = Stream_Buffer(s);
420  size = Stream_Length(s);
421 
422  pixbuf = gdk_pixbuf_loader_new();
423  perr = NULL;
424  if (!gdk_pixbuf_loader_write(pixbuf, data, size, &perr)) {
425  Stream_Free(s, TRUE);
426  g_warning("[RDP] rdp_cliprdr: gdk_pixbuf_loader_write() returned error %s\n", perr->message);
427  } else {
428  if (!gdk_pixbuf_loader_close(pixbuf, &perr)) {
429  g_warning("[RDP] rdp_cliprdr: gdk_pixbuf_loader_close() returned error %s\n", perr->message);
430  perr = NULL;
431  }
432  Stream_Free(s, TRUE);
433  output = g_object_ref(gdk_pixbuf_loader_get_pixbuf(pixbuf));
434  }
435  g_object_unref(pixbuf);
436  break;
437  }
438 
439  case CB_FORMAT_PNG:
440  case CB_FORMAT_JPEG:
441  {
442  pixbuf = gdk_pixbuf_loader_new();
443  gdk_pixbuf_loader_write(pixbuf, data, size, NULL);
444  output = g_object_ref(gdk_pixbuf_loader_get_pixbuf(pixbuf));
445  gdk_pixbuf_loader_close(pixbuf, NULL);
446  g_object_unref(pixbuf);
447  break;
448  }
449  }
450  }
451 
452  pthread_mutex_lock(&clipboard->transfer_clip_mutex);
453  pthread_cond_signal(&clipboard->transfer_clip_cond);
454  if (clipboard->srv_clip_data_wait == SCDW_BUSY_WAIT) {
455  remmina_plugin_service->debug("clibpoard transfer from server completed.");
456  clipboard->srv_data = output;
457  } else {
458  // Clipboard data arrived from server when we are not busywaiting on main loop
459  // Unfortunately, we must discard it
460  remmina_plugin_service->debug("clibpoard transfer from server completed. Data discarded due to abort or timeout.");
461  clipboard->srv_clip_data_wait = SCDW_NONE;
462  }
463  pthread_mutex_unlock(&clipboard->transfer_clip_mutex);
464 
465  return CHANNEL_RC_OK;
466 }
467 
468 void remmina_rdp_cliprdr_request_data(GtkClipboard *gtkClipboard, GtkSelectionData *selection_data, guint info, RemminaProtocolWidget *gp)
469 {
470  TRACE_CALL(__func__);
471 
472  /* Called by GTK when someone press "Paste" on the client side.
473  * We ask to the server the data we need */
474 
475  CLIPRDR_FORMAT_DATA_REQUEST *pFormatDataRequest;
476  rfClipboard *clipboard;
477  rfContext *rfi = GET_PLUGIN_DATA(gp);
478  RemminaPluginRdpEvent rdp_event = { 0 };
479  struct timespec to;
480  struct timeval tv;
481  int rc;
482  time_t tlimit;
483 
484  remmina_plugin_service->debug("A local application has requested remote clipboard data for local format id %d", info);
485 
486  clipboard = &(rfi->clipboard);
487  if (clipboard->srv_clip_data_wait != SCDW_NONE) {
488  g_message("[RDP] Cannot paste now, I’m already transferring clipboard data from server. Try again later\n");
489  return;
490  }
491 
492  clipboard->format = info;
493 
494  /* Request Clipboard content from the server, the request is async */
495 
496  pthread_mutex_lock(&clipboard->transfer_clip_mutex);
497 
498  pFormatDataRequest = (CLIPRDR_FORMAT_DATA_REQUEST *)malloc(sizeof(CLIPRDR_FORMAT_DATA_REQUEST));
499  ZeroMemory(pFormatDataRequest, sizeof(CLIPRDR_FORMAT_DATA_REQUEST));
500  pFormatDataRequest->requestedFormatId = clipboard->format;
501  clipboard->srv_clip_data_wait = SCDW_BUSY_WAIT;
502 
503  remmina_plugin_service->debug("Requesting clipboard data with fotmat %d from the server", clipboard->format);
504 
506  rdp_event.clipboard_formatdatarequest.pFormatDataRequest = pFormatDataRequest;
507  remmina_rdp_event_event_push(gp, &rdp_event);
508 
509  /* Busy wait clibpoard data for CLIPBOARD_TRANSFER_WAIT_TIME seconds.
510  * In the meanwhile allow GTK event loop to proceed */
511 
512  tlimit = time(NULL) + CLIPBOARD_TRANSFER_WAIT_TIME;
513  rc = 100000;
514  while (time(NULL) < tlimit && rc != 0 && clipboard->srv_clip_data_wait == SCDW_BUSY_WAIT) {
515  gettimeofday(&tv, NULL);
516  to.tv_sec = tv.tv_sec;
517  to.tv_nsec = tv.tv_usec * 1000 + 40000000; // wait for 40ms
518  if (to.tv_nsec >= 1000000000) {
519  to.tv_nsec -= 1000000000;
520  to.tv_sec++;
521  }
522  rc = pthread_cond_timedwait(&clipboard->transfer_clip_cond, &clipboard->transfer_clip_mutex, &to);
523  if (rc == 0)
524  break;
525  gtk_main_iteration_do(FALSE);
526  }
527 
528  if (rc == 0) {
529  /* Data has arrived without timeout */
530  if (clipboard->srv_data != NULL) {
531  if (info == CB_FORMAT_PNG || info == CF_DIB || info == CF_DIBV5 || info == CB_FORMAT_JPEG) {
532  gtk_selection_data_set_pixbuf(selection_data, clipboard->srv_data);
533  g_object_unref(clipboard->srv_data);
534  } else {
535  gtk_selection_data_set_text(selection_data, clipboard->srv_data, -1);
536  free(clipboard->srv_data);
537  }
538  }
539  clipboard->srv_clip_data_wait = SCDW_NONE;
540  } else {
541  if (clipboard->srv_clip_data_wait == SCDW_ABORTING) {
542  g_warning("[RDP] Clipboard data wait aborted.");
543  } else {
544  if (rc == ETIMEDOUT)
545  g_warning("[RDP] Clipboard data from the server is not available in %d seconds. No data will be available to user.",
546  CLIPBOARD_TRANSFER_WAIT_TIME);
547  else
548  g_warning("[RDP] internal error: pthread_cond_timedwait() returned %d\n", rc);
549  }
550  clipboard->srv_clip_data_wait = SCDW_NONE;
551  }
552  pthread_mutex_unlock(&clipboard->transfer_clip_mutex);
553 }
554 
555 void remmina_rdp_cliprdr_empty_clipboard(GtkClipboard *gtkClipboard, rfClipboard *clipboard)
556 {
557  TRACE_CALL(__func__);
558  /* No need to do anything here */
559 }
560 
562 {
563  TRACE_CALL(__func__);
564 
565  GtkClipboard *gtkClipboard;
566  rfContext *rfi = GET_PLUGIN_DATA(gp);
567  GdkAtom *targets;
568  gboolean result = 0;
569  gint loccount, srvcount;
570  gint formatId, i;
571  CLIPRDR_FORMAT *formats;
572  gchar *name;
573  struct retp_t {
574  CLIPRDR_FORMAT_LIST pFormatList;
575  CLIPRDR_FORMAT formats[];
576  } *retp;
577 
578  formats = NULL;
579 
580  retp = NULL;
581 
582  gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
583  if (gtkClipboard)
584  result = gtk_clipboard_wait_for_targets(gtkClipboard, &targets, &loccount);
585  remmina_plugin_service->debug("Sending to server the following local clipboard content formats");
586  if (result && loccount > 0) {
587  formats = (CLIPRDR_FORMAT *)malloc(loccount * sizeof(CLIPRDR_FORMAT));
588  srvcount = 0;
589  for (i = 0; i < loccount; i++) {
590  formatId = remmina_rdp_cliprdr_get_format_from_gdkatom(targets[i]);
591  if (formatId != 0) {
592  name = gdk_atom_name(targets[i]);
593  remmina_plugin_service->debug(" local clipboard format %s will be sent to remote as %d", name, formatId);
594  g_free(name);
595  formats[srvcount].formatId = formatId;
596  formats[srvcount].formatName = NULL;
597  srvcount++;
598  }
599  }
600  if (srvcount > 0) {
601  retp = (struct retp_t *)malloc(sizeof(struct retp_t) + sizeof(CLIPRDR_FORMAT) * srvcount);
602  retp->pFormatList.formats = retp->formats;
603  retp->pFormatList.numFormats = srvcount;
604  memcpy(retp->formats, formats, sizeof(CLIPRDR_FORMAT) * srvcount);
605  } else {
606  retp = (struct retp_t *)malloc(sizeof(struct retp_t));
607  retp->pFormatList.formats = NULL;
608  retp->pFormatList.numFormats = 0;
609  }
610  free(formats);
611  } else {
612  retp = (struct retp_t *)malloc(sizeof(struct retp_t) + sizeof(CLIPRDR_FORMAT));
613  retp->pFormatList.formats = NULL;
614  retp->pFormatList.numFormats = 0;
615  }
616 
617  if (result)
618  g_free(targets);
619 
620  retp->pFormatList.msgFlags = CB_RESPONSE_OK;
621 
622  return (CLIPRDR_FORMAT_LIST *)retp;
623 }
624 
626 {
627  TRACE_CALL(__func__);
629 }
630 
631 
633 {
634  TRACE_CALL(__func__);
635  GtkClipboard *gtkClipboard;
636  UINT8 *inbuf = NULL;
637  UINT8 *outbuf = NULL;
638  GdkPixbuf *image = NULL;
639  int size = 0;
640  rfContext *rfi = GET_PLUGIN_DATA(gp);
641  RemminaPluginRdpEvent rdp_event = { 0 };
642 
643  gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
644  if (gtkClipboard) {
645  switch (ui->clipboard.format) {
646  case CF_TEXT:
647  case CF_UNICODETEXT:
648  case CB_FORMAT_HTML:
649  {
650  inbuf = (UINT8 *)gtk_clipboard_wait_for_text(gtkClipboard);
651  break;
652  }
653 
654  case CB_FORMAT_PNG:
655  case CB_FORMAT_JPEG:
656  case CF_DIB:
657  case CF_DIBV5:
658  {
659  image = gtk_clipboard_wait_for_image(gtkClipboard);
660  break;
661  }
662  }
663  }
664 
665  /* No data received, send nothing */
666  if (inbuf != NULL || image != NULL) {
667  switch (ui->clipboard.format) {
668  case CF_TEXT:
669  case CB_FORMAT_HTML:
670  {
671  size = strlen((char *)inbuf);
672  outbuf = lf2crlf(inbuf, &size);
673  break;
674  }
675  case CF_UNICODETEXT:
676  {
677  size = strlen((char *)inbuf);
678  inbuf = lf2crlf(inbuf, &size);
679  size = (ConvertToUnicode(CP_UTF8, 0, (CHAR *)inbuf, -1, (WCHAR **)&outbuf, 0)) * sizeof(WCHAR);
680  g_free(inbuf);
681  break;
682  }
683  case CB_FORMAT_PNG:
684  {
685  gchar *data;
686  gsize buffersize;
687  gdk_pixbuf_save_to_buffer(image, &data, &buffersize, "png", NULL, NULL);
688  outbuf = (UINT8 *)malloc(buffersize);
689  memcpy(outbuf, data, buffersize);
690  size = buffersize;
691  g_object_unref(image);
692  break;
693  }
694  case CB_FORMAT_JPEG:
695  {
696  gchar *data;
697  gsize buffersize;
698  gdk_pixbuf_save_to_buffer(image, &data, &buffersize, "jpeg", NULL, NULL);
699  outbuf = (UINT8 *)malloc(buffersize);
700  memcpy(outbuf, data, buffersize);
701  size = buffersize;
702  g_object_unref(image);
703  break;
704  }
705  case CF_DIB:
706  case CF_DIBV5:
707  {
708  gchar *data;
709  gsize buffersize;
710  gdk_pixbuf_save_to_buffer(image, &data, &buffersize, "bmp", NULL, NULL);
711  size = buffersize - 14;
712  outbuf = (UINT8 *)malloc(size);
713  memcpy(outbuf, data + 14, size);
714  g_object_unref(image);
715  break;
716  }
717  }
718  }
719 
721  rdp_event.clipboard_formatdataresponse.data = outbuf;
722  rdp_event.clipboard_formatdataresponse.size = size;
723  remmina_rdp_event_event_push(gp, &rdp_event);
724 }
725 
727 {
728  TRACE_CALL(__func__);
729  GtkClipboard *gtkClipboard;
730  rfContext *rfi = GET_PLUGIN_DATA(gp);
731 
732  gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
733  if (ui->clipboard.format == CB_FORMAT_PNG || ui->clipboard.format == CF_DIB || ui->clipboard.format == CF_DIBV5 || ui->clipboard.format == CB_FORMAT_JPEG) {
734  gtk_clipboard_set_image(gtkClipboard, ui->clipboard.data);
735  g_object_unref(ui->clipboard.data);
736  } else {
737  gtk_clipboard_set_text(gtkClipboard, ui->clipboard.data, -1);
738  free(ui->clipboard.data);
739  }
740 }
741 
743 {
744  TRACE_CALL(__func__);
745  GtkClipboard *gtkClipboard;
746  GtkTargetEntry *targets;
747  gint n_targets;
748  rfContext *rfi = GET_PLUGIN_DATA(gp);
749 
750  gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
751  if (gtkClipboard) {
752  targets = gtk_target_table_new_from_list(ui->clipboard.targetlist, &n_targets);
753  if (targets) {
754  remmina_plugin_service->debug("setting clipboard with owner to owner %p", gp);
755  gtk_clipboard_set_with_owner(gtkClipboard, targets, n_targets,
756  (GtkClipboardGetFunc)remmina_rdp_cliprdr_request_data,
757  (GtkClipboardClearFunc)remmina_rdp_cliprdr_empty_clipboard, G_OBJECT(gp));
758  gtk_target_table_free(targets, n_targets);
759  }
760  }
761 }
762 
764 {
765  /* When closing a rdp connection, we should check if gp is a clipboard owner.
766  * If it’s an owner, detach it from the clipboard */
767  TRACE_CALL(__func__);
768  rfContext *rfi = GET_PLUGIN_DATA(gp);
769  GtkClipboard *gtkClipboard;
770 
771  if (!rfi || !rfi->drawing_area) return;
772 
773  gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
774  if (gtkClipboard && gtk_clipboard_get_owner(gtkClipboard) == (GObject *)gp)
775  gtk_clipboard_clear(gtkClipboard);
776 
777 }
778 
780 {
781  TRACE_CALL(__func__);
782 
783  switch (ui->clipboard.type) {
786  break;
787 
790  break;
791 
794  break;
795 
798  break;
799  }
800 }
801 
803 {
804  TRACE_CALL(__func__);
805  // Future: initialize rfi->clipboard
806 }
808 {
809  TRACE_CALL(__func__);
810 
811  // Future: deinitialize rfi->clipboard
812 }
813 
815 {
816  if (rfi && rfi->clipboard.srv_clip_data_wait == SCDW_BUSY_WAIT) {
817  remmina_plugin_service->debug("requesting clipboard transfer to abort");
818  /* Allow clipboard transfer from server to terminate */
819  rfi->clipboard.srv_clip_data_wait = SCDW_ABORTING;
820  usleep(100000);
821  }
822 }
823 
824 
825 
826 void remmina_rdp_cliprdr_init(rfContext *rfi, CliprdrClientContext *cliprdr)
827 {
828  TRACE_CALL(__func__);
829 
830  rfClipboard *clipboard;
831  clipboard = &(rfi->clipboard);
832 
833  rfi->clipboard.rfi = rfi;
834  cliprdr->custom = (void *)clipboard;
835 
836  clipboard->context = cliprdr;
837  pthread_mutex_init(&clipboard->transfer_clip_mutex, NULL);
838  pthread_cond_init(&clipboard->transfer_clip_cond, NULL);
839  clipboard->srv_clip_data_wait = SCDW_NONE;
840 
841  cliprdr->MonitorReady = remmina_rdp_cliprdr_monitor_ready;
842  cliprdr->ServerCapabilities = remmina_rdp_cliprdr_server_capabilities;
843  cliprdr->ServerFormatList = remmina_rdp_cliprdr_server_format_list;
844  cliprdr->ServerFormatListResponse = remmina_rdp_cliprdr_server_format_list_response;
845  cliprdr->ServerFormatDataRequest = remmina_rdp_cliprdr_server_format_data_request;
846  cliprdr->ServerFormatDataResponse = remmina_rdp_cliprdr_server_format_data_response;
847 
848 // cliprdr->ServerFileContentsRequest = remmina_rdp_cliprdr_server_file_contents_request;
849 // cliprdr->ServerFileContentsResponse = remmina_rdp_cliprdr_server_file_contents_response;
850 }
void remmina_rdp_cliprdr_set_clipboard_content(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
Definition: rdp_cliprdr.c:726
CliprdrClientContext * context
Definition: rdp_plugin.h:72
struct remmina_plugin_rdp_event::@40::@44 clipboard_formatlist
struct remmina_plugin_rdp_event::@40::@46 clipboard_formatdatarequest
gboolean is_reconnecting
Definition: rdp_plugin.h:269
void(* debug)(const gchar *fmt,...)
Definition: plugin.h:214
static UINT remmina_rdp_cliprdr_server_format_list_response(CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST_RESPONSE *formatListResponse)
Definition: rdp_cliprdr.c:315
int remmina_rdp_cliprdr_server_file_contents_response(CliprdrClientContext *context, CLIPRDR_FILE_CONTENTS_RESPONSE *fileContentsResponse)
Definition: rdp_cliprdr.c:149
void remmina_rdp_cliprdr_send_client_format_list(RemminaProtocolWidget *gp)
Definition: rdp_cliprdr.c:155
enum rf_clipboard::@39 srv_clip_data_wait
static UINT8 * lf2crlf(UINT8 *data, int *size)
Definition: rdp_cliprdr.c:91
void remmina_rdp_cliprdr_init(rfContext *rfi, CliprdrClientContext *cliprdr)
Definition: rdp_cliprdr.c:826
pthread_mutex_t transfer_clip_mutex
Definition: rdp_plugin.h:79
void remmina_rdp_cliprdr_detach_owner(RemminaProtocolWidget *gp)
Definition: rdp_cliprdr.c:763
void * remmina_rdp_event_queue_ui_sync_retptr(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
Definition: rdp_event.c:1279
static UINT remmina_rdp_cliprdr_server_format_data_request(CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_REQUEST *formatDataRequest)
Definition: rdp_cliprdr.c:322
static void crlf2lf(UINT8 *data, size_t *size)
Definition: rdp_cliprdr.c:123
void remmina_rdp_clipboard_init(rfContext *rfi)
Definition: rdp_cliprdr.c:802
RemminaProtocolWidget * protocol_widget
Definition: rdp_plugin.h:247
rfContext * rfi
Definition: rdp_plugin.h:71
void remmina_rdp_cliprdr_request_data(GtkClipboard *gtkClipboard, GtkSelectionData *selection_data, guint info, RemminaProtocolWidget *gp)
Definition: rdp_cliprdr.c:468
gpointer srv_data
Definition: rdp_plugin.h:82
void remmina_rdp_event_process_clipboard(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
Definition: rdp_cliprdr.c:779
RemminaPluginRdpEventType type
Definition: rdp_plugin.h:118
struct remmina_plugin_rdp_event::@40::@45 clipboard_formatdataresponse
void remmina_rdp_cliprdr_set_clipboard_data(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
Definition: rdp_cliprdr.c:742
static UINT remmina_rdp_cliprdr_monitor_ready(CliprdrClientContext *context, const CLIPRDR_MONITOR_READY *monitorReady)
Definition: rdp_cliprdr.c:199
pthread_cond_t transfer_clip_cond
Definition: rdp_plugin.h:80
static void remmina_rdp_cliprdr_mt_get_format_list(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
Definition: rdp_cliprdr.c:625
static RemminaPluginService * remmina_plugin_service
GtkWidget * drawing_area
Definition: rdp_plugin.h:279
int remmina_rdp_event_queue_ui_sync_retint(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
Definition: rdp_event.c:1268
void remmina_rdp_clipboard_abort_transfer(rfContext *rfi)
Definition: rdp_cliprdr.c:814
int remmina_rdp_cliprdr_server_file_contents_request(CliprdrClientContext *context, CLIPRDR_FILE_CONTENTS_REQUEST *fileContentsRequest)
Definition: rdp_cliprdr.c:144
static void remmina_rdp_cliprdr_send_client_capabilities(rfClipboard *clipboard)
Definition: rdp_cliprdr.c:180
void remmina_rdp_cliprdr_empty_clipboard(GtkClipboard *gtkClipboard, rfClipboard *clipboard)
Definition: rdp_cliprdr.c:555
CLIPRDR_FORMAT_LIST * remmina_rdp_cliprdr_get_client_format_list(RemminaProtocolWidget *gp)
Definition: rdp_cliprdr.c:561
rfClipboard clipboard
Definition: rdp_plugin.h:308
void remmina_rdp_clipboard_free(rfContext *rfi)
Definition: rdp_cliprdr.c:807
gboolean connected
Definition: rdp_plugin.h:268
void remmina_rdp_cliprdr_get_target_types(UINT32 **formats, UINT16 *size, GdkAtom *types, int count)
Definition: rdp_cliprdr.c:72
static UINT remmina_rdp_cliprdr_server_capabilities(CliprdrClientContext *context, const CLIPRDR_CAPABILITIES *capabilities)
Definition: rdp_cliprdr.c:212
static UINT remmina_rdp_cliprdr_server_format_list(CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST *formatList)
Definition: rdp_cliprdr.c:219
void remmina_rdp_cliprdr_get_clipboard_data(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
Definition: rdp_cliprdr.c:632
UINT32 format
Definition: rdp_plugin.h:76
void remmina_rdp_event_event_push(RemminaProtocolWidget *gp, const RemminaPluginRdpEvent *e)
Definition: rdp_event.c:92
static UINT remmina_rdp_cliprdr_server_format_data_response(CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_RESPONSE *formatDataResponse)
Definition: rdp_cliprdr.c:343
UINT32 remmina_rdp_cliprdr_get_format_from_gdkatom(GdkAtom atom)
Definition: rdp_cliprdr.c:48
RemminaPluginRdpUiType type
Definition: rdp_plugin.h:190