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
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.
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.
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.
23 #include <X11/Xatom.h>
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
36 #ifdef HAVE_LANGINFO_H
40 #define USE_UNICODE_CLIPBOARD
45 #ifdef USE_UNICODE_CLIPBOARD
46 #define RDP_CF_TEXT CF_UNICODETEXT
48 #define RDP_CF_TEXT CF_TEXT
52 /* Translate LF to CR-LF. To do this, we must allocate more memory.
53 The returned string is null-terminated, as required by CF_TEXT.
54 Does not stop on embedded nulls.
55 The length is updated. */
57 crlf2lf(uint8
* data
, uint32
* length
)
61 while (src
< data
+ *length
)
70 #ifdef USE_UNICODE_CLIPBOARD
71 /* Translate LF to CR-LF. To do this, we must allocate more memory.
72 The returned string is null-terminated, as required by CF_UNICODETEXT.
73 The size is updated. */
75 utf16_lf2crlf(uint8
* data
, uint32
* size
)
78 uint16
*inptr
, *outptr
;
81 /* Worst case: Every char is LF */
82 result
= xmalloc((*size
* 2) + 2);
86 inptr
= (uint16
*) data
;
87 outptr
= (uint16
*) result
;
89 /* Check for a reversed BOM */
90 swap_endianess
= (*inptr
== 0xfffe);
92 while ((uint8
*) inptr
< data
+ *size
)
94 uint16 uvalue
= *inptr
;
96 uvalue
= ((uvalue
<< 8) & 0xff00) + (uvalue
>> 8);
98 *outptr
++ = swap_endianess
? 0x0d00 : 0x0d;
101 *outptr
++ = 0; /* null termination */
102 *size
= (uint8
*) outptr
- result
;
107 /* Translate LF to CR-LF. To do this, we must allocate more memory.
108 The length is updated. */
110 lf2crlf(uint8
* data
, uint32
* length
)
112 uint8
*result
, *p
, *o
;
114 /* Worst case: Every char is LF */
115 result
= xmalloc(*length
* 2);
120 while (p
< data
+ *length
)
126 *length
= o
- result
;
136 xclip_provide_selection(RDPCLIENT
* This
, XSelectionRequestEvent
* req
, Atom type
, unsigned int format
, uint8
* data
,
141 DEBUG_CLIPBOARD(("xclip_provide_selection: requestor=0x%08x, target=%s, property=%s, length=%u\n", (unsigned) req
->requestor
, XGetAtomName(This
->display
, req
->target
), XGetAtomName(This
->display
, req
->property
), (unsigned) length
));
143 XChangeProperty(This
->display
, req
->requestor
, req
->property
,
144 type
, format
, PropModeReplace
, data
, length
);
146 xev
.xselection
.type
= SelectionNotify
;
147 xev
.xselection
.serial
= 0;
148 xev
.xselection
.send_event
= True
;
149 xev
.xselection
.requestor
= req
->requestor
;
150 xev
.xselection
.selection
= req
->selection
;
151 xev
.xselection
.target
= req
->target
;
152 xev
.xselection
.property
= req
->property
;
153 xev
.xselection
.time
= req
->time
;
154 XSendEvent(This
->display
, req
->requestor
, False
, NoEventMask
, &xev
);
157 /* Replies a clipboard requestor, telling that we're unable to satisfy his request for whatever reason.
158 This has the benefit of finalizing the clipboard negotiation and thus not leaving our requestor
159 lingering (and, potentially, stuck). */
161 xclip_refuse_selection(RDPCLIENT
* This
, XSelectionRequestEvent
* req
)
165 DEBUG_CLIPBOARD(("xclip_refuse_selection: requestor=0x%08x, target=%s, property=%s\n",
166 (unsigned) req
->requestor
, XGetAtomName(This
->display
, req
->target
),
167 XGetAtomName(This
->display
, req
->property
)));
169 xev
.xselection
.type
= SelectionNotify
;
170 xev
.xselection
.serial
= 0;
171 xev
.xselection
.send_event
= True
;
172 xev
.xselection
.requestor
= req
->requestor
;
173 xev
.xselection
.selection
= req
->selection
;
174 xev
.xselection
.target
= req
->target
;
175 xev
.xselection
.property
= None
;
176 xev
.xselection
.time
= req
->time
;
177 XSendEvent(This
->display
, req
->requestor
, False
, NoEventMask
, &xev
);
180 /* Wrapper for cliprdr_send_data which also cleans the request state. */
182 helper_cliprdr_send_response(RDPCLIENT
* This
, uint8
* data
, uint32 length
)
184 if (This
->xclip
.rdp_clipboard_request_format
!= 0)
186 cliprdr_send_data(This
, data
, length
);
187 This
->xclip
.rdp_clipboard_request_format
= 0;
188 if (!This
->xclip
.rdesktop_is_selection_owner
)
189 cliprdr_send_simple_native_format_announce(This
, RDP_CF_TEXT
);
193 /* Last resort, when we have to provide clipboard data but for whatever
194 reason couldn't get any.
197 helper_cliprdr_send_empty_response(RDPCLIENT
* This
)
199 helper_cliprdr_send_response(This
, NULL
, 0);
202 /* Replies with clipboard data to RDP, converting it from the target format
203 to the expected RDP format as necessary. Returns true if data was sent.
206 xclip_send_data_with_convert(RDPCLIENT
* This
, uint8
* source
, size_t source_size
, Atom target
)
208 DEBUG_CLIPBOARD(("xclip_send_data_with_convert: target=%s, size=%u\n",
209 XGetAtomName(This
->display
, target
), (unsigned) source_size
));
211 #ifdef USE_UNICODE_CLIPBOARD
212 if (target
== This
->xclip
.format_string_atom
||
213 target
== This
->xclip
.format_unicode_atom
|| target
== This
->xclip
.format_utf8_string_atom
)
215 size_t unicode_buffer_size
;
216 char *unicode_buffer
;
218 size_t unicode_buffer_size_remaining
;
219 char *unicode_buffer_remaining
;
220 char *data_remaining
;
221 size_t data_size_remaining
;
222 uint32 translated_data_size
;
223 uint8
*translated_data
;
225 if (This
->xclip
.rdp_clipboard_request_format
!= RDP_CF_TEXT
)
228 /* Make an attempt to convert any string we send to Unicode.
229 We don't know what the RDP server's ANSI Codepage is, or how to convert
230 to it, so using CF_TEXT is not safe (and is unnecessary, since all
231 WinNT versions are Unicode-minded).
233 if (target
== This
->xclip
.format_string_atom
)
235 char *locale_charset
= nl_langinfo(CODESET
);
236 cd
= iconv_open(WINDOWS_CODEPAGE
, locale_charset
);
237 if (cd
== (iconv_t
) - 1)
239 DEBUG_CLIPBOARD(("Locale charset %s not found in iconv. Unable to convert clipboard text.\n", locale_charset
));
242 unicode_buffer_size
= source_size
* 4;
244 else if (target
== This
->xclip
.format_unicode_atom
)
246 cd
= iconv_open(WINDOWS_CODEPAGE
, "UCS-2");
247 if (cd
== (iconv_t
) - 1)
251 unicode_buffer_size
= source_size
;
253 else if (target
== This
->xclip
.format_utf8_string_atom
)
255 cd
= iconv_open(WINDOWS_CODEPAGE
, "UTF-8");
256 if (cd
== (iconv_t
) - 1)
260 /* UTF-8 is guaranteed to be less or equally compact
261 as UTF-16 for all Unicode chars >=2 bytes.
263 unicode_buffer_size
= source_size
* 2;
270 unicode_buffer
= xmalloc(unicode_buffer_size
);
271 unicode_buffer_size_remaining
= unicode_buffer_size
;
272 unicode_buffer_remaining
= unicode_buffer
;
273 data_remaining
= (char *) source
;
274 data_size_remaining
= source_size
;
275 iconv(cd
, (ICONV_CONST
char **) &data_remaining
, &data_size_remaining
,
276 &unicode_buffer_remaining
, &unicode_buffer_size_remaining
);
279 /* translate linebreaks */
280 translated_data_size
= unicode_buffer_size
- unicode_buffer_size_remaining
;
281 translated_data
= utf16_lf2crlf((uint8
*) unicode_buffer
, &translated_data_size
);
282 if (translated_data
!= NULL
)
284 DEBUG_CLIPBOARD(("Sending Unicode string of %d bytes\n",
285 translated_data_size
));
286 helper_cliprdr_send_response(This
, translated_data
, translated_data_size
);
287 xfree(translated_data
); /* Not the same thing as XFree! */
290 xfree(unicode_buffer
);
295 if (target
== This
->xclip
.format_string_atom
)
297 uint8
*translated_data
;
298 uint32 length
= source_size
;
300 if (This
->xclip
.rdp_clipboard_request_format
!= RDP_CF_TEXT
)
303 DEBUG_CLIPBOARD(("Translating linebreaks before sending data\n"));
304 translated_data
= lf2crlf(source
, &length
);
305 if (translated_data
!= NULL
)
307 helper_cliprdr_send_response(This
, translated_data
, length
);
308 xfree(translated_data
); /* Not the same thing as XFree! */
314 else if (target
== This
->xclip
.rdesktop_native_atom
)
316 helper_cliprdr_send_response(This
, source
, source_size
+ 1);
327 xclip_clear_target_props(RDPCLIENT
* This
)
329 XDeleteProperty(This
->display
, This
->wnd
, This
->xclip
.rdesktop_clipboard_target_atom
);
330 XDeleteProperty(This
->display
, This
->wnd
, This
->xclip
.rdesktop_primary_timestamp_target_atom
);
331 XDeleteProperty(This
->display
, This
->wnd
, This
->xclip
.rdesktop_clipboard_timestamp_target_atom
);
335 xclip_notify_change(RDPCLIENT
* This
)
337 XChangeProperty(This
->display
, DefaultRootWindow(This
->display
),
338 This
->xclip
.rdesktop_selection_notify_atom
, XA_INTEGER
, 32, PropModeReplace
, NULL
, 0);
342 xclip_probe_selections(RDPCLIENT
* This
)
344 Window primary_owner
, clipboard_owner
;
346 if (This
->xclip
.probing_selections
)
348 DEBUG_CLIPBOARD(("Already probing selections. Scheduling reprobe.\n"));
349 This
->xclip
.reprobe_selections
= True
;
353 DEBUG_CLIPBOARD(("Probing selections.\n"));
355 This
->xclip
.probing_selections
= True
;
356 This
->xclip
.reprobe_selections
= False
;
358 xclip_clear_target_props(This
);
360 if (This
->xclip
.auto_mode
)
361 primary_owner
= XGetSelectionOwner(This
->display
, This
->xclip
.primary_atom
);
363 primary_owner
= None
;
365 clipboard_owner
= XGetSelectionOwner(This
->display
, This
->xclip
.clipboard_atom
);
367 /* If we own all relevant selections then don't do anything. */
368 if (((primary_owner
== This
->wnd
) || !This
->xclip
.auto_mode
) && (clipboard_owner
== This
->wnd
))
372 if ((primary_owner
!= None
) && (clipboard_owner
!= None
))
374 This
->xclip
.primary_timestamp
= 0;
375 This
->xclip
.clipboard_timestamp
= 0;
376 XConvertSelection(This
->display
, This
->xclip
.primary_atom
, This
->xclip
.timestamp_atom
,
377 This
->xclip
.rdesktop_primary_timestamp_target_atom
, This
->wnd
, CurrentTime
);
378 XConvertSelection(This
->display
, This
->xclip
.clipboard_atom
, This
->xclip
.timestamp_atom
,
379 This
->xclip
.rdesktop_clipboard_timestamp_target_atom
, This
->wnd
, CurrentTime
);
384 if (primary_owner
!= None
)
386 XConvertSelection(This
->display
, This
->xclip
.primary_atom
, This
->xclip
.targets_atom
,
387 This
->xclip
.rdesktop_clipboard_target_atom
, This
->wnd
, CurrentTime
);
392 if (clipboard_owner
!= None
)
394 XConvertSelection(This
->display
, This
->xclip
.clipboard_atom
, This
->xclip
.targets_atom
,
395 This
->xclip
.rdesktop_clipboard_target_atom
, This
->wnd
, CurrentTime
);
399 DEBUG_CLIPBOARD(("No owner of any selection.\n"));
402 Without XFIXES, we cannot reliably know the formats offered by an
403 upcoming selection owner, so we just lie about him offering
405 cliprdr_send_simple_native_format_announce(This
, RDP_CF_TEXT
);
408 This
->xclip
.probing_selections
= False
;
411 /* This function is called for SelectionNotify events.
412 The SelectionNotify event is sent from the clipboard owner to the requestor
413 after his request was satisfied.
414 If this function is called, we're the requestor side. */
417 xclip_handle_SelectionNotify(RDPCLIENT
* This
, XSelectionEvent
* event
)
419 unsigned long nitems
, bytes_left
;
420 XWindowAttributes wa
;
422 Atom
*supported_targets
;
426 if (event
->property
== None
)
429 DEBUG_CLIPBOARD(("xclip_handle_SelectionNotify: selection=%s, target=%s, property=%s\n",
430 XGetAtomName(This
->display
, event
->selection
),
431 XGetAtomName(This
->display
, event
->target
),
432 XGetAtomName(This
->display
, event
->property
)));
434 if (event
->target
== This
->xclip
.timestamp_atom
)
436 if (event
->selection
== This
->xclip
.primary_atom
)
438 res
= XGetWindowProperty(This
->display
, This
->wnd
,
439 This
->xclip
.rdesktop_primary_timestamp_target_atom
, 0,
440 XMaxRequestSize(This
->display
), False
, AnyPropertyType
,
441 &type
, &format
, &nitems
, &bytes_left
, &data
);
445 res
= XGetWindowProperty(This
->display
, This
->wnd
,
446 This
->xclip
.rdesktop_clipboard_timestamp_target_atom
, 0,
447 XMaxRequestSize(This
->display
), False
, AnyPropertyType
,
448 &type
, &format
, &nitems
, &bytes_left
, &data
);
452 if ((res
!= Success
) || (nitems
!= 1) || (format
!= 32))
454 DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));
458 if (event
->selection
== This
->xclip
.primary_atom
)
460 This
->xclip
.primary_timestamp
= *(Time
*) data
;
461 if (This
->xclip
.primary_timestamp
== 0)
462 This
->xclip
.primary_timestamp
++;
463 XDeleteProperty(This
->display
, This
->wnd
, This
->xclip
.rdesktop_primary_timestamp_target_atom
);
464 DEBUG_CLIPBOARD(("Got PRIMARY timestamp: %u\n",
465 (unsigned) This
->xclip
.primary_timestamp
));
469 This
->xclip
.clipboard_timestamp
= *(Time
*) data
;
470 if (This
->xclip
.clipboard_timestamp
== 0)
471 This
->xclip
.clipboard_timestamp
++;
472 XDeleteProperty(This
->display
, This
->wnd
, This
->xclip
.rdesktop_clipboard_timestamp_target_atom
);
473 DEBUG_CLIPBOARD(("Got CLIPBOARD timestamp: %u\n",
474 (unsigned) This
->xclip
.clipboard_timestamp
));
479 if (This
->xclip
.primary_timestamp
&& This
->xclip
.clipboard_timestamp
)
481 if (This
->xclip
.primary_timestamp
> This
->xclip
.clipboard_timestamp
)
483 DEBUG_CLIPBOARD(("PRIMARY is most recent selection.\n"));
484 XConvertSelection(This
->display
, This
->xclip
.primary_atom
, This
->xclip
.targets_atom
,
485 This
->xclip
.rdesktop_clipboard_target_atom
, This
->wnd
,
490 DEBUG_CLIPBOARD(("CLIPBOARD is most recent selection.\n"));
491 XConvertSelection(This
->display
, This
->xclip
.clipboard_atom
, This
->xclip
.targets_atom
,
492 This
->xclip
.rdesktop_clipboard_target_atom
, This
->wnd
,
500 if (This
->xclip
.probing_selections
&& This
->xclip
.reprobe_selections
)
502 This
->xclip
.probing_selections
= False
;
503 xclip_probe_selections(This
);
507 res
= XGetWindowProperty(This
->display
, This
->wnd
, This
->xclip
.rdesktop_clipboard_target_atom
,
508 0, XMaxRequestSize(This
->display
), False
, AnyPropertyType
,
509 &type
, &format
, &nitems
, &bytes_left
, &data
);
511 xclip_clear_target_props(This
);
515 DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));
519 if (type
== This
->xclip
.incr_atom
)
521 DEBUG_CLIPBOARD(("Received INCR.\n"));
523 XGetWindowAttributes(This
->display
, This
->wnd
, &wa
);
524 if ((wa
.your_event_mask
| PropertyChangeMask
) != wa
.your_event_mask
)
526 XSelectInput(This
->display
, This
->wnd
, (wa
.your_event_mask
| PropertyChangeMask
));
529 This
->xclip
.incr_target
= event
->target
;
530 This
->xclip
.waiting_for_INCR
= 1;
534 /* Negotiate target format */
535 if (event
->target
== This
->xclip
.targets_atom
)
537 /* Determine the best of text This->xclip.targets that we have available:
538 Prefer UTF8_STRING > text/unicode (unspecified encoding) > STRING
539 (ignore TEXT and COMPOUND_TEXT because we don't have code to handle them)
541 int text_target_satisfaction
= 0;
542 Atom best_text_target
= 0; /* measures how much we're satisfied with what we found */
545 supported_targets
= (Atom
*) data
;
546 for (i
= 0; i
< nitems
; i
++)
548 DEBUG_CLIPBOARD(("Target %d: %s\n", i
,
549 XGetAtomName(This
->display
, supported_targets
[i
])));
550 if (supported_targets
[i
] == This
->xclip
.format_string_atom
)
552 if (text_target_satisfaction
< 1)
554 DEBUG_CLIPBOARD(("Other party supports STRING, choosing that as best_target\n"));
555 best_text_target
= supported_targets
[i
];
556 text_target_satisfaction
= 1;
559 #ifdef USE_UNICODE_CLIPBOARD
560 else if (supported_targets
[i
] == This
->xclip
.format_unicode_atom
)
562 if (text_target_satisfaction
< 2)
564 DEBUG_CLIPBOARD(("Other party supports text/unicode, choosing that as best_target\n"));
565 best_text_target
= supported_targets
[i
];
566 text_target_satisfaction
= 2;
569 else if (supported_targets
[i
] == This
->xclip
.format_utf8_string_atom
)
571 if (text_target_satisfaction
< 3)
573 DEBUG_CLIPBOARD(("Other party supports UTF8_STRING, choosing that as best_target\n"));
574 best_text_target
= supported_targets
[i
];
575 text_target_satisfaction
= 3;
579 else if (supported_targets
[i
] == This
->xclip
.rdesktop_clipboard_formats_atom
)
581 if (This
->xclip
.probing_selections
&& (text_target_satisfaction
< 4))
583 DEBUG_CLIPBOARD(("Other party supports native formats, choosing that as best_target\n"));
584 best_text_target
= supported_targets
[i
];
585 text_target_satisfaction
= 4;
591 /* Kickstarting the next step in the process of satisfying RDP's
592 clipboard request -- specifically, requesting the actual clipboard data.
594 if ((best_text_target
!= 0)
595 && (!This
->xclip
.probing_selections
596 || (best_text_target
== This
->xclip
.rdesktop_clipboard_formats_atom
)))
598 XConvertSelection(This
->display
, event
->selection
, best_text_target
,
599 This
->xclip
.rdesktop_clipboard_target_atom
, This
->wnd
, event
->time
);
604 DEBUG_CLIPBOARD(("Unable to find a textual target to satisfy RDP clipboard text request\n"));
610 if (This
->xclip
.probing_selections
)
612 Window primary_owner
, clipboard_owner
;
615 Without XFIXES, we must make sure that the other
616 rdesktop owns all relevant selections or we might try
617 to get a native format from non-rdesktop window later
620 clipboard_owner
= XGetSelectionOwner(This
->display
, This
->xclip
.clipboard_atom
);
622 if (This
->xclip
.auto_mode
)
623 primary_owner
= XGetSelectionOwner(This
->display
, This
->xclip
.primary_atom
);
625 primary_owner
= clipboard_owner
;
627 if (primary_owner
!= clipboard_owner
)
630 DEBUG_CLIPBOARD(("Got fellow rdesktop formats\n"));
631 This
->xclip
.probing_selections
= False
;
632 This
->xclip
.rdesktop_is_selection_owner
= True
;
633 cliprdr_send_native_format_announce(This
, data
, nitems
);
635 else if (!xclip_send_data_with_convert(This
, data
, nitems
, event
->target
))
648 xclip_clear_target_props(This
);
649 if (This
->xclip
.probing_selections
)
651 DEBUG_CLIPBOARD(("Unable to find suitable target. Using default text format.\n"));
652 This
->xclip
.probing_selections
= False
;
653 This
->xclip
.rdesktop_is_selection_owner
= False
;
656 Without XFIXES, we cannot reliably know the formats offered by an
657 upcoming selection owner, so we just lie about him offering
659 cliprdr_send_simple_native_format_announce(This
, RDP_CF_TEXT
);
663 helper_cliprdr_send_empty_response(This
);
668 /* This function is called for SelectionRequest events.
669 The SelectionRequest event is sent from the requestor to the clipboard owner
670 to request clipboard data.
673 xclip_handle_SelectionRequest(RDPCLIENT
* This
, XSelectionRequestEvent
* event
)
675 unsigned long nitems
, bytes_left
;
676 unsigned char *prop_return
;
680 DEBUG_CLIPBOARD(("xclip_handle_SelectionRequest: selection=%s, target=%s, property=%s\n",
681 XGetAtomName(This
->display
, event
->selection
),
682 XGetAtomName(This
->display
, event
->target
),
683 XGetAtomName(This
->display
, event
->property
)));
685 if (event
->target
== This
->xclip
.targets_atom
)
687 xclip_provide_selection(This
, event
, XA_ATOM
, 32, (uint8
*) & This
->xclip
.targets
, This
->xclip
.num_targets
);
690 else if (event
->target
== This
->xclip
.timestamp_atom
)
692 xclip_provide_selection(This
, event
, XA_INTEGER
, 32, (uint8
*) & This
->xclip
.acquire_time
, 1);
695 else if (event
->target
== This
->xclip
.rdesktop_clipboard_formats_atom
)
697 xclip_provide_selection(This
, event
, XA_STRING
, 8, This
->xclip
.formats_data
, This
->xclip
.formats_data_length
);
701 /* All the following This->xclip.targets require an async operation with the RDP server
702 and currently we don't do X clipboard request queueing so we can only
703 handle one such request at a time. */
704 if (This
->xclip
.has_selection_request
)
706 DEBUG_CLIPBOARD(("Error: Another clipboard request was already sent to the RDP server and not yet responded. Refusing this request.\n"));
707 xclip_refuse_selection(This
, event
);
710 if (event
->target
== This
->xclip
.rdesktop_native_atom
)
712 /* Before the requestor makes a request for the _RDESKTOP_NATIVE target,
713 he should declare requestor[property] = CF_SOMETHING. */
714 res
= XGetWindowProperty(This
->display
, event
->requestor
,
715 event
->property
, 0, 1, True
,
716 XA_INTEGER
, &type
, &format
, &nitems
, &bytes_left
,
720 DEBUG_CLIPBOARD(("Requested native format but didn't specifiy which.\n"));
721 xclip_refuse_selection(This
, event
);
725 format
= *(uint32
*) prop_return
;
728 else if (event
->target
== This
->xclip
.format_string_atom
|| event
->target
== XA_STRING
)
730 /* STRING and XA_STRING are defined to be ISO8859-1 */
733 else if (event
->target
== This
->xclip
.format_utf8_string_atom
)
735 #ifdef USE_UNICODE_CLIPBOARD
736 format
= CF_UNICODETEXT
;
738 DEBUG_CLIPBOARD(("Requested target unavailable due to lack of Unicode support. (It was not in TARGETS, so why did you ask for it?!)\n"));
739 xclip_refuse_selection(This
, event
);
743 else if (event
->target
== This
->xclip
.format_unicode_atom
)
745 /* Assuming text/unicode to be UTF-16 */
746 format
= CF_UNICODETEXT
;
750 DEBUG_CLIPBOARD(("Requested target unavailable. (It was not in TARGETS, so why did you ask for it?!)\n"));
751 xclip_refuse_selection(This
, event
);
755 cliprdr_send_data_request(This
, format
);
756 This
->xclip
.selection_request
= *event
;
757 This
->xclip
.has_selection_request
= True
;
758 return; /* wait for data */
762 /* While this rdesktop holds ownership over the clipboard, it means the clipboard data
763 is offered by the RDP server (and when it is pasted inside RDP, there's no network
766 This event (SelectionClear) symbolizes this rdesktop lost onwership of the clipboard
767 to some other X client. We should find out what clipboard formats this other
768 client offers and announce that to RDP. */
770 xclip_handle_SelectionClear(RDPCLIENT
* This
)
772 DEBUG_CLIPBOARD(("xclip_handle_SelectionClear\n"));
773 xclip_notify_change(This
);
774 xclip_probe_selections(This
);
777 /* Called when any property changes in our window or the root window. */
779 xclip_handle_PropertyNotify(RDPCLIENT
* This
, XPropertyEvent
* event
)
781 unsigned long nitems
;
782 unsigned long offset
= 0;
783 unsigned long bytes_left
= 1;
785 XWindowAttributes wa
;
789 if (event
->state
== PropertyNewValue
&& This
->xclip
.waiting_for_INCR
)
791 DEBUG_CLIPBOARD(("x_clip_handle_PropertyNotify: This->xclip.waiting_for_INCR != 0\n"));
793 while (bytes_left
> 0)
795 /* Unlike the specification, we don't set the 'delete' arugment to True
796 since we slurp the INCR's chunks in even-smaller chunks of 4096 bytes. */
797 if ((XGetWindowProperty
798 (This
->display
, This
->wnd
, This
->xclip
.rdesktop_clipboard_target_atom
, offset
, 4096L,
799 False
, AnyPropertyType
, &type
, &format
, &nitems
, &bytes_left
,
808 /* INCR transfer finished */
809 XGetWindowAttributes(This
->display
, This
->wnd
, &wa
);
810 XSelectInput(This
->display
, This
->wnd
,
811 (wa
.your_event_mask
^ PropertyChangeMask
));
813 This
->xclip
.waiting_for_INCR
= 0;
815 if (This
->xclip
.clip_buflen
> 0)
817 if (!xclip_send_data_with_convert
818 (This
, This
->xclip
.clip_buffer
, This
->xclip
.clip_buflen
, This
->xclip
.incr_target
))
820 helper_cliprdr_send_empty_response(This
);
822 xfree(This
->xclip
.clip_buffer
);
823 This
->xclip
.clip_buffer
= NULL
;
824 This
->xclip
.clip_buflen
= 0;
829 /* Another chunk in the INCR transfer */
830 offset
+= (nitems
/ 4); /* offset at which to begin the next slurp */
831 This
->xclip
.clip_buffer
= xrealloc(This
->xclip
.clip_buffer
, This
->xclip
.clip_buflen
+ nitems
);
832 memcpy(This
->xclip
.clip_buffer
+ This
->xclip
.clip_buflen
, data
, nitems
);
833 This
->xclip
.clip_buflen
+= nitems
;
838 XDeleteProperty(This
->display
, This
->wnd
, This
->xclip
.rdesktop_clipboard_target_atom
);
842 if ((event
->atom
== This
->xclip
.rdesktop_selection_notify_atom
) &&
843 (event
->window
== DefaultRootWindow(This
->display
)))
844 xclip_probe_selections(This
);
849 /* Called when the RDP server announces new clipboard data formats.
851 - take ownership over the clipboard
852 - declare those formats in their Windows native form
853 to other rdesktop instances on this X server */
855 ui_clip_format_announce(RDPCLIENT
* This
, uint8
* data
, uint32 length
)
857 This
->xclip
.acquire_time
= This
->last_gesturetime
;
859 XSetSelectionOwner(This
->display
, This
->xclip
.primary_atom
, This
->wnd
, This
->xclip
.acquire_time
);
860 if (XGetSelectionOwner(This
->display
, This
->xclip
.primary_atom
) != This
->wnd
)
861 warning("Failed to aquire ownership of PRIMARY clipboard\n");
863 XSetSelectionOwner(This
->display
, This
->xclip
.clipboard_atom
, This
->wnd
, This
->xclip
.acquire_time
);
864 if (XGetSelectionOwner(This
->display
, This
->xclip
.clipboard_atom
) != This
->wnd
)
865 warning("Failed to aquire ownership of CLIPBOARD clipboard\n");
867 if (This
->xclip
.formats_data
)
868 xfree(This
->xclip
.formats_data
);
869 This
->xclip
.formats_data
= xmalloc(length
);
870 memcpy(This
->xclip
.formats_data
, data
, length
);
871 This
->xclip
.formats_data_length
= length
;
873 xclip_notify_change(This
);
876 /* Called when the RDP server responds with clipboard data (after we've requested it). */
878 ui_clip_handle_data(RDPCLIENT
* This
, uint8
* data
, uint32 length
)
880 BOOL free_data
= False
;
884 xclip_refuse_selection(This
, &This
->xclip
.selection_request
);
885 This
->xclip
.has_selection_request
= False
;
889 if (This
->xclip
.selection_request
.target
== This
->xclip
.format_string_atom
|| This
->xclip
.selection_request
.target
== XA_STRING
)
891 /* We're expecting a CF_TEXT response */
894 /* translate linebreaks */
895 crlf2lf(data
, &length
);
897 /* Only send data up to null byte, if any */
898 firstnull
= (uint8
*) strchr((char *) data
, '\0');
901 length
= firstnull
- data
+ 1;
904 #ifdef USE_UNICODE_CLIPBOARD
905 else if (This
->xclip
.selection_request
.target
== This
->xclip
.format_utf8_string_atom
)
907 /* We're expecting a CF_UNICODETEXT response */
908 iconv_t cd
= iconv_open("UTF-8", WINDOWS_CODEPAGE
);
909 if (cd
!= (iconv_t
) - 1)
911 size_t utf8_length
= length
* 2;
912 char *utf8_data
= malloc(utf8_length
);
913 size_t utf8_length_remaining
= utf8_length
;
914 char *utf8_data_remaining
= utf8_data
;
915 char *data_remaining
= (char *) data
;
916 size_t length_remaining
= (size_t) length
;
917 if (utf8_data
== NULL
)
922 iconv(cd
, (ICONV_CONST
char **) &data_remaining
, &length_remaining
,
923 &utf8_data_remaining
, &utf8_length_remaining
);
926 data
= (uint8
*) utf8_data
;
927 length
= utf8_length
- utf8_length_remaining
;
930 else if (This
->xclip
.selection_request
.target
== This
->xclip
.format_unicode_atom
)
932 /* We're expecting a CF_UNICODETEXT response, so what we're
933 receiving matches our requirements and there's no need
934 for further conversions. */
937 else if (This
->xclip
.selection_request
.target
== This
->xclip
.rdesktop_native_atom
)
943 DEBUG_CLIPBOARD(("ui_clip_handle_data: BUG! I don't know how to convert selection target %s!\n", XGetAtomName(This
->display
, This
->xclip
.selection_request
.target
)));
944 xclip_refuse_selection(This
, &This
->xclip
.selection_request
);
945 This
->xclip
.has_selection_request
= False
;
949 xclip_provide_selection(This
, &This
->xclip
.selection_request
, This
->xclip
.selection_request
.target
, 8, data
, length
- 1);
950 This
->xclip
.has_selection_request
= False
;
957 ui_clip_request_failed(RDPCLIENT
* This
)
959 xclip_refuse_selection(This
, &This
->xclip
.selection_request
);
960 This
->xclip
.has_selection_request
= False
;
964 ui_clip_request_data(RDPCLIENT
* This
, uint32 format
)
966 Window primary_owner
, clipboard_owner
;
968 DEBUG_CLIPBOARD(("Request from server for format %d\n", format
));
969 This
->xclip
.rdp_clipboard_request_format
= format
;
971 if (This
->xclip
.probing_selections
)
973 DEBUG_CLIPBOARD(("ui_clip_request_data: Selection probe in progress. Cannot handle request.\n"));
974 helper_cliprdr_send_empty_response(This
);
978 xclip_clear_target_props(This
);
980 if (This
->xclip
.rdesktop_is_selection_owner
)
982 XChangeProperty(This
->display
, This
->wnd
, This
->xclip
.rdesktop_clipboard_target_atom
,
983 XA_INTEGER
, 32, PropModeReplace
, (unsigned char *) &format
, 1);
985 XConvertSelection(This
->display
, This
->xclip
.primary_atom
, This
->xclip
.rdesktop_native_atom
,
986 This
->xclip
.rdesktop_clipboard_target_atom
, This
->wnd
, CurrentTime
);
990 if (This
->xclip
.auto_mode
)
991 primary_owner
= XGetSelectionOwner(This
->display
, This
->xclip
.primary_atom
);
993 primary_owner
= None
;
995 clipboard_owner
= XGetSelectionOwner(This
->display
, This
->xclip
.clipboard_atom
);
998 if ((primary_owner
!= None
) && (clipboard_owner
!= None
))
1000 This
->xclip
.primary_timestamp
= 0;
1001 This
->xclip
.clipboard_timestamp
= 0;
1002 XConvertSelection(This
->display
, This
->xclip
.primary_atom
, This
->xclip
.timestamp_atom
,
1003 This
->xclip
.rdesktop_primary_timestamp_target_atom
, This
->wnd
, CurrentTime
);
1004 XConvertSelection(This
->display
, This
->xclip
.clipboard_atom
, This
->xclip
.timestamp_atom
,
1005 This
->xclip
.rdesktop_clipboard_timestamp_target_atom
, This
->wnd
, CurrentTime
);
1010 if (primary_owner
!= None
)
1012 XConvertSelection(This
->display
, This
->xclip
.primary_atom
, This
->xclip
.targets_atom
,
1013 This
->xclip
.rdesktop_clipboard_target_atom
, This
->wnd
, CurrentTime
);
1017 /* Just CLIPBOARD */
1018 if (clipboard_owner
!= None
)
1020 XConvertSelection(This
->display
, This
->xclip
.clipboard_atom
, This
->xclip
.targets_atom
,
1021 This
->xclip
.rdesktop_clipboard_target_atom
, This
->wnd
, CurrentTime
);
1025 /* No data available */
1026 helper_cliprdr_send_empty_response(This
);
1030 ui_clip_sync(RDPCLIENT
* This
)
1032 xclip_probe_selections(This
);
1036 ui_clip_set_mode(RDPCLIENT
* This
, const char *optarg
)
1038 This
->rdpclip
= True
;
1040 if (str_startswith(optarg
, "PRIMARYCLIPBOARD"))
1041 This
->xclip
.auto_mode
= True
;
1042 else if (str_startswith(optarg
, "CLIPBOARD"))
1043 This
->xclip
.auto_mode
= False
;
1046 warning("Invalid clipboard mode '%s'.\n", optarg
);
1047 This
->rdpclip
= False
;
1052 xclip_init(RDPCLIENT
* This
)
1057 if (!cliprdr_init(This
))
1060 This
->xclip
.primary_atom
= XInternAtom(This
->display
, "PRIMARY", False
);
1061 This
->xclip
.clipboard_atom
= XInternAtom(This
->display
, "CLIPBOARD", False
);
1062 This
->xclip
.targets_atom
= XInternAtom(This
->display
, "TARGETS", False
);
1063 This
->xclip
.timestamp_atom
= XInternAtom(This
->display
, "TIMESTAMP", False
);
1064 This
->xclip
.rdesktop_clipboard_target_atom
=
1065 XInternAtom(This
->display
, "_RDESKTOP_CLIPBOARD_TARGET", False
);
1066 This
->xclip
.rdesktop_primary_timestamp_target_atom
=
1067 XInternAtom(This
->display
, "_RDESKTOP_PRIMARY_TIMESTAMP_TARGET", False
);
1068 This
->xclip
.rdesktop_clipboard_timestamp_target_atom
=
1069 XInternAtom(This
->display
, "_RDESKTOP_CLIPBOARD_TIMESTAMP_TARGET", False
);
1070 This
->xclip
.incr_atom
= XInternAtom(This
->display
, "INCR", False
);
1071 This
->xclip
.format_string_atom
= XInternAtom(This
->display
, "STRING", False
);
1072 This
->xclip
.format_utf8_string_atom
= XInternAtom(This
->display
, "UTF8_STRING", False
);
1073 This
->xclip
.format_unicode_atom
= XInternAtom(This
->display
, "text/unicode", False
);
1075 /* rdesktop sets _RDESKTOP_SELECTION_NOTIFY on the root window when acquiring the clipboard.
1076 Other interested rdesktops can use this to notify their server of the available formats. */
1077 This
->xclip
.rdesktop_selection_notify_atom
=
1078 XInternAtom(This
->display
, "_RDESKTOP_SELECTION_NOTIFY", False
);
1079 XSelectInput(This
->display
, DefaultRootWindow(This
->display
), PropertyChangeMask
);
1080 This
->xclip
.probing_selections
= False
;
1082 This
->xclip
.rdesktop_native_atom
= XInternAtom(This
->display
, "_RDESKTOP_NATIVE", False
);
1083 This
->xclip
.rdesktop_clipboard_formats_atom
=
1084 XInternAtom(This
->display
, "_RDESKTOP_CLIPBOARD_FORMATS", False
);
1085 This
->xclip
.rdesktop_primary_owner_atom
= XInternAtom(This
->display
, "_RDESKTOP_PRIMARY_OWNER", False
);
1086 This
->xclip
.rdesktop_clipboard_owner_atom
= XInternAtom(This
->display
, "_RDESKTOP_CLIPBOARD_OWNER", False
);
1088 This
->xclip
.num_targets
= 0;
1089 This
->xclip
.targets
[This
->xclip
.num_targets
++] = This
->xclip
.targets_atom
;
1090 This
->xclip
.targets
[This
->xclip
.num_targets
++] = This
->xclip
.timestamp_atom
;
1091 This
->xclip
.targets
[This
->xclip
.num_targets
++] = This
->xclip
.rdesktop_native_atom
;
1092 This
->xclip
.targets
[This
->xclip
.num_targets
++] = This
->xclip
.rdesktop_clipboard_formats_atom
;
1093 #ifdef USE_UNICODE_CLIPBOARD
1094 This
->xclip
.targets
[This
->xclip
.num_targets
++] = This
->xclip
.format_utf8_string_atom
;
1096 This
->xclip
.targets
[This
->xclip
.num_targets
++] = This
->xclip
.format_unicode_atom
;
1097 This
->xclip
.targets
[This
->xclip
.num_targets
++] = This
->xclip
.format_string_atom
;
1098 This
->xclip
.targets
[This
->xclip
.num_targets
++] = XA_STRING
;
1102 xclip_deinit(RDPCLIENT
* This
)
1104 if (XGetSelectionOwner(This
->display
, This
->xclip
.primary_atom
) == This
->wnd
)
1105 XSetSelectionOwner(This
->display
, This
->xclip
.primary_atom
, None
, This
->xclip
.acquire_time
);
1106 if (XGetSelectionOwner(This
->display
, This
->xclip
.clipboard_atom
) == This
->wnd
)
1107 XSetSelectionOwner(This
->display
, This
->xclip
.clipboard_atom
, None
, This
->xclip
.acquire_time
);
1108 xclip_notify_change(This
);