From b48e542deb4b3ff0ea60a226e0c2e768540f3dbe Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Tue, 28 Mar 2006 13:24:48 +0000 Subject: [PATCH] New race free implementation of the rdesktop-to-rdesktop clipboard protocol. We now use the property on the root window merely as a way of notifying other windows that something happend. The actual formats are queried directly from the current clipboard owner. The new model should be easier to extend with XFixes support. git-svn-id: svn://svn.code.sf.net/p/rdesktop/code/trunk/rdesktop@1212 423420c4-83ab-492f-b58f-81f9feb106b5 --- xclip.c | 321 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 229 insertions(+), 92 deletions(-) diff --git a/xclip.c b/xclip.c index 634c53e..e0cdcb7 100644 --- a/xclip.c +++ b/xclip.c @@ -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 clipboard_atom, primary_atom; 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_clipboard_target_atom; 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,9 @@ static int rdp_clipboard_request_format; /* 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; @@ -214,6 +218,8 @@ xclip_provide_selection(XSelectionRequestEvent * req, Atom type, unsigned int fo { 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); @@ -236,6 +242,10 @@ xclip_refuse_selection(XSelectionRequestEvent * req) { 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; @@ -247,35 +257,6 @@ xclip_refuse_selection(XSelectionRequestEvent * req) XSendEvent(g_display, req->requestor, False, NoEventMask, &xev); } -/* Examines the special root window property that contains the native Windows - formats if another rdesktop process owns the clipboard. */ -static void -xclip_get_root_property() -{ - unsigned long nitems; - unsigned long bytes_left; - int format, res; - uint8 *data; - Atom type; - - 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)) - { - DEBUG_CLIPBOARD(("xclip_get_root_property: got fellow rdesktop formats\n")); - 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; -} - /* Wrapper for cliprdr_send_data which also cleans the request state. */ static void helper_cliprdr_send_response(uint8 * data, uint32 length) @@ -304,6 +285,9 @@ helper_cliprdr_send_empty_response() 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) @@ -402,7 +386,7 @@ xclip_send_data_with_convert(uint8 * source, size_t source_size, Atom target) return True; } #endif - else if (target == rdesktop_clipboard_formats_atom) + else if (target == rdesktop_native_atom) { helper_cliprdr_send_response(source, source_size + 1); @@ -422,6 +406,83 @@ xclip_clear_target_props() 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. @@ -511,6 +572,13 @@ xclip_handle_SelectionNotify(XSelectionEvent * event) 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); @@ -535,7 +603,7 @@ xclip_handle_SelectionNotify(XSelectionEvent * event) XFree(data); g_incr_target = event->target; g_waiting_for_INCR = 1; - return; + goto end; } /* Negotiate target format */ @@ -583,17 +651,28 @@ xclip_handle_SelectionNotify(XSelectionEvent * event) } } #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 { @@ -603,21 +682,62 @@ xclip_handle_SelectionNotify(XSelectionEvent * event) } 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. @@ -629,7 +749,6 @@ xclip_handle_SelectionRequest(XSelectionRequestEvent * event) { unsigned long nitems, bytes_left; unsigned char *prop_return; - uint32 *wanted_format; int format, res; Atom type; @@ -648,6 +767,10 @@ xclip_handle_SelectionRequest(XSelectionRequestEvent * event) 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 @@ -659,18 +782,22 @@ xclip_handle_SelectionRequest(XSelectionRequestEvent * event) 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) @@ -718,13 +845,8 @@ void 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. */ @@ -792,10 +914,9 @@ xclip_handle_PropertyNotify(XPropertyEvent * event) return; } - if ((event->atom == rdesktop_clipboard_formats_atom) && - (event->window == DefaultRootWindow(g_display)) && - !have_primary /* not interested in our own events */ ) - xclip_get_root_property(); + if ((event->atom == rdesktop_selection_notify_atom) && + (event->window == DefaultRootWindow(g_display))) + xclip_probe_selections(); } #endif @@ -812,19 +933,19 @@ ui_clip_format_announce(uint8 * data, uint32 length) 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, 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). */ @@ -888,12 +1009,13 @@ ui_clip_handle_data(uint8 * data, uint32 length) 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; @@ -921,6 +1043,13 @@ ui_clip_request_data(uint32 format) 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) @@ -928,7 +1057,7 @@ ui_clip_request_data(uint32 format) 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; } @@ -975,7 +1104,7 @@ ui_clip_request_data(uint32 format) void ui_clip_sync(void) { - xclip_get_root_property(); + xclip_probe_selections(); } void @@ -1019,15 +1148,23 @@ xclip_init(void) format_utf8_string_atom = XInternAtom(g_display, "UTF8_STRING", False); format_unicode_atom = XInternAtom(g_display, "text/unicode", False); - /* rdesktop sets _RDESKTOP_CLIPBOARD_FORMATS on the root window when acquiring the clipboard. + /* 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); - XSelectInput(g_display, DefaultRootWindow(g_display), PropertyChangeMask); + 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; #ifdef USE_UNICODE_CLIPBOARD targets[num_targets++] = format_utf8_string_atom;