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