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 along
18    with this program; if not, write to the Free Software Foundation, Inc.,
19    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 
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. */
56 static void
crlf2lf(uint8 * data,uint32 * length)57 crlf2lf(uint8 * data, uint32 * length)
58 {
59 	uint8 *dst, *src;
60 	src = dst = data;
61 	while (src < data + *length)
62 	{
63 		if (*src != '\x0d')
64 			*dst++ = *src;
65 		src++;
66 	}
67 	*length = dst - data;
68 }
69 
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. */
74 static uint8 *
utf16_lf2crlf(uint8 * data,uint32 * size)75 utf16_lf2crlf(uint8 * data, uint32 * size)
76 {
77 	uint8 *result;
78 	uint16 *inptr, *outptr;
79 	Bool swap_endianess;
80 
81 	/* Worst case: Every char is LF */
82 	result = xmalloc((*size * 2) + 2);
83 	if (result == NULL)
84 		return NULL;
85 
86 	inptr = (uint16 *) data;
87 	outptr = (uint16 *) result;
88 
89 	/* Check for a reversed BOM */
90 	swap_endianess = (*inptr == 0xfffe);
91 
92 	while ((uint8 *) inptr < data + *size)
93 	{
94 		uint16 uvalue = *inptr;
95 		if (swap_endianess)
96 			uvalue = ((uvalue << 8) & 0xff00) + (uvalue >> 8);
97 		if (uvalue == 0x0a)
98 			*outptr++ = swap_endianess ? 0x0d00 : 0x0d;
99 		*outptr++ = *inptr++;
100 	}
101 	*outptr++ = 0;		/* null termination */
102 	*size = (uint8 *) outptr - result;
103 
104 	return result;
105 }
106 #else
107 /* Translate LF to CR-LF. To do this, we must allocate more memory.
108    The length is updated. */
109 static uint8 *
lf2crlf(uint8 * data,uint32 * length)110 lf2crlf(uint8 * data, uint32 * length)
111 {
112 	uint8 *result, *p, *o;
113 
114 	/* Worst case: Every char is LF */
115 	result = xmalloc(*length * 2);
116 
117 	p = data;
118 	o = result;
119 
120 	while (p < data + *length)
121 	{
122 		if (*p == '\x0a')
123 			*o++ = '\x0d';
124 		*o++ = *p++;
125 	}
126 	*length = o - result;
127 
128 	/* Convenience */
129 	*o++ = '\0';
130 
131 	return result;
132 }
133 #endif
134 
135 static void
xclip_provide_selection(RDPCLIENT * This,XSelectionRequestEvent * req,Atom type,unsigned int format,uint8 * data,uint32 length)136 xclip_provide_selection(RDPCLIENT * This, XSelectionRequestEvent * req, Atom type, unsigned int format, uint8 * data,
137 			uint32 length)
138 {
139 	XEvent xev;
140 
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));
142 
143 	XChangeProperty(This->display, req->requestor, req->property,
144 			type, format, PropModeReplace, data, length);
145 
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);
155 }
156 
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). */
160 static void
xclip_refuse_selection(RDPCLIENT * This,XSelectionRequestEvent * req)161 xclip_refuse_selection(RDPCLIENT * This, XSelectionRequestEvent * req)
162 {
163 	XEvent xev;
164 
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)));
168 
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);
178 }
179 
180 /* Wrapper for cliprdr_send_data which also cleans the request state. */
181 static void
helper_cliprdr_send_response(RDPCLIENT * This,uint8 * data,uint32 length)182 helper_cliprdr_send_response(RDPCLIENT * This, uint8 * data, uint32 length)
183 {
184 	if (This->xclip.rdp_clipboard_request_format != 0)
185 	{
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);
190 	}
191 }
192 
193 /* Last resort, when we have to provide clipboard data but for whatever
194    reason couldn't get any.
195  */
196 static void
helper_cliprdr_send_empty_response(RDPCLIENT * This)197 helper_cliprdr_send_empty_response(RDPCLIENT * This)
198 {
199 	helper_cliprdr_send_response(This, NULL, 0);
200 }
201 
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.
204  */
205 static Bool
xclip_send_data_with_convert(RDPCLIENT * This,uint8 * source,size_t source_size,Atom target)206 xclip_send_data_with_convert(RDPCLIENT * This, uint8 * source, size_t source_size, Atom target)
207 {
208 	DEBUG_CLIPBOARD(("xclip_send_data_with_convert: target=%s, size=%u\n",
209 			 XGetAtomName(This->display, target), (unsigned) source_size));
210 
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)
214 	{
215 		size_t unicode_buffer_size;
216 		char *unicode_buffer;
217 		iconv_t cd;
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;
224 
225 		if (This->xclip.rdp_clipboard_request_format != RDP_CF_TEXT)
226 			return False;
227 
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).
232 		 */
233 		if (target == This->xclip.format_string_atom)
234 		{
235 			char *locale_charset = nl_langinfo(CODESET);
236 			cd = iconv_open(WINDOWS_CODEPAGE, locale_charset);
237 			if (cd == (iconv_t) - 1)
238 			{
239 				DEBUG_CLIPBOARD(("Locale charset %s not found in iconv. Unable to convert clipboard text.\n", locale_charset));
240 				return False;
241 			}
242 			unicode_buffer_size = source_size * 4;
243 		}
244 		else if (target == This->xclip.format_unicode_atom)
245 		{
246 			cd = iconv_open(WINDOWS_CODEPAGE, "UCS-2");
247 			if (cd == (iconv_t) - 1)
248 			{
249 				return False;
250 			}
251 			unicode_buffer_size = source_size;
252 		}
253 		else if (target == This->xclip.format_utf8_string_atom)
254 		{
255 			cd = iconv_open(WINDOWS_CODEPAGE, "UTF-8");
256 			if (cd == (iconv_t) - 1)
257 			{
258 				return False;
259 			}
260 			/* UTF-8 is guaranteed to be less or equally compact
261 			   as UTF-16 for all Unicode chars >=2 bytes.
262 			 */
263 			unicode_buffer_size = source_size * 2;
264 		}
265 		else
266 		{
267 			return False;
268 		}
269 
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);
277 		iconv_close(cd);
278 
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)
283 		{
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! */
288 		}
289 
290 		xfree(unicode_buffer);
291 
292 		return True;
293 	}
294 #else
295 	if (target == This->xclip.format_string_atom)
296 	{
297 		uint8 *translated_data;
298 		uint32 length = source_size;
299 
300 		if (This->xclip.rdp_clipboard_request_format != RDP_CF_TEXT)
301 			return False;
302 
303 		DEBUG_CLIPBOARD(("Translating linebreaks before sending data\n"));
304 		translated_data = lf2crlf(source, &length);
305 		if (translated_data != NULL)
306 		{
307 			helper_cliprdr_send_response(This, translated_data, length);
308 			xfree(translated_data);	/* Not the same thing as XFree! */
309 		}
310 
311 		return True;
312 	}
313 #endif
314 	else if (target == This->xclip.rdesktop_native_atom)
315 	{
316 		helper_cliprdr_send_response(This, source, source_size + 1);
317 
318 		return True;
319 	}
320 	else
321 	{
322 		return False;
323 	}
324 }
325 
326 static void
xclip_clear_target_props(RDPCLIENT * This)327 xclip_clear_target_props(RDPCLIENT * This)
328 {
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);
332 }
333 
334 static void
xclip_notify_change(RDPCLIENT * This)335 xclip_notify_change(RDPCLIENT * This)
336 {
337 	XChangeProperty(This->display, DefaultRootWindow(This->display),
338 			This->xclip.rdesktop_selection_notify_atom, XA_INTEGER, 32, PropModeReplace, NULL, 0);
339 }
340 
341 static void
xclip_probe_selections(RDPCLIENT * This)342 xclip_probe_selections(RDPCLIENT * This)
343 {
344 	Window primary_owner, clipboard_owner;
345 
346 	if (This->xclip.probing_selections)
347 	{
348 		DEBUG_CLIPBOARD(("Already probing selections. Scheduling reprobe.\n"));
349 		This->xclip.reprobe_selections = True;
350 		return;
351 	}
352 
353 	DEBUG_CLIPBOARD(("Probing selections.\n"));
354 
355 	This->xclip.probing_selections = True;
356 	This->xclip.reprobe_selections = False;
357 
358 	xclip_clear_target_props(This);
359 
360 	if (This->xclip.auto_mode)
361 		primary_owner = XGetSelectionOwner(This->display, This->xclip.primary_atom);
362 	else
363 		primary_owner = None;
364 
365 	clipboard_owner = XGetSelectionOwner(This->display, This->xclip.clipboard_atom);
366 
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))
369 		goto end;
370 
371 	/* Both available */
372 	if ((primary_owner != None) && (clipboard_owner != None))
373 	{
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);
380 		return;
381 	}
382 
383 	/* Just PRIMARY */
384 	if (primary_owner != None)
385 	{
386 		XConvertSelection(This->display, This->xclip.primary_atom, This->xclip.targets_atom,
387 				  This->xclip.rdesktop_clipboard_target_atom, This->wnd, CurrentTime);
388 		return;
389 	}
390 
391 	/* Just CLIPBOARD */
392 	if (clipboard_owner != None)
393 	{
394 		XConvertSelection(This->display, This->xclip.clipboard_atom, This->xclip.targets_atom,
395 				  This->xclip.rdesktop_clipboard_target_atom, This->wnd, CurrentTime);
396 		return;
397 	}
398 
399 	DEBUG_CLIPBOARD(("No owner of any selection.\n"));
400 
401 	/* FIXME:
402 	   Without XFIXES, we cannot reliably know the formats offered by an
403 	   upcoming selection owner, so we just lie about him offering
404 	   RDP_CF_TEXT. */
405 	cliprdr_send_simple_native_format_announce(This, RDP_CF_TEXT);
406 
407       end:
408 	This->xclip.probing_selections = False;
409 }
410 
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. */
415 #ifndef MAKE_PROTO
416 void
xclip_handle_SelectionNotify(RDPCLIENT * This,XSelectionEvent * event)417 xclip_handle_SelectionNotify(RDPCLIENT * This, XSelectionEvent * event)
418 {
419 	unsigned long nitems, bytes_left;
420 	XWindowAttributes wa;
421 	Atom type;
422 	Atom *supported_targets;
423 	int res, i, format;
424 	uint8 *data = NULL;
425 
426 	if (event->property == None)
427 		goto fail;
428 
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)));
433 
434 	if (event->target == This->xclip.timestamp_atom)
435 	{
436 		if (event->selection == This->xclip.primary_atom)
437 		{
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);
442 		}
443 		else
444 		{
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);
449 		}
450 
451 
452 		if ((res != Success) || (nitems != 1) || (format != 32))
453 		{
454 			DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));
455 			goto fail;
456 		}
457 
458 		if (event->selection == This->xclip.primary_atom)
459 		{
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));
466 		}
467 		else
468 		{
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));
475 		}
476 
477 		XFree(data);
478 
479 		if (This->xclip.primary_timestamp && This->xclip.clipboard_timestamp)
480 		{
481 			if (This->xclip.primary_timestamp > This->xclip.clipboard_timestamp)
482 			{
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,
486 						  event->time);
487 			}
488 			else
489 			{
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,
493 						  event->time);
494 			}
495 		}
496 
497 		return;
498 	}
499 
500 	if (This->xclip.probing_selections && This->xclip.reprobe_selections)
501 	{
502 		This->xclip.probing_selections = False;
503 		xclip_probe_selections(This);
504 		return;
505 	}
506 
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);
510 
511 	xclip_clear_target_props(This);
512 
513 	if (res != Success)
514 	{
515 		DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));
516 		goto fail;
517 	}
518 
519 	if (type == This->xclip.incr_atom)
520 	{
521 		DEBUG_CLIPBOARD(("Received INCR.\n"));
522 
523 		XGetWindowAttributes(This->display, This->wnd, &wa);
524 		if ((wa.your_event_mask | PropertyChangeMask) != wa.your_event_mask)
525 		{
526 			XSelectInput(This->display, This->wnd, (wa.your_event_mask | PropertyChangeMask));
527 		}
528 		XFree(data);
529 		This->xclip.incr_target = event->target;
530 		This->xclip.waiting_for_INCR = 1;
531 		goto end;
532 	}
533 
534 	/* Negotiate target format */
535 	if (event->target == This->xclip.targets_atom)
536 	{
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)
540 		 */
541 		int text_target_satisfaction = 0;
542 		Atom best_text_target = 0;	/* measures how much we're satisfied with what we found */
543 		if (type != None)
544 		{
545 			supported_targets = (Atom *) data;
546 			for (i = 0; i < nitems; i++)
547 			{
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)
551 				{
552 					if (text_target_satisfaction < 1)
553 					{
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;
557 					}
558 				}
559 #ifdef USE_UNICODE_CLIPBOARD
560 				else if (supported_targets[i] == This->xclip.format_unicode_atom)
561 				{
562 					if (text_target_satisfaction < 2)
563 					{
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;
567 					}
568 				}
569 				else if (supported_targets[i] == This->xclip.format_utf8_string_atom)
570 				{
571 					if (text_target_satisfaction < 3)
572 					{
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;
576 					}
577 				}
578 #endif
579 				else if (supported_targets[i] == This->xclip.rdesktop_clipboard_formats_atom)
580 				{
581 					if (This->xclip.probing_selections && (text_target_satisfaction < 4))
582 					{
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;
586 					}
587 				}
588 			}
589 		}
590 
591 		/* Kickstarting the next step in the process of satisfying RDP's
592 		   clipboard request -- specifically, requesting the actual clipboard data.
593 		 */
594 		if ((best_text_target != 0)
595 		    && (!This->xclip.probing_selections
596 			|| (best_text_target == This->xclip.rdesktop_clipboard_formats_atom)))
597 		{
598 			XConvertSelection(This->display, event->selection, best_text_target,
599 					  This->xclip.rdesktop_clipboard_target_atom, This->wnd, event->time);
600 			goto end;
601 		}
602 		else
603 		{
604 			DEBUG_CLIPBOARD(("Unable to find a textual target to satisfy RDP clipboard text request\n"));
605 			goto fail;
606 		}
607 	}
608 	else
609 	{
610 		if (This->xclip.probing_selections)
611 		{
612 			Window primary_owner, clipboard_owner;
613 
614 			/* FIXME:
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
618 			   on. */
619 
620 			clipboard_owner = XGetSelectionOwner(This->display, This->xclip.clipboard_atom);
621 
622 			if (This->xclip.auto_mode)
623 				primary_owner = XGetSelectionOwner(This->display, This->xclip.primary_atom);
624 			else
625 				primary_owner = clipboard_owner;
626 
627 			if (primary_owner != clipboard_owner)
628 				goto fail;
629 
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);
634 		}
635 		else if (!xclip_send_data_with_convert(This, data, nitems, event->target))
636 		{
637 			goto fail;
638 		}
639 	}
640 
641       end:
642 	if (data)
643 		XFree(data);
644 
645 	return;
646 
647       fail:
648 	xclip_clear_target_props(This);
649 	if (This->xclip.probing_selections)
650 	{
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;
654 
655 		/* FIXME:
656 		   Without XFIXES, we cannot reliably know the formats offered by an
657 		   upcoming selection owner, so we just lie about him offering
658 		   RDP_CF_TEXT. */
659 		cliprdr_send_simple_native_format_announce(This, RDP_CF_TEXT);
660 	}
661 	else
662 	{
663 		helper_cliprdr_send_empty_response(This);
664 	}
665 	goto end;
666 }
667 
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.
671  */
672 void
xclip_handle_SelectionRequest(RDPCLIENT * This,XSelectionRequestEvent * event)673 xclip_handle_SelectionRequest(RDPCLIENT * This, XSelectionRequestEvent * event)
674 {
675 	unsigned long nitems, bytes_left;
676 	unsigned char *prop_return;
677 	int format, res;
678 	Atom type;
679 
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)));
684 
685 	if (event->target == This->xclip.targets_atom)
686 	{
687 		xclip_provide_selection(This, event, XA_ATOM, 32, (uint8 *) & This->xclip.targets, This->xclip.num_targets);
688 		return;
689 	}
690 	else if (event->target == This->xclip.timestamp_atom)
691 	{
692 		xclip_provide_selection(This, event, XA_INTEGER, 32, (uint8 *) & This->xclip.acquire_time, 1);
693 		return;
694 	}
695 	else if (event->target == This->xclip.rdesktop_clipboard_formats_atom)
696 	{
697 		xclip_provide_selection(This, event, XA_STRING, 8, This->xclip.formats_data, This->xclip.formats_data_length);
698 	}
699 	else
700 	{
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)
705 		{
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);
708 			return;
709 		}
710 		if (event->target == This->xclip.rdesktop_native_atom)
711 		{
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,
717 						 &prop_return);
718 			if (res != Success)
719 			{
720 				DEBUG_CLIPBOARD(("Requested native format but didn't specifiy which.\n"));
721 				xclip_refuse_selection(This, event);
722 				return;
723 			}
724 
725 			format = *(uint32 *) prop_return;
726 			XFree(prop_return);
727 		}
728 		else if (event->target == This->xclip.format_string_atom || event->target == XA_STRING)
729 		{
730 			/* STRING and XA_STRING are defined to be ISO8859-1 */
731 			format = CF_TEXT;
732 		}
733 		else if (event->target == This->xclip.format_utf8_string_atom)
734 		{
735 #ifdef USE_UNICODE_CLIPBOARD
736 			format = CF_UNICODETEXT;
737 #else
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);
740 			return;
741 #endif
742 		}
743 		else if (event->target == This->xclip.format_unicode_atom)
744 		{
745 			/* Assuming text/unicode to be UTF-16 */
746 			format = CF_UNICODETEXT;
747 		}
748 		else
749 		{
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);
752 			return;
753 		}
754 
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 */
759 	}
760 }
761 
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
764    roundtrip).
765 
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. */
769 void
xclip_handle_SelectionClear(RDPCLIENT * This)770 xclip_handle_SelectionClear(RDPCLIENT * This)
771 {
772 	DEBUG_CLIPBOARD(("xclip_handle_SelectionClear\n"));
773 	xclip_notify_change(This);
774 	xclip_probe_selections(This);
775 }
776 
777 /* Called when any property changes in our window or the root window. */
778 void
xclip_handle_PropertyNotify(RDPCLIENT * This,XPropertyEvent * event)779 xclip_handle_PropertyNotify(RDPCLIENT * This, XPropertyEvent * event)
780 {
781 	unsigned long nitems;
782 	unsigned long offset = 0;
783 	unsigned long bytes_left = 1;
784 	int format;
785 	XWindowAttributes wa;
786 	uint8 *data;
787 	Atom type;
788 
789 	if (event->state == PropertyNewValue && This->xclip.waiting_for_INCR)
790 	{
791 		DEBUG_CLIPBOARD(("x_clip_handle_PropertyNotify: This->xclip.waiting_for_INCR != 0\n"));
792 
793 		while (bytes_left > 0)
794 		{
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,
800 			      &data) != Success))
801 			{
802 				XFree(data);
803 				return;
804 			}
805 
806 			if (nitems == 0)
807 			{
808 				/* INCR transfer finished */
809 				XGetWindowAttributes(This->display, This->wnd, &wa);
810 				XSelectInput(This->display, This->wnd,
811 					     (wa.your_event_mask ^ PropertyChangeMask));
812 				XFree(data);
813 				This->xclip.waiting_for_INCR = 0;
814 
815 				if (This->xclip.clip_buflen > 0)
816 				{
817 					if (!xclip_send_data_with_convert
818 					    (This, This->xclip.clip_buffer, This->xclip.clip_buflen, This->xclip.incr_target))
819 					{
820 						helper_cliprdr_send_empty_response(This);
821 					}
822 					xfree(This->xclip.clip_buffer);
823 					This->xclip.clip_buffer = NULL;
824 					This->xclip.clip_buflen = 0;
825 				}
826 			}
827 			else
828 			{
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;
834 
835 				XFree(data);
836 			}
837 		}
838 		XDeleteProperty(This->display, This->wnd, This->xclip.rdesktop_clipboard_target_atom);
839 		return;
840 	}
841 
842 	if ((event->atom == This->xclip.rdesktop_selection_notify_atom) &&
843 	    (event->window == DefaultRootWindow(This->display)))
844 		xclip_probe_selections(This);
845 }
846 #endif
847 
848 
849 /* Called when the RDP server announces new clipboard data formats.
850    In response, we:
851    - take ownership over the clipboard
852    - declare those formats in their Windows native form
853      to other rdesktop instances on this X server */
854 void
ui_clip_format_announce(RDPCLIENT * This,uint8 * data,uint32 length)855 ui_clip_format_announce(RDPCLIENT * This, uint8 * data, uint32 length)
856 {
857 	This->xclip.acquire_time = This->last_gesturetime;
858 
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");
862 
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");
866 
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;
872 
873 	xclip_notify_change(This);
874 }
875 
876 /* Called when the RDP server responds with clipboard data (after we've requested it). */
877 void
ui_clip_handle_data(RDPCLIENT * This,uint8 * data,uint32 length)878 ui_clip_handle_data(RDPCLIENT * This, uint8 * data, uint32 length)
879 {
880 	BOOL free_data = False;
881 
882 	if (length == 0)
883 	{
884 		xclip_refuse_selection(This, &This->xclip.selection_request);
885 		This->xclip.has_selection_request = False;
886 		return;
887 	}
888 
889 	if (This->xclip.selection_request.target == This->xclip.format_string_atom || This->xclip.selection_request.target == XA_STRING)
890 	{
891 		/* We're expecting a CF_TEXT response */
892 		uint8 *firstnull;
893 
894 		/* translate linebreaks */
895 		crlf2lf(data, &length);
896 
897 		/* Only send data up to null byte, if any */
898 		firstnull = (uint8 *) strchr((char *) data, '\0');
899 		if (firstnull)
900 		{
901 			length = firstnull - data + 1;
902 		}
903 	}
904 #ifdef USE_UNICODE_CLIPBOARD
905 	else if (This->xclip.selection_request.target == This->xclip.format_utf8_string_atom)
906 	{
907 		/* We're expecting a CF_UNICODETEXT response */
908 		iconv_t cd = iconv_open("UTF-8", WINDOWS_CODEPAGE);
909 		if (cd != (iconv_t) - 1)
910 		{
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)
918 			{
919 				iconv_close(cd);
920 				return;
921 			}
922 			iconv(cd, (ICONV_CONST char **) &data_remaining, &length_remaining,
923 			      &utf8_data_remaining, &utf8_length_remaining);
924 			iconv_close(cd);
925 			free_data = True;
926 			data = (uint8 *) utf8_data;
927 			length = utf8_length - utf8_length_remaining;
928 		}
929 	}
930 	else if (This->xclip.selection_request.target == This->xclip.format_unicode_atom)
931 	{
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. */
935 	}
936 #endif
937 	else if (This->xclip.selection_request.target == This->xclip.rdesktop_native_atom)
938 	{
939 		/* Pass as-is */
940 	}
941 	else
942 	{
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;
946 		return;
947 	}
948 
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;
951 
952 	if (free_data)
953 		free(data);
954 }
955 
956 void
ui_clip_request_failed(RDPCLIENT * This)957 ui_clip_request_failed(RDPCLIENT * This)
958 {
959 	xclip_refuse_selection(This, &This->xclip.selection_request);
960 	This->xclip.has_selection_request = False;
961 }
962 
963 void
ui_clip_request_data(RDPCLIENT * This,uint32 format)964 ui_clip_request_data(RDPCLIENT * This, uint32 format)
965 {
966 	Window primary_owner, clipboard_owner;
967 
968 	DEBUG_CLIPBOARD(("Request from server for format %d\n", format));
969 	This->xclip.rdp_clipboard_request_format = format;
970 
971 	if (This->xclip.probing_selections)
972 	{
973 		DEBUG_CLIPBOARD(("ui_clip_request_data: Selection probe in progress. Cannot handle request.\n"));
974 		helper_cliprdr_send_empty_response(This);
975 		return;
976 	}
977 
978 	xclip_clear_target_props(This);
979 
980 	if (This->xclip.rdesktop_is_selection_owner)
981 	{
982 		XChangeProperty(This->display, This->wnd, This->xclip.rdesktop_clipboard_target_atom,
983 				XA_INTEGER, 32, PropModeReplace, (unsigned char *) &format, 1);
984 
985 		XConvertSelection(This->display, This->xclip.primary_atom, This->xclip.rdesktop_native_atom,
986 				  This->xclip.rdesktop_clipboard_target_atom, This->wnd, CurrentTime);
987 		return;
988 	}
989 
990 	if (This->xclip.auto_mode)
991 		primary_owner = XGetSelectionOwner(This->display, This->xclip.primary_atom);
992 	else
993 		primary_owner = None;
994 
995 	clipboard_owner = XGetSelectionOwner(This->display, This->xclip.clipboard_atom);
996 
997 	/* Both available */
998 	if ((primary_owner != None) && (clipboard_owner != None))
999 	{
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);
1006 		return;
1007 	}
1008 
1009 	/* Just PRIMARY */
1010 	if (primary_owner != None)
1011 	{
1012 		XConvertSelection(This->display, This->xclip.primary_atom, This->xclip.targets_atom,
1013 				  This->xclip.rdesktop_clipboard_target_atom, This->wnd, CurrentTime);
1014 		return;
1015 	}
1016 
1017 	/* Just CLIPBOARD */
1018 	if (clipboard_owner != None)
1019 	{
1020 		XConvertSelection(This->display, This->xclip.clipboard_atom, This->xclip.targets_atom,
1021 				  This->xclip.rdesktop_clipboard_target_atom, This->wnd, CurrentTime);
1022 		return;
1023 	}
1024 
1025 	/* No data available */
1026 	helper_cliprdr_send_empty_response(This);
1027 }
1028 
1029 void
ui_clip_sync(RDPCLIENT * This)1030 ui_clip_sync(RDPCLIENT * This)
1031 {
1032 	xclip_probe_selections(This);
1033 }
1034 
1035 void
ui_clip_set_mode(RDPCLIENT * This,const char * optarg)1036 ui_clip_set_mode(RDPCLIENT * This, const char *optarg)
1037 {
1038 	This->rdpclip = True;
1039 
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;
1044 	else
1045 	{
1046 		warning("Invalid clipboard mode '%s'.\n", optarg);
1047 		This->rdpclip = False;
1048 	}
1049 }
1050 
1051 void
xclip_init(RDPCLIENT * This)1052 xclip_init(RDPCLIENT * This)
1053 {
1054 	if (!This->rdpclip)
1055 		return;
1056 
1057 	if (!cliprdr_init(This))
1058 		return;
1059 
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);
1074 
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;
1081 
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);
1087 
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;
1095 #endif
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;
1099 }
1100 
1101 void
xclip_deinit(RDPCLIENT * This)1102 xclip_deinit(RDPCLIENT * This)
1103 {
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);
1109 }
1110