x11-clipboard.cpp revision 053b00a39170eeb29e23145f5cfe42e096c735cf
/** @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.
*/
#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>
/** Do we want to test Utf8 by disabling other text formats? */
static bool g_testUtf8 = false;
/** Do we want to test compount text by disabling other text formats? */
static bool g_testCText = false;
/** Are we currently debugging the clipboard code? */
static bool g_debugClipboard = false;
/** 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[] =
{
};
/** Global context information used by the X11 clipboard backend */
struct _VBOXCLIPBOARDCONTEXTX11
{
/** Opaque data structure describing the front-end. */
/** 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;
/** What is the best text format X11 has to offer? INVALID for none. */
/** Atom corresponding to the X11 text format */
/** What is the best bitmap format X11 has to offer? INVALID for none.
*/
/** Atom corresponding to the X11 Bitmap format */
/** 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 { CLIPBOARD_NUM_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;
}
/* Are we actually connected to the X server? */
static bool g_fHaveX11;
{
int rc = VINF_SUCCESS;
/* Check how much longer will the converted text will be. */
{
/* Not enough buffer space provided - report the amount needed. */
LogFlowFunc (("guest buffer too small: size %d bytes, needed %d. Returning.\n",
}
/* Convert the text. */
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
}
return rc;
}
/**
* Convert the UTF-8 text obtained from the X11 clipboard to UTF-16LE with
* Windows EOLs, place it in the buffer supplied and signal that data has
* arrived.
*
* @param pValue Source UTF-8 text
* @param cbSourceLen Length in 8-bit bytes of the source text
* @param pv Where to store the converted data
* @param cb Length in bytes of the buffer pointed to by pv
* @param pcbActual Where to store the size of the converted data
* @param pClient Pointer to the client context structure
* @note X11 backend code, called from the Xt callback when we wish to read
* the X11 clipboard.
*/
{
char *pu8SrcText = reinterpret_cast<char *>(pValue);
LogFlowFunc (("converting Utf-8 to Utf-16LE. cbSrcLen=%d, cb=%d, pu8SrcText=%.*s\n",
*pcbActual = 0; /* Only set this to the right value on success. */
/* First convert the UTF8 to UTF16 */
if (RT_SUCCESS(rc))
return rc;
}
/**
* Convert the COMPOUND_TEXT obtained from the X11 clipboard to UTF-16LE with
* Windows EOLs, place it in the buffer supplied and signal that data has
* arrived.
*
* @param pValue Source COMPOUND_TEXT
* @param cbSourceLen Length in 8-bit bytes of the source text
* @param pv Where to store the converted data
* @param cb Length in bytes of the buffer pointed to by pv
* @param pcbActual Where to store the size of the converted data
* @param pClient Pointer to the client context structure
* @note X11 backend code, called from the Xt callback when we wish to read
* the X11 clipboard.
*/
{
char **ppu8SrcText = NULL;
int rc = VINF_SUCCESS;
int cProps;
LogFlowFunc (("converting COMPOUND TEXT to Utf-16LE. cbSrcLen=%d, cb=%d, pu8SrcText=%.*s\n",
*pcbActual = 0; /* Only set this to the right value on success. */
/** @todo quick fix for 2.2, do this properly. */
if (cbSrcLen == 0)
{
if (cb < 2)
return VERR_BUFFER_OVERFLOW;
*pcbActual = 2;
return VINF_SUCCESS;
}
/* First convert the compound text to Utf8 */
#ifdef RT_OS_SOLARIS
&ppu8SrcText, &cProps);
#else
#endif
if (xrc < 0)
switch(xrc)
{
case XNoMemory:
rc = VERR_NO_MEMORY;
break;
case XLocaleNotSupported:
case XConverterNotFound:
break;
default:
}
/* Now convert the UTF8 to UTF16 */
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
if (ppu8SrcText != NULL)
return rc;
}
/**
* Convert the Latin1 text obtained from the X11 clipboard to UTF-16LE with
* Windows EOLs, place it in the buffer supplied and signal that data has
* arrived.
*
* @param pValue Source Latin1 text
* @param cbSourceLen Length in 8-bit bytes of the source text
* @param pv Where to store the converted data
* @param cb Length in bytes of the buffer pointed to by cb
* @param pcbActual Where to store the size of the converted data
* @param pClient Pointer to the client context structure
* @note X11 backend code, called from the Xt callback when we wish to read
* the X11 clipboard.
*/
unsigned cbSourceLen, void *pv,
{
char *pu8SourceText = reinterpret_cast<char *>(pValue);
int rc = VINF_SUCCESS;
LogFlowFunc (("converting Latin1 to Utf-16LE. Original is %.*s\n",
*pcbActual = 0; /* Only set this to the right value on success. */
for (unsigned i = 0; i < cbSourceLen; i++)
if (pu8SourceText[i] == LINEFEED)
++cwDestLen;
{
/* Not enough buffer space provided - report the amount needed. */
}
if (RT_SUCCESS(rc))
{
for (unsigned i = 0, j = 0; i < cbSourceLen; ++i, ++j)
if (pu8SourceText[i] != LINEFEED)
else
{
pu16DestText[j] = CARRIAGERETURN;
++j;
pu16DestText[j] = LINEFEED;
}
}
return rc;
}
/**
* 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.
*/
Atom * /* selection */,
long unsigned int *pcLen,
int *piFormat)
{
= reinterpret_cast<VBOXCLIPBOARDREQUEST *>(pClientData);
if (pCtx->fOwnsClipboard == true)
{
/* We don't want to request data from ourselves! */
return;
}
LogFlowFunc(("pCtx->X11TextFormat=%d, pRequest->cb=%d\n",
/* The X Toolkit may have failed to get the clipboard selection for us. */
{
return;
}
/* The clipboard selection may have changed before we could get it. */
{
return;
}
/* In which format is the clipboard data? */
switch (pCtx->X11TextFormat)
{
case CTEXT:
break;
case UTF8:
{
/* If we are given broken Utf-8, we treat it as Latin1. Is this acceptable? */
char *pu8SourceText = reinterpret_cast<char *>(pValue);
{
break;
}
else
{
break;
}
}
default:
LogFunc (("bad target format\n"));
return;
}
pCtx->notifyVBox = true;
}
/**
* Notify the host clipboard about the data formats we support, based on the
* "targets" (available data formats) information obtained from the X11
* clipboard.
* @note X11 backend code, callback for XtGetSelectionValue, called when we
* poll for available targets.
*/
static void vboxClipboardGetTargetsFromX11(Widget,
Atom * /* selection */,
long unsigned int *pcLen,
int *piFormat)
{
reinterpret_cast<VBOXCLIPBOARDCONTEXTX11 *>(pClientData);
* clipboard */
)
{
return;
}
/* Debugging stuff */
if (g_testUtf8)
else if (g_testCText)
for (unsigned i = 0; i < cAtoms; ++i)
{
for (unsigned j = 0; j < RT_ELEMENTS(g_aFormats); ++j)
{
g_aFormats[j].pcszAtom);
if (atomTargets[i] == formatAtom)
{
/* debugging stuff */
&& ( enmRequiredTarget == INVALID
{
}
break;
}
}
if (g_debugClipboard)
{
atomTargets[i]);
if (szAtomName != 0)
{
szAtomName));
}
}
}
{
uint32_t u32Formats = 0;
if (g_debugClipboard)
{
if (atomBestTarget != None)
{
Log2 (("%s: switching to host text target %s. Available targets are:\n",
}
else
Log2(("%s: no supported host text target found. Available targets are:\n",
for (unsigned i = 0; i < cAtoms; ++i)
{
atomTargets[i]);
if (szAtomName != 0)
{
}
}
}
if (enmBestTarget != INVALID)
pCtx->notifyVBox = false;
}
}
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.
*/
XtIntervalId * /* hTimerId */)
{
reinterpret_cast<VBOXCLIPBOARDCONTEXTX11 *>(pUserData);
/* Get the current clipboard contents if we don't own it ourselves */
if (!pCtx->fOwnsClipboard)
{
Log3 (("%s: requesting the targets that the host clipboard offers\n",
}
/* Re-arm our timer */
}
#ifndef TESTCASE
/**
* The main loop of our clipboard reader.
* @note X11 backend code.
*/
{
LogRel(("Shared clipboard: starting host clipboard thread\n"));
reinterpret_cast<VBOXCLIPBOARDCONTEXTX11 *>(pvUser);
LogRel(("Shared clipboard: host 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. */
pCtx->fOwnsClipboard = false;
}
/** 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 host clipboard - the window system may not be running.\n"));
}
if (RT_SUCCESS(rc))
{
{
LogRel(("Shared clipboard: failed to construct the X11 window for the host clipboard manager.\n"));
rc = VERR_NO_MEMORY;
}
else
}
if (RT_SUCCESS(rc))
{
/* Set up a timer to poll the host 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(VBOXCLIPBOARDCONTEXTX11));
{
/*
* 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"));
g_fHaveX11 = false;
return pCtx;
}
if (RTEnvGet("VBOX_CBTEST_UTF8"))
{
g_testUtf8 = true;
LogRel(("Host clipboard: testing Utf8\n"));
}
else if (RTEnvGet("VBOX_CBTEST_CTEXT"))
{
g_testCText = true;
LogRel(("Host clipboard: testing compound text\n"));
}
else if (RTEnvGet("VBOX_CBDEBUG"))
{
g_debugClipboard = true;
LogRel(("Host clipboard: enabling additional debugging output\n"));
}
g_fHaveX11 = true;
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 host X server.
*/
if (!g_fHaveX11)
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 host X server.
*/
if (!g_fHaveX11)
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;
pCtx->notifyVBox = true;
}
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 host X server.
*/
if (!g_fHaveX11)
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 true if we successfully convert the data to the format
* requested, false otherwise.
*
* @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.
*/
*pCtx,
unsigned long *pcLenReturn,
int *piFormatReturn)
{
unsigned cTargets = 0;
LogFlowFunc (("called\n"));
for (unsigned i = 0; i < uListSize; ++i)
{
&& ( g_aFormats[i].u32VBoxFormat
{
g_aFormats[i].pcszAtom);
++cTargets;
}
}
if (g_debugClipboard)
{
for (unsigned i = 0; i < cTargets + 3; i++)
{
if (szAtomName != 0)
{
szAtomName));
}
else
{
atomTargets[i]));
}
}
}
*piFormatReturn = 32;
return true;
}
/** This is a wrapper around VBoxX11ClipboardReadVBoxData that will cache the
* data returned. This is unfortunately necessary, because if the other side
* of the shared clipboard is also an X11 system, it may send a format
* announcement message every time its clipboard is read, for reasons that
* are explained elsewhere. Unfortunately, some applications on our side
* like to read the clipboard several times in short succession in different
* formats. This can fail if it collides with a format announcement message.
* @todo any ideas about how to do this better are welcome.
*/
{
int rc = VINF_SUCCESS;
{
&pCtx->cbUnicodeCache);
if (RT_SUCCESS(rc))
{
rc = VERR_NO_MEMORY;
}
}
else
if (RT_SUCCESS(rc))
return rc;
}
/**
* Satisfy a request from X11 to convert the clipboard text to Utf8. We
* return non-zero terminated text.
* @todo that works, but it is bad. Change it to return zero-terminated
* text.
*
* @returns true if we successfully convert the data to the format
* requested, false otherwise.
*
* @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
* @note X11 backend code, called by the callback for XtOwnSelection.
*/
*pCtx,
unsigned long *pcLenReturn,
int *piFormatReturn)
{
char *pu8DestText;
int rc;
LogFlowFunc (("called\n"));
/* Read the clipboard data from the guest. */
{
/* If vboxClipboardReadVBoxData fails then we may be terminating */
return false;
}
/* How long will the converted text be? */
if (RT_FAILURE(rc))
{
LogRelFunc (("clipboard conversion failed. vboxClipboardUtf16GetLinSize returned %Rrc. Abandoning.\n", rc));
AssertRCReturn(rc, false);
}
if (cwDestLen == 0)
{
LogFlowFunc(("received empty clipboard data from the guest, returning false.\n"));
return false;
}
if (pu16DestText == 0)
{
return false;
}
/* Convert the text. */
if (RT_FAILURE(rc))
{
LogRelFunc (("clipboard conversion failed. vboxClipboardUtf16WinToLin() returned %Rrc. Abandoning.\n", rc));
RTMemFree(reinterpret_cast<void *>(pu16DestText));
return false;
}
/* Allocate enough space, as RTUtf16ToUtf8Ex may fail if the
space is too tightly calculated. */
if (pu8DestText == 0)
{
RTMemFree(reinterpret_cast<void *>(pu16DestText));
return false;
}
/* Convert the Utf16 string to Utf8. */
&cbDestLen);
RTMemFree(reinterpret_cast<void *>(pu16DestText));
if (RT_FAILURE(rc))
{
return false;
}
*piFormatReturn = 8;
return true;
}
/**
* Satisfy a request from X11 to convert the clipboard text to
* COMPOUND_TEXT. We return non-zero terminated text.
* @todo that works, but it is bad. Change it to return zero-terminated
* text.
*
* @returns true if we successfully convert the data to the format
* requested, false otherwise.
*
* @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
* @note X11 backend code, called by the callback for XtOwnSelection.
*/
*pCtx,
unsigned long *pcLenReturn,
int *piFormatReturn)
{
char *pu8DestText = 0;
int rc;
LogFlowFunc (("called\n"));
/* Read the clipboard data from the guest. */
{
/* If vboxClipboardReadVBoxData fails then we may be terminating */
return false;
}
/* How long will the converted text be? */
if (RT_FAILURE(rc))
{
LogRelFunc (("clipboard conversion failed. vboxClipboardUtf16GetLinSize returned %Rrc. Abandoning.\n", rc));
AssertRCReturn(rc, false);
}
if (cwDestLen == 0)
{
LogFlowFunc(("received empty clipboard data from the guest, returning false.\n"));
return false;
}
if (pu16DestText == 0)
{
return false;
}
/* Convert the text. */
if (RT_FAILURE(rc))
{
LogRelFunc (("clipboard conversion failed. vboxClipboardUtf16WinToLin() returned %Rrc. Abandoning.\n", rc));
RTMemFree(reinterpret_cast<void *>(pu16DestText));
return false;
}
/* Convert the Utf16 string to Utf8. */
RTMemFree(reinterpret_cast<void *>(pu16DestText));
if (RT_FAILURE(rc))
{
return false;
}
/* And finally (!) convert the Utf8 text to compound text. */
#ifdef RT_OS_SOLARIS
#else
#endif
if (rc < 0)
{
const char *pcReason;
switch(rc)
{
case XNoMemory:
pcReason = "out of memory";
break;
case XLocaleNotSupported:
pcReason = "locale (Utf8) not supported";
break;
case XConverterNotFound:
pcReason = "converter not found";
break;
default:
pcReason = "unknown error";
}
LogRelFunc (("Xutf8TextListToTextProperty failed. Reason: %s\n",
pcReason));
return false;
}
return true;
}
/**
* Return VBox's clipboard data for an X11 client.
* @note X11 backend code, callback for XtOwnSelection
*/
unsigned long *pcLenReturn,
int *piFormatReturn)
{
LogFlowFunc(("\n"));
/* Drop requests that we receive too late. */
if (!pCtx->fOwnsClipboard)
return false;
)
{
LogFlowFunc(("rc = false\n"));
return false;
}
if (g_debugClipboard)
{
if (szAtomName != 0)
{
}
else
{
}
}
{
}
else
{
for (unsigned i = 0; i < RT_ELEMENTS(g_aFormats); ++i)
{
g_aFormats[i].pcszAtom))
{
break;
}
}
}
switch (enmFormat)
{
case TARGETS:
case UTF8:
case CTEXT:
default:
LogFunc (("bad format\n"));
return false;
}
}
/**
* 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 (("called, giving X11 clipboard ownership\n"));
/* These should be set to the right values as soon as we start polling */
pCtx->fOwnsClipboard = false;
pCtx->notifyVBox = true;
}
#ifndef TESTCASE
{
}
#endif
/** Structure used to pass information about formats that VBox supports */
typedef struct _VBOXCLIPBOARDFORMATS
{
/** Context information for the X11 clipboard */
/** Formats supported by VBox */
/** Worker function for VBoxX11ClipboardAnnounceVBoxFormat which runs on the
* event thread. */
XtIntervalId * /* interval */)
{
/* Extract and free the user data */
/* Invalidate the clipboard cache */
{
}
if (u32Formats == 0)
{
/* This is just an automatism, not a genuine anouncement */
pCtx->fOwnsClipboard = false;
LogFlowFunc(("returning\n"));
return;
}
0) == True)
{
pCtx->fOwnsClipboard = true;
/* Grab the middle-button paste selection too. */
}
else
{
/* Another X11 client claimed the clipboard just after us, so let it
* go again. */
Log2 (("%s: returning clipboard ownership to the X11\n",
/* VBox thinks it currently owns the clipboard, so we must notify it
* as soon as we know what formats X11 has to offer. */
pCtx->notifyVBox = true;
pCtx->fOwnsClipboard = false;
}
LogFlowFunc(("returning\n"));
}
/**
* VBox is taking possession of the shared clipboard.
*
* @param u32Formats Clipboard formats the guest is offering
* @note X11 backend code
*/
{
/*
* Immediately return if we are not connected to the host X server.
*/
if (!g_fHaveX11)
return;
/* This must be freed by the worker callback */
{
}
}
/** Worker function for VBoxX11ClipboardReadX11Data which runs on the event
* thread. */
XtIntervalId * /* interval */)
{
int rc = VINF_SUCCESS;
/* Set this to start with, just in case */
/* Do not continue if we already own the clipboard */
if (pCtx->fOwnsClipboard == true)
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 ourselves. */
}
}
/**
* Called when VBox wants to read the X11 clipboard.
*
* @param pClient Context information about the guest VM
* @param u32Format The format that the guest 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 X11 backend code
*/
{
/* Initially set the size of the data read to zero in case we fail
* somewhere or X11 is not available. */
*pcbActual = 0;
/*
* Immediately return if we are not connected to the host X server.
*/
if (!g_fHaveX11)
return VINF_SUCCESS;
/* The worker function will signal this when it has finished. */
if (RT_SUCCESS(rc))
{
/* We use this to schedule a worker function on the event thread. */
if (RT_SUCCESS(rc))
}
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. */
const char *pcszData)
{
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 (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;
{
unsigned long count = 0;
int format = 0;
|| ( target != g_selTarget
{
/* Otherwise this is probably a caller error. */
/* Could not convert to target. */
return;
}
{
count = 1;
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_fX11Formats = 0;
}
{
if (atom < 0x1000)
return NULL;
{
}
else
{
}
}
{
return 0;
}
void XFreeStringList(char **list)
{
if (list)
}
const char XtStrings [] = "";
const char XtShellStrings [] = "";
#define MAX_BUF_SIZE 256
{
bool retval = false;
if (!clipPollTargets())
RTPrintf("Failed to poll for targets\n");
else if (clipQueryFormats() != VBOX_SHARED_CLIPBOARD_FMT_UNICODETEXT)
else
{
char pc[MAX_BUF_SIZE];
rc);
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)
RTPrintf("Expected: string \"%s\", rc %Rrc (buffer size %u)\n",
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[MAX_BUF_SIZE];
/***********/
sizeof("hello world"), 8);
++cErrs;
++cErrs;
++cErrs;
++cErrs;
sizeof("hello world\n"), 8);
"hello world\r\n", VINF_SUCCESS))
++cErrs;
sizeof("hello world\n"), 8);
"hello world\r\n", VINF_SUCCESS))
++cErrs;
sizeof(""), 8);
++cErrs;
/* This next one is Utf-8 only. */
"100\xE2\x82\xAC" /* 100 Euro */,
sizeof("100\xE2\x82\xAC"), 8);
"100\xE2\x82\xAC", VINF_SUCCESS))
++cErrs;
/***********/
sizeof("hello world"), 8);
++cErrs;
++cErrs;
++cErrs;
++cErrs;
sizeof("hello world\n"), 8);
"hello world\r\n", VINF_SUCCESS))
++cErrs;
sizeof("hello world\n"), 8);
"hello world\r\n", VINF_SUCCESS))
++cErrs;
sizeof(""), 8);
++cErrs;
/***********/
sizeof("hello world"), 8);
++cErrs;
/***********/
0, 8);
if (rc != VERR_NO_DATA)
{
++cErrs;
}
/***********/
if (rc != VERR_NOT_IMPLEMENTED)
{
++cErrs;
}
/***********/
"hello world", sizeof("hello world"), 8))
++cErrs;
"hello world\n", sizeof("hello world\n"), 8))
++cErrs;
"", sizeof(""), 8))
++cErrs;
/* This next one is Utf-8 only. */
"100\xE2\x82\xAC", sizeof("100\xE2\x82\xAC"), 8))
++cErrs;
/***********/
"hello world", sizeof("hello world"), 8))
++cErrs;
"hello world\n", sizeof("hello world\n"), 8))
++cErrs;
"", sizeof(""), 8))
++cErrs;
/***********/
++cErrs;
/***********/
++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 */