/[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 1211 - (show annotations)
Mon Mar 27 12:29:29 2006 UTC (18 years, 2 months ago) by ossman_
File MIME type: text/plain
File size: 31342 byte(s)
Handle when server sends a failure back for a clipboard request.

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

  ViewVC Help
Powered by ViewVC 1.1.26