/[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 1209 - (show annotations)
Mon Mar 27 11:10:09 2006 UTC (18 years, 1 month ago) by ossman_
File MIME type: text/plain
File size: 31138 byte(s)
Handle when server doesn't give us any clipboard data.

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

  ViewVC Help
Powered by ViewVC 1.1.26