/[rdesktop]/sourceforge.net/trunk/rdesktop/xclip.c
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /sourceforge.net/trunk/rdesktop/xclip.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1206 - (show annotations)
Mon Mar 27 08:49:38 2006 UTC (18 years, 3 months ago) by ossman_
File MIME type: text/plain
File size: 30399 byte(s)
Examine timestamps of PRIMARY and CLIPBOARD to determine which is more
recent and should therefore be used.

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

  ViewVC Help
Powered by ViewVC 1.1.26