/[rdesktop]/jpeg/rdesktop/trunk/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 /jpeg/rdesktop/trunk/xclip.c

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC 1.1.26