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