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 along 18 with this program; if not, write to the Free Software Foundation, Inc., 19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 #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 52 /* Translate LF to CR-LF. To do this, we must allocate more memory. 53 The returned string is null-terminated, as required by CF_TEXT. 54 Does not stop on embedded nulls. 55 The length is updated. */ 56 static void 57 crlf2lf(uint8 * data, uint32 * length) 58 { 59 uint8 *dst, *src; 60 src = dst = data; 61 while (src < data + *length) 62 { 63 if (*src != '\x0d') 64 *dst++ = *src; 65 src++; 66 } 67 *length = dst - data; 68 } 69 70 #ifdef USE_UNICODE_CLIPBOARD 71 /* Translate LF to CR-LF. To do this, we must allocate more memory. 72 The returned string is null-terminated, as required by CF_UNICODETEXT. 73 The size is updated. */ 74 static uint8 * 75 utf16_lf2crlf(uint8 * data, uint32 * size) 76 { 77 uint8 *result; 78 uint16 *inptr, *outptr; 79 Bool swap_endianess; 80 81 /* Worst case: Every char is LF */ 82 result = xmalloc((*size * 2) + 2); 83 if (result == NULL) 84 return NULL; 85 86 inptr = (uint16 *) data; 87 outptr = (uint16 *) result; 88 89 /* Check for a reversed BOM */ 90 swap_endianess = (*inptr == 0xfffe); 91 92 while ((uint8 *) inptr < data + *size) 93 { 94 uint16 uvalue = *inptr; 95 if (swap_endianess) 96 uvalue = ((uvalue << 8) & 0xff00) + (uvalue >> 8); 97 if (uvalue == 0x0a) 98 *outptr++ = swap_endianess ? 0x0d00 : 0x0d; 99 *outptr++ = *inptr++; 100 } 101 *outptr++ = 0; /* null termination */ 102 *size = (uint8 *) outptr - result; 103 104 return result; 105 } 106 #else 107 /* Translate LF to CR-LF. To do this, we must allocate more memory. 108 The length is updated. */ 109 static uint8 * 110 lf2crlf(uint8 * data, uint32 * length) 111 { 112 uint8 *result, *p, *o; 113 114 /* Worst case: Every char is LF */ 115 result = xmalloc(*length * 2); 116 117 p = data; 118 o = result; 119 120 while (p < data + *length) 121 { 122 if (*p == '\x0a') 123 *o++ = '\x0d'; 124 *o++ = *p++; 125 } 126 *length = o - result; 127 128 /* Convenience */ 129 *o++ = '\0'; 130 131 return result; 132 } 133 #endif 134 135 static void 136 xclip_provide_selection(RDPCLIENT * This, XSelectionRequestEvent * req, Atom type, unsigned int format, uint8 * data, 137 uint32 length) 138 { 139 XEvent xev; 140 141 DEBUG_CLIPBOARD(("xclip_provide_selection: requestor=0x%08x, target=%s, property=%s, length=%u\n", (unsigned) req->requestor, XGetAtomName(This->display, req->target), XGetAtomName(This->display, req->property), (unsigned) length)); 142 143 XChangeProperty(This->display, req->requestor, req->property, 144 type, format, PropModeReplace, data, length); 145 146 xev.xselection.type = SelectionNotify; 147 xev.xselection.serial = 0; 148 xev.xselection.send_event = True; 149 xev.xselection.requestor = req->requestor; 150 xev.xselection.selection = req->selection; 151 xev.xselection.target = req->target; 152 xev.xselection.property = req->property; 153 xev.xselection.time = req->time; 154 XSendEvent(This->display, req->requestor, False, NoEventMask, &xev); 155 } 156 157 /* Replies a clipboard requestor, telling that we're unable to satisfy his request for whatever reason. 158 This has the benefit of finalizing the clipboard negotiation and thus not leaving our requestor 159 lingering (and, potentially, stuck). */ 160 static void 161 xclip_refuse_selection(RDPCLIENT * This, XSelectionRequestEvent * req) 162 { 163 XEvent xev; 164 165 DEBUG_CLIPBOARD(("xclip_refuse_selection: requestor=0x%08x, target=%s, property=%s\n", 166 (unsigned) req->requestor, XGetAtomName(This->display, req->target), 167 XGetAtomName(This->display, req->property))); 168 169 xev.xselection.type = SelectionNotify; 170 xev.xselection.serial = 0; 171 xev.xselection.send_event = True; 172 xev.xselection.requestor = req->requestor; 173 xev.xselection.selection = req->selection; 174 xev.xselection.target = req->target; 175 xev.xselection.property = None; 176 xev.xselection.time = req->time; 177 XSendEvent(This->display, req->requestor, False, NoEventMask, &xev); 178 } 179 180 /* Wrapper for cliprdr_send_data which also cleans the request state. */ 181 static void 182 helper_cliprdr_send_response(RDPCLIENT * This, uint8 * data, uint32 length) 183 { 184 if (This->xclip.rdp_clipboard_request_format != 0) 185 { 186 cliprdr_send_data(This, data, length); 187 This->xclip.rdp_clipboard_request_format = 0; 188 if (!This->xclip.rdesktop_is_selection_owner) 189 cliprdr_send_simple_native_format_announce(This, RDP_CF_TEXT); 190 } 191 } 192 193 /* Last resort, when we have to provide clipboard data but for whatever 194 reason couldn't get any. 195 */ 196 static void 197 helper_cliprdr_send_empty_response(RDPCLIENT * This) 198 { 199 helper_cliprdr_send_response(This, NULL, 0); 200 } 201 202 /* Replies with clipboard data to RDP, converting it from the target format 203 to the expected RDP format as necessary. Returns true if data was sent. 204 */ 205 static Bool 206 xclip_send_data_with_convert(RDPCLIENT * This, uint8 * source, size_t source_size, Atom target) 207 { 208 DEBUG_CLIPBOARD(("xclip_send_data_with_convert: target=%s, size=%u\n", 209 XGetAtomName(This->display, target), (unsigned) source_size)); 210 211 #ifdef USE_UNICODE_CLIPBOARD 212 if (target == This->xclip.format_string_atom || 213 target == This->xclip.format_unicode_atom || target == This->xclip.format_utf8_string_atom) 214 { 215 size_t unicode_buffer_size; 216 char *unicode_buffer; 217 iconv_t cd; 218 size_t unicode_buffer_size_remaining; 219 char *unicode_buffer_remaining; 220 char *data_remaining; 221 size_t data_size_remaining; 222 uint32 translated_data_size; 223 uint8 *translated_data; 224 225 if (This->xclip.rdp_clipboard_request_format != RDP_CF_TEXT) 226 return False; 227 228 /* Make an attempt to convert any string we send to Unicode. 229 We don't know what the RDP server's ANSI Codepage is, or how to convert 230 to it, so using CF_TEXT is not safe (and is unnecessary, since all 231 WinNT versions are Unicode-minded). 232 */ 233 if (target == This->xclip.format_string_atom) 234 { 235 char *locale_charset = nl_langinfo(CODESET); 236 cd = iconv_open(WINDOWS_CODEPAGE, locale_charset); 237 if (cd == (iconv_t) - 1) 238 { 239 DEBUG_CLIPBOARD(("Locale charset %s not found in iconv. Unable to convert clipboard text.\n", locale_charset)); 240 return False; 241 } 242 unicode_buffer_size = source_size * 4; 243 } 244 else if (target == This->xclip.format_unicode_atom) 245 { 246 cd = iconv_open(WINDOWS_CODEPAGE, "UCS-2"); 247 if (cd == (iconv_t) - 1) 248 { 249 return False; 250 } 251 unicode_buffer_size = source_size; 252 } 253 else if (target == This->xclip.format_utf8_string_atom) 254 { 255 cd = iconv_open(WINDOWS_CODEPAGE, "UTF-8"); 256 if (cd == (iconv_t) - 1) 257 { 258 return False; 259 } 260 /* UTF-8 is guaranteed to be less or equally compact 261 as UTF-16 for all Unicode chars >=2 bytes. 262 */ 263 unicode_buffer_size = source_size * 2; 264 } 265 else 266 { 267 return False; 268 } 269 270 unicode_buffer = xmalloc(unicode_buffer_size); 271 unicode_buffer_size_remaining = unicode_buffer_size; 272 unicode_buffer_remaining = unicode_buffer; 273 data_remaining = (char *) source; 274 data_size_remaining = source_size; 275 iconv(cd, (ICONV_CONST char **) &data_remaining, &data_size_remaining, 276 &unicode_buffer_remaining, &unicode_buffer_size_remaining); 277 iconv_close(cd); 278 279 /* translate linebreaks */ 280 translated_data_size = unicode_buffer_size - unicode_buffer_size_remaining; 281 translated_data = utf16_lf2crlf((uint8 *) unicode_buffer, &translated_data_size); 282 if (translated_data != NULL) 283 { 284 DEBUG_CLIPBOARD(("Sending Unicode string of %d bytes\n", 285 translated_data_size)); 286 helper_cliprdr_send_response(This, translated_data, translated_data_size); 287 xfree(translated_data); /* Not the same thing as XFree! */ 288 } 289 290 xfree(unicode_buffer); 291 292 return True; 293 } 294 #else 295 if (target == This->xclip.format_string_atom) 296 { 297 uint8 *translated_data; 298 uint32 length = source_size; 299 300 if (This->xclip.rdp_clipboard_request_format != RDP_CF_TEXT) 301 return False; 302 303 DEBUG_CLIPBOARD(("Translating linebreaks before sending data\n")); 304 translated_data = lf2crlf(source, &length); 305 if (translated_data != NULL) 306 { 307 helper_cliprdr_send_response(This, translated_data, length); 308 xfree(translated_data); /* Not the same thing as XFree! */ 309 } 310 311 return True; 312 } 313 #endif 314 else if (target == This->xclip.rdesktop_native_atom) 315 { 316 helper_cliprdr_send_response(This, source, source_size + 1); 317 318 return True; 319 } 320 else 321 { 322 return False; 323 } 324 } 325 326 static void 327 xclip_clear_target_props(RDPCLIENT * This) 328 { 329 XDeleteProperty(This->display, This->wnd, This->xclip.rdesktop_clipboard_target_atom); 330 XDeleteProperty(This->display, This->wnd, This->xclip.rdesktop_primary_timestamp_target_atom); 331 XDeleteProperty(This->display, This->wnd, This->xclip.rdesktop_clipboard_timestamp_target_atom); 332 } 333 334 static void 335 xclip_notify_change(RDPCLIENT * This) 336 { 337 XChangeProperty(This->display, DefaultRootWindow(This->display), 338 This->xclip.rdesktop_selection_notify_atom, XA_INTEGER, 32, PropModeReplace, NULL, 0); 339 } 340 341 static void 342 xclip_probe_selections(RDPCLIENT * This) 343 { 344 Window primary_owner, clipboard_owner; 345 346 if (This->xclip.probing_selections) 347 { 348 DEBUG_CLIPBOARD(("Already probing selections. Scheduling reprobe.\n")); 349 This->xclip.reprobe_selections = True; 350 return; 351 } 352 353 DEBUG_CLIPBOARD(("Probing selections.\n")); 354 355 This->xclip.probing_selections = True; 356 This->xclip.reprobe_selections = False; 357 358 xclip_clear_target_props(This); 359 360 if (This->xclip.auto_mode) 361 primary_owner = XGetSelectionOwner(This->display, This->xclip.primary_atom); 362 else 363 primary_owner = None; 364 365 clipboard_owner = XGetSelectionOwner(This->display, This->xclip.clipboard_atom); 366 367 /* If we own all relevant selections then don't do anything. */ 368 if (((primary_owner == This->wnd) || !This->xclip.auto_mode) && (clipboard_owner == This->wnd)) 369 goto end; 370 371 /* Both available */ 372 if ((primary_owner != None) && (clipboard_owner != None)) 373 { 374 This->xclip.primary_timestamp = 0; 375 This->xclip.clipboard_timestamp = 0; 376 XConvertSelection(This->display, This->xclip.primary_atom, This->xclip.timestamp_atom, 377 This->xclip.rdesktop_primary_timestamp_target_atom, This->wnd, CurrentTime); 378 XConvertSelection(This->display, This->xclip.clipboard_atom, This->xclip.timestamp_atom, 379 This->xclip.rdesktop_clipboard_timestamp_target_atom, This->wnd, CurrentTime); 380 return; 381 } 382 383 /* Just PRIMARY */ 384 if (primary_owner != None) 385 { 386 XConvertSelection(This->display, This->xclip.primary_atom, This->xclip.targets_atom, 387 This->xclip.rdesktop_clipboard_target_atom, This->wnd, CurrentTime); 388 return; 389 } 390 391 /* Just CLIPBOARD */ 392 if (clipboard_owner != None) 393 { 394 XConvertSelection(This->display, This->xclip.clipboard_atom, This->xclip.targets_atom, 395 This->xclip.rdesktop_clipboard_target_atom, This->wnd, CurrentTime); 396 return; 397 } 398 399 DEBUG_CLIPBOARD(("No owner of any selection.\n")); 400 401 /* FIXME: 402 Without XFIXES, we cannot reliably know the formats offered by an 403 upcoming selection owner, so we just lie about him offering 404 RDP_CF_TEXT. */ 405 cliprdr_send_simple_native_format_announce(This, RDP_CF_TEXT); 406 407 end: 408 This->xclip.probing_selections = False; 409 } 410 411 /* This function is called for SelectionNotify events. 412 The SelectionNotify event is sent from the clipboard owner to the requestor 413 after his request was satisfied. 414 If this function is called, we're the requestor side. */ 415 #ifndef MAKE_PROTO 416 void 417 xclip_handle_SelectionNotify(RDPCLIENT * This, XSelectionEvent * event) 418 { 419 unsigned long nitems, bytes_left; 420 XWindowAttributes wa; 421 Atom type; 422 Atom *supported_targets; 423 int res, i, format; 424 uint8 *data = NULL; 425 426 if (event->property == None) 427 goto fail; 428 429 DEBUG_CLIPBOARD(("xclip_handle_SelectionNotify: selection=%s, target=%s, property=%s\n", 430 XGetAtomName(This->display, event->selection), 431 XGetAtomName(This->display, event->target), 432 XGetAtomName(This->display, event->property))); 433 434 if (event->target == This->xclip.timestamp_atom) 435 { 436 if (event->selection == This->xclip.primary_atom) 437 { 438 res = XGetWindowProperty(This->display, This->wnd, 439 This->xclip.rdesktop_primary_timestamp_target_atom, 0, 440 XMaxRequestSize(This->display), False, AnyPropertyType, 441 &type, &format, &nitems, &bytes_left, &data); 442 } 443 else 444 { 445 res = XGetWindowProperty(This->display, This->wnd, 446 This->xclip.rdesktop_clipboard_timestamp_target_atom, 0, 447 XMaxRequestSize(This->display), False, AnyPropertyType, 448 &type, &format, &nitems, &bytes_left, &data); 449 } 450 451 452 if ((res != Success) || (nitems != 1) || (format != 32)) 453 { 454 DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n")); 455 goto fail; 456 } 457 458 if (event->selection == This->xclip.primary_atom) 459 { 460 This->xclip.primary_timestamp = *(Time *) data; 461 if (This->xclip.primary_timestamp == 0) 462 This->xclip.primary_timestamp++; 463 XDeleteProperty(This->display, This->wnd, This->xclip.rdesktop_primary_timestamp_target_atom); 464 DEBUG_CLIPBOARD(("Got PRIMARY timestamp: %u\n", 465 (unsigned) This->xclip.primary_timestamp)); 466 } 467 else 468 { 469 This->xclip.clipboard_timestamp = *(Time *) data; 470 if (This->xclip.clipboard_timestamp == 0) 471 This->xclip.clipboard_timestamp++; 472 XDeleteProperty(This->display, This->wnd, This->xclip.rdesktop_clipboard_timestamp_target_atom); 473 DEBUG_CLIPBOARD(("Got CLIPBOARD timestamp: %u\n", 474 (unsigned) This->xclip.clipboard_timestamp)); 475 } 476 477 XFree(data); 478 479 if (This->xclip.primary_timestamp && This->xclip.clipboard_timestamp) 480 { 481 if (This->xclip.primary_timestamp > This->xclip.clipboard_timestamp) 482 { 483 DEBUG_CLIPBOARD(("PRIMARY is most recent selection.\n")); 484 XConvertSelection(This->display, This->xclip.primary_atom, This->xclip.targets_atom, 485 This->xclip.rdesktop_clipboard_target_atom, This->wnd, 486 event->time); 487 } 488 else 489 { 490 DEBUG_CLIPBOARD(("CLIPBOARD is most recent selection.\n")); 491 XConvertSelection(This->display, This->xclip.clipboard_atom, This->xclip.targets_atom, 492 This->xclip.rdesktop_clipboard_target_atom, This->wnd, 493 event->time); 494 } 495 } 496 497 return; 498 } 499 500 if (This->xclip.probing_selections && This->xclip.reprobe_selections) 501 { 502 This->xclip.probing_selections = False; 503 xclip_probe_selections(This); 504 return; 505 } 506 507 res = XGetWindowProperty(This->display, This->wnd, This->xclip.rdesktop_clipboard_target_atom, 508 0, XMaxRequestSize(This->display), False, AnyPropertyType, 509 &type, &format, &nitems, &bytes_left, &data); 510 511 xclip_clear_target_props(This); 512 513 if (res != Success) 514 { 515 DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n")); 516 goto fail; 517 } 518 519 if (type == This->xclip.incr_atom) 520 { 521 DEBUG_CLIPBOARD(("Received INCR.\n")); 522 523 XGetWindowAttributes(This->display, This->wnd, &wa); 524 if ((wa.your_event_mask | PropertyChangeMask) != wa.your_event_mask) 525 { 526 XSelectInput(This->display, This->wnd, (wa.your_event_mask | PropertyChangeMask)); 527 } 528 XFree(data); 529 This->xclip.incr_target = event->target; 530 This->xclip.waiting_for_INCR = 1; 531 goto end; 532 } 533 534 /* Negotiate target format */ 535 if (event->target == This->xclip.targets_atom) 536 { 537 /* Determine the best of text This->xclip.targets that we have available: 538 Prefer UTF8_STRING > text/unicode (unspecified encoding) > STRING 539 (ignore TEXT and COMPOUND_TEXT because we don't have code to handle them) 540 */ 541 int text_target_satisfaction = 0; 542 Atom best_text_target = 0; /* measures how much we're satisfied with what we found */ 543 if (type != None) 544 { 545 supported_targets = (Atom *) data; 546 for (i = 0; i < nitems; i++) 547 { 548 DEBUG_CLIPBOARD(("Target %d: %s\n", i, 549 XGetAtomName(This->display, supported_targets[i]))); 550 if (supported_targets[i] == This->xclip.format_string_atom) 551 { 552 if (text_target_satisfaction < 1) 553 { 554 DEBUG_CLIPBOARD(("Other party supports STRING, choosing that as best_target\n")); 555 best_text_target = supported_targets[i]; 556 text_target_satisfaction = 1; 557 } 558 } 559 #ifdef USE_UNICODE_CLIPBOARD 560 else if (supported_targets[i] == This->xclip.format_unicode_atom) 561 { 562 if (text_target_satisfaction < 2) 563 { 564 DEBUG_CLIPBOARD(("Other party supports text/unicode, choosing that as best_target\n")); 565 best_text_target = supported_targets[i]; 566 text_target_satisfaction = 2; 567 } 568 } 569 else if (supported_targets[i] == This->xclip.format_utf8_string_atom) 570 { 571 if (text_target_satisfaction < 3) 572 { 573 DEBUG_CLIPBOARD(("Other party supports UTF8_STRING, choosing that as best_target\n")); 574 best_text_target = supported_targets[i]; 575 text_target_satisfaction = 3; 576 } 577 } 578 #endif 579 else if (supported_targets[i] == This->xclip.rdesktop_clipboard_formats_atom) 580 { 581 if (This->xclip.probing_selections && (text_target_satisfaction < 4)) 582 { 583 DEBUG_CLIPBOARD(("Other party supports native formats, choosing that as best_target\n")); 584 best_text_target = supported_targets[i]; 585 text_target_satisfaction = 4; 586 } 587 } 588 } 589 } 590 591 /* Kickstarting the next step in the process of satisfying RDP's 592 clipboard request -- specifically, requesting the actual clipboard data. 593 */ 594 if ((best_text_target != 0) 595 && (!This->xclip.probing_selections 596 || (best_text_target == This->xclip.rdesktop_clipboard_formats_atom))) 597 { 598 XConvertSelection(This->display, event->selection, best_text_target, 599 This->xclip.rdesktop_clipboard_target_atom, This->wnd, event->time); 600 goto end; 601 } 602 else 603 { 604 DEBUG_CLIPBOARD(("Unable to find a textual target to satisfy RDP clipboard text request\n")); 605 goto fail; 606 } 607 } 608 else 609 { 610 if (This->xclip.probing_selections) 611 { 612 Window primary_owner, clipboard_owner; 613 614 /* FIXME: 615 Without XFIXES, we must make sure that the other 616 rdesktop owns all relevant selections or we might try 617 to get a native format from non-rdesktop window later 618 on. */ 619 620 clipboard_owner = XGetSelectionOwner(This->display, This->xclip.clipboard_atom); 621 622 if (This->xclip.auto_mode) 623 primary_owner = XGetSelectionOwner(This->display, This->xclip.primary_atom); 624 else 625 primary_owner = clipboard_owner; 626 627 if (primary_owner != clipboard_owner) 628 goto fail; 629 630 DEBUG_CLIPBOARD(("Got fellow rdesktop formats\n")); 631 This->xclip.probing_selections = False; 632 This->xclip.rdesktop_is_selection_owner = True; 633 cliprdr_send_native_format_announce(This, data, nitems); 634 } 635 else if (!xclip_send_data_with_convert(This, data, nitems, event->target)) 636 { 637 goto fail; 638 } 639 } 640 641 end: 642 if (data) 643 XFree(data); 644 645 return; 646 647 fail: 648 xclip_clear_target_props(This); 649 if (This->xclip.probing_selections) 650 { 651 DEBUG_CLIPBOARD(("Unable to find suitable target. Using default text format.\n")); 652 This->xclip.probing_selections = False; 653 This->xclip.rdesktop_is_selection_owner = False; 654 655 /* FIXME: 656 Without XFIXES, we cannot reliably know the formats offered by an 657 upcoming selection owner, so we just lie about him offering 658 RDP_CF_TEXT. */ 659 cliprdr_send_simple_native_format_announce(This, RDP_CF_TEXT); 660 } 661 else 662 { 663 helper_cliprdr_send_empty_response(This); 664 } 665 goto end; 666 } 667 668 /* This function is called for SelectionRequest events. 669 The SelectionRequest event is sent from the requestor to the clipboard owner 670 to request clipboard data. 671 */ 672 void 673 xclip_handle_SelectionRequest(RDPCLIENT * This, XSelectionRequestEvent * event) 674 { 675 unsigned long nitems, bytes_left; 676 unsigned char *prop_return; 677 int format, res; 678 Atom type; 679 680 DEBUG_CLIPBOARD(("xclip_handle_SelectionRequest: selection=%s, target=%s, property=%s\n", 681 XGetAtomName(This->display, event->selection), 682 XGetAtomName(This->display, event->target), 683 XGetAtomName(This->display, event->property))); 684 685 if (event->target == This->xclip.targets_atom) 686 { 687 xclip_provide_selection(This, event, XA_ATOM, 32, (uint8 *) & This->xclip.targets, This->xclip.num_targets); 688 return; 689 } 690 else if (event->target == This->xclip.timestamp_atom) 691 { 692 xclip_provide_selection(This, event, XA_INTEGER, 32, (uint8 *) & This->xclip.acquire_time, 1); 693 return; 694 } 695 else if (event->target == This->xclip.rdesktop_clipboard_formats_atom) 696 { 697 xclip_provide_selection(This, event, XA_STRING, 8, This->xclip.formats_data, This->xclip.formats_data_length); 698 } 699 else 700 { 701 /* All the following This->xclip.targets require an async operation with the RDP server 702 and currently we don't do X clipboard request queueing so we can only 703 handle one such request at a time. */ 704 if (This->xclip.has_selection_request) 705 { 706 DEBUG_CLIPBOARD(("Error: Another clipboard request was already sent to the RDP server and not yet responded. Refusing this request.\n")); 707 xclip_refuse_selection(This, event); 708 return; 709 } 710 if (event->target == This->xclip.rdesktop_native_atom) 711 { 712 /* Before the requestor makes a request for the _RDESKTOP_NATIVE target, 713 he should declare requestor[property] = CF_SOMETHING. */ 714 res = XGetWindowProperty(This->display, event->requestor, 715 event->property, 0, 1, True, 716 XA_INTEGER, &type, &format, &nitems, &bytes_left, 717 &prop_return); 718 if (res != Success) 719 { 720 DEBUG_CLIPBOARD(("Requested native format but didn't specifiy which.\n")); 721 xclip_refuse_selection(This, event); 722 return; 723 } 724 725 format = *(uint32 *) prop_return; 726 XFree(prop_return); 727 } 728 else if (event->target == This->xclip.format_string_atom || event->target == XA_STRING) 729 { 730 /* STRING and XA_STRING are defined to be ISO8859-1 */ 731 format = CF_TEXT; 732 } 733 else if (event->target == This->xclip.format_utf8_string_atom) 734 { 735 #ifdef USE_UNICODE_CLIPBOARD 736 format = CF_UNICODETEXT; 737 #else 738 DEBUG_CLIPBOARD(("Requested target unavailable due to lack of Unicode support. (It was not in TARGETS, so why did you ask for it?!)\n")); 739 xclip_refuse_selection(This, event); 740 return; 741 #endif 742 } 743 else if (event->target == This->xclip.format_unicode_atom) 744 { 745 /* Assuming text/unicode to be UTF-16 */ 746 format = CF_UNICODETEXT; 747 } 748 else 749 { 750 DEBUG_CLIPBOARD(("Requested target unavailable. (It was not in TARGETS, so why did you ask for it?!)\n")); 751 xclip_refuse_selection(This, event); 752 return; 753 } 754 755 cliprdr_send_data_request(This, format); 756 This->xclip.selection_request = *event; 757 This->xclip.has_selection_request = True; 758 return; /* wait for data */ 759 } 760 } 761 762 /* While this rdesktop holds ownership over the clipboard, it means the clipboard data 763 is offered by the RDP server (and when it is pasted inside RDP, there's no network 764 roundtrip). 765 766 This event (SelectionClear) symbolizes this rdesktop lost onwership of the clipboard 767 to some other X client. We should find out what clipboard formats this other 768 client offers and announce that to RDP. */ 769 void 770 xclip_handle_SelectionClear(RDPCLIENT * This) 771 { 772 DEBUG_CLIPBOARD(("xclip_handle_SelectionClear\n")); 773 xclip_notify_change(This); 774 xclip_probe_selections(This); 775 } 776 777 /* Called when any property changes in our window or the root window. */ 778 void 779 xclip_handle_PropertyNotify(RDPCLIENT * This, XPropertyEvent * event) 780 { 781 unsigned long nitems; 782 unsigned long offset = 0; 783 unsigned long bytes_left = 1; 784 int format; 785 XWindowAttributes wa; 786 uint8 *data; 787 Atom type; 788 789 if (event->state == PropertyNewValue && This->xclip.waiting_for_INCR) 790 { 791 DEBUG_CLIPBOARD(("x_clip_handle_PropertyNotify: This->xclip.waiting_for_INCR != 0\n")); 792 793 while (bytes_left > 0) 794 { 795 /* Unlike the specification, we don't set the 'delete' arugment to True 796 since we slurp the INCR's chunks in even-smaller chunks of 4096 bytes. */ 797 if ((XGetWindowProperty 798 (This->display, This->wnd, This->xclip.rdesktop_clipboard_target_atom, offset, 4096L, 799 False, AnyPropertyType, &type, &format, &nitems, &bytes_left, 800 &data) != Success)) 801 { 802 XFree(data); 803 return; 804 } 805 806 if (nitems == 0) 807 { 808 /* INCR transfer finished */ 809 XGetWindowAttributes(This->display, This->wnd, &wa); 810 XSelectInput(This->display, This->wnd, 811 (wa.your_event_mask ^ PropertyChangeMask)); 812 XFree(data); 813 This->xclip.waiting_for_INCR = 0; 814 815 if (This->xclip.clip_buflen > 0) 816 { 817 if (!xclip_send_data_with_convert 818 (This, This->xclip.clip_buffer, This->xclip.clip_buflen, This->xclip.incr_target)) 819 { 820 helper_cliprdr_send_empty_response(This); 821 } 822 xfree(This->xclip.clip_buffer); 823 This->xclip.clip_buffer = NULL; 824 This->xclip.clip_buflen = 0; 825 } 826 } 827 else 828 { 829 /* Another chunk in the INCR transfer */ 830 offset += (nitems / 4); /* offset at which to begin the next slurp */ 831 This->xclip.clip_buffer = xrealloc(This->xclip.clip_buffer, This->xclip.clip_buflen + nitems); 832 memcpy(This->xclip.clip_buffer + This->xclip.clip_buflen, data, nitems); 833 This->xclip.clip_buflen += nitems; 834 835 XFree(data); 836 } 837 } 838 XDeleteProperty(This->display, This->wnd, This->xclip.rdesktop_clipboard_target_atom); 839 return; 840 } 841 842 if ((event->atom == This->xclip.rdesktop_selection_notify_atom) && 843 (event->window == DefaultRootWindow(This->display))) 844 xclip_probe_selections(This); 845 } 846 #endif 847 848 849 /* Called when the RDP server announces new clipboard data formats. 850 In response, we: 851 - take ownership over the clipboard 852 - declare those formats in their Windows native form 853 to other rdesktop instances on this X server */ 854 void 855 ui_clip_format_announce(RDPCLIENT * This, uint8 * data, uint32 length) 856 { 857 This->xclip.acquire_time = This->last_gesturetime; 858 859 XSetSelectionOwner(This->display, This->xclip.primary_atom, This->wnd, This->xclip.acquire_time); 860 if (XGetSelectionOwner(This->display, This->xclip.primary_atom) != This->wnd) 861 warning("Failed to aquire ownership of PRIMARY clipboard\n"); 862 863 XSetSelectionOwner(This->display, This->xclip.clipboard_atom, This->wnd, This->xclip.acquire_time); 864 if (XGetSelectionOwner(This->display, This->xclip.clipboard_atom) != This->wnd) 865 warning("Failed to aquire ownership of CLIPBOARD clipboard\n"); 866 867 if (This->xclip.formats_data) 868 xfree(This->xclip.formats_data); 869 This->xclip.formats_data = xmalloc(length); 870 memcpy(This->xclip.formats_data, data, length); 871 This->xclip.formats_data_length = length; 872 873 xclip_notify_change(This); 874 } 875 876 /* Called when the RDP server responds with clipboard data (after we've requested it). */ 877 void 878 ui_clip_handle_data(RDPCLIENT * This, uint8 * data, uint32 length) 879 { 880 BOOL free_data = False; 881 882 if (length == 0) 883 { 884 xclip_refuse_selection(This, &This->xclip.selection_request); 885 This->xclip.has_selection_request = False; 886 return; 887 } 888 889 if (This->xclip.selection_request.target == This->xclip.format_string_atom || This->xclip.selection_request.target == XA_STRING) 890 { 891 /* We're expecting a CF_TEXT response */ 892 uint8 *firstnull; 893 894 /* translate linebreaks */ 895 crlf2lf(data, &length); 896 897 /* Only send data up to null byte, if any */ 898 firstnull = (uint8 *) strchr((char *) data, '\0'); 899 if (firstnull) 900 { 901 length = firstnull - data + 1; 902 } 903 } 904 #ifdef USE_UNICODE_CLIPBOARD 905 else if (This->xclip.selection_request.target == This->xclip.format_utf8_string_atom) 906 { 907 /* We're expecting a CF_UNICODETEXT response */ 908 iconv_t cd = iconv_open("UTF-8", WINDOWS_CODEPAGE); 909 if (cd != (iconv_t) - 1) 910 { 911 size_t utf8_length = length * 2; 912 char *utf8_data = malloc(utf8_length); 913 size_t utf8_length_remaining = utf8_length; 914 char *utf8_data_remaining = utf8_data; 915 char *data_remaining = (char *) data; 916 size_t length_remaining = (size_t) length; 917 if (utf8_data == NULL) 918 { 919 iconv_close(cd); 920 return; 921 } 922 iconv(cd, (ICONV_CONST char **) &data_remaining, &length_remaining, 923 &utf8_data_remaining, &utf8_length_remaining); 924 iconv_close(cd); 925 free_data = True; 926 data = (uint8 *) utf8_data; 927 length = utf8_length - utf8_length_remaining; 928 } 929 } 930 else if (This->xclip.selection_request.target == This->xclip.format_unicode_atom) 931 { 932 /* We're expecting a CF_UNICODETEXT response, so what we're 933 receiving matches our requirements and there's no need 934 for further conversions. */ 935 } 936 #endif 937 else if (This->xclip.selection_request.target == This->xclip.rdesktop_native_atom) 938 { 939 /* Pass as-is */ 940 } 941 else 942 { 943 DEBUG_CLIPBOARD(("ui_clip_handle_data: BUG! I don't know how to convert selection target %s!\n", XGetAtomName(This->display, This->xclip.selection_request.target))); 944 xclip_refuse_selection(This, &This->xclip.selection_request); 945 This->xclip.has_selection_request = False; 946 return; 947 } 948 949 xclip_provide_selection(This, &This->xclip.selection_request, This->xclip.selection_request.target, 8, data, length - 1); 950 This->xclip.has_selection_request = False; 951 952 if (free_data) 953 free(data); 954 } 955 956 void 957 ui_clip_request_failed(RDPCLIENT * This) 958 { 959 xclip_refuse_selection(This, &This->xclip.selection_request); 960 This->xclip.has_selection_request = False; 961 } 962 963 void 964 ui_clip_request_data(RDPCLIENT * This, uint32 format) 965 { 966 Window primary_owner, clipboard_owner; 967 968 DEBUG_CLIPBOARD(("Request from server for format %d\n", format)); 969 This->xclip.rdp_clipboard_request_format = format; 970 971 if (This->xclip.probing_selections) 972 { 973 DEBUG_CLIPBOARD(("ui_clip_request_data: Selection probe in progress. Cannot handle request.\n")); 974 helper_cliprdr_send_empty_response(This); 975 return; 976 } 977 978 xclip_clear_target_props(This); 979 980 if (This->xclip.rdesktop_is_selection_owner) 981 { 982 XChangeProperty(This->display, This->wnd, This->xclip.rdesktop_clipboard_target_atom, 983 XA_INTEGER, 32, PropModeReplace, (unsigned char *) &format, 1); 984 985 XConvertSelection(This->display, This->xclip.primary_atom, This->xclip.rdesktop_native_atom, 986 This->xclip.rdesktop_clipboard_target_atom, This->wnd, CurrentTime); 987 return; 988 } 989 990 if (This->xclip.auto_mode) 991 primary_owner = XGetSelectionOwner(This->display, This->xclip.primary_atom); 992 else 993 primary_owner = None; 994 995 clipboard_owner = XGetSelectionOwner(This->display, This->xclip.clipboard_atom); 996 997 /* Both available */ 998 if ((primary_owner != None) && (clipboard_owner != None)) 999 { 1000 This->xclip.primary_timestamp = 0; 1001 This->xclip.clipboard_timestamp = 0; 1002 XConvertSelection(This->display, This->xclip.primary_atom, This->xclip.timestamp_atom, 1003 This->xclip.rdesktop_primary_timestamp_target_atom, This->wnd, CurrentTime); 1004 XConvertSelection(This->display, This->xclip.clipboard_atom, This->xclip.timestamp_atom, 1005 This->xclip.rdesktop_clipboard_timestamp_target_atom, This->wnd, CurrentTime); 1006 return; 1007 } 1008 1009 /* Just PRIMARY */ 1010 if (primary_owner != None) 1011 { 1012 XConvertSelection(This->display, This->xclip.primary_atom, This->xclip.targets_atom, 1013 This->xclip.rdesktop_clipboard_target_atom, This->wnd, CurrentTime); 1014 return; 1015 } 1016 1017 /* Just CLIPBOARD */ 1018 if (clipboard_owner != None) 1019 { 1020 XConvertSelection(This->display, This->xclip.clipboard_atom, This->xclip.targets_atom, 1021 This->xclip.rdesktop_clipboard_target_atom, This->wnd, CurrentTime); 1022 return; 1023 } 1024 1025 /* No data available */ 1026 helper_cliprdr_send_empty_response(This); 1027 } 1028 1029 void 1030 ui_clip_sync(RDPCLIENT * This) 1031 { 1032 xclip_probe_selections(This); 1033 } 1034 1035 void 1036 ui_clip_set_mode(RDPCLIENT * This, const char *optarg) 1037 { 1038 This->rdpclip = True; 1039 1040 if (str_startswith(optarg, "PRIMARYCLIPBOARD")) 1041 This->xclip.auto_mode = True; 1042 else if (str_startswith(optarg, "CLIPBOARD")) 1043 This->xclip.auto_mode = False; 1044 else 1045 { 1046 warning("Invalid clipboard mode '%s'.\n", optarg); 1047 This->rdpclip = False; 1048 } 1049 } 1050 1051 void 1052 xclip_init(RDPCLIENT * This) 1053 { 1054 if (!This->rdpclip) 1055 return; 1056 1057 if (!cliprdr_init(This)) 1058 return; 1059 1060 This->xclip.primary_atom = XInternAtom(This->display, "PRIMARY", False); 1061 This->xclip.clipboard_atom = XInternAtom(This->display, "CLIPBOARD", False); 1062 This->xclip.targets_atom = XInternAtom(This->display, "TARGETS", False); 1063 This->xclip.timestamp_atom = XInternAtom(This->display, "TIMESTAMP", False); 1064 This->xclip.rdesktop_clipboard_target_atom = 1065 XInternAtom(This->display, "_RDESKTOP_CLIPBOARD_TARGET", False); 1066 This->xclip.rdesktop_primary_timestamp_target_atom = 1067 XInternAtom(This->display, "_RDESKTOP_PRIMARY_TIMESTAMP_TARGET", False); 1068 This->xclip.rdesktop_clipboard_timestamp_target_atom = 1069 XInternAtom(This->display, "_RDESKTOP_CLIPBOARD_TIMESTAMP_TARGET", False); 1070 This->xclip.incr_atom = XInternAtom(This->display, "INCR", False); 1071 This->xclip.format_string_atom = XInternAtom(This->display, "STRING", False); 1072 This->xclip.format_utf8_string_atom = XInternAtom(This->display, "UTF8_STRING", False); 1073 This->xclip.format_unicode_atom = XInternAtom(This->display, "text/unicode", False); 1074 1075 /* rdesktop sets _RDESKTOP_SELECTION_NOTIFY on the root window when acquiring the clipboard. 1076 Other interested rdesktops can use this to notify their server of the available formats. */ 1077 This->xclip.rdesktop_selection_notify_atom = 1078 XInternAtom(This->display, "_RDESKTOP_SELECTION_NOTIFY", False); 1079 XSelectInput(This->display, DefaultRootWindow(This->display), PropertyChangeMask); 1080 This->xclip.probing_selections = False; 1081 1082 This->xclip.rdesktop_native_atom = XInternAtom(This->display, "_RDESKTOP_NATIVE", False); 1083 This->xclip.rdesktop_clipboard_formats_atom = 1084 XInternAtom(This->display, "_RDESKTOP_CLIPBOARD_FORMATS", False); 1085 This->xclip.rdesktop_primary_owner_atom = XInternAtom(This->display, "_RDESKTOP_PRIMARY_OWNER", False); 1086 This->xclip.rdesktop_clipboard_owner_atom = XInternAtom(This->display, "_RDESKTOP_CLIPBOARD_OWNER", False); 1087 1088 This->xclip.num_targets = 0; 1089 This->xclip.targets[This->xclip.num_targets++] = This->xclip.targets_atom; 1090 This->xclip.targets[This->xclip.num_targets++] = This->xclip.timestamp_atom; 1091 This->xclip.targets[This->xclip.num_targets++] = This->xclip.rdesktop_native_atom; 1092 This->xclip.targets[This->xclip.num_targets++] = This->xclip.rdesktop_clipboard_formats_atom; 1093 #ifdef USE_UNICODE_CLIPBOARD 1094 This->xclip.targets[This->xclip.num_targets++] = This->xclip.format_utf8_string_atom; 1095 #endif 1096 This->xclip.targets[This->xclip.num_targets++] = This->xclip.format_unicode_atom; 1097 This->xclip.targets[This->xclip.num_targets++] = This->xclip.format_string_atom; 1098 This->xclip.targets[This->xclip.num_targets++] = XA_STRING; 1099 } 1100 1101 void 1102 xclip_deinit(RDPCLIENT * This) 1103 { 1104 if (XGetSelectionOwner(This->display, This->xclip.primary_atom) == This->wnd) 1105 XSetSelectionOwner(This->display, This->xclip.primary_atom, None, This->xclip.acquire_time); 1106 if (XGetSelectionOwner(This->display, This->xclip.clipboard_atom) == This->wnd) 1107 XSetSelectionOwner(This->display, This->xclip.clipboard_atom, None, This->xclip.acquire_time); 1108 xclip_notify_change(This); 1109 } 1110