/[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

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

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 435 by astrand, Wed Jul 9 09:18:20 2003 UTC revision 1210 by ossman_, Mon Mar 27 11:31:10 2006 UTC
# Line 23  Line 23 
23  #include <X11/Xatom.h>  #include <X11/Xatom.h>
24  #include "rdesktop.h"  #include "rdesktop.h"
25    
26  #define NUM_TARGETS 6  /*
27      To gain better understanding of this code, one could be assisted by the following documents:
28  extern Display *display;    - Inter-Client Communication Conventions Manual (ICCCM)
29  extern Window wnd;      HTML: http://tronche.com/gui/x/icccm/
30  extern Time last_gesturetime;      PDF:  http://ftp.xfree86.org/pub/XFree86/4.5.0/doc/PDF/icccm.pdf
31      - MSDN: Clipboard Formats
32  static Atom clipboard_atom, primary_atom, targets_atom, timestamp_atom;      http://msdn.microsoft.com/library/en-us/winui/winui/windowsuserinterface/dataexchange/clipboard/clipboardformats.asp
33  static Atom rdesktop_clipboard_target_atom, rdesktop_clipboard_formats_atom, incr_atom;  */
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 7
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 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    /* 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    /* 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    static Atom format_string_atom, format_utf8_string_atom, format_unicode_atom;
94    /* 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  static XSelectionRequestEvent selection_request;  static XSelectionRequestEvent selection_request;
101  static Atom targets[NUM_TARGETS];  /* 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    /* Array of offered clipboard targets that will be sent to fellow X clients upon a TARGETS request. */
110    static Atom targets[MAX_TARGETS];
111    static int num_targets;
112    /* Denotes that this client currently holds the PRIMARY selection. */
113  static int have_primary = 0;  static int have_primary = 0;
114    /* Denotes that an rdesktop (not this rdesktop) is owning the selection,
115       allowing us to interchange Windows native clipboard data directly. */
116  static int rdesktop_is_selection_owner = 0;  static int rdesktop_is_selection_owner = 0;
117    /* Time when we acquired the selection. */
118    static Time acquire_time = 0;
119    
120    /* Denotes that an INCR ("chunked") transfer is in progress. */
121    static int g_waiting_for_INCR = 0;
122    /* Denotes the target format of the ongoing INCR ("chunked") transfer. */
123    static Atom g_incr_target = 0;
124    /* Buffers an INCR transfer. */
125    static uint8 *g_clip_buffer = 0;
126    /* Denotes the size of g_clip_buffer. */
127    static uint32 g_clip_buflen = 0;
128    
129    /* 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    static void
134    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    #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            inptr = (uint16 *) data;
163            outptr = (uint16 *) result;
164    
165            /* Check for a reversed BOM */
166            Bool swap_endianess = (*inptr == 0xfffe);
167    
168            while ((uint8 *) inptr < data + *size)
169            {
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            *outptr++ = 0;          /* null termination */
178            *size = (uint8 *) outptr - result;
179    
180            return result;
181    }
182    #else
183    /* Translate LF to CR-LF. To do this, we must allocate more memory.
184       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    #endif
210    
211  static void  static void
212  xclip_provide_selection(XSelectionRequestEvent * req, Atom type, unsigned int format, uint8 * data,  xclip_provide_selection(XSelectionRequestEvent * req, Atom type, unsigned int format, uint8 * data,
# Line 42  xclip_provide_selection(XSelectionReques Line 214  xclip_provide_selection(XSelectionReques
214  {  {
215          XEvent xev;          XEvent xev;
216    
217          XChangeProperty(display, req->requestor, req->property,          XChangeProperty(g_display, req->requestor, req->property,
218                          type, format, PropModeReplace, data, length);                          type, format, PropModeReplace, data, length);
219    
220          xev.xselection.type = SelectionNotify;          xev.xselection.type = SelectionNotify;
# Line 53  xclip_provide_selection(XSelectionReques Line 225  xclip_provide_selection(XSelectionReques
225          xev.xselection.target = req->target;          xev.xselection.target = req->target;
226          xev.xselection.property = req->property;          xev.xselection.property = req->property;
227          xev.xselection.time = req->time;          xev.xselection.time = req->time;
228          XSendEvent(display, req->requestor, False, NoEventMask, &xev);          XSendEvent(g_display, req->requestor, False, NoEventMask, &xev);
229    }
230    
231    /* 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    xclip_send_data_with_convert(uint8 * source, size_t source_size, Atom target)
277    {
278    #ifdef USE_UNICODE_CLIPBOARD
279            if (target == format_string_atom ||
280                target == format_unicode_atom || target == format_utf8_string_atom)
281            {
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                    char *unicode_buffer;
292                    iconv_t cd;
293    
294                    if (target == format_string_atom)
295                    {
296                            char *locale_charset = nl_langinfo(CODESET);
297                            cd = iconv_open(WINDOWS_CODEPAGE, locale_charset);
298                            if (cd == (iconv_t) - 1)
299                            {
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                            if (cd == (iconv_t) - 1)
309                            {
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                            if (cd == (iconv_t) - 1)
318                            {
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                    char *unicode_buffer_remaining = unicode_buffer;
334                    char *data_remaining = (char *) source;
335                    size_t data_size_remaining = source_size;
336                    iconv(cd, (ICONV_CONST char **) &data_remaining, &data_size_remaining,
337                          &unicode_buffer_remaining, &unicode_buffer_size_remaining);
338                    iconv_close(cd);
339    
340                    /* translate linebreaks */
341                    uint32 translated_data_size = unicode_buffer_size - unicode_buffer_size_remaining;
342                    uint8 *translated_data =
343                            utf16_lf2crlf((uint8 *) unicode_buffer, &translated_data_size);
344                    if (translated_data != NULL)
345                    {
346                            DEBUG_CLIPBOARD(("Sending Unicode string of %d bytes\n",
347                                             translated_data_size));
348                            helper_cliprdr_send_response(translated_data, translated_data_size);
349                            xfree(translated_data); /* Not the same thing as XFree! */
350                    }
351    
352                    xfree(unicode_buffer);
353    
354                    return True;
355            }
356    #else
357            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                            helper_cliprdr_send_response(translated_data, length);
370                            xfree(translated_data); /* Not the same thing as XFree! */
371                    }
372    
373                    return True;
374            }
375    #endif
376            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    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    /* This function is called for SelectionNotify events.
397       The SelectionNotify event is sent from the clipboard owner to the requestor
398       after his request was satisfied.
399       If this function is called, we're the requestor side. */
400    #ifndef MAKE_PROTO
401  void  void
402  xclip_handle_SelectionNotify(XSelectionEvent * event)  xclip_handle_SelectionNotify(XSelectionEvent * event)
403  {  {
404          unsigned long nitems, bytes_left;          unsigned long nitems, bytes_left;
405          Atom type, best_target, text_target;          XWindowAttributes wa;
406            Atom type;
407          Atom *supported_targets;          Atom *supported_targets;
408          int res, i, format;          int res, i, format;
409          uint8 *data;          uint8 *data = NULL;
410    
411          if (event->property == None)          if (event->property == None)
412                  goto fail;                  goto fail;
413    
414          DEBUG_CLIPBOARD(("xclip_handle_SelectionNotify: selection=%s, target=%s, property=%s\n",          DEBUG_CLIPBOARD(("xclip_handle_SelectionNotify: selection=%s, target=%s, property=%s\n",
415                           XGetAtomName(display, event->selection),                           XGetAtomName(g_display, event->selection),
416                           XGetAtomName(display, event->target),                           XGetAtomName(g_display, event->target),
417                           XGetAtomName(display, event->property)));                           XGetAtomName(g_display, event->property)));
418    
419          if (event->property == None)          if (event->target == timestamp_atom)
420                  goto fail;          {
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    
436          res = XGetWindowProperty(display, wnd, rdesktop_clipboard_target_atom,  
437                                   0, XMaxRequestSize(display), True, AnyPropertyType,                  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            res = XGetWindowProperty(g_display, g_wnd, rdesktop_clipboard_target_atom,
486                                     0, XMaxRequestSize(g_display), False, AnyPropertyType,
487                                   &type, &format, &nitems, &bytes_left, &data);                                   &type, &format, &nitems, &bytes_left, &data);
488    
489            xclip_clear_target_props();
490    
491          if (res != Success)          if (res != Success)
492          {          {
493                  DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));                  DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));
494                  goto fail;                  goto fail;
495          }          }
496    
497            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                    g_incr_target = event->target;
508                    g_waiting_for_INCR = 1;
509                    return;
510            }
511    
512            /* Negotiate target format */
513          if (event->target == targets_atom)          if (event->target == targets_atom)
514          {          {
515                  /* FIXME: We should choose format here based on what the server wanted */                  /* Determine the best of text targets that we have available:
516                  best_target = XA_STRING;                     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                    Atom best_text_target = 0;      /* measures how much we're satisfied with what we found */
521                  if (type != None)                  if (type != None)
522                  {                  {
523                          supported_targets = (Atom *) data;                          supported_targets = (Atom *) data;
                         text_target = XInternAtom(display, "TEXT", False);  
524                          for (i = 0; i < nitems; i++)                          for (i = 0; i < nitems; i++)
525                          {                          {
526                                  DEBUG_CLIPBOARD(("Target %d: %s\n", i,                                  DEBUG_CLIPBOARD(("Target %d: %s\n", i,
527                                                   XGetAtomName(display, supported_targets[i])));                                                   XGetAtomName(g_display, supported_targets[i])));
528                                  if (supported_targets[i] == text_target)                                  if (supported_targets[i] == format_string_atom)
529                                    {
530                                            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                                    }
537    #ifdef USE_UNICODE_CLIPBOARD
538                                    else if (supported_targets[i] == format_unicode_atom)
539                                  {                                  {
540                                          DEBUG_CLIPBOARD(("Other party supports TEXT, choosing that as best_target\n"));                                          if (text_target_satisfaction < 2)
541                                          best_target = text_target;                                          {
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    #endif
557                          }                          }
                         XFree(data);  
558                  }                  }
559    
560                  XConvertSelection(display, primary_atom, best_target,                  /* Kickstarting the next step in the process of satisfying RDP's
561                                    rdesktop_clipboard_target_atom, wnd, event->time);                     clipboard request -- specifically, requesting the actual clipboard data.
562                  return;                   */
563                    if (best_text_target != 0)
564                    {
565                            XConvertSelection(g_display, event->selection, best_text_target,
566                                              rdesktop_clipboard_target_atom, g_wnd, event->time);
567                            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          }          }
575            else
         if (type == incr_atom)  
576          {          {
577                  warning("We don't support INCR transfers at this time. Try cutting less data.\n");                  if (!xclip_send_data_with_convert(data, nitems, event->target))
578                  goto fail;                  {
579                            goto fail;
580                    }
581          }          }
582    
         cliprdr_send_data(data, nitems + 1);  
583          XFree(data);          XFree(data);
584    
         if (!rdesktop_is_selection_owner)  
                 cliprdr_send_text_format_announce();  
585          return;          return;
586    
587        fail:        fail:
588          cliprdr_send_data(NULL, 0);          xclip_clear_target_props();
589            if (data)
590                    XFree(data);
591            helper_cliprdr_send_empty_response();
592  }  }
593    
594    /* This function is called for SelectionRequest events.
595       The SelectionRequest event is sent from the requestor to the clipboard owner
596       to request clipboard data.
597     */
598  void  void
599  xclip_handle_SelectionRequest(XSelectionRequestEvent * event)  xclip_handle_SelectionRequest(XSelectionRequestEvent * event)
600  {  {
601          unsigned long nitems, bytes_left;          unsigned long nitems, bytes_left;
602            unsigned char *prop_return;
603          uint32 *wanted_format;          uint32 *wanted_format;
604          int format, res;          int format, res;
605          Atom type;          Atom type;
606    
607          DEBUG_CLIPBOARD(("xclip_handle_SelectionRequest: selection=%s, target=%s, property=%s\n",          DEBUG_CLIPBOARD(("xclip_handle_SelectionRequest: selection=%s, target=%s, property=%s\n",
608                           XGetAtomName(display, event->selection),                           XGetAtomName(g_display, event->selection),
609                           XGetAtomName(display, event->target),                           XGetAtomName(g_display, event->target),
610                           XGetAtomName(display, event->property)));                           XGetAtomName(g_display, event->property)));
611    
612          if (event->target == targets_atom)          if (event->target == targets_atom)
613          {          {
614                  xclip_provide_selection(event, XA_ATOM, 32, (uint8 *) & targets, NUM_TARGETS);                  xclip_provide_selection(event, XA_ATOM, 32, (uint8 *) & targets, num_targets);
615                  return;                  return;
616          }          }
617          else if (event->target == timestamp_atom)          else if (event->target == timestamp_atom)
618          {          {
619                  xclip_provide_selection(event, XA_INTEGER, 32, (uint8 *) & last_gesturetime, 1);                  xclip_provide_selection(event, XA_INTEGER, 32, (uint8 *) & acquire_time, 1);
620                  return;                  return;
621          }          }
         else if (event->target == rdesktop_clipboard_formats_atom)  
         {  
                 res = XGetWindowProperty(display, event->requestor,  
                                          rdesktop_clipboard_target_atom, 0, 1, True, XA_INTEGER,  
                                          &type, &format, &nitems, &bytes_left,  
                                          (unsigned char **) &wanted_format);  
                 format = (res == Success) ? *wanted_format : CF_TEXT;  
         }  
622          else          else
623          {          {
624                  format = CF_TEXT;                  /* 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                                                     rdesktop_clipboard_target_atom, 0, 1, True,
641                                                     XA_INTEGER, &type, &format, &nitems, &bytes_left,
642                                                     &prop_return);
643                            wanted_format = (uint32 *) prop_return;
644                            format = (res == Success) ? *wanted_format : RDP_CF_TEXT;
645                            XFree(prop_return);
646                    }
647                    else if (event->target == format_string_atom || event->target == XA_STRING)
648                    {
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    #ifdef USE_UNICODE_CLIPBOARD
655                            format = CF_UNICODETEXT;
656    #else
657                            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    #endif
661                    }
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);                  cliprdr_send_data_request(format);
675          selection_request = *event;                  selection_request = *event;
676          /* wait for data */                  has_selection_request = True;
677                    return;         /* wait for data */
678            }
679  }  }
680    
681    /* 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       to some other X client. We should find out what clipboard formats this other
687       client offers and announce that to RDP. */
688  void  void
689  xclip_handle_SelectionClear(void)  xclip_handle_SelectionClear(void)
690  {  {
691          DEBUG_CLIPBOARD(("xclip_handle_SelectionClear\n"));          DEBUG_CLIPBOARD(("xclip_handle_SelectionClear\n"));
692          have_primary = 0;          have_primary = 0;
693          XDeleteProperty(display, DefaultRootWindow(display), rdesktop_clipboard_formats_atom);          XDeleteProperty(g_display, DefaultRootWindow(g_display), rdesktop_clipboard_formats_atom);
694          cliprdr_send_text_format_announce();          /* 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  }  }
700    
701    /* Called when any property changes in our window or the root window. */
702  void  void
703  xclip_handle_PropertyNotify(XPropertyEvent * event)  xclip_handle_PropertyNotify(XPropertyEvent * event)
704  {  {
705          unsigned long nitems, bytes_left;          unsigned long nitems;
706            unsigned long offset = 0;
707            unsigned long bytes_left = 1;
708          int format, res;          int format, res;
709            XWindowAttributes wa;
710          uint8 *data;          uint8 *data;
711          Atom type;          Atom type;
712    
713          if (event->atom != rdesktop_clipboard_formats_atom)          if (event->state == PropertyNewValue && g_waiting_for_INCR)
714                  return;          {
715                    DEBUG_CLIPBOARD(("x_clip_handle_PropertyNotify: g_waiting_for_INCR != 0\n"));
716    
717                    while (bytes_left > 0)
718                    {
719                            /* 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                            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                            {
726                                    XFree(data);
727                                    return;
728                            }
729    
730                            if (nitems == 0)
731                            {
732                                    /* INCR transfer finished */
733                                    XGetWindowAttributes(g_display, g_wnd, &wa);
734                                    XSelectInput(g_display, g_wnd,
735                                                 (wa.your_event_mask ^ PropertyChangeMask));
736                                    XFree(data);
737                                    g_waiting_for_INCR = 0;
738    
739                                    if (g_clip_buflen > 0)
740                                    {
741                                            if (!xclip_send_data_with_convert
742                                                (g_clip_buffer, g_clip_buflen, g_incr_target))
743                                            {
744                                                    helper_cliprdr_send_empty_response();
745                                            }
746                                            xfree(g_clip_buffer);
747                                            g_clip_buffer = NULL;
748                                            g_clip_buflen = 0;
749                                    }
750                            }
751                            else
752                            {
753                                    /* Another chunk in the INCR transfer */
754                                    offset += (nitems / 4); /* offset at which to begin the next slurp */
755                                    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    
759          if (have_primary)       /* from us */                                  XFree(data);
760                            }
761                    }
762                    XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_target_atom);
763                  return;                  return;
764            }
765    
766          if (event->state == PropertyNewValue)          if ((event->atom == rdesktop_clipboard_formats_atom) &&
767                (event->window == DefaultRootWindow(g_display)) &&
768                !have_primary /* not interested in our own events */ )
769          {          {
770                  res = XGetWindowProperty(display, DefaultRootWindow(display),                  if (event->state == PropertyNewValue)
                                          rdesktop_clipboard_formats_atom, 0,  
                                          XMaxRequestSize(display), False, XA_STRING, &type, &format,  
                                          &nitems, &bytes_left, &data);  
   
                 if ((res == Success) && (nitems > 0))  
771                  {                  {
772                          cliprdr_send_native_format_announce(data, nitems);                          DEBUG_CLIPBOARD(("xclip_handle_PropertyNotify: getting fellow rdesktop formats\n"));
773                          rdesktop_is_selection_owner = 1;  
774                          return;                          res = XGetWindowProperty(g_display, DefaultRootWindow(g_display),
775                                                     rdesktop_clipboard_formats_atom, 0,
776                                                     XMaxRequestSize(g_display), False, XA_STRING,
777                                                     &type, &format, &nitems, &bytes_left, &data);
778    
779                            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    
787          /* PropertyDelete, or XGetWindowProperty failed */                  /* For some reason, we couldn't announce the native formats */
788          cliprdr_send_text_format_announce();                  cliprdr_send_simple_native_format_announce(RDP_CF_TEXT);
789          rdesktop_is_selection_owner = 0;                  rdesktop_is_selection_owner = 0;
790            }
791  }  }
792    #endif
793    
794    
795    /* 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  void  void
801  ui_clip_format_announce(char *data, uint32 length)  ui_clip_format_announce(uint8 * data, uint32 length)
802  {  {
803          XSetSelectionOwner(display, primary_atom, wnd, last_gesturetime);          acquire_time = g_last_gesturetime;
804          if (XGetSelectionOwner(display, primary_atom) != wnd)  
805            XSetSelectionOwner(g_display, primary_atom, g_wnd, acquire_time);
806            if (XGetSelectionOwner(g_display, primary_atom) != g_wnd)
807          {          {
808                  warning("Failed to aquire ownership of PRIMARY clipboard\n");                  warning("Failed to aquire ownership of PRIMARY clipboard\n");
809                  return;                  return;
810          }          }
811    
812          have_primary = 1;          have_primary = 1;
813          XChangeProperty(display, DefaultRootWindow(display),          XChangeProperty(g_display, DefaultRootWindow(g_display),
814                          rdesktop_clipboard_formats_atom, XA_STRING, 8, PropModeReplace, data,                          rdesktop_clipboard_formats_atom, XA_STRING, 8, PropModeReplace, data,
815                          length);                          length);
816    
817          XSetSelectionOwner(display, clipboard_atom, wnd, last_gesturetime);          XSetSelectionOwner(g_display, clipboard_atom, g_wnd, acquire_time);
818          if (XGetSelectionOwner(display, clipboard_atom) != wnd)          if (XGetSelectionOwner(g_display, clipboard_atom) != g_wnd)
819                  warning("Failed to aquire ownership of CLIPBOARD clipboard\n");                  warning("Failed to aquire ownership of CLIPBOARD clipboard\n");
820  }  }
821    
822    /* Called when the RDP server responds with clipboard data (after we've requested it). */
823  void  void
824  ui_clip_handle_data(char *data, uint32 length)  ui_clip_handle_data(uint8 * data, uint32 length)
825  {  {
826          xclip_provide_selection(&selection_request, XA_STRING, 8, data, length - 1);          BOOL free_data = False;
827    
828            if (length == 0)
829            {
830                    xclip_refuse_selection(&selection_request);
831                    has_selection_request = False;
832                    return;
833            }
834    
835            if (selection_request.target == format_string_atom || selection_request.target == XA_STRING)
836            {
837                    /* We're expecting a CF_TEXT response */
838                    uint8 *firstnull;
839    
840                    /* translate linebreaks */
841                    crlf2lf(data, &length);
842    
843                    /* 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    #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                    if (cd != (iconv_t) - 1)
856                    {
857                            size_t utf8_length = length * 2;
858                            char *utf8_data = malloc(utf8_length);
859                            size_t utf8_length_remaining = utf8_length;
860                            char *utf8_data_remaining = utf8_data;
861                            char *data_remaining = (char *) data;
862                            size_t length_remaining = (size_t) length;
863                            if (utf8_data == NULL)
864                            {
865                                    iconv_close(cd);
866                                    return;
867                            }
868                            iconv(cd, (ICONV_CONST char **) &data_remaining, &length_remaining,
869                                  &utf8_data_remaining, &utf8_length_remaining);
870                            iconv_close(cd);
871                            free_data = True;
872                            data = (uint8 *) utf8_data;
873                            length = utf8_length - utf8_length_remaining;
874                    }
875            }
876            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    
894            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  }  }
900    
901  void  void
902  ui_clip_request_data(uint32 format)  ui_clip_request_data(uint32 format)
903  {  {
904          Window selectionowner;          Window primary_owner, clipboard_owner;
905    
906          DEBUG_CLIPBOARD(("Request from server for format %d\n", format));          DEBUG_CLIPBOARD(("Request from server for format %d\n", format));
907            rdp_clipboard_request_format = format;
908    
909            xclip_clear_target_props();
910    
911          if (rdesktop_is_selection_owner)          if (rdesktop_is_selection_owner)
912          {          {
913                  XChangeProperty(display, wnd, rdesktop_clipboard_target_atom,                  XChangeProperty(g_display, g_wnd, rdesktop_clipboard_target_atom,
914                                  XA_INTEGER, 32, PropModeReplace, (unsigned char *) &format, 1);                                  XA_INTEGER, 32, PropModeReplace, (unsigned char *) &format, 1);
915    
916                  XConvertSelection(display, primary_atom, rdesktop_clipboard_formats_atom,                  XConvertSelection(g_display, primary_atom, rdesktop_clipboard_formats_atom,
917                                    rdesktop_clipboard_target_atom, wnd, CurrentTime);                                    rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
918                  return;                  return;
919          }          }
920    
921          selectionowner = XGetSelectionOwner(display, primary_atom);          if (auto_mode)
922          if (selectionowner != None)                  primary_owner = XGetSelectionOwner(g_display, primary_atom);
923            else
924                    primary_owner = None;
925    
926            clipboard_owner = XGetSelectionOwner(g_display, clipboard_atom);
927    
928            /* Both available */
929            if ((primary_owner != None) && (clipboard_owner != None))
930          {          {
931                  XConvertSelection(display, primary_atom, targets_atom,                  primary_timestamp = 0;
932                                    rdesktop_clipboard_target_atom, wnd, CurrentTime);                  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;                  return;
938          }          }
939    
940          /* No PRIMARY, try CLIPBOARD */          /* Just PRIMARY */
941          selectionowner = XGetSelectionOwner(display, clipboard_atom);          if (primary_owner != None)
         if (selectionowner != None)  
942          {          {
943                  XConvertSelection(display, clipboard_atom, targets_atom,                  XConvertSelection(g_display, primary_atom, targets_atom,
944                                    rdesktop_clipboard_target_atom, wnd, CurrentTime);                                    rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
945                    return;
946            }
947    
948            /* Just CLIPBOARD */
949            if (clipboard_owner != None)
950            {
951                    XConvertSelection(g_display, clipboard_atom, targets_atom,
952                                      rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
953                  return;                  return;
954          }          }
955    
956          /* No data available */          /* No data available */
957          cliprdr_send_data(NULL, 0);          helper_cliprdr_send_empty_response();
958  }  }
959    
960  void  void
961  ui_clip_sync(void)  ui_clip_sync(void)
962  {  {
963          cliprdr_send_text_format_announce();          cliprdr_send_simple_native_format_announce(RDP_CF_TEXT);
964  }  }
965    
966    void
967    ui_clip_set_mode(const char *optarg)
968    {
969            g_rdpclip = True;
970    
971            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  void  void
984  xclip_init(void)  xclip_init(void)
985  {  {
986            if (!g_rdpclip)
987                    return;
988    
989          if (!cliprdr_init())          if (!cliprdr_init())
990                  return;                  return;
991    
992          primary_atom = XInternAtom(display, "PRIMARY", False);          primary_atom = XInternAtom(g_display, "PRIMARY", False);
993          clipboard_atom = XInternAtom(display, "CLIPBOARD", False);          clipboard_atom = XInternAtom(g_display, "CLIPBOARD", False);
994          targets_atom = XInternAtom(display, "TARGETS", False);          targets_atom = XInternAtom(g_display, "TARGETS", False);
995          timestamp_atom = XInternAtom(display, "TIMESTAMP", False);          timestamp_atom = XInternAtom(g_display, "TIMESTAMP", False);
996          rdesktop_clipboard_target_atom = XInternAtom(display, "_RDESKTOP_CLIPBOARD_TARGET", False);          rdesktop_clipboard_target_atom =
997          incr_atom = XInternAtom(display, "INCR", False);                  XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_TARGET", False);
998          targets[0] = targets_atom;          rdesktop_primary_timestamp_target_atom =
999          targets[1] = XInternAtom(display, "TEXT", False);                  XInternAtom(g_display, "_RDESKTOP_PRIMARY_TIMESTAMP_TARGET", False);
1000          targets[2] = XInternAtom(display, "UTF8_STRING", False);          rdesktop_clipboard_timestamp_target_atom =
1001          targets[3] = XInternAtom(display, "text/unicode", False);                  XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_TIMESTAMP_TARGET", False);
1002          targets[4] = XInternAtom(display, "TIMESTAMP", False);          incr_atom = XInternAtom(g_display, "INCR", False);
1003          targets[5] = XA_STRING;          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    
1007          /* rdesktop sets _RDESKTOP_CLIPBOARD_FORMATS on the root window when acquiring the clipboard.          /* 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. */             Other interested rdesktops can use this to notify their server of the available formats. */
1009          rdesktop_clipboard_formats_atom =          rdesktop_clipboard_formats_atom =
1010                  XInternAtom(display, "_RDESKTOP_CLIPBOARD_FORMATS", False);                  XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_FORMATS", False);
1011          XSelectInput(display, DefaultRootWindow(display), PropertyChangeMask);          XSelectInput(g_display, DefaultRootWindow(g_display), PropertyChangeMask);
1012    
1013            num_targets = 0;
1014            targets[num_targets++] = targets_atom;
1015            targets[num_targets++] = timestamp_atom;
1016            targets[num_targets++] = rdesktop_clipboard_formats_atom;
1017    #ifdef USE_UNICODE_CLIPBOARD
1018            targets[num_targets++] = format_utf8_string_atom;
1019    #endif
1020            targets[num_targets++] = format_unicode_atom;
1021            targets[num_targets++] = format_string_atom;
1022            targets[num_targets++] = XA_STRING;
1023  }  }

Legend:
Removed from v.435  
changed lines
  Added in v.1210

  ViewVC Help
Powered by ViewVC 1.1.26