/[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 1208 by ossman_, Mon Mar 27 10:41:22 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    static int g_waiting_for_INCR = 0;
120    /* 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
132    crlf2lf(uint8 * data, uint32 * length)
133    {
134            uint8 *dst, *src;
135            src = dst = data;
136            while (src < data + *length)
137            {
138                    if (*src != '\x0d')
139                            *dst++ = *src;
140                    src++;
141            }
142            *length = dst - data;
143    }
144    
145    #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. */
183    static uint8 *
184    lf2crlf(uint8 * data, uint32 * length)
185    {
186            uint8 *result, *p, *o;
187    
188            /* Worst case: Every char is LF */
189            result = xmalloc(*length * 2);
190    
191            p = data;
192            o = result;
193    
194            while (p < data + *length)
195            {
196                    if (*p == '\x0a')
197                            *o++ = '\x0d';
198                    *o++ = *p++;
199            }
200            *length = o - result;
201    
202            /* Convenience */
203            *o++ = '\0';
204    
205            return result;
206    }
207    #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,
211                          uint32 length)                          uint32 length)
# Line 56  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 73  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 */
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                                                    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                            XConvertSelection(g_display, event->selection, best_text_target,
564                                              rdesktop_clipboard_target_atom, g_wnd, event->time);
565                            return;
566                    }
567                    else
568                    {
569                            DEBUG_CLIPBOARD(("Unable to find a textual target to satisfy RDP clipboard text request\n"));
570                            goto fail;
571                    }
572          }          }
573            else
         if (type == incr_atom)  
574          {          {
575                  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))
576                  goto fail;                  {
577                            goto fail;
578                    }
579          }          }
580    
         cliprdr_send_data(data, nitems + 1);  
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  {  {
599          unsigned long nitems, bytes_left;          unsigned long nitems, bytes_left;
600            unsigned char *prop_return;
601          uint32 *wanted_format;          uint32 *wanted_format;
602          int format, res;          int format, res;
603          Atom type;          Atom type;
# Line 144  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 152  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,  
                                          (unsigned char **) &wanted_format);  
                 format = (res == Success) ? *wanted_format : CF_TEXT;  
         }  
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(char *data, uint32 length)  ui_clip_format_announce(uint8 * data, uint32 length)
800  {  {
801          XSetSelectionOwner(g_display, primary_atom, g_wnd, g_last_gesturetime);          XSetSelectionOwner(g_display, primary_atom, g_wnd, g_last_gesturetime);
802          if (XGetSelectionOwner(g_display, primary_atom) != g_wnd)          if (XGetSelectionOwner(g_display, primary_atom) != g_wnd)
# Line 234  ui_clip_format_announce(char *data, uint Line 815  ui_clip_format_announce(char *data, uint
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(char *data, uint32 length)  ui_clip_handle_data(uint8 * data, uint32 length)
821  {  {
822          xclip_provide_selection(&selection_request, XA_STRING, 8, data, length - 1);          BOOL free_data = False;
823    
824            if (selection_request.target == format_string_atom || selection_request.target == XA_STRING)
825            {
826                    /* We're expecting a CF_TEXT response */
827                    uint8 *firstnull;
828    
829                    /* translate linebreaks */
830                    crlf2lf(data, &length);
831    
832                    /* Only send data up to null byte, if any */
833                    firstnull = (uint8 *) strchr((char *) data, '\0');
834                    if (firstnull)
835                    {
836                            length = firstnull - data + 1;
837                    }
838            }
839    #ifdef USE_UNICODE_CLIPBOARD
840            else if (selection_request.target == format_utf8_string_atom)
841            {
842                    /* We're expecting a CF_UNICODETEXT response */
843                    iconv_t cd = iconv_open("UTF-8", WINDOWS_CODEPAGE);
844                    if (cd != (iconv_t) - 1)
845                    {
846                            size_t utf8_length = length * 2;
847                            char *utf8_data = malloc(utf8_length);
848                            size_t utf8_length_remaining = utf8_length;
849                            char *utf8_data_remaining = utf8_data;
850                            char *data_remaining = (char *) data;
851                            size_t length_remaining = (size_t) length;
852                            if (utf8_data == NULL)
853                            {
854                                    iconv_close(cd);
855                                    return;
856                            }
857                            iconv(cd, (ICONV_CONST char **) &data_remaining, &length_remaining,
858                                  &utf8_data_remaining, &utf8_length_remaining);
859                            iconv_close(cd);
860                            free_data = True;
861                            data = (uint8 *) utf8_data;
862                            length = utf8_length - utf8_length_remaining;
863                    }
864            }
865            else if (selection_request.target == format_unicode_atom)
866            {
867                    /* We're expecting a CF_UNICODETEXT response, so what we're
868                       receiving matches our requirements and there's no need
869                       for further conversions. */
870            }
871    #endif
872            else if (selection_request.target == rdesktop_clipboard_formats_atom)
873            {
874                    /* Pass as-is */
875            }
876            else
877            {
878                    xclip_refuse_selection(&selection_request);
879                    has_selection_request = False;
880                    return;
881            }
882    
883            xclip_provide_selection(&selection_request, selection_request.target, 8, data, length - 1);
884            has_selection_request = False;
885    
886            if (free_data)
887                    free(data);
888  }  }
889    
890  void  void
891  ui_clip_request_data(uint32 format)  ui_clip_request_data(uint32 format)
892  {  {
893          Window selectionowner;          Window primary_owner, clipboard_owner;
894    
895          DEBUG_CLIPBOARD(("Request from server for format %d\n", format));          DEBUG_CLIPBOARD(("Request from server for format %d\n", format));
896            rdp_clipboard_request_format = format;
897    
898            xclip_clear_target_props();
899    
900          if (rdesktop_is_selection_owner)          if (rdesktop_is_selection_owner)
901          {          {
# Line 258  ui_clip_request_data(uint32 format) Line 907  ui_clip_request_data(uint32 format)
907                  return;                  return;
908          }          }
909    
910          selectionowner = XGetSelectionOwner(g_display, primary_atom);          if (auto_mode)
911          if (selectionowner != None)                  primary_owner = XGetSelectionOwner(g_display, primary_atom);
912            else
913                    primary_owner = None;
914    
915            clipboard_owner = XGetSelectionOwner(g_display, clipboard_atom);
916    
917            /* Both available */
918            if ((primary_owner != None) && (clipboard_owner != None))
919            {
920                    primary_timestamp = 0;
921                    clipboard_timestamp = 0;
922                    XConvertSelection(g_display, primary_atom, timestamp_atom,
923                                      rdesktop_primary_timestamp_target_atom, g_wnd, CurrentTime);
924                    XConvertSelection(g_display, clipboard_atom, timestamp_atom,
925                                      rdesktop_clipboard_timestamp_target_atom, g_wnd, CurrentTime);
926                    return;
927            }
928    
929            /* Just PRIMARY */
930            if (primary_owner != None)
931          {          {
932                  XConvertSelection(g_display, primary_atom, targets_atom,                  XConvertSelection(g_display, primary_atom, targets_atom,
933                                    rdesktop_clipboard_target_atom, g_wnd, CurrentTime);                                    rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
934                  return;                  return;
935          }          }
936    
937          /* No PRIMARY, try CLIPBOARD */          /* Just CLIPBOARD */
938          selectionowner = XGetSelectionOwner(g_display, clipboard_atom);          if (clipboard_owner != None)
         if (selectionowner != None)  
939          {          {
940                  XConvertSelection(g_display, clipboard_atom, targets_atom,                  XConvertSelection(g_display, clipboard_atom, targets_atom,
941                                    rdesktop_clipboard_target_atom, g_wnd, CurrentTime);                                    rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
# Line 276  ui_clip_request_data(uint32 format) Line 943  ui_clip_request_data(uint32 format)
943          }          }
944    
945          /* No data available */          /* No data available */
946          cliprdr_send_data(NULL, 0);          helper_cliprdr_send_empty_response();
947  }  }
948    
949  void  void
950  ui_clip_sync(void)  ui_clip_sync(void)
951  {  {
952          cliprdr_send_text_format_announce();          cliprdr_send_simple_native_format_announce(RDP_CF_TEXT);
953  }  }
954    
955    void
956    ui_clip_set_mode(const char *optarg)
957    {
958            g_rdpclip = True;
959    
960            if (str_startswith(optarg, "auto") || str_startswith(optarg, "on")
961                || str_startswith(optarg, "PRIMARYCLIPBOARD"))
962                    auto_mode = True;
963            else if (str_startswith(optarg, "CLIPBOARD"))
964                    auto_mode = False;
965            else
966            {
967                    warning("Invalid clipboard mode '%s'.\n", optarg);
968                    g_rdpclip = False;
969            }
970    }
971    
972  void  void
973  xclip_init(void)  xclip_init(void)
974  {  {
975            if (!g_rdpclip)
976                    return;
977    
978          if (!cliprdr_init())          if (!cliprdr_init())
979                  return;                  return;
980    
# Line 298  xclip_init(void) Line 984  xclip_init(void)
984          timestamp_atom = XInternAtom(g_display, "TIMESTAMP", False);          timestamp_atom = XInternAtom(g_display, "TIMESTAMP", False);
985          rdesktop_clipboard_target_atom =          rdesktop_clipboard_target_atom =
986                  XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_TARGET", False);                  XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_TARGET", False);
987            rdesktop_primary_timestamp_target_atom =
988                    XInternAtom(g_display, "_RDESKTOP_PRIMARY_TIMESTAMP_TARGET", False);
989            rdesktop_clipboard_timestamp_target_atom =
990                    XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_TIMESTAMP_TARGET", False);
991          incr_atom = XInternAtom(g_display, "INCR", False);          incr_atom = XInternAtom(g_display, "INCR", False);
992          targets[0] = targets_atom;          format_string_atom = XInternAtom(g_display, "STRING", False);
993          targets[1] = XInternAtom(g_display, "TEXT", False);          format_utf8_string_atom = XInternAtom(g_display, "UTF8_STRING", False);
994          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;  
995    
996          /* 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.
997             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. */
998          rdesktop_clipboard_formats_atom =          rdesktop_clipboard_formats_atom =
999                  XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_FORMATS", False);                  XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_FORMATS", False);
1000          XSelectInput(g_display, DefaultRootWindow(g_display), PropertyChangeMask);          XSelectInput(g_display, DefaultRootWindow(g_display), PropertyChangeMask);
1001    
1002            num_targets = 0;
1003            targets[num_targets++] = targets_atom;
1004            targets[num_targets++] = timestamp_atom;
1005            targets[num_targets++] = rdesktop_clipboard_formats_atom;
1006    #ifdef USE_UNICODE_CLIPBOARD
1007            targets[num_targets++] = format_utf8_string_atom;
1008    #endif
1009            targets[num_targets++] = format_unicode_atom;
1010            targets[num_targets++] = format_string_atom;
1011            targets[num_targets++] = XA_STRING;
1012  }  }

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

  ViewVC Help
Powered by ViewVC 1.1.26