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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

1 /* -*- 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 /*
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 #define NUM_TARGETS 6
36
37 extern Display *g_display;
38 extern Window g_wnd;
39 extern Time g_last_gesturetime;
40
41 /* 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 static XSelectionRequestEvent selection_request;
73 /* Array of offered clipboard targets that will be sent to fellow X clients upon a TARGETS request. */
74 static Atom targets[NUM_TARGETS];
75 /* Denotes that this client currently holds the PRIMARY selection. */
76 static int have_primary = 0;
77 /* Denotes that an rdesktop (not this rdesktop) is owning the selection,
78 allowing us to interchange Windows native clipboard data directly. */
79 static int rdesktop_is_selection_owner = 0;
80
81 /* Denotes that an INCR ("chunked") transfer is in progress. */
82 static int g_waiting_for_INCR = 0;
83 /* Buffers an INCR transfer. */
84 static uint8 *g_clip_buffer = 0;
85 /* Denotes the size of g_clip_buffer. */
86 static uint32 g_clip_buflen = 0;
87
88 /* 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 static void
93 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 xclip_provide_selection(XSelectionRequestEvent * req, Atom type, unsigned int format, uint8 * data,
136 uint32 length)
137 {
138 XEvent xev;
139
140 XChangeProperty(g_display, req->requestor, req->property,
141 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 XSendEvent(g_display, req->requestor, False, NoEventMask, &xev);
152 }
153
154 /* 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 #ifndef MAKE_PROTO
159 void
160 xclip_handle_SelectionNotify(XSelectionEvent * event)
161 {
162 unsigned long nitems, bytes_left;
163 XWindowAttributes wa;
164 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 XGetAtomName(g_display, event->selection),
174 XGetAtomName(g_display, event->target),
175 XGetAtomName(g_display, event->property)));
176
177 if (event->property == None)
178 goto fail;
179
180 res = XGetWindowProperty(g_display, g_wnd, rdesktop_clipboard_target_atom,
181 0, XMaxRequestSize(g_display), False, AnyPropertyType,
182 &type, &format, &nitems, &bytes_left, &data);
183
184 if (res != Success)
185 {
186 DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));
187 goto fail;
188 }
189
190
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 /* Negotiate target format */
209 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 text_target = XInternAtom(g_display, "TEXT", False);
217 for (i = 0; i < nitems; i++)
218 {
219 DEBUG_CLIPBOARD(("Target %d: %s\n", i,
220 XGetAtomName(g_display, supported_targets[i])));
221 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 break;
226 }
227 }
228 XFree(data);
229 }
230
231 XConvertSelection(g_display, primary_atom, best_target,
232 rdesktop_clipboard_target_atom, g_wnd, event->time);
233 return;
234 }
235
236 /* 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 XFree(data);
253
254 if (!rdesktop_is_selection_owner)
255 cliprdr_send_simple_native_format_announce(CF_TEXT);
256 return;
257
258 fail:
259 XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_target_atom);
260 XFree(data);
261 cliprdr_send_data(NULL, 0);
262 }
263
264 /* 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 void
269 xclip_handle_SelectionRequest(XSelectionRequestEvent * event)
270 {
271 unsigned long nitems, bytes_left;
272 unsigned char *prop_return;
273 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 XGetAtomName(g_display, event->selection),
279 XGetAtomName(g_display, event->target),
280 XGetAtomName(g_display, event->property)));
281
282 if (event->target == targets_atom)
283 {
284 xclip_provide_selection(event, XA_ATOM, 32, (uint8 *) & targets, NUM_TARGETS);
285 return;
286 }
287 else if (event->target == timestamp_atom)
288 {
289 xclip_provide_selection(event, XA_INTEGER, 32, (uint8 *) & g_last_gesturetime, 1);
290 return;
291 }
292 else if (event->target == rdesktop_clipboard_formats_atom)
293 {
294 res = XGetWindowProperty(g_display, event->requestor,
295 rdesktop_clipboard_target_atom, 0, 1, True, XA_INTEGER,
296 &type, &format, &nitems, &bytes_left, &prop_return);
297 wanted_format = (uint32 *) prop_return;
298 format = (res == Success) ? *wanted_format : CF_TEXT;
299 /* FIXME: Need to free returned data? */
300 }
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 /* 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 void
317 xclip_handle_SelectionClear(void)
318 {
319 DEBUG_CLIPBOARD(("xclip_handle_SelectionClear\n"));
320 have_primary = 0;
321 XDeleteProperty(g_display, DefaultRootWindow(g_display), rdesktop_clipboard_formats_atom);
322 cliprdr_send_simple_native_format_announce(CF_TEXT);
323 }
324
325 /* Called when any property changes in our window or the root window. */
326 void
327 xclip_handle_PropertyNotify(XPropertyEvent * event)
328 {
329 unsigned long nitems;
330 unsigned long offset = 0;
331 unsigned long bytes_left = 1;
332 int format, res;
333 XWindowAttributes wa;
334 uint8 *data;
335 Atom type;
336
337 if (event->state == PropertyNewValue && g_waiting_for_INCR)
338 {
339 DEBUG_CLIPBOARD(("x_clip_handle_PropertyNotify: g_waiting_for_INCR != 0\n"));
340
341 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
350 if (nitems == 0)
351 {
352 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
357 if (g_clip_buflen > 0)
358 {
359 cliprdr_send_data(g_clip_buffer, g_clip_buflen + 1);
360
361 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 }
369 else
370 {
371 uint8 *translated_data;
372 uint32 length = nitems;
373 uint8 *tmp;
374
375 offset += (length/4);
376 DEBUG_CLIPBOARD(("Translating linebreaks before sending data\n"));
377 translated_data = lf2crlf(data, &length);
378
379 tmp = xmalloc(length + g_clip_buflen);
380 strncpy((char *) tmp, (char *) g_clip_buffer, g_clip_buflen);
381 xfree(g_clip_buffer);
382
383 strncpy((char *) (tmp + g_clip_buflen), (char *) translated_data, length);
384 xfree(translated_data);
385
386 g_clip_buffer = tmp;
387 g_clip_buflen += length;
388
389 XFree(data);
390 }
391 }
392 XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_target_atom);
393 }
394
395 if (event->atom != rdesktop_clipboard_formats_atom)
396 return;
397
398 if (have_primary) /* from us */
399 return;
400
401 if (event->state == PropertyNewValue)
402 {
403 res = XGetWindowProperty(g_display, DefaultRootWindow(g_display),
404 rdesktop_clipboard_formats_atom, 0,
405 XMaxRequestSize(g_display), False, XA_STRING, &type,
406 &format, &nitems, &bytes_left, &data);
407
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 cliprdr_send_simple_native_format_announce(CF_TEXT);
418 rdesktop_is_selection_owner = 0;
419 }
420 #endif
421
422
423 /* 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 void
429 ui_clip_format_announce(uint8 * data, uint32 length)
430 {
431 XSetSelectionOwner(g_display, primary_atom, g_wnd, g_last_gesturetime);
432 if (XGetSelectionOwner(g_display, primary_atom) != g_wnd)
433 {
434 warning("Failed to aquire ownership of PRIMARY clipboard\n");
435 return;
436 }
437
438 have_primary = 1;
439 XChangeProperty(g_display, DefaultRootWindow(g_display),
440 rdesktop_clipboard_formats_atom, XA_STRING, 8, PropModeReplace, data,
441 length);
442
443 XSetSelectionOwner(g_display, clipboard_atom, g_wnd, g_last_gesturetime);
444 if (XGetSelectionOwner(g_display, clipboard_atom) != g_wnd)
445 warning("Failed to aquire ownership of CLIPBOARD clipboard\n");
446 }
447
448
449 void
450 ui_clip_handle_data(uint8 * data, uint32 length)
451 {
452 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 firstnull = (uint8 *) strchr((char *) data, '\0');
461 if (firstnull)
462 {
463 length = firstnull - data + 1;
464 }
465 }
466
467 xclip_provide_selection(&selection_request, XA_STRING, 8, data, length - 1);
468 }
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 XChangeProperty(g_display, g_wnd, rdesktop_clipboard_target_atom,
480 XA_INTEGER, 32, PropModeReplace, (unsigned char *) &format, 1);
481
482 XConvertSelection(g_display, primary_atom, rdesktop_clipboard_formats_atom,
483 rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
484 return;
485 }
486
487 selectionowner = XGetSelectionOwner(g_display, primary_atom);
488 if (selectionowner != None)
489 {
490 XConvertSelection(g_display, primary_atom, targets_atom,
491 rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
492 return;
493 }
494
495 /* No PRIMARY, try CLIPBOARD */
496 selectionowner = XGetSelectionOwner(g_display, clipboard_atom);
497 if (selectionowner != None)
498 {
499 XConvertSelection(g_display, clipboard_atom, targets_atom,
500 rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
501 return;
502 }
503
504 /* No data available */
505 cliprdr_send_data(NULL, 0);
506 }
507
508 void
509 ui_clip_sync(void)
510 {
511 cliprdr_send_simple_native_format_announce(CF_TEXT);
512 }
513
514
515 void
516 xclip_init(void)
517 {
518 if (!cliprdr_init())
519 return;
520
521 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 rdesktop_clipboard_target_atom =
526 XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_TARGET", False);
527 incr_atom = XInternAtom(g_display, "INCR", False);
528 targets[0] = targets_atom;
529 targets[1] = XInternAtom(g_display, "TEXT", False);
530 targets[2] = XInternAtom(g_display, "STRING", False);
531 targets[3] = XInternAtom(g_display, "text/unicode", False);
532 targets[4] = XInternAtom(g_display, "TIMESTAMP", False);
533 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 rdesktop_clipboard_formats_atom =
538 XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_FORMATS", False);
539 XSelectInput(g_display, DefaultRootWindow(g_display), PropertyChangeMask);
540 }

  ViewVC Help
Powered by ViewVC 1.1.26