/[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 1025 by forsberg, Mon Nov 7 13:15:19 2005 UTC revision 1212 by ossman_, Mon Mar 27 13:03:27 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      - Inter-Client Communication Conventions Manual (ICCCM)
29        HTML: http://tronche.com/gui/x/icccm/
30        PDF:  http://ftp.xfree86.org/pub/XFree86/4.5.0/doc/PDF/icccm.pdf
31      - MSDN: Clipboard Formats
32        http://msdn.microsoft.com/library/en-us/winui/winui/windowsuserinterface/dataexchange/clipboard/clipboardformats.asp
33    */
34    
35    #ifdef HAVE_ICONV
36    #ifdef HAVE_LANGINFO_H
37    #ifdef HAVE_ICONV_H
38    #include <langinfo.h>
39    #include <iconv.h>
40    #define USE_UNICODE_CLIPBOARD
41    #endif
42    #endif
43    #endif
44    
45    #ifdef USE_UNICODE_CLIPBOARD
46    #define RDP_CF_TEXT CF_UNICODETEXT
47    #else
48    #define RDP_CF_TEXT CF_TEXT
49    #endif
50    
51    #define MAX_TARGETS 7
52    
53  extern Display *g_display;  extern Display *g_display;
54  extern Window g_wnd;  extern Window g_wnd;
55  extern Time g_last_gesturetime;  extern Time g_last_gesturetime;
56    extern BOOL g_rdpclip;
57    
58  static Atom clipboard_atom, primary_atom, targets_atom, timestamp_atom;  /* Mode of operation.
59  static Atom rdesktop_clipboard_target_atom, rdesktop_clipboard_formats_atom, incr_atom;     - 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;  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;  static uint8 *g_clip_buffer = 0;
126    /* Denotes the size of g_clip_buffer. */
127  static uint32 g_clip_buflen = 0;  static uint32 g_clip_buflen = 0;
128    
129  /* Replace CR-LF to LF (well, rather removing all CR:s) This is done  /* Translate LF to CR-LF. To do this, we must allocate more memory.
130     in-place. The length is updated. Handles embedded nulls */     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  static void
134  crlf2lf(uint8 * data, uint32 * length)  crlf2lf(uint8 * data, uint32 * length)
135  {  {
# Line 56  crlf2lf(uint8 * data, uint32 * length) Line 144  crlf2lf(uint8 * data, uint32 * length)
144          *length = dst - data;          *length = dst - data;
145  }  }
146    
147  /* Translate LF to CR-LF. To do this, we must allocate more memory.    #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. */     The length is updated. */
185  static uint8 *  static uint8 *
186  lf2crlf(uint8 * data, uint32 * length)  lf2crlf(uint8 * data, uint32 * length)
# Line 82  lf2crlf(uint8 * data, uint32 * length) Line 206  lf2crlf(uint8 * data, uint32 * length)
206    
207          return result;          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 104  xclip_provide_selection(XSelectionReques Line 228  xclip_provide_selection(XSelectionReques
228          XSendEvent(g_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    /* Examines the special root window property that contains the native Windows
251       formats if another rdesktop process owns the clipboard. */
252    static void
253    xclip_get_root_property()
254    {
255            unsigned long nitems;
256            unsigned long bytes_left;
257            int format, res;
258            uint8 *data;
259            Atom type;
260    
261            res = XGetWindowProperty(g_display, DefaultRootWindow(g_display),
262                                     rdesktop_clipboard_formats_atom, 0,
263                                     XMaxRequestSize(g_display), False, XA_STRING,
264                                     &type, &format, &nitems, &bytes_left, &data);
265    
266            if ((res == Success) && (nitems > 0))
267            {
268                    DEBUG_CLIPBOARD(("xclip_get_root_property: got fellow rdesktop formats\n"));
269                    cliprdr_send_native_format_announce(data, nitems);
270                    rdesktop_is_selection_owner = 1;
271                    return;
272            }
273    
274            /* For some reason, we couldn't announce the native formats */
275            cliprdr_send_simple_native_format_announce(RDP_CF_TEXT);
276            rdesktop_is_selection_owner = 0;
277    }
278    
279    /* Wrapper for cliprdr_send_data which also cleans the request state. */
280    static void
281    helper_cliprdr_send_response(uint8 * data, uint32 length)
282    {
283            if (rdp_clipboard_request_format != 0)
284            {
285                    cliprdr_send_data(data, length);
286                    rdp_clipboard_request_format = 0;
287                    if (!rdesktop_is_selection_owner)
288                            cliprdr_send_simple_native_format_announce(RDP_CF_TEXT);
289            }
290    }
291    
292    /* Last resort, when we have to provide clipboard data but for whatever
293       reason couldn't get any.
294     */
295    static void
296    helper_cliprdr_send_empty_response()
297    {
298            helper_cliprdr_send_response(NULL, 0);
299    }
300    
301    /* Replies with clipboard data to RDP, converting it from the target format
302       to the expected RDP format as necessary. Returns true if data was sent.
303     */
304    static Bool
305    xclip_send_data_with_convert(uint8 * source, size_t source_size, Atom target)
306    {
307    #ifdef USE_UNICODE_CLIPBOARD
308            if (target == format_string_atom ||
309                target == format_unicode_atom || target == format_utf8_string_atom)
310            {
311                    if (rdp_clipboard_request_format != RDP_CF_TEXT)
312                            return False;
313    
314                    /* Make an attempt to convert any string we send to Unicode.
315                       We don't know what the RDP server's ANSI Codepage is, or how to convert
316                       to it, so using CF_TEXT is not safe (and is unnecessary, since all
317                       WinNT versions are Unicode-minded).
318                     */
319                    size_t unicode_buffer_size;
320                    char *unicode_buffer;
321                    iconv_t cd;
322    
323                    if (target == format_string_atom)
324                    {
325                            char *locale_charset = nl_langinfo(CODESET);
326                            cd = iconv_open(WINDOWS_CODEPAGE, locale_charset);
327                            if (cd == (iconv_t) - 1)
328                            {
329                                    DEBUG_CLIPBOARD(("Locale charset %s not found in iconv. Unable to convert clipboard text.\n", locale_charset));
330                                    return False;
331                            }
332                            unicode_buffer_size = source_size * 4;
333                    }
334                    else if (target == format_unicode_atom)
335                    {
336                            cd = iconv_open(WINDOWS_CODEPAGE, "UCS-2");
337                            if (cd == (iconv_t) - 1)
338                            {
339                                    return False;
340                            }
341                            unicode_buffer_size = source_size;
342                    }
343                    else if (target == format_utf8_string_atom)
344                    {
345                            cd = iconv_open(WINDOWS_CODEPAGE, "UTF-8");
346                            if (cd == (iconv_t) - 1)
347                            {
348                                    return False;
349                            }
350                            /* UTF-8 is guaranteed to be less or equally compact
351                               as UTF-16 for all Unicode chars >=2 bytes.
352                             */
353                            unicode_buffer_size = source_size * 2;
354                    }
355                    else
356                    {
357                            return False;
358                    }
359    
360                    unicode_buffer = xmalloc(unicode_buffer_size);
361                    size_t unicode_buffer_size_remaining = unicode_buffer_size;
362                    char *unicode_buffer_remaining = unicode_buffer;
363                    char *data_remaining = (char *) source;
364                    size_t data_size_remaining = source_size;
365                    iconv(cd, (ICONV_CONST char **) &data_remaining, &data_size_remaining,
366                          &unicode_buffer_remaining, &unicode_buffer_size_remaining);
367                    iconv_close(cd);
368    
369                    /* translate linebreaks */
370                    uint32 translated_data_size = unicode_buffer_size - unicode_buffer_size_remaining;
371                    uint8 *translated_data =
372                            utf16_lf2crlf((uint8 *) unicode_buffer, &translated_data_size);
373                    if (translated_data != NULL)
374                    {
375                            DEBUG_CLIPBOARD(("Sending Unicode string of %d bytes\n",
376                                             translated_data_size));
377                            helper_cliprdr_send_response(translated_data, translated_data_size);
378                            xfree(translated_data); /* Not the same thing as XFree! */
379                    }
380    
381                    xfree(unicode_buffer);
382    
383                    return True;
384            }
385    #else
386            if (target == format_string_atom)
387            {
388                    uint8 *translated_data;
389                    uint32 length = source_size;
390    
391                    if (rdp_clipboard_request_format != RDP_CF_TEXT)
392                            return False;
393    
394                    DEBUG_CLIPBOARD(("Translating linebreaks before sending data\n"));
395                    translated_data = lf2crlf(source, &length);
396                    if (translated_data != NULL)
397                    {
398                            helper_cliprdr_send_response(translated_data, length);
399                            xfree(translated_data); /* Not the same thing as XFree! */
400                    }
401    
402                    return True;
403            }
404    #endif
405            else if (target == rdesktop_clipboard_formats_atom)
406            {
407                    helper_cliprdr_send_response(source, source_size + 1);
408    
409                    return True;
410            }
411            else
412            {
413                    return False;
414            }
415    }
416    
417    static void
418    xclip_clear_target_props()
419    {
420            XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_target_atom);
421            XDeleteProperty(g_display, g_wnd, rdesktop_primary_timestamp_target_atom);
422            XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_timestamp_target_atom);
423    }
424    
425    /* This function is called for SelectionNotify events.
426       The SelectionNotify event is sent from the clipboard owner to the requestor
427       after his request was satisfied.
428       If this function is called, we're the requestor side. */
429  #ifndef MAKE_PROTO  #ifndef MAKE_PROTO
430  void  void
431  xclip_handle_SelectionNotify(XSelectionEvent * event)  xclip_handle_SelectionNotify(XSelectionEvent * event)
432  {  {
433          unsigned long nitems, bytes_left;          unsigned long nitems, bytes_left;
434          XWindowAttributes wa;          XWindowAttributes wa;
435          Atom type, best_target, text_target;          Atom type;
436          Atom *supported_targets;          Atom *supported_targets;
437          int res, i, format;          int res, i, format;
438          uint8 *data;          uint8 *data = NULL;
439    
440          if (event->property == None)          if (event->property == None)
441                  goto fail;                  goto fail;
# Line 123  xclip_handle_SelectionNotify(XSelectionE Line 445  xclip_handle_SelectionNotify(XSelectionE
445                           XGetAtomName(g_display, event->target),                           XGetAtomName(g_display, event->target),
446                           XGetAtomName(g_display, event->property)));                           XGetAtomName(g_display, event->property)));
447    
448          if (event->property == None)          if (event->target == timestamp_atom)
449                  goto fail;          {
450                    if (event->selection == primary_atom)
451                    {
452                            res = XGetWindowProperty(g_display, g_wnd,
453                                                     rdesktop_primary_timestamp_target_atom, 0,
454                                                     XMaxRequestSize(g_display), False, XA_INTEGER,
455                                                     &type, &format, &nitems, &bytes_left, &data);
456                    }
457                    else
458                    {
459                            res = XGetWindowProperty(g_display, g_wnd,
460                                                     rdesktop_clipboard_timestamp_target_atom, 0,
461                                                     XMaxRequestSize(g_display), False, XA_INTEGER,
462                                                     &type, &format, &nitems, &bytes_left, &data);
463                    }
464    
465    
466                    if ((res != Success) || (nitems != 1))
467                    {
468                            DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));
469                            goto fail;
470                    }
471    
472                    if (event->selection == primary_atom)
473                    {
474                            primary_timestamp = *(Time *) data;
475                            if (primary_timestamp == 0)
476                                    primary_timestamp++;
477                            XDeleteProperty(g_display, g_wnd, rdesktop_primary_timestamp_target_atom);
478                            DEBUG_CLIPBOARD(("Got PRIMARY timestamp: %u\n",
479                                             (unsigned) primary_timestamp));
480                    }
481                    else
482                    {
483                            clipboard_timestamp = *(Time *) data;
484                            if (clipboard_timestamp == 0)
485                                    clipboard_timestamp++;
486                            XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_timestamp_target_atom);
487                            DEBUG_CLIPBOARD(("Got CLIPBOARD timestamp: %u\n",
488                                             (unsigned) clipboard_timestamp));
489                    }
490    
491                    XFree(data);
492    
493                    if (primary_timestamp && clipboard_timestamp)
494                    {
495                            if (primary_timestamp > clipboard_timestamp)
496                            {
497                                    DEBUG_CLIPBOARD(("PRIMARY is most recent selection.\n"));
498                                    XConvertSelection(g_display, primary_atom, targets_atom,
499                                                      rdesktop_clipboard_target_atom, g_wnd,
500                                                      event->time);
501                            }
502                            else
503                            {
504                                    DEBUG_CLIPBOARD(("CLIPBOARD is most recent selection.\n"));
505                                    XConvertSelection(g_display, clipboard_atom, targets_atom,
506                                                      rdesktop_clipboard_target_atom, g_wnd,
507                                                      event->time);
508                            }
509                    }
510    
511                    return;
512            }
513    
514          res = XGetWindowProperty(g_display, g_wnd, rdesktop_clipboard_target_atom,          res = XGetWindowProperty(g_display, g_wnd, rdesktop_clipboard_target_atom,
515                                   0, XMaxRequestSize(g_display), True, AnyPropertyType,                                   0, XMaxRequestSize(g_display), False, AnyPropertyType,
516                                   &type, &format, &nitems, &bytes_left, &data);                                   &type, &format, &nitems, &bytes_left, &data);
517    
518            xclip_clear_target_props();
519    
520          if (res != Success)          if (res != Success)
521          {          {
522                  DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));                  DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));
523                  goto fail;                  goto fail;
524          }          }
525    
526            if (type == incr_atom)
527            {
528                    DEBUG_CLIPBOARD(("Received INCR.\n"));
529    
530                    XGetWindowAttributes(g_display, g_wnd, &wa);
531                    if ((wa.your_event_mask | PropertyChangeMask) != wa.your_event_mask)
532                    {
533                            XSelectInput(g_display, g_wnd, (wa.your_event_mask | PropertyChangeMask));
534                    }
535                    XFree(data);
536                    g_incr_target = event->target;
537                    g_waiting_for_INCR = 1;
538                    return;
539            }
540    
541          /* Negotiate target format */          /* Negotiate target format */
542          if (event->target == targets_atom)          if (event->target == targets_atom)
543          {          {
544                  /* FIXME: We should choose format here based on what the server wanted */                  /* Determine the best of text targets that we have available:
545                  best_target = XA_STRING;                     Prefer UTF8_STRING > text/unicode (unspecified encoding) > STRING
546                       (ignore TEXT and COMPOUND_TEXT because we don't have code to handle them)
547                     */
548                    int text_target_satisfaction = 0;
549                    Atom best_text_target = 0;      /* measures how much we're satisfied with what we found */
550                  if (type != None)                  if (type != None)
551                  {                  {
552                          supported_targets = (Atom *) data;                          supported_targets = (Atom *) data;
                         text_target = XInternAtom(g_display, "TEXT", False);  
553                          for (i = 0; i < nitems; i++)                          for (i = 0; i < nitems; i++)
554                          {                          {
555                                  DEBUG_CLIPBOARD(("Target %d: %s\n", i,                                  DEBUG_CLIPBOARD(("Target %d: %s\n", i,
556                                                   XGetAtomName(g_display, supported_targets[i])));                                                   XGetAtomName(g_display, supported_targets[i])));
557                                  if (supported_targets[i] == text_target)                                  if (supported_targets[i] == format_string_atom)
558                                    {
559                                            if (text_target_satisfaction < 1)
560                                            {
561                                                    DEBUG_CLIPBOARD(("Other party supports STRING, choosing that as best_target\n"));
562                                                    best_text_target = supported_targets[i];
563                                                    text_target_satisfaction = 1;
564                                            }
565                                    }
566    #ifdef USE_UNICODE_CLIPBOARD
567                                    else if (supported_targets[i] == format_unicode_atom)
568                                    {
569                                            if (text_target_satisfaction < 2)
570                                            {
571                                                    DEBUG_CLIPBOARD(("Other party supports text/unicode, choosing that as best_target\n"));
572                                                    best_text_target = supported_targets[i];
573                                                    text_target_satisfaction = 2;
574                                            }
575                                    }
576                                    else if (supported_targets[i] == format_utf8_string_atom)
577                                  {                                  {
578                                          DEBUG_CLIPBOARD(("Other party supports TEXT, choosing that as best_target\n"));                                          if (text_target_satisfaction < 3)
579                                          best_target = text_target;                                          {
580                                          break;                                                  DEBUG_CLIPBOARD(("Other party supports UTF8_STRING, choosing that as best_target\n"));
581                                                    best_text_target = supported_targets[i];
582                                                    text_target_satisfaction = 3;
583                                            }
584                                  }                                  }
585    #endif
586                          }                          }
                         XFree(data);  
                 }  
   
                 XConvertSelection(g_display, primary_atom, best_target,  
                                   rdesktop_clipboard_target_atom, g_wnd, event->time);  
                 return;  
         }  
   
         if (type == incr_atom)  
         {  
                 DEBUG_CLIPBOARD(("Received INCR.\n"));  
   
                 XGetWindowAttributes(g_display, g_wnd, &wa);  
                 if ((wa.your_event_mask | PropertyChangeMask) != wa.your_event_mask)  
                 {  
                         XSelectInput(g_display, g_wnd, (wa.your_event_mask | PropertyChangeMask));  
587                  }                  }
                 XDeleteProperty(g_display, g_wnd, type);  
                 XFree(data);  
                 g_waiting_for_INCR = 1;  
588    
589                  if ((XGetWindowProperty(g_display, g_wnd, rdesktop_clipboard_target_atom, 0,                  /* Kickstarting the next step in the process of satisfying RDP's
590                                          4096L, True, AnyPropertyType,                     clipboard request -- specifically, requesting the actual clipboard data.
591                                          &type, &format, &nitems, &bytes_left, &data) != Success))                   */
592                    if (best_text_target != 0)
593                  {                  {
594                          DEBUG_CLIPBOARD(("XGetWindowProperty failed.\n"));                          XConvertSelection(g_display, event->selection, best_text_target,
595                          goto fail;                                            rdesktop_clipboard_target_atom, g_wnd, event->time);
596                            return;
597                  }                  }
598                  else                  else
599                  {                  {
600                          uint8 *translated_data;                          DEBUG_CLIPBOARD(("Unable to find a textual target to satisfy RDP clipboard text request\n"));
601                          uint32 length = nitems;                          goto fail;
   
                         translated_data = lf2crlf(data, &length);  
   
                         g_clip_buffer = (uint8 *) xmalloc(length);  
                         strncpy((char *) g_clip_buffer, (char *) translated_data, length);  
                         xfree(translated_data);  
                         g_clip_buflen = length;  
   
                         XFree(data);  
                         return;  
602                  }                  }
603          }          }
   
         /* Translate linebreaks, but only if not getting data from  
            other rdesktop instance */  
         if (event->target != rdesktop_clipboard_formats_atom)  
         {  
                 uint8 *translated_data;  
                 uint32 length = nitems;  
   
                 DEBUG_CLIPBOARD(("Translating linebreaks before sending data\n"));  
                 translated_data = lf2crlf(data, &length);  
                 cliprdr_send_data(translated_data, length + 1);  
                 xfree(translated_data); /* Not the same thing as XFree! */  
         }  
604          else          else
605          {          {
606                  cliprdr_send_data(data, nitems + 1);                  if (!xclip_send_data_with_convert(data, nitems, event->target))
607                    {
608                            goto fail;
609                    }
610          }          }
611    
612          XFree(data);          XFree(data);
613    
         if (!rdesktop_is_selection_owner)  
                 cliprdr_send_simple_native_format_announce(CF_TEXT);  
614          return;          return;
615    
616        fail:        fail:
617          cliprdr_send_data(NULL, 0);          xclip_clear_target_props();
618            if (data)
619                    XFree(data);
620            helper_cliprdr_send_empty_response();
621  }  }
622    
623    /* This function is called for SelectionRequest events.
624       The SelectionRequest event is sent from the requestor to the clipboard owner
625       to request clipboard data.
626     */
627  void  void
628  xclip_handle_SelectionRequest(XSelectionRequestEvent * event)  xclip_handle_SelectionRequest(XSelectionRequestEvent * event)
629  {  {
# Line 243  xclip_handle_SelectionRequest(XSelection Line 640  xclip_handle_SelectionRequest(XSelection
640    
641          if (event->target == targets_atom)          if (event->target == targets_atom)
642          {          {
643                  xclip_provide_selection(event, XA_ATOM, 32, (uint8 *) & targets, NUM_TARGETS);                  xclip_provide_selection(event, XA_ATOM, 32, (uint8 *) & targets, num_targets);
644                  return;                  return;
645          }          }
646          else if (event->target == timestamp_atom)          else if (event->target == timestamp_atom)
647          {          {
648                  xclip_provide_selection(event, XA_INTEGER, 32, (uint8 *) & g_last_gesturetime, 1);                  xclip_provide_selection(event, XA_INTEGER, 32, (uint8 *) & acquire_time, 1);
649                  return;                  return;
650          }          }
         else if (event->target == rdesktop_clipboard_formats_atom)  
         {  
                 res = XGetWindowProperty(g_display, event->requestor,  
                                          rdesktop_clipboard_target_atom, 0, 1, True, XA_INTEGER,  
                                          &type, &format, &nitems, &bytes_left, &prop_return);  
                 wanted_format = (uint32 *) prop_return;  
                 format = (res == Success) ? *wanted_format : CF_TEXT;  
                 /* FIXME: Need to free returned data? */  
         }  
651          else          else
652          {          {
653                  format = CF_TEXT;                  /* All the following targets require an async operation with the RDP server
654          }                     and currently we don't do X clipboard request queueing so we can only
655                       handle one such request at a time. */
656                    if (has_selection_request)
657                    {
658                            DEBUG_CLIPBOARD(("Error: Another clipboard request was already sent to the RDP server and not yet responded. Refusing this request.\n"));
659                            xclip_refuse_selection(event);
660                            return;
661                    }
662                    if (event->target == rdesktop_clipboard_formats_atom)
663                    {
664                            /* Before the requestor makes a request for the _RDESKTOP_CLIPBOARD_FORMATS target,
665                               he should declare requestor[_RDESKTOP_CLIPBOARD_TARGET] = CF_SOMETHING.
666                               Otherwise, we default to RDP_CF_TEXT.
667                             */
668                            res = XGetWindowProperty(g_display, event->requestor,
669                                                     rdesktop_clipboard_target_atom, 0, 1, True,
670                                                     XA_INTEGER, &type, &format, &nitems, &bytes_left,
671                                                     &prop_return);
672                            wanted_format = (uint32 *) prop_return;
673                            format = (res == Success) ? *wanted_format : RDP_CF_TEXT;
674                            XFree(prop_return);
675                    }
676                    else if (event->target == format_string_atom || event->target == XA_STRING)
677                    {
678                            /* STRING and XA_STRING are defined to be ISO8859-1 */
679                            format = CF_TEXT;
680                    }
681                    else if (event->target == format_utf8_string_atom)
682                    {
683    #ifdef USE_UNICODE_CLIPBOARD
684                            format = CF_UNICODETEXT;
685    #else
686                            DEBUG_CLIPBOARD(("Requested target unavailable due to lack of Unicode support. (It was not in TARGETS, so why did you ask for it?!)\n"));
687                            xclip_refuse_selection(event);
688                            return;
689    #endif
690                    }
691                    else if (event->target == format_unicode_atom)
692                    {
693                            /* Assuming text/unicode to be UTF-16 */
694                            format = CF_UNICODETEXT;
695                    }
696                    else
697                    {
698                            DEBUG_CLIPBOARD(("Requested target unavailable. (It was not in TARGETS, so why did you ask for it?!)\n"));
699                            xclip_refuse_selection(event);
700                            return;
701                    }
702    
703          cliprdr_send_data_request(format);                  cliprdr_send_data_request(format);
704          selection_request = *event;                  selection_request = *event;
705          /* wait for data */                  has_selection_request = True;
706                    return;         /* wait for data */
707            }
708  }  }
709    
710    /* While this rdesktop holds ownership over the clipboard, it means the clipboard data
711       is offered by the RDP server (and when it is pasted inside RDP, there's no network
712       roundtrip).
713    
714       This event (SelectionClear) symbolizes this rdesktop lost onwership of the clipboard
715       to some other X client. We should find out what clipboard formats this other
716       client offers and announce that to RDP. */
717  void  void
718  xclip_handle_SelectionClear(void)  xclip_handle_SelectionClear(void)
719  {  {
720          DEBUG_CLIPBOARD(("xclip_handle_SelectionClear\n"));          DEBUG_CLIPBOARD(("xclip_handle_SelectionClear\n"));
721          have_primary = 0;          have_primary = 0;
722          XDeleteProperty(g_display, DefaultRootWindow(g_display), rdesktop_clipboard_formats_atom);          XDeleteProperty(g_display, DefaultRootWindow(g_display), rdesktop_clipboard_formats_atom);
723          cliprdr_send_simple_native_format_announce(CF_TEXT);          /* FIXME:
724               Without XFIXES, we cannot reliably know the formats offered by the
725               new owner of the X11 clipboard, so we just lie about him
726               offering RDP_CF_TEXT. */
727            cliprdr_send_simple_native_format_announce(RDP_CF_TEXT);
728  }  }
729    
730    /* Called when any property changes in our window or the root window. */
731  void  void
732  xclip_handle_PropertyNotify(XPropertyEvent * event)  xclip_handle_PropertyNotify(XPropertyEvent * event)
733  {  {
734          unsigned long nitems, bytes_left;          unsigned long nitems;
735          int format, res;          unsigned long offset = 0;
736            unsigned long bytes_left = 1;
737            int format;
738          XWindowAttributes wa;          XWindowAttributes wa;
739          uint8 *data;          uint8 *data;
740          Atom type;          Atom type;
# Line 291  xclip_handle_PropertyNotify(XPropertyEve Line 742  xclip_handle_PropertyNotify(XPropertyEve
742          if (event->state == PropertyNewValue && g_waiting_for_INCR)          if (event->state == PropertyNewValue && g_waiting_for_INCR)
743          {          {
744                  DEBUG_CLIPBOARD(("x_clip_handle_PropertyNotify: g_waiting_for_INCR != 0\n"));                  DEBUG_CLIPBOARD(("x_clip_handle_PropertyNotify: g_waiting_for_INCR != 0\n"));
                 if ((XGetWindowProperty(g_display, g_wnd, rdesktop_clipboard_target_atom, 0,  
                                         4096L, True, AnyPropertyType,  
                                         &type, &format, &nitems, &bytes_left, &data) != Success))  
                 {  
                         XFree(data);  
                         return;  
                 }  
745    
746                  if (nitems == 0)                  while (bytes_left > 0)
747                  {                  {
748                          XGetWindowAttributes(g_display, g_wnd, &wa);                          /* Unlike the specification, we don't set the 'delete' arugment to True
749                          XSelectInput(g_display, g_wnd, (wa.your_event_mask ^ PropertyChangeMask));                             since we slurp the INCR's chunks in even-smaller chunks of 4096 bytes. */
750                          XFree(data);                          if ((XGetWindowProperty
751                          g_waiting_for_INCR = 0;                               (g_display, g_wnd, rdesktop_clipboard_target_atom, offset, 4096L,
752                                  False, AnyPropertyType, &type, &format, &nitems, &bytes_left,
753                          if (g_clip_buflen > 0)                                &data) != Success))
754                          {                          {
755                                  cliprdr_send_data(g_clip_buffer, g_clip_buflen + 1);                                  XFree(data);
756                                    return;
                                 if (!rdesktop_is_selection_owner)  
                                         cliprdr_send_simple_native_format_announce(CF_TEXT);  
   
                                 xfree(g_clip_buffer);  
                                 g_clip_buffer = 0;  
                                 g_clip_buflen = 0;  
757                          }                          }
                 }  
                 else  
                 {  
                         uint8 *translated_data;  
                         uint32 length = nitems;  
                         uint8 *tmp;  
758    
759                          DEBUG_CLIPBOARD(("Translating linebreaks before sending data\n"));                          if (nitems == 0)
760                          translated_data = lf2crlf(data, &length);                          {
761                                    /* INCR transfer finished */
762                          tmp = xmalloc(length + g_clip_buflen);                                  XGetWindowAttributes(g_display, g_wnd, &wa);
763                          strncpy((char *) tmp, (char *) g_clip_buffer, g_clip_buflen);                                  XSelectInput(g_display, g_wnd,
764                          xfree(g_clip_buffer);                                               (wa.your_event_mask ^ PropertyChangeMask));
765                                    XFree(data);
766                          strncpy((char *) (tmp + g_clip_buflen), (char *) translated_data, length);                                  g_waiting_for_INCR = 0;
                         xfree(translated_data);  
767    
768                          g_clip_buffer = tmp;                                  if (g_clip_buflen > 0)
769                          g_clip_buflen += length;                                  {
770                                            if (!xclip_send_data_with_convert
771                                                (g_clip_buffer, g_clip_buflen, g_incr_target))
772                                            {
773                                                    helper_cliprdr_send_empty_response();
774                                            }
775                                            xfree(g_clip_buffer);
776                                            g_clip_buffer = NULL;
777                                            g_clip_buflen = 0;
778                                    }
779                            }
780                            else
781                            {
782                                    /* Another chunk in the INCR transfer */
783                                    offset += (nitems / 4); /* offset at which to begin the next slurp */
784                                    g_clip_buffer = xrealloc(g_clip_buffer, g_clip_buflen + nitems);
785                                    memcpy(g_clip_buffer + g_clip_buflen, data, nitems);
786                                    g_clip_buflen += nitems;
787    
788                          XFree(data);                                  XFree(data);
789                          return;                          }
790                  }                  }
791          }                  XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_target_atom);
   
         if (event->atom != rdesktop_clipboard_formats_atom)  
                 return;  
   
         if (have_primary)       /* from us */  
792                  return;                  return;
   
         if (event->state == PropertyNewValue)  
         {  
                 res = XGetWindowProperty(g_display, DefaultRootWindow(g_display),  
                                          rdesktop_clipboard_formats_atom, 0,  
                                          XMaxRequestSize(g_display), False, XA_STRING, &type,  
                                          &format, &nitems, &bytes_left, &data);  
   
                 if ((res == Success) && (nitems > 0))  
                 {  
                         cliprdr_send_native_format_announce(data, nitems);  
                         rdesktop_is_selection_owner = 1;  
                         return;  
                 }  
793          }          }
794    
795          /* PropertyDelete, or XGetWindowProperty failed */          if ((event->atom == rdesktop_clipboard_formats_atom) &&
796          cliprdr_send_simple_native_format_announce(CF_TEXT);              (event->window == DefaultRootWindow(g_display)) &&
797          rdesktop_is_selection_owner = 0;              !have_primary /* not interested in our own events */ )
798                    xclip_get_root_property();
799  }  }
800  #endif  #endif
801    
802    
803    /* Called when the RDP server announces new clipboard data formats.
804       In response, we:
805       - take ownership over the clipboard
806       - declare those formats in their Windows native form
807         to other rdesktop instances on this X server */
808  void  void
809  ui_clip_format_announce(uint8 * data, uint32 length)  ui_clip_format_announce(uint8 * data, uint32 length)
810  {  {
811          XSetSelectionOwner(g_display, primary_atom, g_wnd, g_last_gesturetime);          acquire_time = g_last_gesturetime;
812    
813            XSetSelectionOwner(g_display, primary_atom, g_wnd, acquire_time);
814          if (XGetSelectionOwner(g_display, primary_atom) != g_wnd)          if (XGetSelectionOwner(g_display, primary_atom) != g_wnd)
815          {          {
816                  warning("Failed to aquire ownership of PRIMARY clipboard\n");                  warning("Failed to aquire ownership of PRIMARY clipboard\n");
# Line 385  ui_clip_format_announce(uint8 * data, ui Line 822  ui_clip_format_announce(uint8 * data, ui
822                          rdesktop_clipboard_formats_atom, XA_STRING, 8, PropModeReplace, data,                          rdesktop_clipboard_formats_atom, XA_STRING, 8, PropModeReplace, data,
823                          length);                          length);
824    
825          XSetSelectionOwner(g_display, clipboard_atom, g_wnd, g_last_gesturetime);          XSetSelectionOwner(g_display, clipboard_atom, g_wnd, acquire_time);
826          if (XGetSelectionOwner(g_display, clipboard_atom) != g_wnd)          if (XGetSelectionOwner(g_display, clipboard_atom) != g_wnd)
827                  warning("Failed to aquire ownership of CLIPBOARD clipboard\n");                  warning("Failed to aquire ownership of CLIPBOARD clipboard\n");
828  }  }
829    
830    /* Called when the RDP server responds with clipboard data (after we've requested it). */
831  void  void
832  ui_clip_handle_data(uint8 * data, uint32 length)  ui_clip_handle_data(uint8 * data, uint32 length)
833  {  {
834          if (selection_request.target != rdesktop_clipboard_formats_atom)          BOOL free_data = False;
835    
836            if (length == 0)
837            {
838                    xclip_refuse_selection(&selection_request);
839                    has_selection_request = False;
840                    return;
841            }
842    
843            if (selection_request.target == format_string_atom || selection_request.target == XA_STRING)
844          {          {
845                    /* We're expecting a CF_TEXT response */
846                  uint8 *firstnull;                  uint8 *firstnull;
847    
848                  /* translate linebreaks */                  /* translate linebreaks */
# Line 408  ui_clip_handle_data(uint8 * data, uint32 Line 855  ui_clip_handle_data(uint8 * data, uint32
855                          length = firstnull - data + 1;                          length = firstnull - data + 1;
856                  }                  }
857          }          }
858    #ifdef USE_UNICODE_CLIPBOARD
859            else if (selection_request.target == format_utf8_string_atom)
860            {
861                    /* We're expecting a CF_UNICODETEXT response */
862                    iconv_t cd = iconv_open("UTF-8", WINDOWS_CODEPAGE);
863                    if (cd != (iconv_t) - 1)
864                    {
865                            size_t utf8_length = length * 2;
866                            char *utf8_data = malloc(utf8_length);
867                            size_t utf8_length_remaining = utf8_length;
868                            char *utf8_data_remaining = utf8_data;
869                            char *data_remaining = (char *) data;
870                            size_t length_remaining = (size_t) length;
871                            if (utf8_data == NULL)
872                            {
873                                    iconv_close(cd);
874                                    return;
875                            }
876                            iconv(cd, (ICONV_CONST char **) &data_remaining, &length_remaining,
877                                  &utf8_data_remaining, &utf8_length_remaining);
878                            iconv_close(cd);
879                            free_data = True;
880                            data = (uint8 *) utf8_data;
881                            length = utf8_length - utf8_length_remaining;
882                    }
883            }
884            else if (selection_request.target == format_unicode_atom)
885            {
886                    /* We're expecting a CF_UNICODETEXT response, so what we're
887                       receiving matches our requirements and there's no need
888                       for further conversions. */
889            }
890    #endif
891            else if (selection_request.target == rdesktop_clipboard_formats_atom)
892            {
893                    /* Pass as-is */
894            }
895            else
896            {
897                    xclip_refuse_selection(&selection_request);
898                    has_selection_request = False;
899                    return;
900            }
901    
902            xclip_provide_selection(&selection_request, selection_request.target, 8, data, length - 1);
903            has_selection_request = False;
904    
905          xclip_provide_selection(&selection_request, XA_STRING, 8, data, length - 1);          if (free_data)
906                    free(data);
907    }
908    
909    void
910    ui_clip_request_failed()
911    {
912            xclip_refuse_selection(&selection_request);
913            has_selection_request = False;
914  }  }
915    
916  void  void
917  ui_clip_request_data(uint32 format)  ui_clip_request_data(uint32 format)
918  {  {
919          Window selectionowner;          Window primary_owner, clipboard_owner;
920    
921          DEBUG_CLIPBOARD(("Request from server for format %d\n", format));          DEBUG_CLIPBOARD(("Request from server for format %d\n", format));
922            rdp_clipboard_request_format = format;
923    
924            xclip_clear_target_props();
925    
926          if (rdesktop_is_selection_owner)          if (rdesktop_is_selection_owner)
927          {          {
# Line 429  ui_clip_request_data(uint32 format) Line 933  ui_clip_request_data(uint32 format)
933                  return;                  return;
934          }          }
935    
936          selectionowner = XGetSelectionOwner(g_display, primary_atom);          if (auto_mode)
937          if (selectionowner != None)                  primary_owner = XGetSelectionOwner(g_display, primary_atom);
938            else
939                    primary_owner = None;
940    
941            clipboard_owner = XGetSelectionOwner(g_display, clipboard_atom);
942    
943            /* Both available */
944            if ((primary_owner != None) && (clipboard_owner != None))
945            {
946                    primary_timestamp = 0;
947                    clipboard_timestamp = 0;
948                    XConvertSelection(g_display, primary_atom, timestamp_atom,
949                                      rdesktop_primary_timestamp_target_atom, g_wnd, CurrentTime);
950                    XConvertSelection(g_display, clipboard_atom, timestamp_atom,
951                                      rdesktop_clipboard_timestamp_target_atom, g_wnd, CurrentTime);
952                    return;
953            }
954    
955            /* Just PRIMARY */
956            if (primary_owner != None)
957          {          {
958                  XConvertSelection(g_display, primary_atom, targets_atom,                  XConvertSelection(g_display, primary_atom, targets_atom,
959                                    rdesktop_clipboard_target_atom, g_wnd, CurrentTime);                                    rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
960                  return;                  return;
961          }          }
962    
963          /* No PRIMARY, try CLIPBOARD */          /* Just CLIPBOARD */
964          selectionowner = XGetSelectionOwner(g_display, clipboard_atom);          if (clipboard_owner != None)
         if (selectionowner != None)  
965          {          {
966                  XConvertSelection(g_display, clipboard_atom, targets_atom,                  XConvertSelection(g_display, clipboard_atom, targets_atom,
967                                    rdesktop_clipboard_target_atom, g_wnd, CurrentTime);                                    rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
# Line 447  ui_clip_request_data(uint32 format) Line 969  ui_clip_request_data(uint32 format)
969          }          }
970    
971          /* No data available */          /* No data available */
972          cliprdr_send_data(NULL, 0);          helper_cliprdr_send_empty_response();
973  }  }
974    
975  void  void
976  ui_clip_sync(void)  ui_clip_sync(void)
977  {  {
978          cliprdr_send_simple_native_format_announce(CF_TEXT);          xclip_get_root_property();
979  }  }
980    
981    void
982    ui_clip_set_mode(const char *optarg)
983    {
984            g_rdpclip = True;
985    
986            if (str_startswith(optarg, "auto") || str_startswith(optarg, "on")
987                || str_startswith(optarg, "PRIMARYCLIPBOARD"))
988                    auto_mode = True;
989            else if (str_startswith(optarg, "CLIPBOARD"))
990                    auto_mode = False;
991            else
992            {
993                    warning("Invalid clipboard mode '%s'.\n", optarg);
994                    g_rdpclip = False;
995            }
996    }
997    
998  void  void
999  xclip_init(void)  xclip_init(void)
1000  {  {
1001            if (!g_rdpclip)
1002                    return;
1003    
1004          if (!cliprdr_init())          if (!cliprdr_init())
1005                  return;                  return;
1006    
# Line 469  xclip_init(void) Line 1010  xclip_init(void)
1010          timestamp_atom = XInternAtom(g_display, "TIMESTAMP", False);          timestamp_atom = XInternAtom(g_display, "TIMESTAMP", False);
1011          rdesktop_clipboard_target_atom =          rdesktop_clipboard_target_atom =
1012                  XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_TARGET", False);                  XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_TARGET", False);
1013            rdesktop_primary_timestamp_target_atom =
1014                    XInternAtom(g_display, "_RDESKTOP_PRIMARY_TIMESTAMP_TARGET", False);
1015            rdesktop_clipboard_timestamp_target_atom =
1016                    XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_TIMESTAMP_TARGET", False);
1017          incr_atom = XInternAtom(g_display, "INCR", False);          incr_atom = XInternAtom(g_display, "INCR", False);
1018          targets[0] = targets_atom;          format_string_atom = XInternAtom(g_display, "STRING", False);
1019          targets[1] = XInternAtom(g_display, "TEXT", False);          format_utf8_string_atom = XInternAtom(g_display, "UTF8_STRING", False);
1020          targets[2] = XInternAtom(g_display, "STRING", False);          format_unicode_atom = XInternAtom(g_display, "text/unicode", False);
         targets[3] = XInternAtom(g_display, "text/unicode", False);  
         targets[4] = XInternAtom(g_display, "TIMESTAMP", False);  
         targets[5] = XA_STRING;  
1021    
1022          /* 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.
1023             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. */
1024          rdesktop_clipboard_formats_atom =          rdesktop_clipboard_formats_atom =
1025                  XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_FORMATS", False);                  XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_FORMATS", False);
1026          XSelectInput(g_display, DefaultRootWindow(g_display), PropertyChangeMask);          XSelectInput(g_display, DefaultRootWindow(g_display), PropertyChangeMask);
1027    
1028            num_targets = 0;
1029            targets[num_targets++] = targets_atom;
1030            targets[num_targets++] = timestamp_atom;
1031            targets[num_targets++] = rdesktop_clipboard_formats_atom;
1032    #ifdef USE_UNICODE_CLIPBOARD
1033            targets[num_targets++] = format_utf8_string_atom;
1034    #endif
1035            targets[num_targets++] = format_unicode_atom;
1036            targets[num_targets++] = format_string_atom;
1037            targets[num_targets++] = XA_STRING;
1038  }  }

Legend:
Removed from v.1025  
changed lines
  Added in v.1212

  ViewVC Help
Powered by ViewVC 1.1.26