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

Legend:
Removed from v.608  
changed lines
  Added in v.1209

  ViewVC Help
Powered by ViewVC 1.1.26