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