/[rdesktop]/jpeg/rdesktop/trunk/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 /jpeg/rdesktop/trunk/xclip.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1027 - (hide annotations)
Mon Nov 14 14:11:42 2005 UTC (18 years, 6 months ago) by forsberg
Original Path: sourceforge.net/trunk/rdesktop/xclip.c
File MIME type: text/plain
File size: 16667 byte(s)
Applied patch with comments from  Ilya Konstantinov. Closes Patch 1354336.

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     }
205    
206     XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_target_atom);
207    
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 forsberg 1026 fail:
259     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 forsberg 1026 unsigned long nitems;
330     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 forsberg 1026 while (bytes_left > 0) {
342     if ((XGetWindowProperty(g_display, g_wnd, rdesktop_clipboard_target_atom, offset,
343     4096L, False, AnyPropertyType,
344     &type, &format, &nitems, &bytes_left, &data) != Success))
345     {
346     XFree(data);
347     return;
348     }
349 astrand 913
350 forsberg 1026 if (nitems == 0)
351 astrand 913 {
352 forsberg 1026 XGetWindowAttributes(g_display, g_wnd, &wa);
353     XSelectInput(g_display, g_wnd, (wa.your_event_mask ^ PropertyChangeMask));
354     XFree(data);
355     g_waiting_for_INCR = 0;
356 astrand 913
357 forsberg 1026 if (g_clip_buflen > 0)
358     {
359     cliprdr_send_data(g_clip_buffer, g_clip_buflen + 1);
360 astrand 913
361 forsberg 1026 if (!rdesktop_is_selection_owner)
362     cliprdr_send_simple_native_format_announce(CF_TEXT);
363    
364     xfree(g_clip_buffer);
365     g_clip_buffer = 0;
366     g_clip_buflen = 0;
367     }
368 astrand 913 }
369 forsberg 1026 else
370     {
371     uint8 *translated_data;
372     uint32 length = nitems;
373     uint8 *tmp;
374 astrand 913
375 forsberg 1026 offset += (length/4);
376     DEBUG_CLIPBOARD(("Translating linebreaks before sending data\n"));
377     translated_data = lf2crlf(data, &length);
378 astrand 913
379 forsberg 1026 tmp = xmalloc(length + g_clip_buflen);
380     strncpy((char *) tmp, (char *) g_clip_buffer, g_clip_buflen);
381     xfree(g_clip_buffer);
382 astrand 913
383 forsberg 1026 strncpy((char *) (tmp + g_clip_buflen), (char *) translated_data, length);
384     xfree(translated_data);
385 astrand 913
386 forsberg 1026 g_clip_buffer = tmp;
387     g_clip_buflen += length;
388 astrand 913
389 forsberg 1026 XFree(data);
390     }
391 astrand 913 }
392 forsberg 1026 XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_target_atom);
393 astrand 913 }
394    
395 matthewc 432 if (event->atom != rdesktop_clipboard_formats_atom)
396     return;
397    
398 astrand 435 if (have_primary) /* from us */
399 matthewc 432 return;
400    
401     if (event->state == PropertyNewValue)
402     {
403 jsorg71 450 res = XGetWindowProperty(g_display, DefaultRootWindow(g_display),
404 astrand 435 rdesktop_clipboard_formats_atom, 0,
405 astrand 456 XMaxRequestSize(g_display), False, XA_STRING, &type,
406     &format, &nitems, &bytes_left, &data);
407 matthewc 432
408     if ((res == Success) && (nitems > 0))
409     {
410     cliprdr_send_native_format_announce(data, nitems);
411     rdesktop_is_selection_owner = 1;
412     return;
413     }
414     }
415    
416     /* PropertyDelete, or XGetWindowProperty failed */
417 forsberg 1025 cliprdr_send_simple_native_format_announce(CF_TEXT);
418 matthewc 432 rdesktop_is_selection_owner = 0;
419     }
420 astrand 942 #endif
421 matthewc 432
422    
423 forsberg 1027 /* Called when the RDP server announces new clipboard data formats.
424     In response, we:
425     - take ownership over the clipboard
426     - declare those formats in their Windows native form
427     to other rdesktop instances on this X server */
428 matthewc 432 void
429 astrand 542 ui_clip_format_announce(uint8 * data, uint32 length)
430 matthewc 432 {
431 jsorg71 450 XSetSelectionOwner(g_display, primary_atom, g_wnd, g_last_gesturetime);
432     if (XGetSelectionOwner(g_display, primary_atom) != g_wnd)
433 matthewc 432 {
434     warning("Failed to aquire ownership of PRIMARY clipboard\n");
435     return;
436     }
437    
438     have_primary = 1;
439 jsorg71 450 XChangeProperty(g_display, DefaultRootWindow(g_display),
440 astrand 435 rdesktop_clipboard_formats_atom, XA_STRING, 8, PropModeReplace, data,
441     length);
442 matthewc 432
443 jsorg71 450 XSetSelectionOwner(g_display, clipboard_atom, g_wnd, g_last_gesturetime);
444     if (XGetSelectionOwner(g_display, clipboard_atom) != g_wnd)
445 matthewc 432 warning("Failed to aquire ownership of CLIPBOARD clipboard\n");
446     }
447    
448    
449     void
450 astrand 542 ui_clip_handle_data(uint8 * data, uint32 length)
451 matthewc 432 {
452 astrand 551 if (selection_request.target != rdesktop_clipboard_formats_atom)
453     {
454     uint8 *firstnull;
455    
456     /* translate linebreaks */
457     crlf2lf(data, &length);
458    
459     /* Only send data up to null byte, if any */
460 astrand 608 firstnull = (uint8 *) strchr((char *) data, '\0');
461 astrand 551 if (firstnull)
462     {
463     length = firstnull - data + 1;
464     }
465     }
466    
467 astrand 435 xclip_provide_selection(&selection_request, XA_STRING, 8, data, length - 1);
468 matthewc 432 }
469    
470     void
471     ui_clip_request_data(uint32 format)
472     {
473     Window selectionowner;
474    
475     DEBUG_CLIPBOARD(("Request from server for format %d\n", format));
476    
477     if (rdesktop_is_selection_owner)
478     {
479 jsorg71 450 XChangeProperty(g_display, g_wnd, rdesktop_clipboard_target_atom,
480 matthewc 432 XA_INTEGER, 32, PropModeReplace, (unsigned char *) &format, 1);
481    
482 jsorg71 450 XConvertSelection(g_display, primary_atom, rdesktop_clipboard_formats_atom,
483     rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
484 matthewc 432 return;
485     }
486    
487 jsorg71 450 selectionowner = XGetSelectionOwner(g_display, primary_atom);
488 matthewc 432 if (selectionowner != None)
489     {
490 jsorg71 450 XConvertSelection(g_display, primary_atom, targets_atom,
491     rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
492 matthewc 432 return;
493     }
494    
495     /* No PRIMARY, try CLIPBOARD */
496 jsorg71 450 selectionowner = XGetSelectionOwner(g_display, clipboard_atom);
497 matthewc 432 if (selectionowner != None)
498     {
499 jsorg71 450 XConvertSelection(g_display, clipboard_atom, targets_atom,
500     rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
501 matthewc 432 return;
502     }
503    
504     /* No data available */
505     cliprdr_send_data(NULL, 0);
506     }
507    
508     void
509     ui_clip_sync(void)
510     {
511 forsberg 1025 cliprdr_send_simple_native_format_announce(CF_TEXT);
512 matthewc 432 }
513    
514    
515     void
516     xclip_init(void)
517     {
518     if (!cliprdr_init())
519     return;
520    
521 jsorg71 450 primary_atom = XInternAtom(g_display, "PRIMARY", False);
522     clipboard_atom = XInternAtom(g_display, "CLIPBOARD", False);
523     targets_atom = XInternAtom(g_display, "TARGETS", False);
524     timestamp_atom = XInternAtom(g_display, "TIMESTAMP", False);
525 astrand 456 rdesktop_clipboard_target_atom =
526     XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_TARGET", False);
527 jsorg71 450 incr_atom = XInternAtom(g_display, "INCR", False);
528 matthewc 432 targets[0] = targets_atom;
529 jsorg71 450 targets[1] = XInternAtom(g_display, "TEXT", False);
530 forsberg 707 targets[2] = XInternAtom(g_display, "STRING", False);
531 jsorg71 450 targets[3] = XInternAtom(g_display, "text/unicode", False);
532     targets[4] = XInternAtom(g_display, "TIMESTAMP", False);
533 matthewc 432 targets[5] = XA_STRING;
534    
535     /* rdesktop sets _RDESKTOP_CLIPBOARD_FORMATS on the root window when acquiring the clipboard.
536     Other interested rdesktops can use this to notify their server of the available formats. */
537 astrand 435 rdesktop_clipboard_formats_atom =
538 jsorg71 450 XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_FORMATS", False);
539     XSelectInput(g_display, DefaultRootWindow(g_display), PropertyChangeMask);
540 matthewc 432 }

  ViewVC Help
Powered by ViewVC 1.1.26