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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1028 - (hide annotations)
Mon Nov 14 14:46:16 2005 UTC (18 years, 7 months ago) by astrand
File MIME type: text/plain
File size: 16701 byte(s)
Indent fixes

1 matthewc 432 /* -*- c-basic-offset: 8 -*-
2     rdesktop: A Remote Desktop Protocol client.
3     Protocol services - Clipboard functions
4     Copyright (C) Erik Forsberg <forsberg@cendio.se> 2003
5     Copyright (C) Matthew Chapman 2003
6    
7     This program is free software; you can redistribute it and/or modify
8     it under the terms of the GNU General Public License as published by
9     the Free Software Foundation; either version 2 of the License, or
10     (at your option) any later version.
11    
12     This program is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15     GNU General Public License for more details.
16    
17     You should have received a copy of the GNU General Public License
18     along with this program; if not, write to the Free Software
19     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20     */
21    
22     #include <X11/Xlib.h>
23     #include <X11/Xatom.h>
24     #include "rdesktop.h"
25    
26 forsberg 1027 /*
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 matthewc 432 #define NUM_TARGETS 6
36    
37 jsorg71 450 extern Display *g_display;
38     extern Window g_wnd;
39     extern Time g_last_gesturetime;
40 matthewc 432
41 forsberg 1027 /* Atoms of the two X selections we're dealing with: CLIPBOARD (explicit-copy) and PRIMARY (selection-copy) */
42     static Atom clipboard_atom, primary_atom;
43     /* Atom of the TARGETS clipboard target */
44     static Atom targets_atom;
45     /* Atom of the TIMESTAMP clipboard target */
46     static Atom timestamp_atom;
47     /* Atom _RDESKTOP_CLIPBOARD_TARGET which has multiple uses:
48     - The 'property' argument in XConvertSelection calls: This is the property of our
49     window into which XConvertSelection will store the received clipboard data.
50     - In a clipboard request of target _RDESKTOP_CLIPBOARD_FORMATS, an XA_INTEGER-typed
51     property carrying the Windows native (CF_...) format desired by the requestor.
52     Requestor set this property (e.g. requestor_wnd[_RDESKTOP_CLIPBOARD_TARGET] = CF_TEXT)
53     before requesting clipboard data from a fellow rdesktop using
54     the _RDESKTOP_CLIPBOARD_FORMATS target. */
55     static Atom rdesktop_clipboard_target_atom;
56     /* Atom _RDESKTOP_CLIPBOARD_FORMATS which has multiple uses:
57     - The clipboard target (X jargon for "clipboard format") for rdesktop-to-rdesktop interchange
58     of Windows native clipboard data.
59     This target cannot be used standalone; the requestor must keep the
60     _RDESKTOP_CLIPBOARD_TARGET property on his window denoting
61     the Windows native clipboard format being requested.
62     - The root window property set by rdesktop when it owns the clipboard,
63     denoting all Windows native clipboard formats it offers via
64     requests of the _RDESKTOP_CLIPBOARD_FORMATS target. */
65     static Atom rdesktop_clipboard_formats_atom;
66     /* Atom of the INCR clipboard type (see ICCCM on "INCR Properties") */
67     static Atom incr_atom;
68     /* Stores the last "selection request" (= another X client requesting clipboard data from us).
69     To satisfy such a request, we request the clipboard data from the RDP server.
70     When we receive the response from the RDP server (asynchronously), this variable gives us
71     the context to proceed. */
72 matthewc 432 static XSelectionRequestEvent selection_request;
73 forsberg 1027 /* Array of offered clipboard targets that will be sent to fellow X clients upon a TARGETS request. */
74 matthewc 432 static Atom targets[NUM_TARGETS];
75 forsberg 1027 /* Denotes that this client currently holds the PRIMARY selection. */
76 matthewc 432 static int have_primary = 0;
77 forsberg 1027 /* Denotes that an rdesktop (not this rdesktop) is owning the selection,
78     allowing us to interchange Windows native clipboard data directly. */
79 matthewc 432 static int rdesktop_is_selection_owner = 0;
80    
81 forsberg 1027 /* Denotes that an INCR ("chunked") transfer is in progress. */
82 astrand 913 static int g_waiting_for_INCR = 0;
83 forsberg 1027 /* Buffers an INCR transfer. */
84 astrand 913 static uint8 *g_clip_buffer = 0;
85 forsberg 1027 /* Denotes the size of g_clip_buffer. */
86 astrand 913 static uint32 g_clip_buflen = 0;
87 astrand 551
88 forsberg 1027 /* Translate LF to CR-LF. To do this, we must allocate more memory.
89     The returned string is null-terminated, as required by CF_TEXT.
90     Does not stop on embedded nulls.
91     The length is updated. */
92 matthewc 432 static void
93 astrand 551 crlf2lf(uint8 * data, uint32 * length)
94     {
95     uint8 *dst, *src;
96     src = dst = data;
97     while (src < data + *length)
98     {
99     if (*src != '\x0d')
100     *dst++ = *src;
101     src++;
102     }
103     *length = dst - data;
104     }
105    
106     /* Translate LF to CR-LF. To do this, we must allocate more memory.
107     The length is updated. */
108     static uint8 *
109     lf2crlf(uint8 * data, uint32 * length)
110     {
111     uint8 *result, *p, *o;
112    
113     /* Worst case: Every char is LF */
114     result = xmalloc(*length * 2);
115    
116     p = data;
117     o = result;
118    
119     while (p < data + *length)
120     {
121     if (*p == '\x0a')
122     *o++ = '\x0d';
123     *o++ = *p++;
124     }
125     *length = o - result;
126    
127     /* Convenience */
128     *o++ = '\0';
129    
130     return result;
131     }
132    
133    
134     static void
135 astrand 435 xclip_provide_selection(XSelectionRequestEvent * req, Atom type, unsigned int format, uint8 * data,
136     uint32 length)
137 matthewc 432 {
138     XEvent xev;
139    
140 jsorg71 450 XChangeProperty(g_display, req->requestor, req->property,
141 matthewc 432 type, format, PropModeReplace, data, length);
142    
143     xev.xselection.type = SelectionNotify;
144     xev.xselection.serial = 0;
145     xev.xselection.send_event = True;
146     xev.xselection.requestor = req->requestor;
147     xev.xselection.selection = req->selection;
148     xev.xselection.target = req->target;
149     xev.xselection.property = req->property;
150     xev.xselection.time = req->time;
151 jsorg71 450 XSendEvent(g_display, req->requestor, False, NoEventMask, &xev);
152 matthewc 432 }
153    
154 forsberg 1027 /* This function is called for SelectionNotify events.
155     The SelectionNotify message is sent from the clipboard owner to the requestor
156     after his request was satisfied.
157     If this function is called, we're the requestor side. */
158 astrand 942 #ifndef MAKE_PROTO
159 astrand 462 void
160 matthewc 432 xclip_handle_SelectionNotify(XSelectionEvent * event)
161     {
162     unsigned long nitems, bytes_left;
163 astrand 913 XWindowAttributes wa;
164 matthewc 432 Atom type, best_target, text_target;
165     Atom *supported_targets;
166     int res, i, format;
167     uint8 *data;
168    
169     if (event->property == None)
170     goto fail;
171    
172     DEBUG_CLIPBOARD(("xclip_handle_SelectionNotify: selection=%s, target=%s, property=%s\n",
173 jsorg71 450 XGetAtomName(g_display, event->selection),
174     XGetAtomName(g_display, event->target),
175     XGetAtomName(g_display, event->property)));
176 matthewc 432
177     if (event->property == None)
178     goto fail;
179    
180 jsorg71 450 res = XGetWindowProperty(g_display, g_wnd, rdesktop_clipboard_target_atom,
181 forsberg 1026 0, XMaxRequestSize(g_display), False, AnyPropertyType,
182 matthewc 432 &type, &format, &nitems, &bytes_left, &data);
183    
184     if (res != Success)
185     {
186     DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));
187     goto fail;
188     }
189    
190 forsberg 1026
191     if (type == incr_atom)
192     {
193     DEBUG_CLIPBOARD(("Received INCR.\n"));
194    
195     XGetWindowAttributes(g_display, g_wnd, &wa);
196     if ((wa.your_event_mask | PropertyChangeMask) != wa.your_event_mask)
197     {
198     XSelectInput(g_display, g_wnd, (wa.your_event_mask | PropertyChangeMask));
199     }
200     XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_target_atom);
201     XFree(data);
202     g_waiting_for_INCR = 1;
203     return;
204 astrand 1028 }
205    
206 forsberg 1026 XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_target_atom);
207 astrand 1028
208 astrand 551 /* Negotiate target format */
209 matthewc 432 if (event->target == targets_atom)
210     {
211     /* FIXME: We should choose format here based on what the server wanted */
212     best_target = XA_STRING;
213     if (type != None)
214     {
215     supported_targets = (Atom *) data;
216 jsorg71 450 text_target = XInternAtom(g_display, "TEXT", False);
217 matthewc 432 for (i = 0; i < nitems; i++)
218     {
219 astrand 435 DEBUG_CLIPBOARD(("Target %d: %s\n", i,
220 jsorg71 450 XGetAtomName(g_display, supported_targets[i])));
221 matthewc 432 if (supported_targets[i] == text_target)
222     {
223     DEBUG_CLIPBOARD(("Other party supports TEXT, choosing that as best_target\n"));
224     best_target = text_target;
225 astrand 551 break;
226 matthewc 432 }
227     }
228     XFree(data);
229     }
230    
231 jsorg71 450 XConvertSelection(g_display, primary_atom, best_target,
232     rdesktop_clipboard_target_atom, g_wnd, event->time);
233 matthewc 432 return;
234     }
235    
236 astrand 551 /* Translate linebreaks, but only if not getting data from
237     other rdesktop instance */
238     if (event->target != rdesktop_clipboard_formats_atom)
239     {
240     uint8 *translated_data;
241     uint32 length = nitems;
242    
243     DEBUG_CLIPBOARD(("Translating linebreaks before sending data\n"));
244     translated_data = lf2crlf(data, &length);
245     cliprdr_send_data(translated_data, length + 1);
246     xfree(translated_data); /* Not the same thing as XFree! */
247     }
248     else
249     {
250     cliprdr_send_data(data, nitems + 1);
251     }
252 matthewc 432 XFree(data);
253    
254     if (!rdesktop_is_selection_owner)
255 forsberg 1025 cliprdr_send_simple_native_format_announce(CF_TEXT);
256 matthewc 432 return;
257    
258 astrand 1028 fail:
259 forsberg 1026 XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_target_atom);
260     XFree(data);
261 matthewc 432 cliprdr_send_data(NULL, 0);
262     }
263    
264 forsberg 1027 /* This function is called for SelectionRequest events.
265     The SelectionRequest message is sent from the requestor to the clipboard owner
266     to request clipboard data.
267     */
268 astrand 462 void
269 astrand 435 xclip_handle_SelectionRequest(XSelectionRequestEvent * event)
270 matthewc 432 {
271     unsigned long nitems, bytes_left;
272 astrand 465 unsigned char *prop_return;
273 matthewc 432 uint32 *wanted_format;
274     int format, res;
275     Atom type;
276    
277     DEBUG_CLIPBOARD(("xclip_handle_SelectionRequest: selection=%s, target=%s, property=%s\n",
278 jsorg71 450 XGetAtomName(g_display, event->selection),
279     XGetAtomName(g_display, event->target),
280     XGetAtomName(g_display, event->property)));
281 matthewc 432
282     if (event->target == targets_atom)
283     {
284 astrand 435 xclip_provide_selection(event, XA_ATOM, 32, (uint8 *) & targets, NUM_TARGETS);
285 matthewc 432 return;
286     }
287     else if (event->target == timestamp_atom)
288     {
289 jsorg71 450 xclip_provide_selection(event, XA_INTEGER, 32, (uint8 *) & g_last_gesturetime, 1);
290 matthewc 432 return;
291     }
292     else if (event->target == rdesktop_clipboard_formats_atom)
293     {
294 jsorg71 450 res = XGetWindowProperty(g_display, event->requestor,
295 astrand 435 rdesktop_clipboard_target_atom, 0, 1, True, XA_INTEGER,
296 astrand 468 &type, &format, &nitems, &bytes_left, &prop_return);
297 astrand 465 wanted_format = (uint32 *) prop_return;
298 matthewc 432 format = (res == Success) ? *wanted_format : CF_TEXT;
299 astrand 465 /* FIXME: Need to free returned data? */
300 matthewc 432 }
301     else
302     {
303     format = CF_TEXT;
304     }
305    
306     cliprdr_send_data_request(format);
307     selection_request = *event;
308     /* wait for data */
309     }
310    
311 forsberg 1027 /* When this rdesktop holds ownership over the clipboard, it means the clipboard data
312     is offered by the RDP server (and when its pasted inside RDP, there's no network
313     roundtrip). This event symbolizes this rdesktop lost onwership of the clipboard
314     to some other X client. We should find out what clipboard formats this other
315     client offers and announce that to RDP. */
316 astrand 462 void
317 matthewc 432 xclip_handle_SelectionClear(void)
318     {
319     DEBUG_CLIPBOARD(("xclip_handle_SelectionClear\n"));
320     have_primary = 0;
321 jsorg71 450 XDeleteProperty(g_display, DefaultRootWindow(g_display), rdesktop_clipboard_formats_atom);
322 forsberg 1025 cliprdr_send_simple_native_format_announce(CF_TEXT);
323 matthewc 432 }
324    
325 forsberg 1027 /* Called when any property changes in our window or the root window. */
326 astrand 462 void
327 astrand 435 xclip_handle_PropertyNotify(XPropertyEvent * event)
328 matthewc 432 {
329 astrand 1028 unsigned long nitems;
330 forsberg 1026 unsigned long offset = 0;
331     unsigned long bytes_left = 1;
332 matthewc 432 int format, res;
333 astrand 913 XWindowAttributes wa;
334 matthewc 432 uint8 *data;
335     Atom type;
336    
337 astrand 913 if (event->state == PropertyNewValue && g_waiting_for_INCR)
338     {
339     DEBUG_CLIPBOARD(("x_clip_handle_PropertyNotify: g_waiting_for_INCR != 0\n"));
340    
341 astrand 1028 while (bytes_left > 0)
342     {
343     if ((XGetWindowProperty
344     (g_display, g_wnd, rdesktop_clipboard_target_atom, offset, 4096L,
345     False, AnyPropertyType, &type, &format, &nitems, &bytes_left,
346     &data) != Success))
347 forsberg 1026 {
348     XFree(data);
349     return;
350     }
351 astrand 913
352 forsberg 1026 if (nitems == 0)
353 astrand 913 {
354 forsberg 1026 XGetWindowAttributes(g_display, g_wnd, &wa);
355 astrand 1028 XSelectInput(g_display, g_wnd,
356     (wa.your_event_mask ^ PropertyChangeMask));
357 forsberg 1026 XFree(data);
358     g_waiting_for_INCR = 0;
359 astrand 913
360 forsberg 1026 if (g_clip_buflen > 0)
361     {
362     cliprdr_send_data(g_clip_buffer, g_clip_buflen + 1);
363 astrand 913
364 forsberg 1026 if (!rdesktop_is_selection_owner)
365     cliprdr_send_simple_native_format_announce(CF_TEXT);
366    
367     xfree(g_clip_buffer);
368     g_clip_buffer = 0;
369     g_clip_buflen = 0;
370     }
371 astrand 913 }
372 forsberg 1026 else
373     {
374     uint8 *translated_data;
375     uint32 length = nitems;
376     uint8 *tmp;
377 astrand 913
378 astrand 1028 offset += (length / 4);
379 forsberg 1026 DEBUG_CLIPBOARD(("Translating linebreaks before sending data\n"));
380     translated_data = lf2crlf(data, &length);
381 astrand 913
382 forsberg 1026 tmp = xmalloc(length + g_clip_buflen);
383     strncpy((char *) tmp, (char *) g_clip_buffer, g_clip_buflen);
384     xfree(g_clip_buffer);
385 astrand 913
386 astrand 1028 strncpy((char *) (tmp + g_clip_buflen), (char *) translated_data,
387     length);
388 forsberg 1026 xfree(translated_data);
389 astrand 913
390 forsberg 1026 g_clip_buffer = tmp;
391     g_clip_buflen += length;
392 astrand 913
393 forsberg 1026 XFree(data);
394     }
395 astrand 913 }
396 forsberg 1026 XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_target_atom);
397 astrand 913 }
398    
399 matthewc 432 if (event->atom != rdesktop_clipboard_formats_atom)
400     return;
401    
402 astrand 435 if (have_primary) /* from us */
403 matthewc 432 return;
404    
405     if (event->state == PropertyNewValue)
406     {
407 jsorg71 450 res = XGetWindowProperty(g_display, DefaultRootWindow(g_display),
408 astrand 435 rdesktop_clipboard_formats_atom, 0,
409 astrand 456 XMaxRequestSize(g_display), False, XA_STRING, &type,
410     &format, &nitems, &bytes_left, &data);
411 matthewc 432
412     if ((res == Success) && (nitems > 0))
413     {
414     cliprdr_send_native_format_announce(data, nitems);
415     rdesktop_is_selection_owner = 1;
416     return;
417     }
418     }
419    
420     /* PropertyDelete, or XGetWindowProperty failed */
421 forsberg 1025 cliprdr_send_simple_native_format_announce(CF_TEXT);
422 matthewc 432 rdesktop_is_selection_owner = 0;
423     }
424 astrand 942 #endif
425 matthewc 432
426    
427 forsberg 1027 /* Called when the RDP server announces new clipboard data formats.
428     In response, we:
429     - take ownership over the clipboard
430     - declare those formats in their Windows native form
431     to other rdesktop instances on this X server */
432 matthewc 432 void
433 astrand 542 ui_clip_format_announce(uint8 * data, uint32 length)
434 matthewc 432 {
435 jsorg71 450 XSetSelectionOwner(g_display, primary_atom, g_wnd, g_last_gesturetime);
436     if (XGetSelectionOwner(g_display, primary_atom) != g_wnd)
437 matthewc 432 {
438     warning("Failed to aquire ownership of PRIMARY clipboard\n");
439     return;
440     }
441    
442     have_primary = 1;
443 jsorg71 450 XChangeProperty(g_display, DefaultRootWindow(g_display),
444 astrand 435 rdesktop_clipboard_formats_atom, XA_STRING, 8, PropModeReplace, data,
445     length);
446 matthewc 432
447 jsorg71 450 XSetSelectionOwner(g_display, clipboard_atom, g_wnd, g_last_gesturetime);
448     if (XGetSelectionOwner(g_display, clipboard_atom) != g_wnd)
449 matthewc 432 warning("Failed to aquire ownership of CLIPBOARD clipboard\n");
450     }
451    
452    
453     void
454 astrand 542 ui_clip_handle_data(uint8 * data, uint32 length)
455 matthewc 432 {
456 astrand 551 if (selection_request.target != rdesktop_clipboard_formats_atom)
457     {
458     uint8 *firstnull;
459    
460     /* translate linebreaks */
461     crlf2lf(data, &length);
462    
463     /* Only send data up to null byte, if any */
464 astrand 608 firstnull = (uint8 *) strchr((char *) data, '\0');
465 astrand 551 if (firstnull)
466     {
467     length = firstnull - data + 1;
468     }
469     }
470    
471 astrand 435 xclip_provide_selection(&selection_request, XA_STRING, 8, data, length - 1);
472 matthewc 432 }
473    
474     void
475     ui_clip_request_data(uint32 format)
476     {
477     Window selectionowner;
478    
479     DEBUG_CLIPBOARD(("Request from server for format %d\n", format));
480    
481     if (rdesktop_is_selection_owner)
482     {
483 jsorg71 450 XChangeProperty(g_display, g_wnd, rdesktop_clipboard_target_atom,
484 matthewc 432 XA_INTEGER, 32, PropModeReplace, (unsigned char *) &format, 1);
485    
486 jsorg71 450 XConvertSelection(g_display, primary_atom, rdesktop_clipboard_formats_atom,
487     rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
488 matthewc 432 return;
489     }
490    
491 jsorg71 450 selectionowner = XGetSelectionOwner(g_display, primary_atom);
492 matthewc 432 if (selectionowner != None)
493     {
494 jsorg71 450 XConvertSelection(g_display, primary_atom, targets_atom,
495     rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
496 matthewc 432 return;
497     }
498    
499     /* No PRIMARY, try CLIPBOARD */
500 jsorg71 450 selectionowner = XGetSelectionOwner(g_display, clipboard_atom);
501 matthewc 432 if (selectionowner != None)
502     {
503 jsorg71 450 XConvertSelection(g_display, clipboard_atom, targets_atom,
504     rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
505 matthewc 432 return;
506     }
507    
508     /* No data available */
509     cliprdr_send_data(NULL, 0);
510     }
511    
512     void
513     ui_clip_sync(void)
514     {
515 forsberg 1025 cliprdr_send_simple_native_format_announce(CF_TEXT);
516 matthewc 432 }
517    
518    
519     void
520     xclip_init(void)
521     {
522     if (!cliprdr_init())
523     return;
524    
525 jsorg71 450 primary_atom = XInternAtom(g_display, "PRIMARY", False);
526     clipboard_atom = XInternAtom(g_display, "CLIPBOARD", False);
527     targets_atom = XInternAtom(g_display, "TARGETS", False);
528     timestamp_atom = XInternAtom(g_display, "TIMESTAMP", False);
529 astrand 456 rdesktop_clipboard_target_atom =
530     XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_TARGET", False);
531 jsorg71 450 incr_atom = XInternAtom(g_display, "INCR", False);
532 matthewc 432 targets[0] = targets_atom;
533 jsorg71 450 targets[1] = XInternAtom(g_display, "TEXT", False);
534 forsberg 707 targets[2] = XInternAtom(g_display, "STRING", False);
535 jsorg71 450 targets[3] = XInternAtom(g_display, "text/unicode", False);
536     targets[4] = XInternAtom(g_display, "TIMESTAMP", False);
537 matthewc 432 targets[5] = XA_STRING;
538    
539     /* rdesktop sets _RDESKTOP_CLIPBOARD_FORMATS on the root window when acquiring the clipboard.
540     Other interested rdesktops can use this to notify their server of the available formats. */
541 astrand 435 rdesktop_clipboard_formats_atom =
542 jsorg71 450 XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_FORMATS", False);
543     XSelectInput(g_display, DefaultRootWindow(g_display), PropertyChangeMask);
544 matthewc 432 }

  ViewVC Help
Powered by ViewVC 1.1.26