--- sourceforge.net/trunk/rdesktop/xclip.c 2006/01/05 11:56:57 1038 +++ sourceforge.net/trunk/rdesktop/xclip.c 2007/01/04 05:39:39 1365 @@ -1,8 +1,8 @@ /* -*- c-basic-offset: 8 -*- rdesktop: A Remote Desktop Protocol client. Protocol services - Clipboard functions - Copyright (C) Erik Forsberg 2003 - Copyright (C) Matthew Chapman 2003 + Copyright (C) Erik Forsberg 2003-2007 + Copyright (C) Matthew Chapman 2003-2007 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -48,37 +48,54 @@ #define RDP_CF_TEXT CF_TEXT #endif -#define MAX_TARGETS 7 +#define MAX_TARGETS 8 extern Display *g_display; extern Window g_wnd; extern Time g_last_gesturetime; +extern BOOL g_rdpclip; +/* Mode of operation. + - Auto: Look at both PRIMARY and CLIPBOARD and use the most recent. + - Non-auto: Look at just CLIPBOARD. */ +static BOOL auto_mode = True; /* Atoms of the two X selections we're dealing with: CLIPBOARD (explicit-copy) and PRIMARY (selection-copy) */ static Atom clipboard_atom, primary_atom; /* Atom of the TARGETS clipboard target */ static Atom targets_atom; /* Atom of the TIMESTAMP clipboard target */ static Atom timestamp_atom; -/* Atom _RDESKTOP_CLIPBOARD_TARGET which has multiple uses: - - The 'property' argument in XConvertSelection calls: This is the property of our - window into which XConvertSelection will store the received clipboard data. - - In a clipboard request of target _RDESKTOP_CLIPBOARD_FORMATS, an XA_INTEGER-typed - property carrying the Windows native (CF_...) format desired by the requestor. - Requestor set this property (e.g. requestor_wnd[_RDESKTOP_CLIPBOARD_TARGET] = CF_TEXT) - before requesting clipboard data from a fellow rdesktop using - the _RDESKTOP_CLIPBOARD_FORMATS target. */ +/* Atom _RDESKTOP_CLIPBOARD_TARGET which is used as the 'property' argument in + XConvertSelection calls: This is the property of our window into which + XConvertSelection will store the received clipboard data. */ static Atom rdesktop_clipboard_target_atom; -/* Atom _RDESKTOP_CLIPBOARD_FORMATS which has multiple uses: - - The clipboard target (X jargon for "clipboard format") for rdesktop-to-rdesktop interchange - of Windows native clipboard data. - This target cannot be used standalone; the requestor must keep the - _RDESKTOP_CLIPBOARD_TARGET property on his window denoting - the Windows native clipboard format being requested. - - The root window property set by rdesktop when it owns the clipboard, - denoting all Windows native clipboard formats it offers via - requests of the _RDESKTOP_CLIPBOARD_FORMATS target. */ +/* Atoms _RDESKTOP_PRIMARY_TIMESTAMP_TARGET and _RDESKTOP_CLIPBOARD_TIMESTAMP_TARGET + are used to store the timestamps for when a window got ownership of the selections. + We use these to determine which is more recent and should be used. */ +static Atom rdesktop_primary_timestamp_target_atom, rdesktop_clipboard_timestamp_target_atom; +/* Storage for timestamps since we get them in two separate notifications. */ +static Time primary_timestamp, clipboard_timestamp; +/* Clipboard target for getting a list of native Windows clipboard formats. The + presence of this target indicates that the selection owner is another rdesktop. */ static Atom rdesktop_clipboard_formats_atom; +/* The clipboard target (X jargon for "clipboard format") for rdesktop-to-rdesktop + interchange of Windows native clipboard data. The requestor must supply the + desired native Windows clipboard format in the associated property. */ +static Atom rdesktop_native_atom; +/* Local copy of the list of native Windows clipboard formats. */ +static uint8 *formats_data = NULL; +static uint32 formats_data_length = 0; +/* We need to know when another rdesktop process gets or loses ownership of a + selection. Without XFixes we do this by touching a property on the root window + which will generate PropertyNotify notifications. */ +static Atom rdesktop_selection_notify_atom; +/* State variables that indicate if we're currently probing the targets of the + selection owner. reprobe_selections indicate that the ownership changed in + the middle of the current probe so it should be restarted. */ +static BOOL probing_selections, reprobe_selections; +/* Atoms _RDESKTOP_PRIMARY_OWNER and _RDESKTOP_CLIPBOARD_OWNER. Used as properties + on the root window to indicate which selections that are owned by rdesktop. */ +static Atom rdesktop_primary_owner_atom, rdesktop_clipboard_owner_atom; static Atom format_string_atom, format_utf8_string_atom, format_unicode_atom; /* Atom of the INCR clipboard type (see ICCCM on "INCR Properties") */ static Atom incr_atom; @@ -98,11 +115,11 @@ /* Array of offered clipboard targets that will be sent to fellow X clients upon a TARGETS request. */ static Atom targets[MAX_TARGETS]; static int num_targets; -/* Denotes that this client currently holds the PRIMARY selection. */ -static int have_primary = 0; /* Denotes that an rdesktop (not this rdesktop) is owning the selection, allowing us to interchange Windows native clipboard data directly. */ -static int rdesktop_is_selection_owner = 0; +static BOOL rdesktop_is_selection_owner = False; +/* Time when we acquired the selection. */ +static Time acquire_time = 0; /* Denotes that an INCR ("chunked") transfer is in progress. */ static int g_waiting_for_INCR = 0; @@ -140,6 +157,7 @@ { uint8 *result; uint16 *inptr, *outptr; + Bool swap_endianess; /* Worst case: Every char is LF */ result = xmalloc((*size * 2) + 2); @@ -150,7 +168,7 @@ outptr = (uint16 *) result; /* Check for a reversed BOM */ - Bool swap_endianess = (*inptr == 0xfffe); + swap_endianess = (*inptr == 0xfffe); while ((uint8 *) inptr < data + *size) { @@ -201,6 +219,8 @@ { XEvent xev; + DEBUG_CLIPBOARD(("xclip_provide_selection: requestor=0x%08x, target=%s, property=%s, length=%u\n", (unsigned) req->requestor, XGetAtomName(g_display, req->target), XGetAtomName(g_display, req->property), (unsigned) length)); + XChangeProperty(g_display, req->requestor, req->property, type, format, PropModeReplace, data, length); @@ -223,6 +243,10 @@ { XEvent xev; + DEBUG_CLIPBOARD(("xclip_refuse_selection: requestor=0x%08x, target=%s, property=%s\n", + (unsigned) req->requestor, XGetAtomName(g_display, req->target), + XGetAtomName(g_display, req->property))); + xev.xselection.type = SelectionNotify; xev.xselection.serial = 0; xev.xselection.send_event = True; @@ -262,10 +286,23 @@ static Bool xclip_send_data_with_convert(uint8 * source, size_t source_size, Atom target) { + DEBUG_CLIPBOARD(("xclip_send_data_with_convert: target=%s, size=%u\n", + XGetAtomName(g_display, target), (unsigned) source_size)); + #ifdef USE_UNICODE_CLIPBOARD if (target == format_string_atom || target == format_unicode_atom || target == format_utf8_string_atom) { + size_t unicode_buffer_size; + char *unicode_buffer; + iconv_t cd; + size_t unicode_buffer_size_remaining; + char *unicode_buffer_remaining; + char *data_remaining; + size_t data_size_remaining; + uint32 translated_data_size; + uint8 *translated_data; + if (rdp_clipboard_request_format != RDP_CF_TEXT) return False; @@ -274,10 +311,6 @@ to it, so using CF_TEXT is not safe (and is unnecessary, since all WinNT versions are Unicode-minded). */ - size_t unicode_buffer_size; - char *unicode_buffer; - iconv_t cd; - if (target == format_string_atom) { char *locale_charset = nl_langinfo(CODESET); @@ -316,23 +349,22 @@ } unicode_buffer = xmalloc(unicode_buffer_size); - size_t unicode_buffer_size_remaining = unicode_buffer_size; - char *unicode_buffer_remaining = unicode_buffer; - char *data_remaining = (char *) source; - size_t data_size_remaining = source_size; - iconv(cd, &data_remaining, &data_size_remaining, &unicode_buffer_remaining, - &unicode_buffer_size_remaining); + unicode_buffer_size_remaining = unicode_buffer_size; + unicode_buffer_remaining = unicode_buffer; + data_remaining = (char *) source; + data_size_remaining = source_size; + iconv(cd, (ICONV_CONST char **) &data_remaining, &data_size_remaining, + &unicode_buffer_remaining, &unicode_buffer_size_remaining); iconv_close(cd); /* translate linebreaks */ - uint32 translated_data_size = unicode_buffer_size - unicode_buffer_size_remaining; - uint8 *translated_data = - utf16_lf2crlf((uint8 *) unicode_buffer, &translated_data_size); + translated_data_size = unicode_buffer_size - unicode_buffer_size_remaining; + translated_data = utf16_lf2crlf((uint8 *) unicode_buffer, &translated_data_size); if (translated_data != NULL) { DEBUG_CLIPBOARD(("Sending Unicode string of %d bytes\n", translated_data_size)); - cliprdr_send_data(translated_data, translated_data_size); + helper_cliprdr_send_response(translated_data, translated_data_size); xfree(translated_data); /* Not the same thing as XFree! */ } @@ -353,14 +385,14 @@ translated_data = lf2crlf(source, &length); if (translated_data != NULL) { - cliprdr_send_data(translated_data, length); + helper_cliprdr_send_response(translated_data, length); xfree(translated_data); /* Not the same thing as XFree! */ } return True; } #endif - else if (target == rdesktop_clipboard_formats_atom) + else if (target == rdesktop_native_atom) { helper_cliprdr_send_response(source, source_size + 1); @@ -372,6 +404,91 @@ } } +static void +xclip_clear_target_props() +{ + XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_target_atom); + XDeleteProperty(g_display, g_wnd, rdesktop_primary_timestamp_target_atom); + XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_timestamp_target_atom); +} + +static void +xclip_notify_change() +{ + XChangeProperty(g_display, DefaultRootWindow(g_display), + rdesktop_selection_notify_atom, XA_INTEGER, 32, PropModeReplace, NULL, 0); +} + +static void +xclip_probe_selections() +{ + Window primary_owner, clipboard_owner; + + if (probing_selections) + { + DEBUG_CLIPBOARD(("Already probing selections. Scheduling reprobe.\n")); + reprobe_selections = True; + return; + } + + DEBUG_CLIPBOARD(("Probing selections.\n")); + + probing_selections = True; + reprobe_selections = False; + + xclip_clear_target_props(); + + if (auto_mode) + primary_owner = XGetSelectionOwner(g_display, primary_atom); + else + primary_owner = None; + + clipboard_owner = XGetSelectionOwner(g_display, clipboard_atom); + + /* If we own all relevant selections then don't do anything. */ + if (((primary_owner == g_wnd) || !auto_mode) && (clipboard_owner == g_wnd)) + goto end; + + /* Both available */ + if ((primary_owner != None) && (clipboard_owner != None)) + { + primary_timestamp = 0; + clipboard_timestamp = 0; + XConvertSelection(g_display, primary_atom, timestamp_atom, + rdesktop_primary_timestamp_target_atom, g_wnd, CurrentTime); + XConvertSelection(g_display, clipboard_atom, timestamp_atom, + rdesktop_clipboard_timestamp_target_atom, g_wnd, CurrentTime); + return; + } + + /* Just PRIMARY */ + if (primary_owner != None) + { + XConvertSelection(g_display, primary_atom, targets_atom, + rdesktop_clipboard_target_atom, g_wnd, CurrentTime); + return; + } + + /* Just CLIPBOARD */ + if (clipboard_owner != None) + { + XConvertSelection(g_display, clipboard_atom, targets_atom, + rdesktop_clipboard_target_atom, g_wnd, CurrentTime); + return; + } + + DEBUG_CLIPBOARD(("No owner of any selection.\n")); + + /* FIXME: + Without XFIXES, we cannot reliably know the formats offered by an + upcoming selection owner, so we just lie about him offering + RDP_CF_TEXT. */ + cliprdr_send_simple_native_format_announce(RDP_CF_TEXT); + + end: + probing_selections = False; +} + /* This function is called for SelectionNotify events. The SelectionNotify event is sent from the clipboard owner to the requestor after his request was satisfied. @@ -385,7 +502,7 @@ Atom type; Atom *supported_targets; int res, i, format; - uint8 *data; + uint8 *data = NULL; if (event->property == None) goto fail; @@ -395,13 +512,85 @@ XGetAtomName(g_display, event->target), XGetAtomName(g_display, event->property))); - if (event->property == None) - goto fail; + if (event->target == timestamp_atom) + { + if (event->selection == primary_atom) + { + res = XGetWindowProperty(g_display, g_wnd, + rdesktop_primary_timestamp_target_atom, 0, + XMaxRequestSize(g_display), False, AnyPropertyType, + &type, &format, &nitems, &bytes_left, &data); + } + else + { + res = XGetWindowProperty(g_display, g_wnd, + rdesktop_clipboard_timestamp_target_atom, 0, + XMaxRequestSize(g_display), False, AnyPropertyType, + &type, &format, &nitems, &bytes_left, &data); + } + + + if ((res != Success) || (nitems != 1) || (format != 32)) + { + DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n")); + goto fail; + } + + if (event->selection == primary_atom) + { + primary_timestamp = *(Time *) data; + if (primary_timestamp == 0) + primary_timestamp++; + XDeleteProperty(g_display, g_wnd, rdesktop_primary_timestamp_target_atom); + DEBUG_CLIPBOARD(("Got PRIMARY timestamp: %u\n", + (unsigned) primary_timestamp)); + } + else + { + clipboard_timestamp = *(Time *) data; + if (clipboard_timestamp == 0) + clipboard_timestamp++; + XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_timestamp_target_atom); + DEBUG_CLIPBOARD(("Got CLIPBOARD timestamp: %u\n", + (unsigned) clipboard_timestamp)); + } + + XFree(data); + + if (primary_timestamp && clipboard_timestamp) + { + if (primary_timestamp > clipboard_timestamp) + { + DEBUG_CLIPBOARD(("PRIMARY is most recent selection.\n")); + XConvertSelection(g_display, primary_atom, targets_atom, + rdesktop_clipboard_target_atom, g_wnd, + event->time); + } + else + { + DEBUG_CLIPBOARD(("CLIPBOARD is most recent selection.\n")); + XConvertSelection(g_display, clipboard_atom, targets_atom, + rdesktop_clipboard_target_atom, g_wnd, + event->time); + } + } + + return; + } + + if (probing_selections && reprobe_selections) + { + probing_selections = False; + xclip_probe_selections(); + return; + } res = XGetWindowProperty(g_display, g_wnd, rdesktop_clipboard_target_atom, 0, XMaxRequestSize(g_display), False, AnyPropertyType, &type, &format, &nitems, &bytes_left, &data); + xclip_clear_target_props(); + if (res != Success) { DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n")); @@ -417,15 +606,12 @@ { XSelectInput(g_display, g_wnd, (wa.your_event_mask | PropertyChangeMask)); } - XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_target_atom); XFree(data); g_incr_target = event->target; g_waiting_for_INCR = 1; - return; + goto end; } - XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_target_atom); - /* Negotiate target format */ if (event->target == targets_atom) { @@ -471,17 +657,28 @@ } } #endif + else if (supported_targets[i] == rdesktop_clipboard_formats_atom) + { + if (probing_selections && (text_target_satisfaction < 4)) + { + DEBUG_CLIPBOARD(("Other party supports native formats, choosing that as best_target\n")); + best_text_target = supported_targets[i]; + text_target_satisfaction = 4; + } + } } } /* Kickstarting the next step in the process of satisfying RDP's clipboard request -- specifically, requesting the actual clipboard data. */ - if (best_text_target != 0) + if ((best_text_target != 0) + && (!probing_selections + || (best_text_target == rdesktop_clipboard_formats_atom))) { - XConvertSelection(g_display, clipboard_atom, best_text_target, + XConvertSelection(g_display, event->selection, best_text_target, rdesktop_clipboard_target_atom, g_wnd, event->time); - return; + goto end; } else { @@ -491,20 +688,62 @@ } else { - if (!xclip_send_data_with_convert(data, nitems, event->target)) + if (probing_selections) + { + Window primary_owner, clipboard_owner; + + /* FIXME: + Without XFIXES, we must make sure that the other + rdesktop owns all relevant selections or we might try + to get a native format from non-rdesktop window later + on. */ + + clipboard_owner = XGetSelectionOwner(g_display, clipboard_atom); + + if (auto_mode) + primary_owner = XGetSelectionOwner(g_display, primary_atom); + else + primary_owner = clipboard_owner; + + if (primary_owner != clipboard_owner) + goto fail; + + DEBUG_CLIPBOARD(("Got fellow rdesktop formats\n")); + probing_selections = False; + rdesktop_is_selection_owner = True; + cliprdr_send_native_format_announce(data, nitems); + } + else if (!xclip_send_data_with_convert(data, nitems, event->target)) { goto fail; } } - XFree(data); + end: + if (data) + XFree(data); return; fail: - XDeleteProperty(g_display, g_wnd, rdesktop_clipboard_target_atom); - XFree(data); - helper_cliprdr_send_empty_response(); + xclip_clear_target_props(); + if (probing_selections) + { + DEBUG_CLIPBOARD(("Unable to find suitable target. Using default text format.\n")); + probing_selections = False; + rdesktop_is_selection_owner = False; + + /* FIXME: + Without XFIXES, we cannot reliably know the formats offered by an + upcoming selection owner, so we just lie about him offering + RDP_CF_TEXT. */ + cliprdr_send_simple_native_format_announce(RDP_CF_TEXT); + } + else + { + helper_cliprdr_send_empty_response(); + } + goto end; } /* This function is called for SelectionRequest events. @@ -516,7 +755,6 @@ { unsigned long nitems, bytes_left; unsigned char *prop_return; - uint32 *wanted_format; int format, res; Atom type; @@ -532,9 +770,13 @@ } else if (event->target == timestamp_atom) { - xclip_provide_selection(event, XA_INTEGER, 32, (uint8 *) & g_last_gesturetime, 1); + xclip_provide_selection(event, XA_INTEGER, 32, (uint8 *) & acquire_time, 1); return; } + else if (event->target == rdesktop_clipboard_formats_atom) + { + xclip_provide_selection(event, XA_STRING, 8, formats_data, formats_data_length); + } else { /* All the following targets require an async operation with the RDP server @@ -546,18 +788,22 @@ xclip_refuse_selection(event); return; } - if (event->target == rdesktop_clipboard_formats_atom) + if (event->target == rdesktop_native_atom) { - /* Before the requestor makes a request for the _RDESKTOP_CLIPBOARD_FORMATS target, - he should declare requestor[_RDESKTOP_CLIPBOARD_TARGET] = CF_SOMETHING. - Otherwise, we default to RDP_CF_TEXT. - */ + /* Before the requestor makes a request for the _RDESKTOP_NATIVE target, + he should declare requestor[property] = CF_SOMETHING. */ res = XGetWindowProperty(g_display, event->requestor, - rdesktop_clipboard_target_atom, 0, 1, True, + event->property, 0, 1, True, XA_INTEGER, &type, &format, &nitems, &bytes_left, &prop_return); - wanted_format = (uint32 *) prop_return; - format = (res == Success) ? *wanted_format : RDP_CF_TEXT; + if (res != Success) + { + DEBUG_CLIPBOARD(("Requested native format but didn't specifiy which.\n")); + xclip_refuse_selection(event); + return; + } + + format = *(uint32 *) prop_return; XFree(prop_return); } else if (event->target == format_string_atom || event->target == XA_STRING) @@ -605,13 +851,8 @@ xclip_handle_SelectionClear(void) { DEBUG_CLIPBOARD(("xclip_handle_SelectionClear\n")); - have_primary = 0; - XDeleteProperty(g_display, DefaultRootWindow(g_display), rdesktop_clipboard_formats_atom); - /* FIXME: - Without XFIXES, we cannot reliably know the formats offered by the - new owner of the X11 clipboard, so we just lie about him - offering RDP_CF_TEXT. */ - cliprdr_send_simple_native_format_announce(RDP_CF_TEXT); + xclip_notify_change(); + xclip_probe_selections(); } /* Called when any property changes in our window or the root window. */ @@ -621,7 +862,7 @@ unsigned long nitems; unsigned long offset = 0; unsigned long bytes_left = 1; - int format, res; + int format; XWindowAttributes wa; uint8 *data; Atom type; @@ -679,31 +920,9 @@ return; } - if ((event->atom == rdesktop_clipboard_formats_atom) && - (event->window == DefaultRootWindow(g_display)) && - !have_primary /* not interested in our own events */ ) - { - if (event->state == PropertyNewValue) - { - DEBUG_CLIPBOARD(("xclip_handle_PropertyNotify: getting fellow rdesktop formats\n")); - - res = XGetWindowProperty(g_display, DefaultRootWindow(g_display), - rdesktop_clipboard_formats_atom, 0, - XMaxRequestSize(g_display), False, XA_STRING, - &type, &format, &nitems, &bytes_left, &data); - - if ((res == Success) && (nitems > 0)) - { - cliprdr_send_native_format_announce(data, nitems); - rdesktop_is_selection_owner = 1; - return; - } - } - - /* For some reason, we couldn't announce the native formats */ - cliprdr_send_simple_native_format_announce(RDP_CF_TEXT); - rdesktop_is_selection_owner = 0; - } + if ((event->atom == rdesktop_selection_notify_atom) && + (event->window == DefaultRootWindow(g_display))) + xclip_probe_selections(); } #endif @@ -716,21 +935,23 @@ void ui_clip_format_announce(uint8 * data, uint32 length) { - XSetSelectionOwner(g_display, primary_atom, g_wnd, g_last_gesturetime); + acquire_time = g_last_gesturetime; + + XSetSelectionOwner(g_display, primary_atom, g_wnd, acquire_time); if (XGetSelectionOwner(g_display, primary_atom) != g_wnd) - { warning("Failed to aquire ownership of PRIMARY clipboard\n"); - return; - } - - have_primary = 1; - XChangeProperty(g_display, DefaultRootWindow(g_display), - rdesktop_clipboard_formats_atom, XA_STRING, 8, PropModeReplace, data, - length); - XSetSelectionOwner(g_display, clipboard_atom, g_wnd, g_last_gesturetime); + XSetSelectionOwner(g_display, clipboard_atom, g_wnd, acquire_time); if (XGetSelectionOwner(g_display, clipboard_atom) != g_wnd) warning("Failed to aquire ownership of CLIPBOARD clipboard\n"); + + if (formats_data) + xfree(formats_data); + formats_data = xmalloc(length); + memcpy(formats_data, data, length); + formats_data_length = length; + + xclip_notify_change(); } /* Called when the RDP server responds with clipboard data (after we've requested it). */ @@ -739,6 +960,13 @@ { BOOL free_data = False; + if (length == 0) + { + xclip_refuse_selection(&selection_request); + has_selection_request = False; + return; + } + if (selection_request.target == format_string_atom || selection_request.target == XA_STRING) { /* We're expecting a CF_TEXT response */ @@ -772,8 +1000,8 @@ iconv_close(cd); return; } - iconv(cd, &data_remaining, &length_remaining, &utf8_data_remaining, - &utf8_length_remaining); + iconv(cd, (ICONV_CONST char **) &data_remaining, &length_remaining, + &utf8_data_remaining, &utf8_length_remaining); iconv_close(cd); free_data = True; data = (uint8 *) utf8_data; @@ -787,12 +1015,13 @@ for further conversions. */ } #endif - else if (selection_request.target == rdesktop_clipboard_formats_atom) + else if (selection_request.target == rdesktop_native_atom) { /* Pass as-is */ } else { + DEBUG_CLIPBOARD(("ui_clip_handle_data: BUG! I don't know how to convert selection target %s!\n", XGetAtomName(g_display, selection_request.target))); xclip_refuse_selection(&selection_request); has_selection_request = False; return; @@ -806,34 +1035,68 @@ } void +ui_clip_request_failed() +{ + xclip_refuse_selection(&selection_request); + has_selection_request = False; +} + +void ui_clip_request_data(uint32 format) { - Window selectionowner; + Window primary_owner, clipboard_owner; DEBUG_CLIPBOARD(("Request from server for format %d\n", format)); rdp_clipboard_request_format = format; + if (probing_selections) + { + DEBUG_CLIPBOARD(("ui_clip_request_data: Selection probe in progress. Cannot handle request.\n")); + helper_cliprdr_send_empty_response(); + return; + } + + xclip_clear_target_props(); + if (rdesktop_is_selection_owner) { XChangeProperty(g_display, g_wnd, rdesktop_clipboard_target_atom, XA_INTEGER, 32, PropModeReplace, (unsigned char *) &format, 1); - XConvertSelection(g_display, primary_atom, rdesktop_clipboard_formats_atom, + XConvertSelection(g_display, primary_atom, rdesktop_native_atom, rdesktop_clipboard_target_atom, g_wnd, CurrentTime); return; } - selectionowner = XGetSelectionOwner(g_display, primary_atom); - if (selectionowner != None) + if (auto_mode) + primary_owner = XGetSelectionOwner(g_display, primary_atom); + else + primary_owner = None; + + clipboard_owner = XGetSelectionOwner(g_display, clipboard_atom); + + /* Both available */ + if ((primary_owner != None) && (clipboard_owner != None)) + { + primary_timestamp = 0; + clipboard_timestamp = 0; + XConvertSelection(g_display, primary_atom, timestamp_atom, + rdesktop_primary_timestamp_target_atom, g_wnd, CurrentTime); + XConvertSelection(g_display, clipboard_atom, timestamp_atom, + rdesktop_clipboard_timestamp_target_atom, g_wnd, CurrentTime); + return; + } + + /* Just PRIMARY */ + if (primary_owner != None) { XConvertSelection(g_display, primary_atom, targets_atom, rdesktop_clipboard_target_atom, g_wnd, CurrentTime); return; } - /* No PRIMARY, try CLIPBOARD */ - selectionowner = XGetSelectionOwner(g_display, clipboard_atom); - if (selectionowner != None) + /* Just CLIPBOARD */ + if (clipboard_owner != None) { XConvertSelection(g_display, clipboard_atom, targets_atom, rdesktop_clipboard_target_atom, g_wnd, CurrentTime); @@ -841,19 +1104,37 @@ } /* No data available */ - cliprdr_send_data(NULL, 0); + helper_cliprdr_send_empty_response(); } void ui_clip_sync(void) { - cliprdr_send_simple_native_format_announce(RDP_CF_TEXT); + xclip_probe_selections(); } +void +ui_clip_set_mode(const char *optarg) +{ + g_rdpclip = True; + + if (str_startswith(optarg, "PRIMARYCLIPBOARD")) + auto_mode = True; + else if (str_startswith(optarg, "CLIPBOARD")) + auto_mode = False; + else + { + warning("Invalid clipboard mode '%s'.\n", optarg); + g_rdpclip = False; + } +} void xclip_init(void) { + if (!g_rdpclip) + return; + if (!cliprdr_init()) return; @@ -863,24 +1144,47 @@ timestamp_atom = XInternAtom(g_display, "TIMESTAMP", False); rdesktop_clipboard_target_atom = XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_TARGET", False); + rdesktop_primary_timestamp_target_atom = + XInternAtom(g_display, "_RDESKTOP_PRIMARY_TIMESTAMP_TARGET", False); + rdesktop_clipboard_timestamp_target_atom = + XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_TIMESTAMP_TARGET", False); incr_atom = XInternAtom(g_display, "INCR", False); format_string_atom = XInternAtom(g_display, "STRING", False); format_utf8_string_atom = XInternAtom(g_display, "UTF8_STRING", False); format_unicode_atom = XInternAtom(g_display, "text/unicode", False); + + /* rdesktop sets _RDESKTOP_SELECTION_NOTIFY on the root window when acquiring the clipboard. + Other interested rdesktops can use this to notify their server of the available formats. */ + rdesktop_selection_notify_atom = + XInternAtom(g_display, "_RDESKTOP_SELECTION_NOTIFY", False); + XSelectInput(g_display, DefaultRootWindow(g_display), PropertyChangeMask); + probing_selections = False; + + rdesktop_native_atom = XInternAtom(g_display, "_RDESKTOP_NATIVE", False); + rdesktop_clipboard_formats_atom = + XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_FORMATS", False); + rdesktop_primary_owner_atom = XInternAtom(g_display, "_RDESKTOP_PRIMARY_OWNER", False); + rdesktop_clipboard_owner_atom = XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_OWNER", False); + num_targets = 0; targets[num_targets++] = targets_atom; targets[num_targets++] = timestamp_atom; + targets[num_targets++] = rdesktop_native_atom; targets[num_targets++] = rdesktop_clipboard_formats_atom; - targets[num_targets++] = format_string_atom; #ifdef USE_UNICODE_CLIPBOARD targets[num_targets++] = format_utf8_string_atom; #endif targets[num_targets++] = format_unicode_atom; + targets[num_targets++] = format_string_atom; targets[num_targets++] = XA_STRING; +} - /* rdesktop sets _RDESKTOP_CLIPBOARD_FORMATS on the root window when acquiring the clipboard. - Other interested rdesktops can use this to notify their server of the available formats. */ - rdesktop_clipboard_formats_atom = - XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_FORMATS", False); - XSelectInput(g_display, DefaultRootWindow(g_display), PropertyChangeMask); +void +xclip_deinit(void) +{ + if (XGetSelectionOwner(g_display, primary_atom) == g_wnd) + XSetSelectionOwner(g_display, primary_atom, None, acquire_time); + if (XGetSelectionOwner(g_display, clipboard_atom) == g_wnd) + XSetSelectionOwner(g_display, clipboard_atom, None, acquire_time); + xclip_notify_change(); }