/* -*- c-basic-offset: 8 -*-
rdesktop: A Remote Desktop Protocol client.
Protocol services - Clipboard functions
Copyright 2003-2008 Erik Forsberg <forsberg@cendio.se> for Cendio AB
Copyright (C) Matthew Chapman <matthewc.unsw.edu.au> 2003-2008
Copyright 2006-2011 Pierre Ossman <ossman@cendio.se> for Cendio AB
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Oracle GPL Disclaimer: For the avoidance of doubt, except that if any license choice
* other than GPL or LGPL is available it will apply instead, Oracle elects to use only
* the General Public License version 2 (GPLv2) at this time for any software where
* a choice of GPL license versions is made available with the language indicating
* that GPLv2 or any later version may be used, or where a choice of which version
* of the GPL is applied is otherwise unspecified.
*/
#include "rdesktop.h"
/*
To gain better understanding of this code, one could be assisted by the following documents:
- Inter-Client Communication Conventions Manual (ICCCM)
- MSDN: Clipboard Formats
*/
#ifdef HAVE_ICONV
#ifdef HAVE_LANGINFO_H
#ifdef HAVE_ICONV_H
#include <langinfo.h>
#if defined(RT_OS_SOLARIS) && !defined(_XPG6)
# define VBOX_XPG6_TMP_DEF
# define _XPG6
#endif
#if defined(RT_OS_SOLARIS) && defined(__USE_LEGACY_PROTOTYPES__)
# define VBOX_LEGACY_PROTO_TMP_DEF
# endif
#include <iconv.h>
#if defined(VBOX_XPG6_TMP_DEF)
#endif
#if defined(VBOX_LEGACY_PROTO_TMP_DEF)
# define __USE_LEGACY_PROTOTYPES__
#endif
#define USE_UNICODE_CLIPBOARD
#endif
#endif
#endif
#ifdef USE_UNICODE_CLIPBOARD
#else
#endif
extern Time g_last_gesturetime;
/* Mode of operation.
- Auto: Look at both PRIMARY and CLIPBOARD and use the most recent.
- Non-auto: Look at just CLIPBOARD. */
/* Atoms of the two X selections we're dealing with: CLIPBOARD (explicit-copy) and PRIMARY (selection-copy) */
/* Atom of the TARGETS clipboard target */
/* Atom of the TIMESTAMP clipboard 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. */
/* 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. */
/* Storage for timestamps since we get them in two separate notifications. */
/* Clipboard target for getting a list of native Windows clipboard formats. The
presence of this target indicates that the selection owner is another rdesktop. */
/* 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. */
/* Local copy of the list of native Windows clipboard formats. */
/* 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. */
/* 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. */
/* Atoms _RDESKTOP_PRIMARY_OWNER and _RDESKTOP_CLIPBOARD_OWNER. Used as properties
on the root window to indicate which selections that are owned by rdesktop. */
/* Atom of the INCR clipboard type (see ICCCM on "INCR Properties") */
/* Stores the last "selection request" (= another X client requesting clipboard data from us).
To satisfy such a request, we request the clipboard data from the RDP server.
When we receive the response from the RDP server (asynchronously), this variable gives us
the context to proceed. */
/* Denotes we have a pending selection request. */
/* Stores the clipboard format (CF_TEXT, CF_UNICODETEXT etc.) requested in the last
CLIPDR_DATA_REQUEST (= the RDP server requesting clipboard data from us).
When we receive this data from whatever X client offering it, this variable gives us
the context to proceed.
*/
static int rdp_clipboard_request_format;
/* Array of offered clipboard targets that will be sent to fellow X clients upon a TARGETS request. */
static int num_targets;
/* Denotes that an rdesktop (not this rdesktop) is owning the selection,
allowing us to interchange Windows native clipboard data directly. */
/* Time when we acquired the selection. */
/* Denotes that an INCR ("chunked") transfer is in progress. */
static int g_waiting_for_INCR = 0;
/* Denotes the target format of the ongoing INCR ("chunked") transfer. */
/* Buffers an INCR transfer. */
/* Denotes the size of g_clip_buffer. */
/* Translates CR-LF to LF.
Changes the string in-place.
Does not stop on embedded nulls.
The length is updated. */
static void
{
{
if (*src != '\x0d')
src++;
}
}
#ifdef USE_UNICODE_CLIPBOARD
/* Translate LF to CR-LF. To do this, we must allocate more memory.
The returned string is null-terminated, as required by CF_UNICODETEXT.
The size is updated. */
static uint8 *
{
/* Worst case: Every char is LF */
return NULL;
/* Check for a reversed BOM */
{
if (swap_endianess)
}
*outptr++ = 0; /* null termination */
return result;
}
#else
/* Translate LF to CR-LF. To do this, we must allocate more memory.
The length is updated. */
static uint8 *
{
/* Worst case: Every char is LF */
p = data;
o = result;
{
*o++ = '\x0d';
previous = *p;
*o++ = *p++;
}
/* Convenience */
*o++ = '\0';
return result;
}
#endif
static void
{
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));
}
/* Replies a clipboard requestor, telling that we're unable to satisfy his request for whatever reason.
This has the benefit of finalizing the clipboard negotiation and thus not leaving our requestor
lingering (and, potentially, stuck). */
static void
{
DEBUG_CLIPBOARD(("xclip_refuse_selection: requestor=0x%08x, target=%s, property=%s\n",
}
/* Wrapper for cliprdr_send_data which also cleans the request state. */
static void
{
if (rdp_clipboard_request_format != 0)
{
}
}
/* Last resort, when we have to provide clipboard data but for whatever
reason couldn't get any.
*/
static void
{
}
/* Replies with clipboard data to RDP, converting it from the target format
to the expected RDP format as necessary. Returns true if data was sent.
*/
static RD_BOOL
{
DEBUG_CLIPBOARD(("xclip_send_data_with_convert: target=%s, size=%u\n",
#ifdef USE_UNICODE_CLIPBOARD
if (target == format_string_atom ||
{
char *unicode_buffer;
char *unicode_buffer_remaining;
char *data_remaining;
return False;
/* Make an attempt to convert any string we send to Unicode.
We don't know what the RDP server's ANSI Codepage is, or how to convert
to it, so using CF_TEXT is not safe (and is unnecessary, since all
WinNT versions are Unicode-minded).
*/
if (target == format_string_atom)
{
{
DEBUG_CLIPBOARD(("Locale charset %s not found in iconv. Unable to convert clipboard text.\n", locale_charset));
return False;
}
}
else if (target == format_unicode_atom)
{
{
return False;
}
}
else if (target == format_utf8_string_atom)
{
{
return False;
}
/* UTF-8 is guaranteed to be less or equally compact
as UTF-16 for all Unicode chars >=2 bytes.
*/
}
else
{
return False;
}
data_remaining = (char *) source;
/* translate linebreaks */
if (translated_data != NULL)
{
DEBUG_CLIPBOARD(("Sending Unicode string of %d bytes\n",
}
return True;
}
#else
if (target == format_string_atom)
{
return False;
DEBUG_CLIPBOARD(("Translating linebreaks before sending data\n"));
if (translated_data != NULL)
{
}
return True;
}
#endif
else if (target == rdesktop_native_atom)
{
return True;
}
else
{
return False;
}
}
static void
{
}
static void
{
}
static void
{
if (probing_selections)
{
DEBUG_CLIPBOARD(("Already probing selections. Scheduling reprobe.\n"));
return;
}
DEBUG_CLIPBOARD(("Probing selections.\n"));
if (auto_mode)
else
/* If we own all relevant selections then don't do anything. */
goto end;
/* Both available */
{
primary_timestamp = 0;
clipboard_timestamp = 0;
return;
}
/* Just PRIMARY */
if (primary_owner != None)
{
return;
}
/* Just CLIPBOARD */
if (clipboard_owner != None)
{
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. */
end:
}
/* This function is called for SelectionNotify events.
The SelectionNotify event is sent from the clipboard owner to the requestor
after his request was satisfied.
If this function is called, we're the requestor side. */
#ifndef MAKE_PROTO
void
{
goto fail;
DEBUG_CLIPBOARD(("xclip_handle_SelectionNotify: selection=%s, target=%s, property=%s\n",
{
{
}
else
{
}
{
DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));
goto fail;
}
{
if (primary_timestamp == 0)
DEBUG_CLIPBOARD(("Got PRIMARY timestamp: %u\n",
(unsigned) primary_timestamp));
}
else
{
if (clipboard_timestamp == 0)
DEBUG_CLIPBOARD(("Got CLIPBOARD timestamp: %u\n",
(unsigned) clipboard_timestamp));
}
if (primary_timestamp && clipboard_timestamp)
{
{
DEBUG_CLIPBOARD(("PRIMARY is most recent selection.\n"));
}
else
{
DEBUG_CLIPBOARD(("CLIPBOARD is most recent selection.\n"));
}
}
return;
}
if (probing_selections && reprobe_selections)
{
return;
}
{
DEBUG_CLIPBOARD(("XGetWindowProperty failed!\n"));
goto fail;
}
{
DEBUG_CLIPBOARD(("Received INCR.\n"));
{
}
g_waiting_for_INCR = 1;
goto end;
}
/* Negotiate target format */
{
/* Determine the best of text targets that we have available:
(ignore TEXT and COMPOUND_TEXT because we don't have code to handle them)
*/
int text_target_satisfaction = 0;
{
for (i = 0; i < nitems; i++)
{
DEBUG_CLIPBOARD(("Target %d: %s\n", i,
if (supported_targets[i] == format_string_atom)
{
if (text_target_satisfaction < 1)
{
DEBUG_CLIPBOARD(("Other party supports STRING, choosing that as best_target\n"));
}
}
#ifdef USE_UNICODE_CLIPBOARD
else if (supported_targets[i] == format_unicode_atom)
{
if (text_target_satisfaction < 2)
{
DEBUG_CLIPBOARD(("Other party supports text/unicode, choosing that as best_target\n"));
}
}
else if (supported_targets[i] == format_utf8_string_atom)
{
if (text_target_satisfaction < 3)
{
DEBUG_CLIPBOARD(("Other party supports UTF8_STRING, choosing that as best_target\n"));
}
}
#endif
else if (supported_targets[i] == rdesktop_clipboard_formats_atom)
{
{
DEBUG_CLIPBOARD(("Other party supports native formats, choosing that as best_target\n"));
}
}
}
}
/* Kickstarting the next step in the process of satisfying RDP's
clipboard request -- specifically, requesting the actual clipboard data.
*/
if ((best_text_target != 0)
&& (!probing_selections
{
goto end;
}
else
{
DEBUG_CLIPBOARD(("Unable to find a textual target to satisfy RDP clipboard text request\n"));
goto fail;
}
}
else
{
if (probing_selections)
{
/* 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. */
if (auto_mode)
else
if (primary_owner != clipboard_owner)
goto fail;
DEBUG_CLIPBOARD(("Got fellow rdesktop formats\n"));
}
{
goto fail;
}
}
end:
if (data)
return;
fail:
if (probing_selections)
{
DEBUG_CLIPBOARD(("Unable to find suitable target. Using default text format.\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. */
}
else
{
}
goto end;
}
/* This function is called for SelectionRequest events.
The SelectionRequest event is sent from the requestor to the clipboard owner
to request clipboard data.
*/
void
{
DEBUG_CLIPBOARD(("xclip_handle_SelectionRequest: selection=%s, target=%s, property=%s\n",
{
return;
}
{
return;
}
{
}
else
{
/* All the following targets require an async operation with the RDP server
and currently we don't do X clipboard request queueing so we can only
handle one such request at a time. */
{
DEBUG_CLIPBOARD(("Error: Another clipboard request was already sent to the RDP server and not yet responded. Refusing this request.\n"));
return;
}
{
/* Before the requestor makes a request for the _RDESKTOP_NATIVE target,
he should declare requestor[property] = CF_SOMETHING. */
&prop_return);
{
DEBUG_CLIPBOARD(("Requested native format but didn't specifiy which.\n"));
return;
}
}
{
/* STRING and XA_STRING are defined to be ISO8859-1 */
}
{
#ifdef USE_UNICODE_CLIPBOARD
#else
DEBUG_CLIPBOARD(("Requested target unavailable due to lack of Unicode support. (It was not in TARGETS, so why did you ask for it?!)\n"));
return;
#endif
}
{
}
else
{
DEBUG_CLIPBOARD(("Requested target unavailable. (It was not in TARGETS, so why did you ask for it?!)\n"));
return;
}
return; /* wait for data */
}
}
/* While this rdesktop holds ownership over the clipboard, it means the clipboard data
is offered by the RDP server (and when it is pasted inside RDP, there's no network
roundtrip).
This event (SelectionClear) symbolizes this rdesktop lost onwership of the clipboard
to some other X client. We should find out what clipboard formats this other
client offers and announce that to RDP. */
void
{
DEBUG_CLIPBOARD(("xclip_handle_SelectionClear\n"));
}
/* Called when any property changes in our window or the root window. */
void
{
unsigned long nitems;
unsigned long offset = 0;
int format;
{
DEBUG_CLIPBOARD(("x_clip_handle_PropertyNotify: g_waiting_for_INCR != 0\n"));
while (bytes_left > 0)
{
/* Unlike the specification, we don't set the 'delete' arugment to True
since we slurp the INCR's chunks in even-smaller chunks of 4096 bytes. */
if ((XGetWindowProperty
{
return;
}
if (nitems == 0)
{
/* INCR transfer finished */
g_waiting_for_INCR = 0;
if (g_clip_buflen > 0)
{
{
}
g_clip_buflen = 0;
}
}
else
{
/* Another chunk in the INCR transfer */
g_clip_buflen += nitems;
}
}
return;
}
}
#endif
/* Called when the RDP server announces new clipboard data formats.
In response, we:
- take ownership over the clipboard
- declare those formats in their Windows native form
to other rdesktop instances on this X server */
void
{
warning("Failed to aquire ownership of PRIMARY clipboard\n");
warning("Failed to aquire ownership of CLIPBOARD clipboard\n");
if (formats_data)
}
/* Called when the RDP server responds with clipboard data (after we've requested it). */
void
{
if (length == 0)
{
return;
}
{
/* We're expecting a CF_TEXT response */
/* translate linebreaks */
/* Only send data up to null byte, if any */
if (firstnull)
{
}
}
#ifdef USE_UNICODE_CLIPBOARD
{
/* We're expecting a CF_UNICODETEXT response */
{
{
return;
}
/* translate linebreaks (works just as well on UTF-8) */
}
}
{
/* We're expecting a CF_UNICODETEXT response, so what we're
receiving matches our requirements and there's no need
for further conversions. */
}
#endif
{
/* 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)));
return;
}
if (free_data)
}
void
{
}
void
{
if (probing_selections)
{
DEBUG_CLIPBOARD(("ui_clip_request_data: Selection probe in progress. Cannot handle request.\n"));
return;
}
{
return;
}
if (auto_mode)
else
/* Both available */
{
primary_timestamp = 0;
clipboard_timestamp = 0;
return;
}
/* Just PRIMARY */
if (primary_owner != None)
{
return;
}
/* Just CLIPBOARD */
if (clipboard_owner != None)
{
return;
}
/* No data available */
}
void
ui_clip_sync(void)
{
}
void
{
else
{
}
}
void
xclip_init(void)
{
if (!g_rdpclip)
return;
if (!cliprdr_init())
return;
/* 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. */
num_targets = 0;
#ifdef USE_UNICODE_CLIPBOARD
#endif
}
void
xclip_deinit(void)
{
}