--- sourceforge.net/trunk/rdesktop/xclip.c 2006/03/27 09:20:24 1207 +++ sourceforge.net/trunk/rdesktop/xclip.c 2006/03/28 14:46:47 1215 @@ -48,7 +48,7 @@ #define RDP_CF_TEXT CF_TEXT #endif -#define MAX_TARGETS 7 +#define MAX_TARGETS 8 extern Display *g_display; extern Window g_wnd; @@ -65,14 +65,9 @@ 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; /* 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. @@ -80,16 +75,27 @@ 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; -/* 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. */ +/* 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; @@ -109,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; @@ -212,6 +218,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); @@ -234,6 +242,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; @@ -273,6 +285,9 @@ 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) @@ -371,7 +386,7 @@ return True; } #endif - else if (target == rdesktop_clipboard_formats_atom) + else if (target == rdesktop_native_atom) { helper_cliprdr_send_response(source, source_size + 1); @@ -391,6 +406,83 @@ 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. @@ -480,6 +572,13 @@ 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); @@ -504,7 +603,7 @@ XFree(data); g_incr_target = event->target; g_waiting_for_INCR = 1; - return; + goto end; } /* Negotiate target format */ @@ -552,17 +651,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, event->selection, best_text_target, rdesktop_clipboard_target_atom, g_wnd, event->time); - return; + goto end; } else { @@ -572,21 +682,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: xclip_clear_target_props(); - if (data) - XFree(data); - helper_cliprdr_send_empty_response(); + 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. @@ -598,7 +749,6 @@ { unsigned long nitems, bytes_left; unsigned char *prop_return; - uint32 *wanted_format; int format, res; Atom type; @@ -614,9 +764,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 @@ -628,18 +782,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) @@ -687,13 +845,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. */ @@ -703,7 +856,7 @@ unsigned long nitems; unsigned long offset = 0; unsigned long bytes_left = 1; - int format, res; + int format; XWindowAttributes wa; uint8 *data; Atom type; @@ -761,31 +914,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 @@ -798,21 +929,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). */ @@ -821,6 +954,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 */ @@ -869,12 +1009,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; @@ -888,6 +1029,13 @@ } void +ui_clip_request_failed() +{ + xclip_refuse_selection(&selection_request); + has_selection_request = False; +} + +void ui_clip_request_data(uint32 format) { Window primary_owner, clipboard_owner; @@ -895,6 +1043,13 @@ 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) @@ -902,7 +1057,7 @@ 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; } @@ -949,7 +1104,7 @@ void ui_clip_sync(void) { - cliprdr_send_simple_native_format_announce(RDP_CF_TEXT); + xclip_probe_selections(); } void @@ -957,8 +1112,7 @@ { g_rdpclip = True; - if (str_startswith(optarg, "auto") || str_startswith(optarg, "on") - || str_startswith(optarg, "PRIMARYCLIPBOARD")) + if (str_startswith(optarg, "PRIMARYCLIPBOARD")) auto_mode = True; else if (str_startswith(optarg, "CLIPBOARD")) auto_mode = False; @@ -992,20 +1146,39 @@ 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(); }