/[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 1215 - (show annotations)
Tue Mar 28 14:46:47 2006 UTC (18 years, 2 months ago) by ossman_
Original Path: sourceforge.net/trunk/rdesktop/xclip.c
File MIME type: text/plain
File size: 35900 byte(s)
Cut down own the redundant clipboard mode arguments.

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

  ViewVC Help
Powered by ViewVC 1.1.26