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
This commit is contained in:
Pierre Ossman 2006-03-28 13:24:48 +00:00
parent 91779a2fe3
commit b48e542deb

321
xclip.c
View File

@ -48,7 +48,7 @@
#define RDP_CF_TEXT CF_TEXT #define RDP_CF_TEXT CF_TEXT
#endif #endif
#define MAX_TARGETS 7 #define MAX_TARGETS 8
extern Display *g_display; extern Display *g_display;
extern Window g_wnd; extern Window g_wnd;
@ -65,14 +65,9 @@ static Atom clipboard_atom, primary_atom;
static Atom targets_atom; static Atom targets_atom;
/* Atom of the TIMESTAMP clipboard target */ /* Atom of the TIMESTAMP clipboard target */
static Atom timestamp_atom; static Atom timestamp_atom;
/* Atom _RDESKTOP_CLIPBOARD_TARGET which has multiple uses: /* Atom _RDESKTOP_CLIPBOARD_TARGET which is used as the 'property' argument in
- The 'property' argument in XConvertSelection calls: This is the property of our XConvertSelection calls: This is the property of our window into which
window into which XConvertSelection will store the received clipboard data. 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. */
static Atom rdesktop_clipboard_target_atom; static Atom rdesktop_clipboard_target_atom;
/* Atoms _RDESKTOP_PRIMARY_TIMESTAMP_TARGET and _RDESKTOP_CLIPBOARD_TIMESTAMP_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. 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; static Atom rdesktop_primary_timestamp_target_atom, rdesktop_clipboard_timestamp_target_atom;
/* Storage for timestamps since we get them in two separate notifications. */ /* Storage for timestamps since we get them in two separate notifications. */
static Time primary_timestamp, clipboard_timestamp; static Time primary_timestamp, clipboard_timestamp;
/* Atom _RDESKTOP_CLIPBOARD_FORMATS which has multiple uses: /* Clipboard target for getting a list of native Windows clipboard formats. The
- The clipboard target (X jargon for "clipboard format") for rdesktop-to-rdesktop interchange presence of this target indicates that the selection owner is another rdesktop. */
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. */
static Atom rdesktop_clipboard_formats_atom; 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; static Atom format_string_atom, format_utf8_string_atom, format_unicode_atom;
/* Atom of the INCR clipboard type (see ICCCM on "INCR Properties") */ /* Atom of the INCR clipboard type (see ICCCM on "INCR Properties") */
static Atom incr_atom; 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. */ /* Array of offered clipboard targets that will be sent to fellow X clients upon a TARGETS request. */
static Atom targets[MAX_TARGETS]; static Atom targets[MAX_TARGETS];
static int num_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, /* Denotes that an rdesktop (not this rdesktop) is owning the selection,
allowing us to interchange Windows native clipboard data directly. */ 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. */ /* Time when we acquired the selection. */
static Time acquire_time = 0; static Time acquire_time = 0;
@ -214,6 +218,8 @@ xclip_provide_selection(XSelectionRequestEvent * req, Atom type, unsigned int fo
{ {
XEvent xev; 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, XChangeProperty(g_display, req->requestor, req->property,
type, format, PropModeReplace, data, length); type, format, PropModeReplace, data, length);
@ -236,6 +242,10 @@ xclip_refuse_selection(XSelectionRequestEvent * req)
{ {
XEvent xev; 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.type = SelectionNotify;
xev.xselection.serial = 0; xev.xselection.serial = 0;
xev.xselection.send_event = True; xev.xselection.send_event = True;
@ -247,35 +257,6 @@ xclip_refuse_selection(XSelectionRequestEvent * req)
XSendEvent(g_display, req->requestor, False, NoEventMask, &xev); 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. */ /* Wrapper for cliprdr_send_data which also cleans the request state. */
static void static void
helper_cliprdr_send_response(uint8 * data, uint32 length) helper_cliprdr_send_response(uint8 * data, uint32 length)
@ -304,6 +285,9 @@ helper_cliprdr_send_empty_response()
static Bool static Bool
xclip_send_data_with_convert(uint8 * source, size_t source_size, Atom target) 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 #ifdef USE_UNICODE_CLIPBOARD
if (target == format_string_atom || if (target == format_string_atom ||
target == format_unicode_atom || target == format_utf8_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; return True;
} }
#endif #endif
else if (target == rdesktop_clipboard_formats_atom) else if (target == rdesktop_native_atom)
{ {
helper_cliprdr_send_response(source, source_size + 1); 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); 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. /* This function is called for SelectionNotify events.
The SelectionNotify event is sent from the clipboard owner to the requestor The SelectionNotify event is sent from the clipboard owner to the requestor
after his request was satisfied. after his request was satisfied.
@ -511,6 +572,13 @@ xclip_handle_SelectionNotify(XSelectionEvent * event)
return; return;
} }
if (probing_selections && reprobe_selections)
{
probing_selections = False;
xclip_probe_selections();
return;
}
res = XGetWindowProperty(g_display, g_wnd, rdesktop_clipboard_target_atom, res = XGetWindowProperty(g_display, g_wnd, rdesktop_clipboard_target_atom,
0, XMaxRequestSize(g_display), False, AnyPropertyType, 0, XMaxRequestSize(g_display), False, AnyPropertyType,
&type, &format, &nitems, &bytes_left, &data); &type, &format, &nitems, &bytes_left, &data);
@ -535,7 +603,7 @@ xclip_handle_SelectionNotify(XSelectionEvent * event)
XFree(data); XFree(data);
g_incr_target = event->target; g_incr_target = event->target;
g_waiting_for_INCR = 1; g_waiting_for_INCR = 1;
return; goto end;
} }
/* Negotiate target format */ /* Negotiate target format */
@ -583,17 +651,28 @@ xclip_handle_SelectionNotify(XSelectionEvent * event)
} }
} }
#endif #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 /* Kickstarting the next step in the process of satisfying RDP's
clipboard request -- specifically, requesting the actual clipboard data. 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, XConvertSelection(g_display, event->selection, best_text_target,
rdesktop_clipboard_target_atom, g_wnd, event->time); rdesktop_clipboard_target_atom, g_wnd, event->time);
return; goto end;
} }
else else
{ {
@ -603,21 +682,62 @@ xclip_handle_SelectionNotify(XSelectionEvent * event)
} }
else 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; goto fail;
} }
} }
XFree(data); end:
if (data)
XFree(data);
return; return;
fail: fail:
xclip_clear_target_props(); xclip_clear_target_props();
if (data) if (probing_selections)
XFree(data); {
helper_cliprdr_send_empty_response(); 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. /* This function is called for SelectionRequest events.
@ -629,7 +749,6 @@ xclip_handle_SelectionRequest(XSelectionRequestEvent * event)
{ {
unsigned long nitems, bytes_left; unsigned long nitems, bytes_left;
unsigned char *prop_return; unsigned char *prop_return;
uint32 *wanted_format;
int format, res; int format, res;
Atom type; Atom type;
@ -648,6 +767,10 @@ xclip_handle_SelectionRequest(XSelectionRequestEvent * event)
xclip_provide_selection(event, XA_INTEGER, 32, (uint8 *) & acquire_time, 1); xclip_provide_selection(event, XA_INTEGER, 32, (uint8 *) & acquire_time, 1);
return; return;
} }
else if (event->target == rdesktop_clipboard_formats_atom)
{
xclip_provide_selection(event, XA_STRING, 8, formats_data, formats_data_length);
}
else else
{ {
/* All the following targets require an async operation with the RDP server /* 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); xclip_refuse_selection(event);
return; 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, /* Before the requestor makes a request for the _RDESKTOP_NATIVE target,
he should declare requestor[_RDESKTOP_CLIPBOARD_TARGET] = CF_SOMETHING. he should declare requestor[property] = CF_SOMETHING. */
Otherwise, we default to RDP_CF_TEXT.
*/
res = XGetWindowProperty(g_display, event->requestor, 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, XA_INTEGER, &type, &format, &nitems, &bytes_left,
&prop_return); &prop_return);
wanted_format = (uint32 *) prop_return; if (res != Success)
format = (res == Success) ? *wanted_format : RDP_CF_TEXT; {
DEBUG_CLIPBOARD(("Requested native format but didn't specifiy which.\n"));
xclip_refuse_selection(event);
return;
}
format = *(uint32 *) prop_return;
XFree(prop_return); XFree(prop_return);
} }
else if (event->target == format_string_atom || event->target == XA_STRING) else if (event->target == format_string_atom || event->target == XA_STRING)
@ -718,13 +845,8 @@ void
xclip_handle_SelectionClear(void) xclip_handle_SelectionClear(void)
{ {
DEBUG_CLIPBOARD(("xclip_handle_SelectionClear\n")); DEBUG_CLIPBOARD(("xclip_handle_SelectionClear\n"));
have_primary = 0; xclip_notify_change();
XDeleteProperty(g_display, DefaultRootWindow(g_display), rdesktop_clipboard_formats_atom); xclip_probe_selections();
/* 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);
} }
/* Called when any property changes in our window or the root window. */ /* Called when any property changes in our window or the root window. */
@ -792,10 +914,9 @@ xclip_handle_PropertyNotify(XPropertyEvent * event)
return; return;
} }
if ((event->atom == rdesktop_clipboard_formats_atom) && if ((event->atom == rdesktop_selection_notify_atom) &&
(event->window == DefaultRootWindow(g_display)) && (event->window == DefaultRootWindow(g_display)))
!have_primary /* not interested in our own events */ ) xclip_probe_selections();
xclip_get_root_property();
} }
#endif #endif
@ -812,19 +933,19 @@ ui_clip_format_announce(uint8 * data, uint32 length)
XSetSelectionOwner(g_display, primary_atom, g_wnd, acquire_time); XSetSelectionOwner(g_display, primary_atom, g_wnd, acquire_time);
if (XGetSelectionOwner(g_display, primary_atom) != g_wnd) if (XGetSelectionOwner(g_display, primary_atom) != g_wnd)
{
warning("Failed to aquire ownership of PRIMARY clipboard\n"); 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); XSetSelectionOwner(g_display, clipboard_atom, g_wnd, acquire_time);
if (XGetSelectionOwner(g_display, clipboard_atom) != g_wnd) if (XGetSelectionOwner(g_display, clipboard_atom) != g_wnd)
warning("Failed to aquire ownership of CLIPBOARD clipboard\n"); 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). */ /* 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. */ for further conversions. */
} }
#endif #endif
else if (selection_request.target == rdesktop_clipboard_formats_atom) else if (selection_request.target == rdesktop_native_atom)
{ {
/* Pass as-is */ /* Pass as-is */
} }
else 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); xclip_refuse_selection(&selection_request);
has_selection_request = False; has_selection_request = False;
return; return;
@ -921,6 +1043,13 @@ ui_clip_request_data(uint32 format)
DEBUG_CLIPBOARD(("Request from server for format %d\n", format)); DEBUG_CLIPBOARD(("Request from server for format %d\n", format));
rdp_clipboard_request_format = 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(); xclip_clear_target_props();
if (rdesktop_is_selection_owner) if (rdesktop_is_selection_owner)
@ -928,7 +1057,7 @@ ui_clip_request_data(uint32 format)
XChangeProperty(g_display, g_wnd, rdesktop_clipboard_target_atom, XChangeProperty(g_display, g_wnd, rdesktop_clipboard_target_atom,
XA_INTEGER, 32, PropModeReplace, (unsigned char *) &format, 1); 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); rdesktop_clipboard_target_atom, g_wnd, CurrentTime);
return; return;
} }
@ -975,7 +1104,7 @@ ui_clip_request_data(uint32 format)
void void
ui_clip_sync(void) ui_clip_sync(void)
{ {
xclip_get_root_property(); xclip_probe_selections();
} }
void void
@ -1019,15 +1148,23 @@ xclip_init(void)
format_utf8_string_atom = XInternAtom(g_display, "UTF8_STRING", False); format_utf8_string_atom = XInternAtom(g_display, "UTF8_STRING", False);
format_unicode_atom = XInternAtom(g_display, "text/unicode", 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. */ 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 = rdesktop_clipboard_formats_atom =
XInternAtom(g_display, "_RDESKTOP_CLIPBOARD_FORMATS", False); 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; num_targets = 0;
targets[num_targets++] = targets_atom; targets[num_targets++] = targets_atom;
targets[num_targets++] = timestamp_atom; targets[num_targets++] = timestamp_atom;
targets[num_targets++] = rdesktop_native_atom;
targets[num_targets++] = rdesktop_clipboard_formats_atom; targets[num_targets++] = rdesktop_clipboard_formats_atom;
#ifdef USE_UNICODE_CLIPBOARD #ifdef USE_UNICODE_CLIPBOARD
targets[num_targets++] = format_utf8_string_atom; targets[num_targets++] = format_utf8_string_atom;