VirtualBox

source: vbox/trunk/src/VBox/RDP/client/xclip.c@ 13384

Last change on this file since 13384 was 11982, checked in by vboxsync, 16 years ago

All: license header changes for 2.0 (OSE headers, add Sun GPL/LGPL disclaimer)

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette