/[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 1028 - (show annotations)
Mon Nov 14 14:46:16 2005 UTC (18 years, 6 months ago) by astrand
File MIME type: text/plain
File size: 16701 byte(s)
Indent fixes

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 {
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 {
348 XFree(data);
349 return;
350 }
351
352 if (nitems == 0)
353 {
354 XGetWindowAttributes(g_display, g_wnd, &wa);
355 XSelectInput(g_display, g_wnd,
356 (wa.your_event_mask ^ PropertyChangeMask));
357 XFree(data);
358 g_waiting_for_INCR = 0;
359
360 if (g_clip_buflen > 0)
361 {
362 cliprdr_send_data(g_clip_buffer, g_clip_buflen + 1);
363
364 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 }
372 else
373 {
374 uint8 *translated_data;
375 uint32 length = nitems;
376 uint8 *tmp;
377
378 offset += (length / 4);
379 DEBUG_CLIPBOARD(("Translating linebreaks before sending data\n"));
380 translated_data = lf2crlf(data, &length);
381
382 tmp = xmalloc(length + g_clip_buflen);
383 strncpy((char *) tmp, (char *) g_clip_buffer, g_clip_buflen);
384 xfree(g_clip_buffer);
385
386 strncpy((char *) (tmp + g_clip_buflen), (char *) translated_data,
387 length);
388 xfree(translated_data);
389
390 g_clip_buffer = tmp;
391 g_clip_buflen += length;
392
393 XFree(data);
394 }
395 }
396 XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_target_atom);
397 }
398
399 if (event->atom != rdesktop_clipboard_formats_atom)
400 return;
401
402 if (have_primary) /* from us */
403 return;
404
405 if (event->state == PropertyNewValue)
406 {
407 res = XGetWindowProperty(g_display, DefaultRootWindow(g_display),
408 rdesktop_clipboard_formats_atom, 0,
409 XMaxRequestSize(g_display), False, XA_STRING, &type,
410 &format, &nitems, &bytes_left, &data);
411
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 cliprdr_send_simple_native_format_announce(CF_TEXT);
422 rdesktop_is_selection_owner = 0;
423 }
424 #endif
425
426
427 /* 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 void
433 ui_clip_format_announce(uint8 * data, uint32 length)
434 {
435 XSetSelectionOwner(g_display, primary_atom, g_wnd, g_last_gesturetime);
436 if (XGetSelectionOwner(g_display, primary_atom) != g_wnd)
437 {
438 warning("Failed to aquire ownership of PRIMARY clipboard\n");
439 return;
440 }
441
442 have_primary = 1;
443 XChangeProperty(g_display, DefaultRootWindow(g_display),
444 rdesktop_clipboard_formats_atom, XA_STRING, 8, PropModeReplace, data,
445 length);
446
447 XSetSelectionOwner(g_display, clipboard_atom, g_wnd, g_last_gesturetime);
448 if (XGetSelectionOwner(g_display, clipboard_atom) != g_wnd)
449 warning("Failed to aquire ownership of CLIPBOARD clipboard\n");
450 }
451
452
453 void
454 ui_clip_handle_data(uint8 * data, uint32 length)
455 {
456 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 firstnull = (uint8 *) strchr((char *) data, '\0');
465 if (firstnull)
466 {
467 length = firstnull - data + 1;
468 }
469 }
470
471 xclip_provide_selection(&selection_request, XA_STRING, 8, data, length - 1);
472 }
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 XChangeProperty(g_display, g_wnd, rdesktop_clipboard_target_atom,
484 XA_INTEGER, 32, PropModeReplace, (unsigned char *) &format, 1);
485
486 XConvertSelection(g_display, primary_atom, rdesktop_clipboard_formats_atom,
487 rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
488 return;
489 }
490
491 selectionowner = XGetSelectionOwner(g_display, primary_atom);
492 if (selectionowner != None)
493 {
494 XConvertSelection(g_display, primary_atom, targets_atom,
495 rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
496 return;
497 }
498
499 /* No PRIMARY, try CLIPBOARD */
500 selectionowner = XGetSelectionOwner(g_display, clipboard_atom);
501 if (selectionowner != None)
502 {
503 XConvertSelection(g_display, clipboard_atom, targets_atom,
504 rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
505 return;
506 }
507
508 /* No data available */
509 cliprdr_send_data(NULL, 0);
510 }
511
512 void
513 ui_clip_sync(void)
514 {
515 cliprdr_send_simple_native_format_announce(CF_TEXT);
516 }
517
518
519 void
520 xclip_init(void)
521 {
522 if (!cliprdr_init())
523 return;
524
525 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 rdesktop_clipboard_target_atom =
530 XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_TARGET", False);
531 incr_atom = XInternAtom(g_display, "INCR", False);
532 targets[0] = targets_atom;
533 targets[1] = XInternAtom(g_display, "TEXT", False);
534 targets[2] = XInternAtom(g_display, "STRING", False);
535 targets[3] = XInternAtom(g_display, "text/unicode", False);
536 targets[4] = XInternAtom(g_display, "TIMESTAMP", False);
537 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 rdesktop_clipboard_formats_atom =
542 XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_FORMATS", False);
543 XSelectInput(g_display, DefaultRootWindow(g_display), PropertyChangeMask);
544 }

  ViewVC Help
Powered by ViewVC 1.1.26