1 /* -*- c-basic-offset: 8 -*-
2 rdesktop: A Remote Desktop Protocol client.
3 Protocol services - Clipboard functions
4 Copyright (C) Erik Forsberg <forsberg@cendio.se> 2003
5 Copyright (C) Matthew Chapman 2003
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 along
18 with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 */
21
22 #include <X11/Xlib.h>
23 #include <X11/Xatom.h>
24 #include "rdesktop.h"
25
26 /*
27 To gain better understanding of this code, one could be assisted by the following documents:
28 - Inter-Client Communication Conventions Manual (ICCCM)
29 HTML: http://tronche.com/gui/x/icccm/
30 PDF: http://ftp.xfree86.org/pub/XFree86/4.5.0/doc/PDF/icccm.pdf
31 - MSDN: Clipboard Formats
32 http://msdn.microsoft.com/library/en-us/winui/winui/windowsuserinterface/dataexchange/clipboard/clipboardformats.asp
33 */
34
35 #ifdef HAVE_ICONV
36 #ifdef HAVE_LANGINFO_H
37 #ifdef HAVE_ICONV_H
38 #include <langinfo.h>
39 #include <iconv.h>
40 #define USE_UNICODE_CLIPBOARD
41 #endif
42 #endif
43 #endif
44
45 #ifdef USE_UNICODE_CLIPBOARD
46 #define RDP_CF_TEXT CF_UNICODETEXT
47 #else
48 #define RDP_CF_TEXT CF_TEXT
49 #endif
50
51
52 /* Translate LF to CR-LF. To do this, we must allocate more memory.
53 The returned string is null-terminated, as required by CF_TEXT.
54 Does not stop on embedded nulls.
55 The length is updated. */
56 static void
crlf2lf(uint8 * data,uint32 * length)57 crlf2lf(uint8 * data, uint32 * length)
58 {
59 uint8 *dst, *src;
60 src = dst = data;
61 while (src < data + *length)
62 {
63 if (*src != '\x0d')
64 *dst++ = *src;
65 src++;
66 }
67 *length = dst - data;
68 }
69
70 #ifdef USE_UNICODE_CLIPBOARD
71 /* Translate LF to CR-LF. To do this, we must allocate more memory.
72 The returned string is null-terminated, as required by CF_UNICODETEXT.
73 The size is updated. */
74 static uint8 *
utf16_lf2crlf(uint8 * data,uint32 * size)75 utf16_lf2crlf(uint8 * data, uint32 * size)
76 {
77 uint8 *result;
78 uint16 *inptr, *outptr;
79 Bool swap_endianess;
80
81 /* Worst case: Every char is LF */
82 result = xmalloc((*size * 2) + 2);
83 if (result == NULL)
84 return NULL;
85
86 inptr = (uint16 *) data;
87 outptr = (uint16 *) result;
88
89 /* Check for a reversed BOM */
90 swap_endianess = (*inptr == 0xfffe);
91
92 while ((uint8 *) inptr < data + *size)
93 {
94 uint16 uvalue = *inptr;
95 if (swap_endianess)
96 uvalue = ((uvalue << 8) & 0xff00) + (uvalue >> 8);
97 if (uvalue == 0x0a)
98 *outptr++ = swap_endianess ? 0x0d00 : 0x0d;
99 *outptr++ = *inptr++;
100 }
101 *outptr++ = 0; /* null termination */
102 *size = (uint8 *) outptr - result;
103
104 return result;
105 }
106 #else
107 /* Translate LF to CR-LF. To do this, we must allocate more memory.
108 The length is updated. */
109 static uint8 *
lf2crlf(uint8 * data,uint32 * length)110 lf2crlf(uint8 * data, uint32 * length)
111 {
112 uint8 *result, *p, *o;
113
114 /* Worst case: Every char is LF */
115 result = xmalloc(*length * 2);
116
117 p = data;
118 o = result;
119
120 while (p < data + *length)
121 {
122 if (*p == '\x0a')
123 *o++ = '\x0d';
124 *o++ = *p++;
125 }
126 *length = o - result;
127
128 /* Convenience */
129 *o++ = '\0';
130
131 return result;
132 }
133 #endif
134
135 static void
xclip_provide_selection(RDPCLIENT * This,XSelectionRequestEvent * req,Atom type,unsigned int format,uint8 * data,uint32 length)136 xclip_provide_selection(RDPCLIENT * This, XSelectionRequestEvent * req, Atom type, unsigned int format, uint8 * data,
137 uint32 length)
138 {
139 XEvent xev;
140
141 DEBUG_CLIPBOARD(("xclip_provide_selection: requestor=0x%08x, target=%s, property=%s, length=%u\n", (unsigned) req->requestor, XGetAtomName(This->display, req->target), XGetAtomName(This->display, req->property), (unsigned) length));
142
143 XChangeProperty(This->display, req->requestor, req->property,
144 type, format, PropModeReplace, data, length);
145
146 xev.xselection.type = SelectionNotify;
147 xev.xselection.serial = 0;
148 xev.xselection.send_event = True;
149 xev.xselection.requestor = req->requestor;
150 xev.xselection.selection = req->selection;
151 xev.xselection.target = req->target;
152 xev.xselection.property = req->property;
153 xev.xselection.time = req->time;
154 XSendEvent(This->display, req->requestor, False, NoEventMask, &xev);
155 }
156
157 /* Replies a clipboard requestor, telling that we're unable to satisfy his request for whatever reason.
158 This has the benefit of finalizing the clipboard negotiation and thus not leaving our requestor
159 lingering (and, potentially, stuck). */
160 static void
xclip_refuse_selection(RDPCLIENT * This,XSelectionRequestEvent * req)161 xclip_refuse_selection(RDPCLIENT * This, XSelectionRequestEvent * req)
162 {
163 XEvent xev;
164
165 DEBUG_CLIPBOARD(("xclip_refuse_selection: requestor=0x%08x, target=%s, property=%s\n",
166 (unsigned) req->requestor, XGetAtomName(This->display, req->target),
167 XGetAtomName(This->display, req->property)));
168
169 xev.xselection.type = SelectionNotify;
170 xev.xselection.serial = 0;
171 xev.xselection.send_event = True;
172 xev.xselection.requestor = req->requestor;
173 xev.xselection.selection = req->selection;
174 xev.xselection.target = req->target;
175 xev.xselection.property = None;
176 xev.xselection.time = req->time;
177 XSendEvent(This->display, req->requestor, False, NoEventMask, &xev);
178 }
179
180 /* Wrapper for cliprdr_send_data which also cleans the request state. */
181 static void
helper_cliprdr_send_response(RDPCLIENT * This,uint8 * data,uint32 length)182 helper_cliprdr_send_response(RDPCLIENT * This, uint8 * data, uint32 length)
183 {
184 if (This->xclip.rdp_clipboard_request_format != 0)
185 {
186 cliprdr_send_data(This, data, length);
187 This->xclip.rdp_clipboard_request_format = 0;
188 if (!This->xclip.rdesktop_is_selection_owner)
189 cliprdr_send_simple_native_format_announce(This, RDP_CF_TEXT);
190 }
191 }
192
193 /* Last resort, when we have to provide clipboard data but for whatever
194 reason couldn't get any.
195 */
196 static void
helper_cliprdr_send_empty_response(RDPCLIENT * This)197 helper_cliprdr_send_empty_response(RDPCLIENT * This)
198 {
199 helper_cliprdr_send_response(This, NULL, 0);
200 }
201
202 /* Replies with clipboard data to RDP, converting it from the target format
203 to the expected RDP format as necessary. Returns true if data was sent.
204 */
205 static Bool
xclip_send_data_with_convert(RDPCLIENT * This,uint8 * source,size_t source_size,Atom target)206 xclip_send_data_with_convert(RDPCLIENT * This, uint8 * source, size_t source_size, Atom target)
207 {
208 DEBUG_CLIPBOARD(("xclip_send_data_with_convert: target=%s, size=%u\n",
209 XGetAtomName(This->display, target), (unsigned) source_size));
210
211 #ifdef USE_UNICODE_CLIPBOARD
212 if (target == This->xclip.format_string_atom ||
213 target == This->xclip.format_unicode_atom || target == This->xclip.format_utf8_string_atom)
214 {
215 size_t unicode_buffer_size;
216 char *unicode_buffer;
217 iconv_t cd;
218 size_t unicode_buffer_size_remaining;
219 char *unicode_buffer_remaining;
220 char *data_remaining;
221 size_t data_size_remaining;
222 uint32 translated_data_size;
223 uint8 *translated_data;
224
225 if (This->xclip.rdp_clipboard_request_format != RDP_CF_TEXT)
226 return False;
227
228 /* Make an attempt to convert any string we send to Unicode.
229 We don't know what the RDP server's ANSI Codepage is, or how to convert
230 to it, so using CF_TEXT is not safe (and is unnecessary, since all
231 WinNT versions are Unicode-minded).
232 */
233 if (target == This->xclip.format_string_atom)
234 {
235 char *locale_charset = nl_langinfo(CODESET);
236 cd = iconv_open(WINDOWS_CODEPAGE, locale_charset);
237 if (cd == (iconv_t) - 1)
238 {
239 DEBUG_CLIPBOARD(("Locale charset %s not found in iconv. Unable to convert clipboard text.\n", locale_charset));
240 return False;
241 }
242 unicode_buffer_size = source_size * 4;
243 }
244 else if (target == This->xclip.format_unicode_atom)
245 {
246 cd = iconv_open(WINDOWS_CODEPAGE, "UCS-2");
247 if (cd == (iconv_t) - 1)
248 {
249 return False;
250 }
251 unicode_buffer_size = source_size;
252 }
253 else if (target == This->xclip.format_utf8_string_atom)
254 {
255 cd = iconv_open(WINDOWS_CODEPAGE, "UTF-8");
256 if (cd == (iconv_t) - 1)
257 {
258 return False;
259 }
260 /* UTF-8 is guaranteed to be less or equally compact
261 as UTF-16 for all Unicode chars >=2 bytes.
262 */
263 unicode_buffer_size = source_size * 2;
264 }
265 else
266 {
267 return False;
268 }
269
270 unicode_buffer = xmalloc(unicode_buffer_size);
271 unicode_buffer_size_remaining = unicode_buffer_size;
272 unicode_buffer_remaining = unicode_buffer;
273 data_remaining = (char *) source;
274 data_size_remaining = source_size;
275 iconv(cd, (ICONV_CONST char **) &data_remaining, &data_size_remaining,
276 &unicode_buffer_remaining, &unicode_buffer_size_remaining);
277 iconv_close(cd);
278
279 /* translate linebreaks */
280 translated_data_size = unicode_buffer_size - unicode_buffer_size_remaining;
281 translated_data = utf16_lf2crlf((uint8 *) unicode_buffer, &translated_data_size);
282 if (translated_data != NULL)
283 {
284 DEBUG_CLIPBOARD(("Sending Unicode string of %d bytes\n",
285 translated_data_size));
286 helper_cliprdr_send_response(This, translated_data, translated_data_size);
287 xfree(translated_data); /* Not the same thing as XFree! */
288 }
289
290 xfree(unicode_buffer);
291
292 return True;
293 }
294 #else
295 if (target == This->xclip.format_string_atom)
296 {
297 uint8 *translated_data;
298 uint32 length = source_size;
299
300 if (This->xclip.rdp_clipboard_request_format != RDP_CF_TEXT)
301 return False;
302
303 DEBUG_CLIPBOARD(("Translating linebreaks before sending data\n"));
304 translated_data = lf2crlf(source, &length);
305 if (translated_data != NULL)
306 {
307 helper_cliprdr_send_response(This, translated_data, length);
308 xfree(translated_data); /* Not the same thing as XFree! */
309 }
310
311 return True;
312 }
313 #endif
314 else if (target == This->xclip.rdesktop_native_atom)
315 {
316 helper_cliprdr_send_response(This, source, source_size + 1);
317
318 return True;
319 }
320 else
321 {
322 return False;
323 }
324 }
325
326 static void
xclip_clear_target_props(RDPCLIENT * This)327 xclip_clear_target_props(RDPCLIENT * This)
328 {
329 XDeleteProperty(This->display, This->wnd, This->xclip.rdesktop_clipboard_target_atom);
330 XDeleteProperty(This->display, This->wnd, This->xclip.rdesktop_primary_timestamp_target_atom);
331 XDeleteProperty(This->display, This->wnd, This->xclip.rdesktop_clipboard_timestamp_target_atom);
332 }
333
334 static void
xclip_notify_change(RDPCLIENT * This)335 xclip_notify_change(RDPCLIENT * This)
336 {
337 XChangeProperty(This->display, DefaultRootWindow(This->display),
338 This->xclip.rdesktop_selection_notify_atom, XA_INTEGER, 32, PropModeReplace, NULL, 0);
339 }
340
341 static void
xclip_probe_selections(RDPCLIENT * This)342 xclip_probe_selections(RDPCLIENT * This)
343 {
344 Window primary_owner, clipboard_owner;
345
346 if (This->xclip.probing_selections)
347 {
348 DEBUG_CLIPBOARD(("Already probing selections. Scheduling reprobe.\n"));
349 This->xclip.reprobe_selections = True;
350 return;
351 }
352
353 DEBUG_CLIPBOARD(("Probing selections.\n"));
354
355 This->xclip.probing_selections = True;
356 This->xclip.reprobe_selections = False;
357
358 xclip_clear_target_props(This);
359
360 if (This->xclip.auto_mode)
361 primary_owner = XGetSelectionOwner(This->display, This->xclip.primary_atom);
362 else
363 primary_owner = None;
364
365 clipboard_owner = XGetSelectionOwner(This->display, This->xclip.clipboard_atom);
366
367 /* If we own all relevant selections then don't do anything. */
368 if (((primary_owner == This->wnd) || !This->xclip.auto_mode) && (clipboard_owner == This->wnd))
369 goto end;
370
371 /* Both available */
372 if ((primary_owner != None) && (clipboard_owner != None))
373 {
374 This->xclip.primary_timestamp = 0;
375 This->xclip.clipboard_timestamp = 0;
376 XConvertSelection(This->display, This->xclip.primary_atom, This->xclip.timestamp_atom,
377 This->xclip.rdesktop_primary_timestamp_target_atom, This->wnd, CurrentTime);
378 XConvertSelection(This->display, This->xclip.clipboard_atom, This->xclip.timestamp_atom,
379 This->xclip.rdesktop_clipboard_timestamp_target_atom, This->wnd, CurrentTime);
380 return;
381 }
382
383 /* Just PRIMARY */
384 if (primary_owner != None)
385 {
386 XConvertSelection(This->display, This->xclip.primary_atom, This->xclip.targets_atom,
387 This->xclip.rdesktop_clipboard_target_atom, This->wnd, CurrentTime);
388 return;
389 }
390
391 /* Just CLIPBOARD */
392 if (clipboard_owner != None)
393 {
394 XConvertSelection(This->display, This->xclip.clipboard_atom, This->xclip.targets_atom,
395 This->xclip.rdesktop_clipboard_target_atom, This->wnd, CurrentTime);
396 return;
397 }
398
399 DEBUG_CLIPBOARD(("No owner of any selection.\n"));
400
401 /* FIXME:
402 Without XFIXES, we cannot reliably know the formats offered by an
403 upcoming selection owner, so we just lie about him offering
404 RDP_CF_TEXT. */
405 cliprdr_send_simple_native_format_announce(This, RDP_CF_TEXT);
406
407 end:
408 This->xclip.probing_selections = False;
409 }
410
411 /* This function is called for SelectionNotify events.
412 The SelectionNotify event is sent from the clipboard owner to the requestor
413 after his request was satisfied.
414 If this function is called, we're the requestor side. */
415 #ifndef MAKE_PROTO
416 void
xclip_handle_SelectionNotify(RDPCLIENT * This,XSelectionEvent * event)417 xclip_handle_SelectionNotify(RDPCLIENT * This, XSelectionEvent * event)
418 {
419 unsigned long nitems, bytes_left;
420 XWindowAttributes wa;
421 Atom type;
422 Atom *supported_targets;
423 int res, i, format;
424 uint8 *data = NULL;
425
426 if (event->property == None)
427 goto fail;
428
429 DEBUG_CLIPBOARD(("xclip_handle_SelectionNotify: selection=%s, target=%s, property=%s\n",
430 XGetAtomName(This->display, event->selection),
431 XGetAtomName(This->display, event->target),
432 XGetAtomName(This->display, event->property)));
433
434 if (event->target == This->xclip.timestamp_atom)
435 {
436 if (event->selection == This->xclip.primary_atom)
437 {
438 res = XGetWindowProperty(This->display, This->wnd,
439 This->xclip.rdesktop_primary_timestamp_target_atom, 0,
440 XMaxRequestSize(This->display), False, AnyPropertyType,
441 &type, &format, &nitems, &bytes_left, &data);
442 }
443 else
444 {
445 res = XGetWindowProperty(This->display, This->wnd,
446 This->xclip.rdesktop_clipboard_timestamp_target_atom, 0,
447 XMaxRequestSize(This->display), False, AnyPropertyType,
448 &type, &format, &nitems, &bytes_left, &data);
449 }
450
451
452 if ((res != Success) || (nitems != 1) || (format != 32))
453 {
454 DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));
455 goto fail;
456 }
457
458 if (event->selection == This->xclip.primary_atom)
459 {
460 This->xclip.primary_timestamp = *(Time *) data;
461 if (This->xclip.primary_timestamp == 0)
462 This->xclip.primary_timestamp++;
463 XDeleteProperty(This->display, This->wnd, This->xclip.rdesktop_primary_timestamp_target_atom);
464 DEBUG_CLIPBOARD(("Got PRIMARY timestamp: %u\n",
465 (unsigned) This->xclip.primary_timestamp));
466 }
467 else
468 {
469 This->xclip.clipboard_timestamp = *(Time *) data;
470 if (This->xclip.clipboard_timestamp == 0)
471 This->xclip.clipboard_timestamp++;
472 XDeleteProperty(This->display, This->wnd, This->xclip.rdesktop_clipboard_timestamp_target_atom);
473 DEBUG_CLIPBOARD(("Got CLIPBOARD timestamp: %u\n",
474 (unsigned) This->xclip.clipboard_timestamp));
475 }
476
477 XFree(data);
478
479 if (This->xclip.primary_timestamp && This->xclip.clipboard_timestamp)
480 {
481 if (This->xclip.primary_timestamp > This->xclip.clipboard_timestamp)
482 {
483 DEBUG_CLIPBOARD(("PRIMARY is most recent selection.\n"));
484 XConvertSelection(This->display, This->xclip.primary_atom, This->xclip.targets_atom,
485 This->xclip.rdesktop_clipboard_target_atom, This->wnd,
486 event->time);
487 }
488 else
489 {
490 DEBUG_CLIPBOARD(("CLIPBOARD is most recent selection.\n"));
491 XConvertSelection(This->display, This->xclip.clipboard_atom, This->xclip.targets_atom,
492 This->xclip.rdesktop_clipboard_target_atom, This->wnd,
493 event->time);
494 }
495 }
496
497 return;
498 }
499
500 if (This->xclip.probing_selections && This->xclip.reprobe_selections)
501 {
502 This->xclip.probing_selections = False;
503 xclip_probe_selections(This);
504 return;
505 }
506
507 res = XGetWindowProperty(This->display, This->wnd, This->xclip.rdesktop_clipboard_target_atom,
508 0, XMaxRequestSize(This->display), False, AnyPropertyType,
509 &type, &format, &nitems, &bytes_left, &data);
510
511 xclip_clear_target_props(This);
512
513 if (res != Success)
514 {
515 DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));
516 goto fail;
517 }
518
519 if (type == This->xclip.incr_atom)
520 {
521 DEBUG_CLIPBOARD(("Received INCR.\n"));
522
523 XGetWindowAttributes(This->display, This->wnd, &wa);
524 if ((wa.your_event_mask | PropertyChangeMask) != wa.your_event_mask)
525 {
526 XSelectInput(This->display, This->wnd, (wa.your_event_mask | PropertyChangeMask));
527 }
528 XFree(data);
529 This->xclip.incr_target = event->target;
530 This->xclip.waiting_for_INCR = 1;
531 goto end;
532 }
533
534 /* Negotiate target format */
535 if (event->target == This->xclip.targets_atom)
536 {
537 /* Determine the best of text This->xclip.targets that we have available:
538 Prefer UTF8_STRING > text/unicode (unspecified encoding) > STRING
539 (ignore TEXT and COMPOUND_TEXT because we don't have code to handle them)
540 */
541 int text_target_satisfaction = 0;
542 Atom best_text_target = 0; /* measures how much we're satisfied with what we found */
543 if (type != None)
544 {
545 supported_targets = (Atom *) data;
546 for (i = 0; i < nitems; i++)
547 {
548 DEBUG_CLIPBOARD(("Target %d: %s\n", i,
549 XGetAtomName(This->display, supported_targets[i])));
550 if (supported_targets[i] == This->xclip.format_string_atom)
551 {
552 if (text_target_satisfaction < 1)
553 {
554 DEBUG_CLIPBOARD(("Other party supports STRING, choosing that as best_target\n"));
555 best_text_target = supported_targets[i];
556 text_target_satisfaction = 1;
557 }
558 }
559 #ifdef USE_UNICODE_CLIPBOARD
560 else if (supported_targets[i] == This->xclip.format_unicode_atom)
561 {
562 if (text_target_satisfaction < 2)
563 {
564 DEBUG_CLIPBOARD(("Other party supports text/unicode, choosing that as best_target\n"));
565 best_text_target = supported_targets[i];
566 text_target_satisfaction = 2;
567 }
568 }
569 else if (supported_targets[i] == This->xclip.format_utf8_string_atom)
570 {
571 if (text_target_satisfaction < 3)
572 {
573 DEBUG_CLIPBOARD(("Other party supports UTF8_STRING, choosing that as best_target\n"));
574 best_text_target = supported_targets[i];
575 text_target_satisfaction = 3;
576 }
577 }
578 #endif
579 else if (supported_targets[i] == This->xclip.rdesktop_clipboard_formats_atom)
580 {
581 if (This->xclip.probing_selections && (text_target_satisfaction < 4))
582 {
583 DEBUG_CLIPBOARD(("Other party supports native formats, choosing that as best_target\n"));
584 best_text_target = supported_targets[i];
585 text_target_satisfaction = 4;
586 }
587 }
588 }
589 }
590
591 /* Kickstarting the next step in the process of satisfying RDP's
592 clipboard request -- specifically, requesting the actual clipboard data.
593 */
594 if ((best_text_target != 0)
595 && (!This->xclip.probing_selections
596 || (best_text_target == This->xclip.rdesktop_clipboard_formats_atom)))
597 {
598 XConvertSelection(This->display, event->selection, best_text_target,
599 This->xclip.rdesktop_clipboard_target_atom, This->wnd, event->time);
600 goto end;
601 }
602 else
603 {
604 DEBUG_CLIPBOARD(("Unable to find a textual target to satisfy RDP clipboard text request\n"));
605 goto fail;
606 }
607 }
608 else
609 {
610 if (This->xclip.probing_selections)
611 {
612 Window primary_owner, clipboard_owner;
613
614 /* FIXME:
615 Without XFIXES, we must make sure that the other
616 rdesktop owns all relevant selections or we might try
617 to get a native format from non-rdesktop window later
618 on. */
619
620 clipboard_owner = XGetSelectionOwner(This->display, This->xclip.clipboard_atom);
621
622 if (This->xclip.auto_mode)
623 primary_owner = XGetSelectionOwner(This->display, This->xclip.primary_atom);
624 else
625 primary_owner = clipboard_owner;
626
627 if (primary_owner != clipboard_owner)
628 goto fail;
629
630 DEBUG_CLIPBOARD(("Got fellow rdesktop formats\n"));
631 This->xclip.probing_selections = False;
632 This->xclip.rdesktop_is_selection_owner = True;
633 cliprdr_send_native_format_announce(This, data, nitems);
634 }
635 else if (!xclip_send_data_with_convert(This, data, nitems, event->target))
636 {
637 goto fail;
638 }
639 }
640
641 end:
642 if (data)
643 XFree(data);
644
645 return;
646
647 fail:
648 xclip_clear_target_props(This);
649 if (This->xclip.probing_selections)
650 {
651 DEBUG_CLIPBOARD(("Unable to find suitable target. Using default text format.\n"));
652 This->xclip.probing_selections = False;
653 This->xclip.rdesktop_is_selection_owner = False;
654
655 /* FIXME:
656 Without XFIXES, we cannot reliably know the formats offered by an
657 upcoming selection owner, so we just lie about him offering
658 RDP_CF_TEXT. */
659 cliprdr_send_simple_native_format_announce(This, RDP_CF_TEXT);
660 }
661 else
662 {
663 helper_cliprdr_send_empty_response(This);
664 }
665 goto end;
666 }
667
668 /* This function is called for SelectionRequest events.
669 The SelectionRequest event is sent from the requestor to the clipboard owner
670 to request clipboard data.
671 */
672 void
xclip_handle_SelectionRequest(RDPCLIENT * This,XSelectionRequestEvent * event)673 xclip_handle_SelectionRequest(RDPCLIENT * This, XSelectionRequestEvent * event)
674 {
675 unsigned long nitems, bytes_left;
676 unsigned char *prop_return;
677 int format, res;
678 Atom type;
679
680 DEBUG_CLIPBOARD(("xclip_handle_SelectionRequest: selection=%s, target=%s, property=%s\n",
681 XGetAtomName(This->display, event->selection),
682 XGetAtomName(This->display, event->target),
683 XGetAtomName(This->display, event->property)));
684
685 if (event->target == This->xclip.targets_atom)
686 {
687 xclip_provide_selection(This, event, XA_ATOM, 32, (uint8 *) & This->xclip.targets, This->xclip.num_targets);
688 return;
689 }
690 else if (event->target == This->xclip.timestamp_atom)
691 {
692 xclip_provide_selection(This, event, XA_INTEGER, 32, (uint8 *) & This->xclip.acquire_time, 1);
693 return;
694 }
695 else if (event->target == This->xclip.rdesktop_clipboard_formats_atom)
696 {
697 xclip_provide_selection(This, event, XA_STRING, 8, This->xclip.formats_data, This->xclip.formats_data_length);
698 }
699 else
700 {
701 /* All the following This->xclip.targets require an async operation with the RDP server
702 and currently we don't do X clipboard request queueing so we can only
703 handle one such request at a time. */
704 if (This->xclip.has_selection_request)
705 {
706 DEBUG_CLIPBOARD(("Error: Another clipboard request was already sent to the RDP server and not yet responded. Refusing this request.\n"));
707 xclip_refuse_selection(This, event);
708 return;
709 }
710 if (event->target == This->xclip.rdesktop_native_atom)
711 {
712 /* Before the requestor makes a request for the _RDESKTOP_NATIVE target,
713 he should declare requestor[property] = CF_SOMETHING. */
714 res = XGetWindowProperty(This->display, event->requestor,
715 event->property, 0, 1, True,
716 XA_INTEGER, &type, &format, &nitems, &bytes_left,
717 &prop_return);
718 if (res != Success)
719 {
720 DEBUG_CLIPBOARD(("Requested native format but didn't specifiy which.\n"));
721 xclip_refuse_selection(This, event);
722 return;
723 }
724
725 format = *(uint32 *) prop_return;
726 XFree(prop_return);
727 }
728 else if (event->target == This->xclip.format_string_atom || event->target == XA_STRING)
729 {
730 /* STRING and XA_STRING are defined to be ISO8859-1 */
731 format = CF_TEXT;
732 }
733 else if (event->target == This->xclip.format_utf8_string_atom)
734 {
735 #ifdef USE_UNICODE_CLIPBOARD
736 format = CF_UNICODETEXT;
737 #else
738 DEBUG_CLIPBOARD(("Requested target unavailable due to lack of Unicode support. (It was not in TARGETS, so why did you ask for it?!)\n"));
739 xclip_refuse_selection(This, event);
740 return;
741 #endif
742 }
743 else if (event->target == This->xclip.format_unicode_atom)
744 {
745 /* Assuming text/unicode to be UTF-16 */
746 format = CF_UNICODETEXT;
747 }
748 else
749 {
750 DEBUG_CLIPBOARD(("Requested target unavailable. (It was not in TARGETS, so why did you ask for it?!)\n"));
751 xclip_refuse_selection(This, event);
752 return;
753 }
754
755 cliprdr_send_data_request(This, format);
756 This->xclip.selection_request = *event;
757 This->xclip.has_selection_request = True;
758 return; /* wait for data */
759 }
760 }
761
762 /* While this rdesktop holds ownership over the clipboard, it means the clipboard data
763 is offered by the RDP server (and when it is pasted inside RDP, there's no network
764 roundtrip).
765
766 This event (SelectionClear) symbolizes this rdesktop lost onwership of the clipboard
767 to some other X client. We should find out what clipboard formats this other
768 client offers and announce that to RDP. */
769 void
xclip_handle_SelectionClear(RDPCLIENT * This)770 xclip_handle_SelectionClear(RDPCLIENT * This)
771 {
772 DEBUG_CLIPBOARD(("xclip_handle_SelectionClear\n"));
773 xclip_notify_change(This);
774 xclip_probe_selections(This);
775 }
776
777 /* Called when any property changes in our window or the root window. */
778 void
xclip_handle_PropertyNotify(RDPCLIENT * This,XPropertyEvent * event)779 xclip_handle_PropertyNotify(RDPCLIENT * This, XPropertyEvent * event)
780 {
781 unsigned long nitems;
782 unsigned long offset = 0;
783 unsigned long bytes_left = 1;
784 int format;
785 XWindowAttributes wa;
786 uint8 *data;
787 Atom type;
788
789 if (event->state == PropertyNewValue && This->xclip.waiting_for_INCR)
790 {
791 DEBUG_CLIPBOARD(("x_clip_handle_PropertyNotify: This->xclip.waiting_for_INCR != 0\n"));
792
793 while (bytes_left > 0)
794 {
795 /* Unlike the specification, we don't set the 'delete' arugment to True
796 since we slurp the INCR's chunks in even-smaller chunks of 4096 bytes. */
797 if ((XGetWindowProperty
798 (This->display, This->wnd, This->xclip.rdesktop_clipboard_target_atom, offset, 4096L,
799 False, AnyPropertyType, &type, &format, &nitems, &bytes_left,
800 &data) != Success))
801 {
802 XFree(data);
803 return;
804 }
805
806 if (nitems == 0)
807 {
808 /* INCR transfer finished */
809 XGetWindowAttributes(This->display, This->wnd, &wa);
810 XSelectInput(This->display, This->wnd,
811 (wa.your_event_mask ^ PropertyChangeMask));
812 XFree(data);
813 This->xclip.waiting_for_INCR = 0;
814
815 if (This->xclip.clip_buflen > 0)
816 {
817 if (!xclip_send_data_with_convert
818 (This, This->xclip.clip_buffer, This->xclip.clip_buflen, This->xclip.incr_target))
819 {
820 helper_cliprdr_send_empty_response(This);
821 }
822 xfree(This->xclip.clip_buffer);
823 This->xclip.clip_buffer = NULL;
824 This->xclip.clip_buflen = 0;
825 }
826 }
827 else
828 {
829 /* Another chunk in the INCR transfer */
830 offset += (nitems / 4); /* offset at which to begin the next slurp */
831 This->xclip.clip_buffer = xrealloc(This->xclip.clip_buffer, This->xclip.clip_buflen + nitems);
832 memcpy(This->xclip.clip_buffer + This->xclip.clip_buflen, data, nitems);
833 This->xclip.clip_buflen += nitems;
834
835 XFree(data);
836 }
837 }
838 XDeleteProperty(This->display, This->wnd, This->xclip.rdesktop_clipboard_target_atom);
839 return;
840 }
841
842 if ((event->atom == This->xclip.rdesktop_selection_notify_atom) &&
843 (event->window == DefaultRootWindow(This->display)))
844 xclip_probe_selections(This);
845 }
846 #endif
847
848
849 /* Called when the RDP server announces new clipboard data formats.
850 In response, we:
851 - take ownership over the clipboard
852 - declare those formats in their Windows native form
853 to other rdesktop instances on this X server */
854 void
ui_clip_format_announce(RDPCLIENT * This,uint8 * data,uint32 length)855 ui_clip_format_announce(RDPCLIENT * This, uint8 * data, uint32 length)
856 {
857 This->xclip.acquire_time = This->last_gesturetime;
858
859 XSetSelectionOwner(This->display, This->xclip.primary_atom, This->wnd, This->xclip.acquire_time);
860 if (XGetSelectionOwner(This->display, This->xclip.primary_atom) != This->wnd)
861 warning("Failed to aquire ownership of PRIMARY clipboard\n");
862
863 XSetSelectionOwner(This->display, This->xclip.clipboard_atom, This->wnd, This->xclip.acquire_time);
864 if (XGetSelectionOwner(This->display, This->xclip.clipboard_atom) != This->wnd)
865 warning("Failed to aquire ownership of CLIPBOARD clipboard\n");
866
867 if (This->xclip.formats_data)
868 xfree(This->xclip.formats_data);
869 This->xclip.formats_data = xmalloc(length);
870 memcpy(This->xclip.formats_data, data, length);
871 This->xclip.formats_data_length = length;
872
873 xclip_notify_change(This);
874 }
875
876 /* Called when the RDP server responds with clipboard data (after we've requested it). */
877 void
ui_clip_handle_data(RDPCLIENT * This,uint8 * data,uint32 length)878 ui_clip_handle_data(RDPCLIENT * This, uint8 * data, uint32 length)
879 {
880 BOOL free_data = False;
881
882 if (length == 0)
883 {
884 xclip_refuse_selection(This, &This->xclip.selection_request);
885 This->xclip.has_selection_request = False;
886 return;
887 }
888
889 if (This->xclip.selection_request.target == This->xclip.format_string_atom || This->xclip.selection_request.target == XA_STRING)
890 {
891 /* We're expecting a CF_TEXT response */
892 uint8 *firstnull;
893
894 /* translate linebreaks */
895 crlf2lf(data, &length);
896
897 /* Only send data up to null byte, if any */
898 firstnull = (uint8 *) strchr((char *) data, '\0');
899 if (firstnull)
900 {
901 length = firstnull - data + 1;
902 }
903 }
904 #ifdef USE_UNICODE_CLIPBOARD
905 else if (This->xclip.selection_request.target == This->xclip.format_utf8_string_atom)
906 {
907 /* We're expecting a CF_UNICODETEXT response */
908 iconv_t cd = iconv_open("UTF-8", WINDOWS_CODEPAGE);
909 if (cd != (iconv_t) - 1)
910 {
911 size_t utf8_length = length * 2;
912 char *utf8_data = malloc(utf8_length);
913 size_t utf8_length_remaining = utf8_length;
914 char *utf8_data_remaining = utf8_data;
915 char *data_remaining = (char *) data;
916 size_t length_remaining = (size_t) length;
917 if (utf8_data == NULL)
918 {
919 iconv_close(cd);
920 return;
921 }
922 iconv(cd, (ICONV_CONST char **) &data_remaining, &length_remaining,
923 &utf8_data_remaining, &utf8_length_remaining);
924 iconv_close(cd);
925 free_data = True;
926 data = (uint8 *) utf8_data;
927 length = utf8_length - utf8_length_remaining;
928 }
929 }
930 else if (This->xclip.selection_request.target == This->xclip.format_unicode_atom)
931 {
932 /* We're expecting a CF_UNICODETEXT response, so what we're
933 receiving matches our requirements and there's no need
934 for further conversions. */
935 }
936 #endif
937 else if (This->xclip.selection_request.target == This->xclip.rdesktop_native_atom)
938 {
939 /* Pass as-is */
940 }
941 else
942 {
943 DEBUG_CLIPBOARD(("ui_clip_handle_data: BUG! I don't know how to convert selection target %s!\n", XGetAtomName(This->display, This->xclip.selection_request.target)));
944 xclip_refuse_selection(This, &This->xclip.selection_request);
945 This->xclip.has_selection_request = False;
946 return;
947 }
948
949 xclip_provide_selection(This, &This->xclip.selection_request, This->xclip.selection_request.target, 8, data, length - 1);
950 This->xclip.has_selection_request = False;
951
952 if (free_data)
953 free(data);
954 }
955
956 void
ui_clip_request_failed(RDPCLIENT * This)957 ui_clip_request_failed(RDPCLIENT * This)
958 {
959 xclip_refuse_selection(This, &This->xclip.selection_request);
960 This->xclip.has_selection_request = False;
961 }
962
963 void
ui_clip_request_data(RDPCLIENT * This,uint32 format)964 ui_clip_request_data(RDPCLIENT * This, uint32 format)
965 {
966 Window primary_owner, clipboard_owner;
967
968 DEBUG_CLIPBOARD(("Request from server for format %d\n", format));
969 This->xclip.rdp_clipboard_request_format = format;
970
971 if (This->xclip.probing_selections)
972 {
973 DEBUG_CLIPBOARD(("ui_clip_request_data: Selection probe in progress. Cannot handle request.\n"));
974 helper_cliprdr_send_empty_response(This);
975 return;
976 }
977
978 xclip_clear_target_props(This);
979
980 if (This->xclip.rdesktop_is_selection_owner)
981 {
982 XChangeProperty(This->display, This->wnd, This->xclip.rdesktop_clipboard_target_atom,
983 XA_INTEGER, 32, PropModeReplace, (unsigned char *) &format, 1);
984
985 XConvertSelection(This->display, This->xclip.primary_atom, This->xclip.rdesktop_native_atom,
986 This->xclip.rdesktop_clipboard_target_atom, This->wnd, CurrentTime);
987 return;
988 }
989
990 if (This->xclip.auto_mode)
991 primary_owner = XGetSelectionOwner(This->display, This->xclip.primary_atom);
992 else
993 primary_owner = None;
994
995 clipboard_owner = XGetSelectionOwner(This->display, This->xclip.clipboard_atom);
996
997 /* Both available */
998 if ((primary_owner != None) && (clipboard_owner != None))
999 {
1000 This->xclip.primary_timestamp = 0;
1001 This->xclip.clipboard_timestamp = 0;
1002 XConvertSelection(This->display, This->xclip.primary_atom, This->xclip.timestamp_atom,
1003 This->xclip.rdesktop_primary_timestamp_target_atom, This->wnd, CurrentTime);
1004 XConvertSelection(This->display, This->xclip.clipboard_atom, This->xclip.timestamp_atom,
1005 This->xclip.rdesktop_clipboard_timestamp_target_atom, This->wnd, CurrentTime);
1006 return;
1007 }
1008
1009 /* Just PRIMARY */
1010 if (primary_owner != None)
1011 {
1012 XConvertSelection(This->display, This->xclip.primary_atom, This->xclip.targets_atom,
1013 This->xclip.rdesktop_clipboard_target_atom, This->wnd, CurrentTime);
1014 return;
1015 }
1016
1017 /* Just CLIPBOARD */
1018 if (clipboard_owner != None)
1019 {
1020 XConvertSelection(This->display, This->xclip.clipboard_atom, This->xclip.targets_atom,
1021 This->xclip.rdesktop_clipboard_target_atom, This->wnd, CurrentTime);
1022 return;
1023 }
1024
1025 /* No data available */
1026 helper_cliprdr_send_empty_response(This);
1027 }
1028
1029 void
ui_clip_sync(RDPCLIENT * This)1030 ui_clip_sync(RDPCLIENT * This)
1031 {
1032 xclip_probe_selections(This);
1033 }
1034
1035 void
ui_clip_set_mode(RDPCLIENT * This,const char * optarg)1036 ui_clip_set_mode(RDPCLIENT * This, const char *optarg)
1037 {
1038 This->rdpclip = True;
1039
1040 if (str_startswith(optarg, "PRIMARYCLIPBOARD"))
1041 This->xclip.auto_mode = True;
1042 else if (str_startswith(optarg, "CLIPBOARD"))
1043 This->xclip.auto_mode = False;
1044 else
1045 {
1046 warning("Invalid clipboard mode '%s'.\n", optarg);
1047 This->rdpclip = False;
1048 }
1049 }
1050
1051 void
xclip_init(RDPCLIENT * This)1052 xclip_init(RDPCLIENT * This)
1053 {
1054 if (!This->rdpclip)
1055 return;
1056
1057 if (!cliprdr_init(This))
1058 return;
1059
1060 This->xclip.primary_atom = XInternAtom(This->display, "PRIMARY", False);
1061 This->xclip.clipboard_atom = XInternAtom(This->display, "CLIPBOARD", False);
1062 This->xclip.targets_atom = XInternAtom(This->display, "TARGETS", False);
1063 This->xclip.timestamp_atom = XInternAtom(This->display, "TIMESTAMP", False);
1064 This->xclip.rdesktop_clipboard_target_atom =
1065 XInternAtom(This->display, "_RDESKTOP_CLIPBOARD_TARGET", False);
1066 This->xclip.rdesktop_primary_timestamp_target_atom =
1067 XInternAtom(This->display, "_RDESKTOP_PRIMARY_TIMESTAMP_TARGET", False);
1068 This->xclip.rdesktop_clipboard_timestamp_target_atom =
1069 XInternAtom(This->display, "_RDESKTOP_CLIPBOARD_TIMESTAMP_TARGET", False);
1070 This->xclip.incr_atom = XInternAtom(This->display, "INCR", False);
1071 This->xclip.format_string_atom = XInternAtom(This->display, "STRING", False);
1072 This->xclip.format_utf8_string_atom = XInternAtom(This->display, "UTF8_STRING", False);
1073 This->xclip.format_unicode_atom = XInternAtom(This->display, "text/unicode", False);
1074
1075 /* rdesktop sets _RDESKTOP_SELECTION_NOTIFY on the root window when acquiring the clipboard.
1076 Other interested rdesktops can use this to notify their server of the available formats. */
1077 This->xclip.rdesktop_selection_notify_atom =
1078 XInternAtom(This->display, "_RDESKTOP_SELECTION_NOTIFY", False);
1079 XSelectInput(This->display, DefaultRootWindow(This->display), PropertyChangeMask);
1080 This->xclip.probing_selections = False;
1081
1082 This->xclip.rdesktop_native_atom = XInternAtom(This->display, "_RDESKTOP_NATIVE", False);
1083 This->xclip.rdesktop_clipboard_formats_atom =
1084 XInternAtom(This->display, "_RDESKTOP_CLIPBOARD_FORMATS", False);
1085 This->xclip.rdesktop_primary_owner_atom = XInternAtom(This->display, "_RDESKTOP_PRIMARY_OWNER", False);
1086 This->xclip.rdesktop_clipboard_owner_atom = XInternAtom(This->display, "_RDESKTOP_CLIPBOARD_OWNER", False);
1087
1088 This->xclip.num_targets = 0;
1089 This->xclip.targets[This->xclip.num_targets++] = This->xclip.targets_atom;
1090 This->xclip.targets[This->xclip.num_targets++] = This->xclip.timestamp_atom;
1091 This->xclip.targets[This->xclip.num_targets++] = This->xclip.rdesktop_native_atom;
1092 This->xclip.targets[This->xclip.num_targets++] = This->xclip.rdesktop_clipboard_formats_atom;
1093 #ifdef USE_UNICODE_CLIPBOARD
1094 This->xclip.targets[This->xclip.num_targets++] = This->xclip.format_utf8_string_atom;
1095 #endif
1096 This->xclip.targets[This->xclip.num_targets++] = This->xclip.format_unicode_atom;
1097 This->xclip.targets[This->xclip.num_targets++] = This->xclip.format_string_atom;
1098 This->xclip.targets[This->xclip.num_targets++] = XA_STRING;
1099 }
1100
1101 void
xclip_deinit(RDPCLIENT * This)1102 xclip_deinit(RDPCLIENT * This)
1103 {
1104 if (XGetSelectionOwner(This->display, This->xclip.primary_atom) == This->wnd)
1105 XSetSelectionOwner(This->display, This->xclip.primary_atom, None, This->xclip.acquire_time);
1106 if (XGetSelectionOwner(This->display, This->xclip.clipboard_atom) == This->wnd)
1107 XSetSelectionOwner(This->display, This->xclip.clipboard_atom, None, This->xclip.acquire_time);
1108 xclip_notify_change(This);
1109 }
1110