VirtualBox

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

Last change on this file since 8057 was 7826, checked in by vboxsync, 17 years ago

Export modified rdesktop version including USB support to OSE. It's GPL anyway.

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