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

Legend:
Removed from v.462  
changed lines
  Added in v.1211

  ViewVC Help
Powered by ViewVC 1.1.26