/[rdesktop]/sourceforge.net/trunk/rdesktop/xclip.c
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Annotation of /sourceforge.net/trunk/rdesktop/xclip.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1210 - (hide annotations)
Mon Mar 27 11:31:10 2006 UTC (18 years, 2 months ago) by ossman_
File MIME type: text/plain
File size: 31230 byte(s)
Store the time when we acquired the selection so that we can correctly
report it when queried.

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

  ViewVC Help
Powered by ViewVC 1.1.26