/[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 1212 - (show annotations)
Mon Mar 27 13:03:27 2006 UTC (18 years, 2 months ago) by ossman_
File MIME type: text/plain
File size: 31527 byte(s)
Examine the magic root window property on startup so that we correctly handle
when another rdesktop owns the clipboard when we start.

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

  ViewVC Help
Powered by ViewVC 1.1.26