x11-clipboard.cpp revision 268ef2c42969a538650d97e78f52e504d07b35be
/** @file
*
* Shared Clipboard:
* X11 backend code.
*/
/*
* Copyright (C) 2006-2007 Sun Microsystems, Inc.
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 USA or visit http://www.sun.com if you need
* additional information or have any questions.
*/
/* Note: to automatically run regression tests on the shared clipboard, set
* the make variable VBOX_RUN_X11_CLIPBOARD_TEST=1 while building. If you
* often make changes to the clipboard code, setting this variable in
* LocalConfig.kmk will cause the tests to be run every time the code is
* changed. */
#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
#include <errno.h>
#include <unistd.h>
#ifdef RT_OS_SOLARIS
#endif
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <iprt/semaphore.h>
/** The different clipboard formats which we support. */
enum CLIPFORMAT
{
INVALID = 0,
};
/** The table mapping X11 names to data formats and to the corresponding
* VBox clipboard formats (currently only Unicode) */
static struct _CLIPFORMATTABLE
{
/** The X11 atom name of the format (several names can match one format)
*/
const char *pcszAtom;
/** The format corresponding to the name */
/** The corresponding VBox clipboard format */
} g_aFormats[] =
{
{ "INVALID", INVALID, 0 },
};
typedef unsigned CLIPX11FORMAT;
enum
{
NIL_CLIPX11FORMAT = 0,
};
/** Return the atom corresponding to a supported X11 format.
* @param widget a valid Xt widget
*/
{
}
/** Return the CLIPFORMAT corresponding to a supported X11 format. */
{
}
/** Return the atom corresponding to a supported X11 format. */
{
}
/** Lookup the X11 format matching a given X11 atom.
* @returns the format on success, NIL_CLIPX11FORMAT on failure
* @param widget a valid Xt widget
*/
{
for (unsigned i = 0; i < RT_ELEMENTS(g_aFormats); ++i)
return i;
return NIL_CLIPX11FORMAT;
}
/**
* Enumerates supported X11 clipboard formats corresponding to a given VBox
* format.
* @returns the next matching X11 format in the list, or NIL_CLIPX11FORMAT if
* there are no more
* @param lastFormat The value returned from the last call of this function.
* Use NIL_CLIPX11FORMAT to start the enumeration.
*/
{
if (u32VBoxFormats & clipVBoxFormatForX11Format(i))
return i;
return NIL_CLIPX11FORMAT;
}
/** Global context information used by the X11 clipboard backend */
struct _CLIPBACKEND
{
/** Opaque data structure describing the front-end. */
/** Is an X server actually available? */
bool fHaveX11;
/** The X Toolkit application context structure */
/** We have a separate thread to wait for Window and Clipboard events */
/** The X Toolkit widget which we use as our clipboard client. It is never made visible. */
/** Does VBox currently own the clipboard? If so, we don't need to poll
* X11 for supported formats. */
bool fOwnsClipboard;
/** The best text format X11 has to offer, as an index into the formats
* table */
/** The best bitmap format X11 has to offer, as an index into the formats
* table */
/** What formats does VBox have on offer? */
/** Windows hosts and guests cache the clipboard data they receive.
* Since we have no way of knowing whether their cache is still valid,
* we always send a "data changed" message after a successful transfer
* to invalidate it. */
bool notifyVBox;
/** Cache of the last unicode data that we received */
void *pvUnicodeCache;
/** Size of the unicode data in the cache */
/** When we wish the clipboard to exit, we have to wake up the event
* loop. We do this by writing into a pipe. This end of the pipe is
* the end that another thread can write to. */
int wakeupPipeWrite;
/** The reader end of the pipe */
int wakeupPipeRead;
};
/** The number of simultaneous instances we support. For all normal purposes
* we should never need more than one. For the testcase it is convenient to
* have a second instance that the first can interact with in order to have
* a more controlled environment. */
enum { CLIP_MAX_CONTEXTS = 20 };
/** Array of structures for mapping Xt widgets to context pointers. We
* need this because the widget clipboard callbacks do not pass user data. */
static struct {
/** The widget we want to associate the context with */
/** The context associated with the widget */
/** Register a new X11 clipboard context. */
{
bool found = false;
for (unsigned i = 0; i < RT_ELEMENTS(g_contexts); ++i)
{
{
found = true;
}
}
}
/** Unregister an X11 clipboard context. */
{
bool found = false;
for (unsigned i = 0; i < RT_ELEMENTS(g_contexts); ++i)
{
{
found = true;
}
}
}
/** Find an X11 clipboard context. */
{
for (unsigned i = 0; i < RT_ELEMENTS(g_contexts); ++i)
{
{
return g_contexts[i].pCtx;
}
}
return NULL;
}
/** Convert an atom name string to an X11 atom, looking it up in a cache
* before asking the server */
{
return retval;
}
#ifndef TESTCASE
{
}
#endif
/**
* Report the formats currently supported by the X11 clipboard to VBox.
*/
{
}
/**
* Forget which formats were previously in the X11 clipboard. Should be
* called when we grab the clipboard, so that when we lose it again the poller
* will notify us when new formats become available. */
{
}
/** Tell VBox that X11 currently has nothing in its clipboard. */
{
}
/**
* Go through an array of X11 clipboard targets to see if we can support any
* of them and if relevant to choose the ones we prefer (e.g. we like Utf8
* better than compound text).
* @param pCtx the clipboard backend context structure
* @param pTargets the list of targets
* @param cTargets the size of the list in @a pTargets
* @param pChanged This is set to true if the formats available have changed
* from VBox's point of view, and to false otherwise.
* Somehow this didn't feel right as a return value.
*/
{
bool changed = false;
for (unsigned i = 0; i < cTargets; ++i)
{
pTargets[i]);
if (format != NIL_CLIPX11FORMAT)
{
if ( (clipVBoxFormatForX11Format(format)
{
}
}
}
{
changed = true;
}
if (pChanged)
}
/**
* Notify the VBox clipboard about available data formats, based on the
* "targets" information obtained from the X11 clipboard.
* @note callback for XtGetSelectionValue, called on a polling loop
*/
int *piFormat)
{
CLIPBACKEND *pCtx =
reinterpret_cast<CLIPBACKEND *>(pClientData);
bool changed = true;
if (pCtx->fOwnsClipboard)
/* VBox raced us and we lost. So we don't want to report anything. */
changed = false;
|| !pTargets /* Conversion failed */)
else
if (changed)
}
XtIntervalId * /* hTimerId */);
#ifndef TESTCASE
{
}
#endif
/**
* This timer callback is called every 200ms to check the contents of the X11
* clipboard.
* @note X11 backend code, callback for XtAppAddTimeOut, recursively
* re-armed.
* @todo Use the XFIXES extension to check for new clipboard data when
* available.
*/
{
/* Get the current clipboard contents if we don't own it ourselves */
if (!pCtx->fOwnsClipboard)
{
Log3 (("%s: requesting the targets that the X11 clipboard offers\n",
}
/* Re-arm our timer */
}
#ifndef TESTCASE
/**
* The main loop of our clipboard reader.
* @note X11 backend code.
*/
{
LogRel(("Shared clipboard: starting shared clipboard thread\n"));
LogRel(("Shared clipboard: shared clipboard thread terminated successfully\n"));
return VINF_SUCCESS;
}
#endif
/** X11 specific uninitialisation for the shared clipboard.
* @note X11 backend code.
*/
{
{
/* Valid widget + invalid appcontext = bug. But don't return yet. */
}
if (pCtx->appContext)
if (pCtx->wakeupPipeRead != 0)
if (pCtx->wakeupPipeWrite != 0)
pCtx->wakeupPipeRead = 0;
pCtx->wakeupPipeWrite = 0;
}
/** Worker function for stopping the clipboard which runs on the event
* thread. */
XtInputId * /* id */)
{
/* This might mean that we are getting stopped twice. */
/* Set the termination flag to tell the Xt event loop to exit. We
* reiterate that any outstanding requests from the X11 event loop to
* the VBox part *must* have returned before we do this. */
}
/** X11 specific initialisation for the shared clipboard.
* @note X11 backend code.
*/
{
/* Create a window and make it a clipboard viewer. */
int cArgc = 0;
char *pcArgv = 0;
int rc = VINF_SUCCESS;
/* Make sure we are thread safe */
/* Set up the Clipbard application context and main window. We call all
* these functions directly instead of calling XtOpenApplication() so
* that we can fail gracefully if we can't get an X11 display. */
{
LogRel(("Shared clipboard: failed to connect to the X11 clipboard - the window system may not be running.\n"));
}
if (RT_SUCCESS(rc))
{
1, NULL);
{
LogRel(("Shared clipboard: failed to construct the X11 window for the shared clipboard manager.\n"));
rc = VERR_NO_MEMORY;
}
else
}
if (RT_SUCCESS(rc))
{
/* Set up a timer to poll the X11 clipboard */
}
/* Create the pipes */
int pipes[2];
{
}
else
if (RT_FAILURE(rc))
return rc;
}
/**
* Construct the X11 backend of the shared clipboard.
* @note X11 backend code
*/
{
int rc;
RTMemAllocZ(sizeof(CLIPBACKEND));
{
/*
* If we don't find the DISPLAY environment variable we assume that
* we are not connected to an X11 server. Don't actually try to do
* this then, just fail silently and report success on every call.
* This is important for VBoxHeadless.
*/
LogRelFunc(("X11 DISPLAY variable not set -- disabling shared clipboard\n"));
return pCtx;
}
LogRel(("Initializing X11 clipboard backend\n"));
if (pCtx)
return pCtx;
}
/**
* Destruct the shared clipboard X11 backend.
* @note X11 backend code
*/
{
/*
* Immediately return if we are not connected to the X server.
*/
return;
/* We set this to NULL when the event thread exits. It really should
* have exited at this point, when we are about to unload the code from
* memory. */
}
/**
* Announce to the X11 backend that we are ready to start.
*/
{
int rc = VINF_SUCCESS;
LogFlowFunc(("\n"));
/*
* Immediately return if we are not connected to the X server.
*/
return VINF_SUCCESS;
#ifndef TESTCASE
if (RT_SUCCESS(rc))
{
if (RT_FAILURE(rc))
LogRel(("Failed to initialise the shared clipboard X11 backend.\n"));
}
#endif
if (RT_SUCCESS(rc))
{
pCtx->fOwnsClipboard = false;
}
return rc;
}
/** String written to the wakeup pipe. */
#define WAKE_UP_STRING "WakeUp!"
/** Length of the string written. */
/**
* Shut down the shared clipboard X11 backend.
* @note X11 backend code
* @note Any requests from this object to get clipboard data from VBox
* *must* have completed or aborted before we are called, as
* otherwise the X11 event loop will still be waiting for the request
* to return and will not be able to terminate.
*/
{
unsigned count = 0;
/*
* Immediately return if we are not connected to the X server.
*/
return VINF_SUCCESS;
LogRelFunc(("stopping the shared clipboard X11 backend\n"));
/* Write to the "stop" pipe */
do
{
++count;
if (RT_SUCCESS(rc))
else
return rc;
}
/**
* Satisfy a request from X11 for clipboard targets supported by VBox.
*
* @returns iprt status code
* @param atomTypeReturn The type of the data we are returning
* @param pValReturn A pointer to the data we are returning. This
* should be set to memory allocated by XtMalloc,
* which will be freed later by the Xt toolkit.
* @param pcLenReturn The length of the data we are returning
* @param piFormatReturn The format (8bit, 16bit, 32bit) of the data we are
* returning
* @note X11 backend code, called by the XtOwnSelection callback.
*/
unsigned long *pcLenReturn,
int *piFormatReturn)
{
* sizeof(Atom));
unsigned cTargets = 0;
LogFlowFunc (("called\n"));
do
{
if (format != NIL_CLIPX11FORMAT)
{
format);
++cTargets;
}
} while (format != NIL_CLIPX11FORMAT);
/* We always offer these */
*piFormatReturn = 32;
return VINF_SUCCESS;
}
/** This is a wrapper around ClipRequestDataForX11 that will cache the
* data returned.
*/
{
int rc = VINF_SUCCESS;
{
&pCtx->cbUnicodeCache);
if (RT_SUCCESS(rc))
{
rc = VERR_NO_MEMORY;
}
}
else
if (RT_SUCCESS(rc))
return rc;
}
/**
* Calculate a buffer size large enough to hold the source Windows format
* text converted into Unix Utf8, including the null terminator
* @returns iprt status code
* @param pwsz the source text in UCS-2 with Windows EOLs
* @param cwc the size in USC-2 elements of the source text, with or
* without the terminator
* @param pcbActual where to store the buffer size needed
*/
{
if (RT_SUCCESS(rc))
return rc;
}
/**
* Convert text from Windows format (UCS-2 with CRLF line endings) to standard
* Utf-8.
*
* @returns iprt status code
*
* @param pwszSrc the text to be converted
* @param cbSrc the length of @a pwszSrc in bytes
* @param pszBuf where to write the converted string
* @param cbBuf the size of the buffer pointed to by @a pszBuf
* @param pcbActual where to store the size of the converted string.
* optional.
*/
{
int rc = VINF_SUCCESS;
/* How long will the converted text be? */
rc = VERR_NO_DATA;
if (RT_SUCCESS(rc))
if (!pwszTmp)
rc = VERR_NO_MEMORY;
/* Convert the text. */
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
/* Convert the Utf16 string to Utf8. */
&cbDest);
if (pcbActual)
if (RT_SUCCESS(rc))
pszBuf));
return rc;
}
/**
* Satisfy a request from X11 to convert the clipboard text to Utf-8. We
* return null-terminated text, but can cope with non-null-terminated input.
*
* @returns iprt status code
* @param pDisplay an X11 display structure, needed for conversions
* performed by Xlib
* @param pv the text to be converted (UCS-2 with Windows EOLs)
* @param cb the length of the text in @cb in bytes
* @param atomTypeReturn where to store the atom for the type of the data
* we are returning
* @param pValReturn where to store the pointer to the data we are
* returning. This should be to memory allocated by
* XtMalloc, which will be freed by the Xt toolkit
* later.
* @param pcLenReturn where to store the length of the data we are
* returning
* @param piFormatReturn where to store the bit width (8, 16, 32) of the
* data we are returning
*/
unsigned long *pcLenReturn,
int *piFormatReturn)
{
/* This may slightly overestimate the space needed. */
if (RT_SUCCESS(rc))
{
if (pszDest)
&cbActual);
if (RT_SUCCESS(rc))
{
*atomTypeReturn = *atomTarget;
*pcLenReturn = cbActual;
*piFormatReturn = 8;
}
}
return rc;
}
/**
* Satisfy a request from X11 to convert the clipboard text to
* COMPOUND_TEXT. We return null-terminated text, but can cope with non-null-
* terminated input.
*
* @returns iprt status code
* @param pDisplay an X11 display structure, needed for conversions
* performed by Xlib
* @param pv the text to be converted (UCS-2 with Windows EOLs)
* @param cb the length of the text in @cb in bytes
* @param atomTypeReturn where to store the atom for the type of the data
* we are returning
* @param pValReturn where to store the pointer to the data we are
* returning. This should be to memory allocated by
* XtMalloc, which will be freed by the Xt toolkit
* later.
* @param pcLenReturn where to store the length of the data we are
* returning
* @param piFormatReturn where to store the bit width (8, 16, 32) of the
* data we are returning
*/
unsigned long *pcLenReturn,
int *piFormatReturn)
{
AssertPtrReturn(pDisplay, false);
AssertPtrReturn(pwszSrc, false);
if (RT_SUCCESS(rc))
{
if (!pszTmp)
rc = VERR_NO_MEMORY;
}
if (RT_SUCCESS(rc))
&cbActual);
/* And finally (!) convert the Utf8 text to compound text. */
#ifdef X_HAVE_UTF8_STRING
if (RT_SUCCESS(rc))
#else
if (RT_SUCCESS(rc))
#endif
if (RT_SUCCESS(rc))
return rc;
}
/**
* Does this atom correspond to one of the two selection types we support?
* @param widget a valid Xt widget
* @param selType the atom in question
*/
{
}
unsigned long *pcLenReturn,
int *piFormatReturn)
{
int rc = VINF_SUCCESS;
*atomTarget);
{
rc = VERR_NO_DATA;
}
else
return rc;
}
/**
* Return VBox's clipboard data for an X11 client.
* @note X11 backend code, callback for XtOwnSelection
*/
unsigned long *pcLenReturn,
int *piFormatReturn)
{
int rc = VINF_SUCCESS;
LogFlowFunc(("\n"));
return false;
else
return RT_SUCCESS(rc);
}
/**
* This is called by the X toolkit intrinsics to let us know that another
* X11 client has taken the clipboard. In this case we notify VBox that
* we want ownership of the clipboard.
* @note X11 backend code, callback for XtOwnSelection
*/
{
LogFlowFunc (("\n"));
/* These should be set to the right values as soon as we start polling */
pCtx->fOwnsClipboard = false;
}
/** Structure used to pass information about formats that VBox supports */
typedef struct _CLIPNEWVBOXFORMATS
{
/** Context information for the X11 clipboard */
/** Formats supported by VBox */
/** Invalidates the local cache of the data in the VBox clipboard. */
{
{
}
}
/** Gives up ownership of the X11 clipboard */
{
pCtx->fOwnsClipboard = false;
pCtx->vboxFormats = 0;
}
/**
* Take possession of the X11 clipboard (and middle-button selection).
*/
{
{
pCtx->fOwnsClipboard = true;
/* Grab the middle-button paste selection too. */
}
else
/* Someone raced us to get the clipboard and they won. */
pCtx->fOwnsClipboard = false;
}
/**
* Worker function for ClipAnnounceFormatToX11 which runs on the
* event thread.
* @param pUserData Pointer to a CLIPNEWVBOXFORMATS structure containing
* information about the VBox formats available and the
* clipboard context data. Must be freed by the worker.
*/
XtIntervalId * /* interval */)
{
if (u32Formats == 0)
else
LogFlowFunc(("returning\n"));
}
/**
* VBox is taking possession of the shared clipboard.
*
* @param u32Formats Clipboard formats that VBox is offering
* @note X11 backend code
*/
{
/*
* Immediately return if we are not connected to the X server.
*/
return;
/* This must be freed by the worker callback */
{
}
}
/**
* Massage generic Utf16 with CR end-of-lines into the format Windows expects
* and return the result in a RTMemAlloc allocated buffer.
* @returns IPRT status code
* @param pwcSrc The source Utf16
* @param cwcSrc The number of 16bit elements in @a pwcSrc, not counting
* the terminating zero
* @param ppwszDest Where to store the buffer address
* @param pcbDest On success, where to store the number of bytes written.
* Undefined otherwise. Optional
*/
{
ppwszDest));
if (pcbDest)
*pcbDest = 0;
if (RT_SUCCESS(rc))
{
if (!pwszDest)
rc = VERR_NO_MEMORY;
}
if (RT_SUCCESS(rc))
cwcDest);
if (RT_SUCCESS(rc))
{
if (pcbDest)
}
else
if (pcbDest)
return rc;
}
/**
* Convert Utf-8 text with CR end-of-lines into Utf-16 as Windows expects it
* and return the result in a RTMemAlloc allocated buffer.
* @returns IPRT status code
* @param pcSrc The source Utf-8
* @param cbSrc The size of the source in bytes, not counting the
* terminating zero
* @param ppwszDest Where to store the buffer address
* @param pcbDest On success, where to store the number of bytes written.
* Undefined otherwise. Optional
*/
{
ppwszDest));
if (pcbDest)
*pcbDest = 0;
/* Intermediate conversion to UTF16 */
if (RT_SUCCESS(rc))
if (pcbDest)
return rc;
}
/**
* Convert COMPOUND TEXT with CR end-of-lines into Utf-16 as Windows expects
* it and return the result in a RTMemAlloc allocated buffer.
* @returns IPRT status code
* conversion
* @param pcSrc The source text
* @param cbSrc The size of the source in bytes, not counting the
* terminating zero
* @param ppwszDest Where to store the buffer address
* @param pcbDest On success, where to store the number of bytes written.
* Undefined otherwise. Optional
*/
{
if (pcbDest)
*pcbDest = 0;
/* Special case as X*TextProperty* can't seem to handle empty strings. */
if (cbSrc == 0)
{
if (!ppwszDest)
return VERR_NO_MEMORY;
**ppwszDest = 0;
if (pcbDest)
*pcbDest = 2;
return VINF_SUCCESS;
}
if (pcbDest)
*pcbDest = 0;
/* Intermediate conversion to Utf8 */
int rc = VINF_SUCCESS;
int cProps;
#ifdef X_HAVE_UTF8_STRING
#else
#endif
if (xrc < 0)
/* Now convert the UTF8 to UTF16 */
if (RT_SUCCESS(rc))
if (pcbDest)
return rc;
}
/**
* Convert Latin-1 text with CR end-of-lines into Utf-16 as Windows expects
* it and return the result in a RTMemAlloc allocated buffer.
* @returns IPRT status code
* @param pcSrc The source text
* @param cbSrc The size of the source in bytes, not counting the
* terminating zero
* @param ppwszDest Where to store the buffer address
* @param pcbDest On success, where to store the number of bytes written.
* Undefined otherwise. Optional
*/
{
int rc = VINF_SUCCESS;
/* Calculate the space needed */
unsigned cwcDest = 0;
cwcDest += 2;
else
++cwcDest;
++cwcDest; /* Leave space for the terminator */
if (pcbDest)
if (!pwszDest)
rc = VERR_NO_MEMORY;
/* And do the convertion, bearing in mind that Latin-1 expands "naturally"
* to Utf-16. */
if (RT_SUCCESS(rc))
{
for (unsigned i = 0, j = 0; i < cbSrc; ++i, ++j)
else
{
pwszDest[j] = CARRIAGERETURN;
++j;
}
}
if (RT_SUCCESS(rc))
else
if (pcbDest)
return rc;
}
/** A structure containing information about where to store a request
* for the X11 clipboard contents. */
struct _CLIPREADX11CBREQ
{
/** The format VBox would like the data in */
/** The text format we requested from X11 if we requested text */
/** The clipboard context this request is associated with */
/** The request structure passed in from the backend. */
};
typedef struct _CLIPREADX11CBREQ CLIPREADX11CBREQ;
/**
* Convert the text obtained from the X11 clipboard to UTF-16LE with Windows
* EOLs, place it in the buffer supplied and signal that data has arrived.
* @note X11 backend code, callback for XtGetSelectionValue, for use when
* the X11 clipboard contains a text format we understand.
*/
int *piFormat)
{
LogFlowFunc(("pReq->mFormat=%02X, pReq->mTextFormat=%u, pReq->mCtx=%p\n",
int rc = VINF_SUCCESS;
/* The clipboard selection may have changed before we could get it. */
rc = VERR_NO_DATA;
rc = VERR_TIMEOUT;
{
/* In which format is the clipboard data? */
{
case CTEXT:
break;
case UTF8:
{
/* If we are given broken Utf-8, we treat it as Latin1. Is
* this acceptable? */
0)))
else
break;
}
default:
}
}
else
if (RT_SUCCESS(rc))
/* The other end may want to cache the data, so pretend we have new
* data, as we have no way of telling when new data really does
* arrive. */
else
}
/** Worker function for ClipRequestDataFromX11 which runs on the event
* thread. */
XtIntervalId * /* interval */)
{
int rc = VINF_SUCCESS;
/* Do not continue if we already own the clipboard */
if (pCtx->fOwnsClipboard == true)
rc = VERR_TIMEOUT;
else
{
/*
* VBox wants to read data in the given format.
*/
{
/* VBox thinks we have data and we don't */
rc = VERR_NO_DATA;
else
/* Send out a request for the data to the current clipboard
* owner */
}
else
}
if (RT_FAILURE(rc))
{
/* The clipboard callback was never scheduled, so we must signal
* that the request processing is finished and clean up ourselves. */
NULL, 0);
}
}
/**
* Called when VBox wants to read the X11 clipboard.
*
* @returns iprt status code
* @param pCtx Context data for the clipboard backend
* @param u32Format The format that the VBox would like to receive the data
* in
* @param pv Where to write the data to
* @param cb The size of the buffer to write the data to
* @param pcbActual Where to write the actual size of the written data
* @note We allocate a request structure which must be freed by the worker
*/
{
/*
* Immediately return if we are not connected to the X server.
*/
return VERR_NO_DATA;
int rc = VINF_SUCCESS;
if (!pX11Req)
rc = VERR_NO_MEMORY;
else
{
/* We use this to schedule a worker function on the event thread. */
}
return rc;
}
#ifdef TESTCASE
#include <iprt/initterm.h>
#include <poll.h>
#define TEST_NAME "tstClipboardX11"
/* Our X11 clipboard target poller */
/* User data for the poller function. */
/* For the testcase, we install the poller function in a global variable
* which is called when the testcase updates the X11 targets. */
{
g_pfnPoller = proc;
}
static bool clipPollTargets()
{
if (!g_pfnPoller)
return false;
return true;
}
/* For the purpose of the test case, we just execute the procedure to be
* scheduled, as we are running single threaded. */
{
}
/* The data in the simulated VBox clipboard */
static int g_vboxDataRC = VINF_SUCCESS;
static void *g_vboxDatapv = NULL;
static uint32_t g_vboxDatacb = 0;
/* Set empty data in the simulated VBox clipboard. */
{
g_vboxDatapv = NULL;
g_vboxDatacb = 0;
}
/* Set the data in the simulated VBox clipboard. */
{
if (RT_FAILURE(rc))
return rc;
return VERR_NO_MEMORY;
if (g_vboxDatapv)
g_vboxDatapv = pv;
g_vboxDatacb = cb;
return VINF_SUCCESS;
}
/* Return the data in the simulated VBox clipboard. */
{
*pcb = g_vboxDatacb;
if (g_vboxDatapv != NULL)
{
}
return g_vboxDataRC;
}
{ return (Display *) 0xffff; }
{
/* We don't fully reimplement this API for obvious reasons. */
/* We simplify the conversion by only accepting ASCII. */
for (unsigned i = 0; (*list)[i] != 0; ++i)
return 0;
}
{
}
const XTextProperty *text_prop,
char ***list_return, int *count_return)
{
int rc = 0;
{
*list_return = NULL;
*count_return = 0;
return 0;
}
/* Only accept simple ASCII properties */
char **ppList = (char **)RTMemAlloc(sizeof(char *));
if (pValue)
{
}
if (ppList)
{
}
else
{
/* NULL-terminate the string */
*count_return = 1;
*list_return = ppList;
}
return rc;
}
const XTextProperty *text_prop,
char ***list_return, int *count_return)
{
}
void XtDestroyWidget(Widget w) {}
void XtToolkitInitialize(void) {}
{ return (Display *)0xffff; }
{ return TEST_WIDGET; }
{ return 0xffff; }
/* Atoms we need other than the formats we support. */
static const char *g_apszSupAtoms[] =
{
"PRIMARY", "CLIPBOARD", "TARGETS", "MULTIPLE", "TIMESTAMP"
};
/* This just looks for the atom names in a couple of tables and returns an
* index with an offset added. */
{
/* What we support is: */
for (unsigned i = 0; i < RT_ELEMENTS(g_aFormats); ++i)
{
}
for (unsigned i = 0; i < RT_ELEMENTS(g_apszSupAtoms); ++i)
{
}
return rc;
}
/* The current values of the X selection, which will be returned to the
* XtGetSelectionValue callback. */
static Atom g_selTarget = 0;
static const void *g_pSelData = NULL;
static unsigned long g_cSelData = 0;
static int g_selFormat = 0;
static bool g_fTargetsTimeout = false;
static bool g_fTargetsFailure = false;
{
unsigned long count = 0;
int format = 0;
|| ( target != g_selTarget
{
/* Otherwise this is probably a caller error. */
/* Could not convert to target. */
return;
}
{
if (g_fTargetsFailure)
else
format = 32;
}
else
{
: NULL;
}
if (!pValue)
{
count = 0;
format = 0;
}
}
/* The formats currently on offer from X11 via the shared clipboard */
static uint32_t g_fX11Formats = 0;
{
}
static uint32_t clipQueryFormats()
{
return g_fX11Formats;
}
/* Does our clipboard code currently own the selection? */
static bool g_ownsSel = false;
/* The procedure that is called when we should convert the selection to a
* given format. */
/* The procedure which is called when we lose the selection. */
/* The procedure which is called when the selection transfer has completed. */
{
return True; /* We don't really care about this. */
g_ownsSel = true; /* Always succeed. */
g_pfnSelLose = lose;
g_pfnSelDone = done;
return True;
}
{
g_ownsSel = false;
g_pfnSelLose = NULL;
g_pfnSelDone = NULL;
}
/* Request the shared clipboard to convert its data to a given format. */
int *format)
{
if (target == 0)
return false;
/* Initialise all return values in case we make a quick exit. */
*length = 0;
*format = 0;
if (!g_ownsSel)
return false;
if (!g_pfnSelConvert)
return false;
return false;
if (g_pfnSelDone)
return true;
}
/* Set the current X selection data */
const void *data,
{
g_pSelData = data;
g_cSelData = count;
if (g_pfnSelLose)
g_ownsSel = false;
g_fTargetsTimeout = false;
g_fTargetsFailure = false;
}
/* Configure if and how the X11 TARGETS clipboard target will fail */
{
}
{
if (atom < 0x1000)
return NULL;
{
}
else
{
}
}
{
return 0;
}
void XFreeStringList(char **list)
{
if (list)
}
#define MAX_BUF_SIZE 256
static int g_completedRC = VINF_SUCCESS;
static int g_completedCB = 0;
static char g_completedBuf[MAX_BUF_SIZE];
{
if (cb <= MAX_BUF_SIZE)
{
g_completedRC = rc;
}
else
g_completedCB = cb;
}
{
*prc = g_completedRC;
*ppc = g_completedBuf;
*pcb = g_completedCB;
*ppReq = g_completedReq;
}
#ifdef RT_OS_SOLARIS_10
char XtStrings [] = "";
char XtShellStrings [] = "";
Display* /* display */,
XTextProperty* /* text_prop */,
char*** /* list_return */,
int* /* count_return */
)
{
return 0;
}
#else
const char XtStrings [] = "";
const char XtShellStrings [] = "";
#endif
int rcExp)
{
bool retval = false;
if (!clipPollTargets())
RTPrintf("Failed to poll for targets\n");
else if (clipQueryFormats() != VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
else
{
char *pc;
pReq);
int rc = VINF_SUCCESS;
rc);
RTPrintf("Wrong returned request data, expected %p, got %p\n",
else if (RT_FAILURE(rcExp))
retval = true;
else
{
if (RT_SUCCESS(rc))
{
{
RTPrintf("Returned string is the wrong size, string \"%.*ls\", size %u\n",
cbExp);
}
else
{
retval = true;
else
RTPrintf("Returned string \"%.*ls\" does not match expected string \"%s\"\n",
}
}
}
}
if (!retval)
return retval;
}
int rcExp)
{
bool retval = false;
if (!clipPollTargets())
RTPrintf("Failed to poll for targets\n");
else if (clipQueryFormats() != VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
else
{
char *pc;
pReq);
int rc = VINF_SUCCESS;
rc);
RTPrintf("Wrong returned request data, expected %p, got %p\n",
else if (RT_FAILURE(rcExp))
retval = true;
else
{
{
RTPrintf("Returned string is the wrong size, string \"%.*ls\", size %u\n",
cbExp);
}
else
{
retval = true;
else
RTPrintf("Returned string \"%.*ls\" does not match expected string \"%s\"\n",
}
}
}
if (!retval)
return retval;
}
int formatExp)
{
bool retval = false;
unsigned long length;
int format;
{
lenExp))
{
RTPrintf("Bad data: type %d, (expected %d), length %u, (%u), format %d (%d),\n",
}
else
retval = true;
}
else
RTPrintf("Conversion failed\n");
if (!retval)
return retval;
}
const char *pcszTarget)
{
bool retval = false;
unsigned long length;
int format;
retval = true;
if (!retval)
{
RTPrintf("Conversion to target %s, should have failed but didn't\n",
RTPrintf("Returned type %d, length %u, format %d, value \"%.*s\"\n",
}
return retval;
}
int main()
{
RTR3Init();
unsigned cErrs = 0;
char *pc;
/*** Utf-8 from X11 ***/
/* Simple test */
sizeof("hello world"), 8);
++cErrs;
/* With an embedded carriage return */
"hello\nworld", sizeof("hello\nworld"), 8);
++cErrs;
/* With an embedded CRLF */
"hello\r\nworld", sizeof("hello\r\nworld"), 8);
++cErrs;
/* With an embedded LFCR */
"hello\n\rworld", sizeof("hello\n\rworld"), 8);
++cErrs;
/* An empty string */
sizeof(""), 8);
++cErrs;
/* With an embedded Utf-8 character. */
"100\xE2\x82\xAC" /* 100 Euro */,
sizeof("100\xE2\x82\xAC"), 8);
++cErrs;
/* A non-zero-terminated string */
"hello world", sizeof("hello world") - 2, 8);
++cErrs;
/*** COMPOUND TEXT from X11 ***/
/* Simple test */
sizeof("hello world"), 8);
++cErrs;
/* With an embedded carriage return */
sizeof("hello\nworld"), 8);
++cErrs;
/* With an embedded CRLF */
sizeof("hello\r\nworld"), 8);
++cErrs;
/* With an embedded LFCR */
sizeof("hello\n\rworld"), 8);
++cErrs;
/* An empty string */
sizeof(""), 8);
++cErrs;
/* A non-zero-terminated string */
"hello world", sizeof("hello world") - 2, 8);
++cErrs;
/*** Latin1 from X11 ***/
/* Simple test */
sizeof("Georges Dupr\xEA"), 8);
++cErrs;
/* With an embedded carriage return */
sizeof("Georges\nDupr\xEA"), 8);
++cErrs;
/* With an embedded CRLF */
sizeof("Georges\r\nDupr\xEA"), 8);
++cErrs;
/* With an embedded LFCR */
sizeof("Georges\n\rDupr\xEA"), 8);
++cErrs;
/* A non-zero-terminated string */
"Georges Dupr\xEA!",
sizeof("Georges Dupr\xEA!") - 2, 8);
++cErrs;
/*** Timeout from X11 ***/
sizeof("hello world"), 8);
++cErrs;
/*** No data in X11 clipboard ***/
0, 8);
pReq);
if (rc != VERR_NO_DATA)
{
++cErrs;
}
{
RTPrintf("Wrong returned request data, expected %p, got %p\n",
++cErrs;
}
/*** request for an invalid VBox format from X11 ***/
if (rc != VERR_NOT_IMPLEMENTED)
{
++cErrs;
}
{
RTPrintf("Wrong returned request data, expected %p, got %p\n",
++cErrs;
}
/*** Targets timeout from X11 ***/
sizeof("hello world"), 8);
clipSetTargetsFailure(true, false);
if (!clipPollTargets())
{
RTPrintf("Failed to poll for targets\n");
++cErrs;
}
else if (clipQueryFormats() != 0)
{
++cErrs;
}
/*** Targets failure from X11 ***/
sizeof("hello world"), 8);
clipSetTargetsFailure(false, true);
if (!clipPollTargets())
{
RTPrintf("Failed to poll for targets\n");
++cErrs;
}
else if (clipQueryFormats() != 0)
{
++cErrs;
}
/*** Utf-8 from VBox ***/
/* Simple test */
sizeof("hello world") * 2);
"hello world", sizeof("hello world"), 8))
++cErrs;
/* With an embedded carriage return */
sizeof("hello\r\nworld") * 2);
"hello\nworld", sizeof("hello\nworld"), 8))
++cErrs;
/* With an embedded CRCRLF */
sizeof("hello\r\r\nworld") * 2);
"hello\r\nworld", sizeof("hello\r\nworld"), 8))
++cErrs;
/* With an embedded CRLFCR */
sizeof("hello\r\n\rworld") * 2);
"hello\n\rworld", sizeof("hello\n\rworld"), 8))
++cErrs;
/* An empty string */
"", sizeof(""), 8))
++cErrs;
/* With an embedded Utf-8 character. */
10);
"100\xE2\x82\xAC", sizeof("100\xE2\x82\xAC"), 8))
++cErrs;
/* A non-zero-terminated string */
sizeof("hello world") * 2 - 4);
"hello worl", sizeof("hello worl"), 8))
++cErrs;
/*** COMPOUND TEXT from VBox ***/
/* Simple test */
sizeof("hello world") * 2);
"hello world", sizeof("hello world"), 8))
++cErrs;
/* With an embedded carriage return */
sizeof("hello\r\nworld") * 2);
"hello\nworld", sizeof("hello\nworld"), 8))
++cErrs;
/* With an embedded CRCRLF */
sizeof("hello\r\r\nworld") * 2);
"hello\r\nworld", sizeof("hello\r\nworld"), 8))
++cErrs;
/* With an embedded CRLFCR */
sizeof("hello\r\n\rworld") * 2);
"hello\n\rworld", sizeof("hello\n\rworld"), 8))
++cErrs;
/* An empty string */
"", sizeof(""), 8))
++cErrs;
/* A non-zero-terminated string */
sizeof("hello world") * 2 - 4);
"hello worl", sizeof("hello worl"), 8))
++cErrs;
/*** Timeout from VBox ***/
++cErrs;
/*** No data in VBox clipboard ***/
++cErrs;
/*** An unknown VBox format ***/
++cErrs;
if (cErrs > 0)
return cErrs > 0 ? 1 : 0;
}
#endif
#ifdef SMOKETEST
/* This is a simple test case that just starts a copy of the X11 clipboard
* backend, checks the X11 clipboard and exits. If ever needed I will add an
* interactive mode in which the user can read and copy to the clipboard from
* the command line. */
#include <iprt/initterm.h>
#define TEST_NAME "tstClipboardX11Smoke"
{
return VERR_NO_DATA;
}
{}
{}
int main()
{
int rc = VINF_SUCCESS;
RTR3Init();
/* We can't test anything without an X session, so just return success
* in that case. */
if (!RTEnvGet("DISPLAY"))
{
return 0;
}
/* Give the clipboard time to synchronise. */
RTThreadSleep(500);
return 0;
}
#endif /* SMOKETEST defined */