/[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 1207 - (hide annotations)
Mon Mar 27 09:20:24 2006 UTC (18 years, 2 months ago) by ossman_
File MIME type: text/plain
File size: 31023 byte(s)
Control clipboard behaviour (and even disable it) using command line options.

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

  ViewVC Help
Powered by ViewVC 1.1.26