VirtualBox

source: vbox/trunk/src/VBox/RDP/client-1.8.4/xclip.c@ 77807

Last change on this file since 77807 was 55123, checked in by vboxsync, 9 years ago

rdesktop 1.8.3 modified for VBox

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