diff options
Diffstat (limited to 'src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp')
-rw-r--r-- | src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp | 2428 |
1 files changed, 2428 insertions, 0 deletions
diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp new file mode 100644 index 00000000..8e1bc162 --- /dev/null +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp @@ -0,0 +1,2428 @@ +/** @file + * Shared Clipboard: Common X11 code. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/* Note: to automatically run regression tests on the Shared Clipboard, + * execute the tstClipboardGH-X11 testcase. If you often make changes to the + * clipboard code, adding the line + * + * OTHERS += $(PATH_tstClipboardGH-X11)/tstClipboardGH-X11.run + * + * to LocalConfig.kmk will cause the tests to be run every time the code is + * changed. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD + +#include <errno.h> + +#include <dlfcn.h> +#include <fcntl.h> +#include <unistd.h> + +#ifdef RT_OS_SOLARIS +#include <tsol/label.h> +#endif + +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/Intrinsic.h> +#include <X11/Shell.h> +#include <X11/Xproto.h> +#include <X11/StringDefs.h> + +#include <iprt/assert.h> +#include <iprt/types.h> +#include <iprt/mem.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> +#include <iprt/utf16.h> +#include <iprt/uri.h> + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include <iprt/cpp/list.h> +# include <iprt/cpp/ministring.h> +# include <VBox/GuestHost/SharedClipboard-transfers.h> +#endif + +#include <VBox/log.h> +#include <VBox/version.h> + +#include <VBox/GuestHost/SharedClipboard.h> +#include <VBox/GuestHost/SharedClipboard-x11.h> +#include <VBox/GuestHost/clipboard-helper.h> +#include <VBox/HostServices/VBoxClipboardSvc.h> + +/** Own macro for declaring function visibility / linkage based on whether this + * code runs as part of test cases or not. */ +#ifdef TESTCASE +# define SHCL_X11_DECL(x) x +#else +# define SHCL_X11_DECL(x) static x +#endif + + +/********************************************************************************************************************************* +* Externals * +*********************************************************************************************************************************/ +#ifdef TESTCASE +extern void tstThreadScheduleCall(void (*proc)(void *, void *), void *client_data); +extern void tstClipRequestData(SHCLX11CTX* pCtx, SHCLX11FMTIDX target, void *closure); +extern void tstRequestTargets(SHCLX11CTX* pCtx); +#endif + + +/********************************************************************************************************************************* +* Prototypes * +*********************************************************************************************************************************/ +class formats; +SHCL_X11_DECL(Atom) clipGetAtom(PSHCLX11CTX pCtx, const char *pszName); +SHCL_X11_DECL(void) clipQueryX11Targets(PSHCLX11CTX pCtx); + +static int clipInitInternal(PSHCLX11CTX pCtx); +static void clipUninitInternal(PSHCLX11CTX pCtx); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** + * The table maps X11 names to data formats + * and to the corresponding VBox clipboard formats. + */ +SHCL_X11_DECL(SHCLX11FMTTABLE) g_aFormats[] = +{ + { "INVALID", SHCLX11FMT_INVALID, VBOX_SHCL_FMT_NONE }, + + { "UTF8_STRING", SHCLX11FMT_UTF8, VBOX_SHCL_FMT_UNICODETEXT }, + { "text/plain;charset=UTF-8", SHCLX11FMT_UTF8, VBOX_SHCL_FMT_UNICODETEXT }, + { "text/plain;charset=utf-8", SHCLX11FMT_UTF8, VBOX_SHCL_FMT_UNICODETEXT }, + { "STRING", SHCLX11FMT_TEXT, VBOX_SHCL_FMT_UNICODETEXT }, + { "TEXT", SHCLX11FMT_TEXT, VBOX_SHCL_FMT_UNICODETEXT }, + { "text/plain", SHCLX11FMT_TEXT, VBOX_SHCL_FMT_UNICODETEXT }, + + { "text/html", SHCLX11FMT_HTML, VBOX_SHCL_FMT_HTML }, + { "text/html;charset=utf-8", SHCLX11FMT_HTML, VBOX_SHCL_FMT_HTML }, + { "application/x-moz-nativehtml", SHCLX11FMT_HTML, VBOX_SHCL_FMT_HTML }, + + { "image/bmp", SHCLX11FMT_BMP, VBOX_SHCL_FMT_BITMAP }, + { "image/x-bmp", SHCLX11FMT_BMP, VBOX_SHCL_FMT_BITMAP }, + { "image/x-MS-bmp", SHCLX11FMT_BMP, VBOX_SHCL_FMT_BITMAP }, + /** @todo Inkscape exports image/png but not bmp... */ + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + { "text/uri-list", SHCLX11FMT_URI_LIST, VBOX_SHCL_FMT_URI_LIST }, + { "x-special/gnome-copied-files", SHCLX11FMT_URI_LIST, VBOX_SHCL_FMT_URI_LIST }, + { "x-special/nautilus-clipboard", SHCLX11FMT_URI_LIST, VBOX_SHCL_FMT_URI_LIST }, + { "application/x-kde-cutselection", SHCLX11FMT_URI_LIST, VBOX_SHCL_FMT_URI_LIST }, + /** @todo Anything else we need to add here? */ + /** @todo Add Wayland / Weston support. */ +#endif +}; + + +#ifdef TESTCASE +# ifdef RT_OS_SOLARIS_10 +char XtStrings [] = ""; +WidgetClassRec* applicationShellWidgetClass; +char XtShellStrings [] = ""; +int XmbTextPropertyToTextList( + Display* /* display */, + XTextProperty* /* text_prop */, + char*** /* list_return */, + int* /* count_return */ +) +{ + return 0; +} +# else +const char XtStrings [] = ""; +_WidgetClassRec* applicationShellWidgetClass; +const char XtShellStrings [] = ""; +# endif /* RT_OS_SOLARIS_10 */ +#endif /* TESTCASE */ + + +/********************************************************************************************************************************* +* Defines * +*********************************************************************************************************************************/ + +#define SHCL_MAX_X11_FORMATS RT_ELEMENTS(g_aFormats) + + +/********************************************************************************************************************************* +* Internal structures * +*********************************************************************************************************************************/ + +/** + * A structure containing information about where to store a request + * for the X11 clipboard contents. + */ +typedef struct _CLIPREADX11CBREQ +{ + /** The format VBox would like the data in. */ + SHCLFORMAT uFmtVBox; + /** The format we requested from X11. */ + SHCLX11FMTIDX idxFmtX11; + /** The clipboard context this request is associated with. */ + SHCLX11CTX *pCtx; + /** The request structure passed in from the backend. */ + CLIPREADCBREQ *pReq; +} CLIPREADX11CBREQ; + + + +#ifdef TESTCASE +/** + * Return the max. number of elements in the X11 format table. + * Used by the testing code in tstClipboardGH-X11.cpp + * which cannot use RT_ELEMENTS(g_aFormats) directly. + * + * @return size_t The number of elements in the g_aFormats array. + */ +SHCL_X11_DECL(size_t) clipReportMaxX11Formats(void) +{ + return (RT_ELEMENTS(g_aFormats)); +} +#endif + +/** + * Returns the atom corresponding to a supported X11 format. + * + * @returns Found atom to the corresponding X11 format. + * @param pCtx The X11 clipboard context to use. + * @param uFmtIdx Format index to look up atom for. + */ +static Atom clipAtomForX11Format(PSHCLX11CTX pCtx, SHCLX11FMTIDX uFmtIdx) +{ + AssertReturn(uFmtIdx < RT_ELEMENTS(g_aFormats), 0); + return clipGetAtom(pCtx, g_aFormats[uFmtIdx].pcszAtom); +} + +/** + * Returns the SHCLX11FMT corresponding to a supported X11 format. + * + * @return SHCLX11FMT for a specific format index. + * @param uFmtIdx Format index to look up SHCLX11CLIPFMT for. + */ +SHCL_X11_DECL(SHCLX11FMT) clipRealFormatForX11Format(SHCLX11FMTIDX uFmtIdx) +{ + AssertReturn(uFmtIdx < RT_ELEMENTS(g_aFormats), SHCLX11FMT_INVALID); + return g_aFormats[uFmtIdx].enmFmtX11; +} + +/** + * Returns the VBox format corresponding to a supported X11 format. + * + * @return SHCLFORMAT for a specific format index. + * @param uFmtIdx Format index to look up VBox format for. + */ +static SHCLFORMAT clipVBoxFormatForX11Format(SHCLX11FMTIDX uFmtIdx) +{ + AssertReturn(uFmtIdx < RT_ELEMENTS(g_aFormats), VBOX_SHCL_FMT_NONE); + return g_aFormats[uFmtIdx].uFmtVBox; +} + +/** + * Looks up the X11 format matching a given X11 atom. + * + * @returns The format on success, NIL_CLIPX11FORMAT on failure. + * @param pCtx The X11 clipboard context to use. + * @param atomFormat Atom to look up X11 format for. + */ +static SHCLX11FMTIDX clipFindX11FormatByAtom(PSHCLX11CTX pCtx, Atom atomFormat) +{ + for (unsigned i = 0; i < RT_ELEMENTS(g_aFormats); ++i) + if (clipAtomForX11Format(pCtx, i) == atomFormat) + { + LogFlowFunc(("Returning index %u for atom '%s'\n", i, g_aFormats[i].pcszAtom)); + return i; + } + return NIL_CLIPX11FORMAT; +} + +/** + * Enumerates supported X11 clipboard formats corresponding to given VBox formats. + * + * @returns The next matching X11 format index in the list, or NIL_CLIPX11FORMAT if there are no more. + * @param uFormatsVBox VBox formats to enumerate supported X11 clipboard formats for. + * @param lastFmtIdx The value returned from the last call of this function. + * Use NIL_CLIPX11FORMAT to start the enumeration. + */ +static SHCLX11FMTIDX clipEnumX11Formats(SHCLFORMATS uFormatsVBox, + SHCLX11FMTIDX lastFmtIdx) +{ + for (unsigned i = lastFmtIdx + 1; i < RT_ELEMENTS(g_aFormats); ++i) + { + if (uFormatsVBox & clipVBoxFormatForX11Format(i)) + return i; + } + + return NIL_CLIPX11FORMAT; +} + +/** + * 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 +{ + /** Pointer to widget we want to associate the context with. */ + Widget pWidget; + /** Pointer to X11 context associated with the widget. */ + PSHCLX11CTX pCtx; +} g_aContexts[VBOX_SHARED_CLIPBOARD_X11_CONNECTIONS_MAX]; + +/** + * Registers a new X11 clipboard context. + * + * @returns VBox status code. + * @param pCtx The X11 clipboard context to use. + */ +static int clipRegisterContext(PSHCLX11CTX pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_PARAMETER); + + bool fFound = false; + + Widget pWidget = pCtx->pWidget; + AssertReturn(pWidget != NULL, VERR_INVALID_PARAMETER); + + for (unsigned i = 0; i < RT_ELEMENTS(g_aContexts); ++i) + { + AssertReturn( (g_aContexts[i].pWidget != pWidget) + && (g_aContexts[i].pCtx != pCtx), VERR_WRONG_ORDER); + if (g_aContexts[i].pWidget == NULL && !fFound) + { + AssertReturn(g_aContexts[i].pCtx == NULL, VERR_INTERNAL_ERROR); + g_aContexts[i].pWidget = pWidget; + g_aContexts[i].pCtx = pCtx; + fFound = true; + } + } + + return fFound ? VINF_SUCCESS : VERR_OUT_OF_RESOURCES; +} + +/** + * Unregister an X11 clipboard context. + * + * @param pCtx The X11 clipboard context to use. + */ +static void clipUnregisterContext(PSHCLX11CTX pCtx) +{ + AssertPtrReturnVoid(pCtx); + + Widget pWidget = pCtx->pWidget; + AssertPtrReturnVoid(pWidget); + + bool fFound = false; + for (unsigned i = 0; i < RT_ELEMENTS(g_aContexts); ++i) + { + Assert(!fFound || g_aContexts[i].pWidget != pWidget); + if (g_aContexts[i].pWidget == pWidget) + { + Assert(g_aContexts[i].pCtx != NULL); + g_aContexts[i].pWidget = NULL; + g_aContexts[i].pCtx = NULL; + fFound = true; + } + } +} + +/** + * Finds a X11 clipboard context for a specific X11 widget. + * + * @returns Pointer to associated X11 clipboard context if found, or NULL if not found. + * @param pWidget X11 widget to return X11 clipboard context for. + */ +static PSHCLX11CTX clipLookupContext(Widget pWidget) +{ + AssertPtrReturn(pWidget, NULL); + + for (unsigned i = 0; i < RT_ELEMENTS(g_aContexts); ++i) + { + if (g_aContexts[i].pWidget == pWidget) + { + Assert(g_aContexts[i].pCtx != NULL); + return g_aContexts[i].pCtx; + } + } + + return NULL; +} + +/** + * Converts an atom name string to an X11 atom, looking it up in a cache before asking the server. + * + * @returns Found X11 atom. + * @param pCtx The X11 clipboard context to use. + * @param pcszName Name of atom to return atom for. + */ +SHCL_X11_DECL(Atom) clipGetAtom(PSHCLX11CTX pCtx, const char *pcszName) +{ + AssertPtrReturn(pcszName, None); + return XInternAtom(XtDisplay(pCtx->pWidget), pcszName, False); +} + +/** String written to the wakeup pipe. */ +#define WAKE_UP_STRING "WakeUp!" +/** Length of the string written. */ +#define WAKE_UP_STRING_LEN ( sizeof(WAKE_UP_STRING) - 1 ) + +/** + * Schedules a function call to run on the Xt event thread by passing it to + * the application context as a 0ms timeout and waking up the event loop by + * writing to the wakeup pipe which it monitors. + */ +static int clipThreadScheduleCall(PSHCLX11CTX pCtx, + void (*proc)(void *, void *), + void *client_data) +{ + LogFlowFunc(("proc=%p, client_data=%p\n", proc, client_data)); + +#ifndef TESTCASE + AssertReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->pAppContext, VERR_INVALID_POINTER); + + XtAppAddTimeOut(pCtx->pAppContext, 0, (XtTimerCallbackProc)proc, + (XtPointer)client_data); + ssize_t cbWritten = write(pCtx->wakeupPipeWrite, WAKE_UP_STRING, WAKE_UP_STRING_LEN); + Assert(cbWritten == WAKE_UP_STRING_LEN); + RT_NOREF(cbWritten); +#else + RT_NOREF(pCtx); + tstThreadScheduleCall(proc, client_data); +#endif + + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; +} + +/** + * Reports the formats currently supported by the X11 clipboard to VBox. + * + * @note Runs in Xt event thread. + * + * @param pCtx The X11 clipboard context to use. + */ +static void clipReportFormatsToVBox(PSHCLX11CTX pCtx) +{ + SHCLFORMATS vboxFmt = clipVBoxFormatForX11Format(pCtx->idxFmtText); + vboxFmt |= clipVBoxFormatForX11Format(pCtx->idxFmtBmp); + vboxFmt |= clipVBoxFormatForX11Format(pCtx->idxFmtHTML); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + vboxFmt |= clipVBoxFormatForX11Format(pCtx->idxFmtURI); +#endif + + LogFlowFunc(("idxFmtText=%u ('%s'), idxFmtBmp=%u ('%s'), idxFmtHTML=%u ('%s')", + pCtx->idxFmtText, g_aFormats[pCtx->idxFmtText].pcszAtom, + pCtx->idxFmtBmp, g_aFormats[pCtx->idxFmtBmp].pcszAtom, + pCtx->idxFmtHTML, g_aFormats[pCtx->idxFmtHTML].pcszAtom)); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + LogFlowFunc((", idxFmtURI=%u ('%s')", pCtx->idxFmtURI, g_aFormats[pCtx->idxFmtURI].pcszAtom)); +#endif + LogFlow((" -> vboxFmt=%#x\n", vboxFmt)); + +#ifdef LOG_ENABLED + char *pszFmts = ShClFormatsToStrA(vboxFmt); + AssertPtrReturnVoid(pszFmts); + LogRel2(("Shared Clipboard: X11 reported available VBox formats '%s'\n", pszFmts)); + RTStrFree(pszFmts); +#endif + + pCtx->Callbacks.pfnReportFormats(pCtx->pFrontend, vboxFmt, NULL /* pvUser */); +} + +/** + * Forgets which formats were previously in the X11 clipboard. Called when we + * grab the clipboard. + * + * @param pCtx The X11 clipboard context to use. + */ +static void clipResetX11Formats(PSHCLX11CTX pCtx) +{ + LogFlowFuncEnter(); + + pCtx->idxFmtText = 0; + pCtx->idxFmtBmp = 0; + pCtx->idxFmtHTML = 0; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + pCtx->idxFmtURI = 0; +#endif +} + +/** + * Tells VBox that X11 currently has nothing in its clipboard. + * + * @param pCtx The X11 clipboard context to use. + */ +SHCL_X11_DECL(void) clipReportEmpty(PSHCLX11CTX pCtx) +{ + clipResetX11Formats(pCtx); + clipReportFormatsToVBox(pCtx); +} + +/** + * Go through an array of X11 clipboard targets to see if they contain a text + * format we can support, and if so choose the ones we prefer (e.g. we like + * UTF-8 better than plain text). + * + * @return Index to supported X clipboard format. + * @param pCtx The X11 clipboard context to use. + * @param paIdxFmtTargets The list of targets. + * @param cTargets The size of the list in @a pTargets. + */ +SHCL_X11_DECL(SHCLX11FMTIDX) clipGetTextFormatFromTargets(PSHCLX11CTX pCtx, + SHCLX11FMTIDX *paIdxFmtTargets, + size_t cTargets) +{ + AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT); + AssertReturn(RT_VALID_PTR(paIdxFmtTargets) || cTargets == 0, NIL_CLIPX11FORMAT); + + SHCLX11FMTIDX idxFmtText = NIL_CLIPX11FORMAT; + SHCLX11FMT fmtTextX11 = SHCLX11FMT_INVALID; + for (unsigned i = 0; i < cTargets; ++i) + { + SHCLX11FMTIDX idxFmt = paIdxFmtTargets[i]; + if (idxFmt != NIL_CLIPX11FORMAT) + { + if ( (clipVBoxFormatForX11Format(idxFmt) == VBOX_SHCL_FMT_UNICODETEXT) + && fmtTextX11 < clipRealFormatForX11Format(idxFmt)) + { + fmtTextX11 = clipRealFormatForX11Format(idxFmt); + idxFmtText = idxFmt; + } + } + } + return idxFmtText; +} + +/** + * Goes through an array of X11 clipboard targets to see if they contain a bitmap + * format we can support, and if so choose the ones we prefer (e.g. we like + * BMP better than PNG because we don't have to convert). + * + * @return Supported X clipboard format. + * @param pCtx The X11 clipboard context to use. + * @param paIdxFmtTargets The list of targets. + * @param cTargets The size of the list in @a pTargets. + */ +static SHCLX11FMTIDX clipGetBitmapFormatFromTargets(PSHCLX11CTX pCtx, + SHCLX11FMTIDX *paIdxFmtTargets, + size_t cTargets) +{ + AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT); + AssertReturn(RT_VALID_PTR(paIdxFmtTargets) || cTargets == 0, NIL_CLIPX11FORMAT); + + SHCLX11FMTIDX idxFmtBmp = NIL_CLIPX11FORMAT; + SHCLX11FMT fmtBmpX11 = SHCLX11FMT_INVALID; + for (unsigned i = 0; i < cTargets; ++i) + { + SHCLX11FMTIDX idxFmt = paIdxFmtTargets[i]; + if (idxFmt != NIL_CLIPX11FORMAT) + { + if ( (clipVBoxFormatForX11Format(idxFmt) == VBOX_SHCL_FMT_BITMAP) + && fmtBmpX11 < clipRealFormatForX11Format(idxFmt)) + { + fmtBmpX11 = clipRealFormatForX11Format(idxFmt); + idxFmtBmp = idxFmt; + } + } + } + return idxFmtBmp; +} + +/** + * Goes through an array of X11 clipboard targets to see if they contain a HTML + * format we can support, and if so choose the ones we prefer. + * + * @return Supported X clipboard format. + * @param pCtx The X11 clipboard context to use. + * @param paIdxFmtTargets The list of targets. + * @param cTargets The size of the list in @a pTargets. + */ +static SHCLX11FMTIDX clipGetHtmlFormatFromTargets(PSHCLX11CTX pCtx, + SHCLX11FMTIDX *paIdxFmtTargets, + size_t cTargets) +{ + AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT); + AssertReturn(RT_VALID_PTR(paIdxFmtTargets) || cTargets == 0, NIL_CLIPX11FORMAT); + + SHCLX11FMTIDX idxFmtHTML = NIL_CLIPX11FORMAT; + SHCLX11FMT fmxHTMLX11 = SHCLX11FMT_INVALID; + for (unsigned i = 0; i < cTargets; ++i) + { + SHCLX11FMTIDX idxFmt = paIdxFmtTargets[i]; + if (idxFmt != NIL_CLIPX11FORMAT) + { + if ( (clipVBoxFormatForX11Format(idxFmt) == VBOX_SHCL_FMT_HTML) + && fmxHTMLX11 < clipRealFormatForX11Format(idxFmt)) + { + fmxHTMLX11 = clipRealFormatForX11Format(idxFmt); + idxFmtHTML = idxFmt; + } + } + } + return idxFmtHTML; +} + +# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Goes through an array of X11 clipboard targets to see if they contain an URI list + * format we can support, and if so choose the ones we prefer. + * + * @return Supported X clipboard format. + * @param pCtx The X11 clipboard context to use. + * @param paIdxFmtTargets The list of targets. + * @param cTargets The size of the list in @a pTargets. + */ +static SHCLX11FMTIDX clipGetURIListFormatFromTargets(PSHCLX11CTX pCtx, + SHCLX11FMTIDX *paIdxFmtTargets, + size_t cTargets) +{ + AssertPtrReturn(pCtx, NIL_CLIPX11FORMAT); + AssertReturn(RT_VALID_PTR(paIdxFmtTargets) || cTargets == 0, NIL_CLIPX11FORMAT); + + SHCLX11FMTIDX idxFmtURI = NIL_CLIPX11FORMAT; + SHCLX11FMT fmtURIX11 = SHCLX11FMT_INVALID; + for (unsigned i = 0; i < cTargets; ++i) + { + SHCLX11FMTIDX idxFmt = paIdxFmtTargets[i]; + if (idxFmt != NIL_CLIPX11FORMAT) + { + if ( (clipVBoxFormatForX11Format(idxFmt) == VBOX_SHCL_FMT_URI_LIST) + && fmtURIX11 < clipRealFormatForX11Format(idxFmt)) + { + fmtURIX11 = clipRealFormatForX11Format(idxFmt); + idxFmtURI = idxFmt; + } + } + } + return idxFmtURI; +} +# endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + +/** + * Goes 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 plain text). + * + * @param pCtx The X11 clipboard context to use. + * @param paIdxFmtTargets The list of targets. + * @param cTargets The size of the list in @a pTargets. + */ +static void clipGetFormatsFromTargets(PSHCLX11CTX pCtx, + SHCLX11FMTIDX *paIdxFmtTargets, size_t cTargets) +{ + AssertPtrReturnVoid(pCtx); + AssertPtrReturnVoid(paIdxFmtTargets); + + SHCLX11FMTIDX idxFmtText = clipGetTextFormatFromTargets(pCtx, paIdxFmtTargets, cTargets); + if (pCtx->idxFmtText != idxFmtText) + pCtx->idxFmtText = idxFmtText; + + pCtx->idxFmtBmp = SHCLX11FMT_INVALID; /* not yet supported */ /** @todo r=andy Check this. */ + SHCLX11FMTIDX idxFmtBmp = clipGetBitmapFormatFromTargets(pCtx, paIdxFmtTargets, cTargets); + if (pCtx->idxFmtBmp != idxFmtBmp) + pCtx->idxFmtBmp = idxFmtBmp; + + SHCLX11FMTIDX idxFmtHTML = clipGetHtmlFormatFromTargets(pCtx, paIdxFmtTargets, cTargets); + if (pCtx->idxFmtHTML != idxFmtHTML) + pCtx->idxFmtHTML = idxFmtHTML; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + SHCLX11FMTIDX idxFmtURI = clipGetURIListFormatFromTargets(pCtx, paIdxFmtTargets, cTargets); + if (pCtx->idxFmtURI != idxFmtURI) + pCtx->idxFmtURI = idxFmtURI; +#endif +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY +DECLINLINE(bool) clipGetXtBusy(PSHCLX11CTX pCtx) +{ + LogFlowFunc(("fXtBusy=%RTbool, fXtNeedsUpdate=%RTbool\n", pCtx->fXtBusy, pCtx->fXtNeedsUpdate)); + return pCtx->fXtBusy; +} + +DECLINLINE(bool) clipGetXtNeedsUpdate(PSHCLX11CTX pCtx) +{ + LogFlowFunc(("fXtBusy=%RTbool, fXtNeedsUpdate=%RTbool\n", pCtx->fXtBusy, pCtx->fXtNeedsUpdate)); + return pCtx->fXtNeedsUpdate; +} + +DECLINLINE(bool) clipSetXtBusy(PSHCLX11CTX pCtx, bool fBusy) +{ + pCtx->fXtBusy = fBusy; + LogFlowFunc(("fXtBusy=%RTbool, fXtNeedsUpdate=%RTbool\n", pCtx->fXtBusy, pCtx->fXtNeedsUpdate)); + return pCtx->fXtBusy; +} + +DECLINLINE(bool) clipSetXtNeedsUpdate(PSHCLX11CTX pCtx, bool fNeedsUpdate) +{ + pCtx->fXtNeedsUpdate = fNeedsUpdate; + LogFlowFunc(("fXtBusy=%RTbool, fXtNeedsUpdate=%RTbool\n", pCtx->fXtBusy, pCtx->fXtNeedsUpdate)); + return pCtx->fXtNeedsUpdate; +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY */ + +/** + * Updates the context's information about targets currently supported by X11, + * based on an array of X11 atoms. + * + * @param pCtx The X11 clipboard context to use. + * @param pTargets The array of atoms describing the targets supported. + * @param cTargets The size of the array @a pTargets. + */ +SHCL_X11_DECL(void) clipUpdateX11Targets(PSHCLX11CTX pCtx, SHCLX11FMTIDX *paIdxFmtTargets, size_t cTargets) +{ + LogFlowFuncEnter(); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY + clipSetXtBusy(pCtx, false); + if (clipGetXtNeedsUpdate(pCtx)) + { + /* We may already be out of date. */ + clipSetXtNeedsUpdate(pCtx, false); + clipQueryX11Targets(pCtx); + return; + } +#endif + + if (paIdxFmtTargets == NULL) + { + /* No data available */ + clipReportEmpty(pCtx); + return; + } + + clipGetFormatsFromTargets(pCtx, paIdxFmtTargets, cTargets); + clipReportFormatsToVBox(pCtx); +} + +/** + * Notifies the VBox clipboard about available data formats ("targets" on X11), + * based on the information obtained from the X11 clipboard. + * + * @note Callback installed by clipQueryX11Targets() for XtGetSelectionValue(). + * @note This function is treated as API glue, and as such is not part of any + * unit test. So keep it simple, be paranoid and log everything. + */ +SHCL_X11_DECL(void) clipQueryX11TargetsCallback(Widget widget, XtPointer pClient, + Atom * /* selection */, Atom *atomType, + XtPointer pValue, long unsigned int *pcLen, + int *piFormat) +{ + RT_NOREF(piFormat); + + PSHCLX11CTX pCtx = reinterpret_cast<SHCLX11CTX *>(pClient); + + LogFlowFunc(("pValue=%p, *pcLen=%u, *atomType=%d%s\n", + pValue, *pcLen, *atomType, *atomType == XT_CONVERT_FAIL ? " (XT_CONVERT_FAIL)" : "")); + + Atom *pAtoms = (Atom *)pValue; + + unsigned cFormats = *pcLen; + + LogRel2(("Shared Clipboard: Querying X11 formats ...\n")); + LogRel2(("Shared Clipboard: %u X11 formats were found\n", cFormats)); + + SHCLX11FMTIDX *paIdxFmt = NULL; + if ( cFormats + && pValue + && (*atomType != XT_CONVERT_FAIL /* time out */)) + { + /* Allocated array to hold the format indices. */ + paIdxFmt = (SHCLX11FMTIDX *)RTMemAllocZ(cFormats * sizeof(SHCLX11FMTIDX)); + } + +#if !defined(TESTCASE) + if (pValue) + { + for (unsigned i = 0; i < cFormats; ++i) + { + if (pAtoms[i]) + { + char *pszName = XGetAtomName(XtDisplay(widget), pAtoms[i]); + LogRel2(("Shared Clipboard: Found X11 format '%s'\n", pszName)); + XFree(pszName); + } + else + LogFunc(("Found empty target\n")); + } + } +#endif + + if (paIdxFmt) + { + for (unsigned i = 0; i < cFormats; ++i) + { + for (unsigned j = 0; j < RT_ELEMENTS(g_aFormats); ++j) + { + Atom target = XInternAtom(XtDisplay(widget), + g_aFormats[j].pcszAtom, False); + if (*(pAtoms + i) == target) + paIdxFmt[i] = j; + } +#if !defined(TESTCASE) + if (paIdxFmt[i] != SHCLX11FMT_INVALID) + LogRel2(("Shared Clipboard: Reporting X11 format '%s'\n", g_aFormats[paIdxFmt[i]].pcszAtom)); +#endif + } + } + else + LogFunc(("Reporting empty targets (none reported or allocation failure)\n")); + + clipUpdateX11Targets(pCtx, paIdxFmt, cFormats); + RTMemFree(paIdxFmt); + + XtFree(reinterpret_cast<char *>(pValue)); +} + +/** + * Queries the current formats ("targets") of the X11 clipboard ("CLIPBOARD"). + * + * @param pCtx The X11 clipboard context to use. + */ +SHCL_X11_DECL(void) clipQueryX11Targets(PSHCLX11CTX pCtx) +{ +#ifndef TESTCASE + +# ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY + if (clipGetXtBusy(pCtx)) + { + clipSetXtNeedsUpdate(pCtx, true); + return; + } + clipSetXtBusy(pCtx, true); +# endif + + XtGetSelectionValue(pCtx->pWidget, + clipGetAtom(pCtx, "CLIPBOARD"), + clipGetAtom(pCtx, "TARGETS"), + clipQueryX11TargetsCallback, pCtx, + CurrentTime); +#else + tstRequestTargets(pCtx); +#endif +} + +typedef struct +{ + int type; /* event base */ + unsigned long serial; + Bool send_event; + Display *display; + Window window; + int subtype; + Window owner; + Atom selection; + Time timestamp; + Time selection_timestamp; +} XFixesSelectionNotifyEvent; + +#ifndef TESTCASE +/** + * Waits until an event arrives and handle it if it is an XFIXES selection + * event, which Xt doesn't know about. + * + * @param pCtx The X11 clipboard context to use. + */ +static void clipPeekEventAndDoXFixesHandling(PSHCLX11CTX pCtx) +{ + union + { + XEvent event; + XFixesSelectionNotifyEvent fixes; + } event = { { 0 } }; + + if (XtAppPeekEvent(pCtx->pAppContext, &event.event)) + { + if ( (event.event.type == pCtx->fixesEventBase) + && (event.fixes.owner != XtWindow(pCtx->pWidget))) + { + if ( (event.fixes.subtype == 0 /* XFixesSetSelectionOwnerNotify */) + && (event.fixes.owner != 0)) + clipQueryX11Targets(pCtx); + else + clipReportEmpty(pCtx); + } + } +} + +/** + * The main loop of our X11 event thread. + * + * @returns VBox status code. + * @param hThreadSelf Associated thread handle. + * @param pvUser Pointer to the X11 clipboard context to use. + */ +static DECLCALLBACK(int) clipThreadMain(RTTHREAD hThreadSelf, void *pvUser) +{ + PSHCLX11CTX pCtx = (PSHCLX11CTX)pvUser; + AssertPtr(pCtx); + + LogFlowFunc(("pCtx=%p\n", pCtx)); + + bool fSignalled = false; /* Whether we have signalled the parent already or not. */ + + int rc = clipInitInternal(pCtx); + if (RT_SUCCESS(rc)) + { + rc = clipRegisterContext(pCtx); + if (RT_SUCCESS(rc)) + { + if (pCtx->fGrabClipboardOnStart) + clipQueryX11Targets(pCtx); + + pCtx->fThreadStarted = true; + + /* We're now ready to run, tell parent. */ + int rc2 = RTThreadUserSignal(hThreadSelf); + AssertRC(rc2); + + fSignalled = true; + + while (XtAppGetExitFlag(pCtx->pAppContext) == FALSE) + { + clipPeekEventAndDoXFixesHandling(pCtx); + XtAppProcessEvent(pCtx->pAppContext, XtIMAll); + } + + LogRel(("Shared Clipboard: X11 event thread exiting\n")); + + clipUnregisterContext(pCtx); + } + else + { + LogRel(("Shared Clipboard: unable to register clip context: %Rrc\n", rc)); + } + + clipUninitInternal(pCtx); + } + + if (!fSignalled) /* Signal parent if we didn't do so yet. */ + { + int rc2 = RTThreadUserSignal(hThreadSelf); + AssertRC(rc2); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Worker function for stopping the clipboard which runs on the event + * thread. + * + * @param pvUserData Pointer to the X11 clipboard context to use. + */ +static void clipThreadSignalStop(void *pvUserData, void *) +{ + PSHCLX11CTX pCtx = (PSHCLX11CTX)pvUserData; + + /* This might mean that we are getting stopped twice. */ + Assert(pCtx->pWidget != NULL); + + /* 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. */ + XtAppSetExitFlag(pCtx->pAppContext); +} + +/** + * Sets up the XFixes library and load the XFixesSelectSelectionInput symbol. + */ +static int clipLoadXFixes(Display *pDisplay, PSHCLX11CTX pCtx) +{ + int rc; + + void *hFixesLib = dlopen("libXfixes.so.1", RTLD_LAZY); + if (!hFixesLib) + hFixesLib = dlopen("libXfixes.so.2", RTLD_LAZY); + if (!hFixesLib) + hFixesLib = dlopen("libXfixes.so.3", RTLD_LAZY); + if (!hFixesLib) + hFixesLib = dlopen("libXfixes.so.4", RTLD_LAZY); + if (hFixesLib) + { + /* For us, a NULL function pointer is a failure */ + pCtx->fixesSelectInput = (void (*)(Display *, Window, Atom, long unsigned int)) + (uintptr_t)dlsym(hFixesLib, "XFixesSelectSelectionInput"); + if (pCtx->fixesSelectInput) + { + int dummy1 = 0; + int dummy2 = 0; + if (XQueryExtension(pDisplay, "XFIXES", &dummy1, &pCtx->fixesEventBase, &dummy2) != 0) + { + if (pCtx->fixesEventBase >= 0) + { + rc = VINF_SUCCESS; + } + else + { + LogRel(("Shared Clipboard: fixesEventBase is less than zero: %d\n", pCtx->fixesEventBase)); + rc = VERR_NOT_SUPPORTED; + } + } + else + { + LogRel(("Shared Clipboard: XQueryExtension failed\n")); + rc = VERR_NOT_SUPPORTED; + } + } + else + { + LogRel(("Shared Clipboard: Symbol XFixesSelectSelectionInput not found!\n")); + rc = VERR_NOT_SUPPORTED; + } + } + else + { + LogRel(("Shared Clipboard: libxFixes.so.* not found!\n")); + rc = VERR_NOT_SUPPORTED; + } + return rc; +} + +/** + * This is the callback which is scheduled when data is available on the + * wakeup pipe. It simply reads all data from the pipe. + * + * @param pvUserData Pointer to the X11 clipboard context to use. + */ +static void clipThreadDrainWakeupPipe(XtPointer pvUserData, int *, XtInputId *) +{ + LogFlowFuncEnter(); + + PSHCLX11CTX pCtx = (PSHCLX11CTX)pvUserData; + char acBuf[WAKE_UP_STRING_LEN]; + + while (read(pCtx->wakeupPipeRead, acBuf, sizeof(acBuf)) > 0) {} +} +#endif /* !TESTCASE */ + +/** + * X11-specific initialisation for the Shared Clipboard. + * + * Note: Must be called from the thread serving the Xt stuff. + * + * @returns VBox status code. + * @param pCtx The X11 clipboard context to init. + */ +static int clipInitInternal(PSHCLX11CTX pCtx) +{ + LogFlowFunc(("pCtx=%p\n", pCtx)); + + /* Make sure we are thread safe. */ + XtToolkitThreadInitialize(); + + /* + * Set up the Clipboard 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. + */ + XtToolkitInitialize(); + + int rc = VINF_SUCCESS; + + Assert(pCtx->pAppContext == NULL); /* No nested initialization. */ + pCtx->pAppContext = XtCreateApplicationContext(); + if (pCtx->pAppContext == NULL) + { + LogRel(("Shared Clipboard: Failed to create Xt application context\n")); + return VERR_NOT_SUPPORTED; /** @todo Fudge! */ + } + + /* Create a window and make it a clipboard viewer. */ + int cArgc = 0; + char *pcArgv = 0; + Display *pDisplay = XtOpenDisplay(pCtx->pAppContext, 0, 0, "VBoxShCl", 0, 0, &cArgc, &pcArgv); + if (pDisplay == NULL) + { + LogRel(("Shared Clipboard: Failed to connect to the X11 clipboard - the window system may not be running\n")); + rc = VERR_NOT_SUPPORTED; + } + +#ifndef TESTCASE + if (RT_SUCCESS(rc)) + { + rc = clipLoadXFixes(pDisplay, pCtx); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Failed to load the XFIXES extension\n")); + } +#endif + + if (RT_SUCCESS(rc)) + { + pCtx->pWidget = XtVaAppCreateShell(0, "VBoxShCl", + applicationShellWidgetClass, + pDisplay, XtNwidth, 1, XtNheight, + 1, NULL); + if (pCtx->pWidget == NULL) + { + LogRel(("Shared Clipboard: Failed to create Xt app shell\n")); + rc = VERR_NO_MEMORY; /** @todo r=andy Improve this. */ + } + else + { +#ifndef TESTCASE + if (!XtAppAddInput(pCtx->pAppContext, pCtx->wakeupPipeRead, + (XtPointer) XtInputReadMask, + clipThreadDrainWakeupPipe, (XtPointer) pCtx)) + { + LogRel(("Shared Clipboard: Failed to add input to Xt app context\n")); + rc = VERR_ACCESS_DENIED; /** @todo r=andy Improve this. */ + } +#endif + } + } + + if (RT_SUCCESS(rc)) + { + XtSetMappedWhenManaged(pCtx->pWidget, false); + XtRealizeWidget(pCtx->pWidget); + +#ifndef TESTCASE + /* Enable clipboard update notification. */ + pCtx->fixesSelectInput(pDisplay, XtWindow(pCtx->pWidget), + clipGetAtom(pCtx, "CLIPBOARD"), + 7 /* All XFixes*Selection*NotifyMask flags */); +#endif + } + + if (RT_FAILURE(rc)) + { + LogRel(("Shared Clipboard: Initialisation failed: %Rrc\n", rc)); + clipUninitInternal(pCtx); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * X11-specific uninitialisation for the Shared Clipboard. + * + * Note: Must be called from the thread serving the Xt stuff. + * + * @param pCtx The X11 clipboard context to uninit. + */ +static void clipUninitInternal(PSHCLX11CTX pCtx) +{ + AssertPtrReturnVoid(pCtx); + + LogFlowFunc(("pCtx=%p\n", pCtx)); + + if (pCtx->pWidget) + { + /* Valid widget + invalid appcontext = bug. But don't return yet. */ + AssertPtr(pCtx->pAppContext); + + XtDestroyWidget(pCtx->pWidget); + pCtx->pWidget = NULL; + } + + if (pCtx->pAppContext) + { + XtDestroyApplicationContext(pCtx->pAppContext); + pCtx->pAppContext = NULL; + } + + LogFlowFuncLeaveRC(VINF_SUCCESS); +} + +/** + * Sets the callback table, internal version. + * + * @param pCtx The clipboard context. + * @param pCallbacks Callback table to set. If NULL, the current callback table will be cleared. + */ +static void shClX11SetCallbacksInternal(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks) +{ + if (pCallbacks) + { + memcpy(&pCtx->Callbacks, pCallbacks, sizeof(SHCLCALLBACKS)); + } + else + RT_ZERO(pCtx->Callbacks); +} + +/** + * Sets the callback table. + * + * @param pCtx The clipboard context. + * @param pCallbacks Callback table to set. If NULL, the current callback table will be cleared. + */ +void ShClX11SetCallbacks(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks) +{ + shClX11SetCallbacksInternal(pCtx, pCallbacks); +} + +/** + * Initializes a X11 context of the Shared Clipboard. + * + * @returns VBox status code. + * @param pCtx The clipboard context to initialize. + * @param pCallbacks Callback table to use. + * @param pParent Parent context to use. + * @param fHeadless Whether the code runs in a headless environment or not. + */ +int ShClX11Init(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks, PSHCLCONTEXT pParent, bool fHeadless) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + LogFlowFunc(("pCtx=%p\n", pCtx)); + + int rc = VINF_SUCCESS; + + RT_BZERO(pCtx, sizeof(SHCLX11CTX)); + + if (fHeadless) + { + /* + * 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. + */ + LogRel(("Shared Clipboard: X11 DISPLAY variable not set -- disabling clipboard sharing\n")); + } + + /* Install given callbacks. */ + shClX11SetCallbacksInternal(pCtx, pCallbacks); + + pCtx->fHaveX11 = !fHeadless; + pCtx->pFrontend = pParent; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY + pCtx->fXtBusy = false; + pCtx->fXtNeedsUpdate = false; +#endif + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + ShClTransferHttpServerInit(&pCtx->HttpCtx.HttpServer); +#endif + +#ifdef TESTCASE + if (RT_SUCCESS(rc)) + { + /** @todo The testcases currently do not utilize the threading code. So init stuff here. */ + rc = clipInitInternal(pCtx); + if (RT_SUCCESS(rc)) + rc = clipRegisterContext(pCtx); + } +#endif + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Destroys a Shared Clipboard X11 context. + * + * @param pCtx The X11 clipboard context to destroy. + */ +void ShClX11Destroy(PSHCLX11CTX pCtx) +{ + if (!pCtx) + return; + + LogFlowFunc(("pCtx=%p\n", pCtx)); + +#ifdef TESTCASE + /** @todo The testcases currently do not utilize the threading code. So uninit stuff here. */ + clipUnregisterContext(pCtx); + clipUninitInternal(pCtx); +#endif + + if (pCtx->fHaveX11) + { + /* 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. */ + Assert(pCtx->pWidget == NULL); + } +} + +#ifndef TESTCASE +/** + * Starts our own Xt even thread for handling Shared Clipboard messages, extended version. + * + * @returns VBox status code. + * @param pCtx The X11 clipboard context to use. + * @param pszName Thread name to use. + * @param fGrab Whether we should try to grab the shared clipboard at once. + */ +int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + /* + * Immediately return if we are not connected to the X server. + */ + if (!pCtx->fHaveX11) + return VINF_SUCCESS; + + pCtx->fGrabClipboardOnStart = fGrab; + + clipResetX11Formats(pCtx); + + int rc; + + /* + * Create the pipes. + ** @todo r=andy Replace this with RTPipe API. + */ + int pipes[2]; + if (!pipe(pipes)) + { + pCtx->wakeupPipeRead = pipes[0]; + pCtx->wakeupPipeWrite = pipes[1]; + + if (!fcntl(pCtx->wakeupPipeRead, F_SETFL, O_NONBLOCK)) + { + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromErrno(errno); + } + else + rc = RTErrConvertFromErrno(errno); + + if (RT_SUCCESS(rc)) + { + LogRel2(("Shared Clipboard: Starting X11 event thread ...\n")); + + rc = RTThreadCreate(&pCtx->Thread, clipThreadMain, pCtx, 0, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, pszName); + if (RT_SUCCESS(rc)) + rc = RTThreadUserWait(pCtx->Thread, RT_MS_30SEC /* msTimeout */); + + if (RT_FAILURE(rc)) + { + LogRel(("Shared Clipboard: Failed to start the X11 event thread with %Rrc\n", rc)); + clipUninitInternal(pCtx); + } + else + { + if (!pCtx->fThreadStarted) + { + LogRel(("Shared Clipboard: X11 event thread reported an error while starting\n")); + } + else + LogRel2(("Shared Clipboard: X11 event thread started\n")); + } + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Starts our own Xt even thread for handling Shared Clipboard messages. + * + * @returns VBox status code. + * @param pCtx The X11 clipboard context to use. + * @param fGrab Whether we should try to grab the shared clipboard at once. + */ +int ShClX11ThreadStart(PSHCLX11CTX pCtx, bool fGrab) +{ + return ShClX11ThreadStartEx(pCtx, "SHCLX11", fGrab); +} + +/** + * Stops the Shared Clipboard Xt even thread. + * + * @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. + * + * @returns VBox status code. + * @param pCtx The X11 clipboard context to use. + */ +int ShClX11ThreadStop(PSHCLX11CTX pCtx) +{ + int rc; + /* + * Immediately return if we are not connected to the X server. + */ + if (!pCtx->fHaveX11) + return VINF_SUCCESS; + + LogRel2(("Shared Clipboard: Signalling the X11 event thread to stop\n")); + + /* Write to the "stop" pipe. */ + rc = clipThreadScheduleCall(pCtx, clipThreadSignalStop, (XtPointer)pCtx); + if (RT_FAILURE(rc)) + { + LogRel(("Shared Clipboard: cannot notify X11 event thread on shutdown with %Rrc\n", rc)); + return rc; + } + + LogRel2(("Shared Clipboard: Waiting for X11 event thread to stop ...\n")); + + int rcThread; + rc = RTThreadWait(pCtx->Thread, RT_MS_30SEC /* msTimeout */, &rcThread); + if (RT_SUCCESS(rc)) + rc = rcThread; + if (RT_SUCCESS(rc)) + { + if (pCtx->wakeupPipeRead != 0) + { + close(pCtx->wakeupPipeRead); + pCtx->wakeupPipeRead = 0; + } + + if (pCtx->wakeupPipeWrite != 0) + { + close(pCtx->wakeupPipeWrite); + pCtx->wakeupPipeWrite = 0; + } + } + + if (RT_SUCCESS(rc)) + { + LogRel2(("Shared Clipboard: X11 event thread stopped successfully\n")); + } + else + LogRel(("Shared Clipboard: Stopping X11 event thread failed with %Rrc\n", rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} +#endif /* !TESTCASE */ + +/** + * Returns the targets supported by VBox. + * + * This will return a list of atoms which tells the caller + * what kind of clipboard formats we support. + * + * @returns VBox status code. + * @param pCtx The X11 clipboard context to use. + * @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. + */ +static int clipCreateX11Targets(PSHCLX11CTX pCtx, Atom *atomTypeReturn, + XtPointer *pValReturn, + unsigned long *pcLenReturn, + int *piFormatReturn) +{ + const unsigned cFixedTargets = 3; /* See below. */ + + Atom *pAtomTargets = (Atom *)XtMalloc((SHCL_MAX_X11_FORMATS + cFixedTargets) * sizeof(Atom)); + if (!pAtomTargets) + return VERR_NO_MEMORY; + + unsigned cTargets = 0; + SHCLX11FMTIDX idxFmt = NIL_CLIPX11FORMAT; + do + { + idxFmt = clipEnumX11Formats(pCtx->vboxFormats, idxFmt); + if (idxFmt != NIL_CLIPX11FORMAT) + { + pAtomTargets[cTargets] = clipAtomForX11Format(pCtx, idxFmt); + ++cTargets; + } + } while (idxFmt != NIL_CLIPX11FORMAT); + + /* We always offer these fixed targets. */ + pAtomTargets[cTargets] = clipGetAtom(pCtx, "TARGETS"); + pAtomTargets[cTargets + 1] = clipGetAtom(pCtx, "MULTIPLE"); + pAtomTargets[cTargets + 2] = clipGetAtom(pCtx, "TIMESTAMP"); + + *atomTypeReturn = XA_ATOM; + *pValReturn = (XtPointer)pAtomTargets; + *pcLenReturn = cTargets + cFixedTargets; + *piFormatReturn = 32; + + LogFlowFunc(("cTargets=%u\n", cTargets + cFixedTargets)); + + return VINF_SUCCESS; +} + +/** + * Helper for ShClX11RequestDataForX11Callback() that will cache the data returned. + * + * @returns VBox status code. VERR_NO_DATA if no data available. + * @param pCtx The X11 clipboard context to use. + * @param uFmt Clipboard format to read data in. + * @param ppv Returns an allocated buffer with data read on success. + * Needs to be free'd with RTMemFree() by the caller. + * @param pcb Returns the amount of data read (in bytes) on success. + */ +static int shClX11RequestDataForX11CallbackHelper(PSHCLX11CTX pCtx, SHCLFORMAT uFmt, + void **ppv, uint32_t *pcb) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(ppv, VERR_INVALID_POINTER); + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + + LogFlowFunc(("pCtx=%p, uFmt=%#x\n", pCtx, uFmt)); + + int rc = VINF_SUCCESS; + + void *pv = NULL; + uint32_t cb = 0; + + if (uFmt == VBOX_SHCL_FMT_UNICODETEXT) + { + if (pCtx->pvUnicodeCache == NULL) /** @todo r=andy Using string cache here? */ + rc = pCtx->Callbacks.pfnOnRequestDataFromSource(pCtx->pFrontend, uFmt, &pCtx->pvUnicodeCache, &pCtx->cbUnicodeCache, + NULL /* pvUser */); + if ( RT_SUCCESS(rc) + /* Catch misbehaving callbacks. */ + && pCtx->pvUnicodeCache + && pCtx->cbUnicodeCache) + { + pv = RTMemDup(pCtx->pvUnicodeCache, pCtx->cbUnicodeCache); + if (pv) + cb = pCtx->cbUnicodeCache; + else + rc = VERR_NO_MEMORY; + } + } + else + rc = pCtx->Callbacks.pfnOnRequestDataFromSource(pCtx->pFrontend, uFmt, &pv, &cb, NULL /* pvUser */); + + + /* Safey net in case the callbacks above misbehave + * (must return VERR_NO_DATA if no data available). */ + if ( RT_SUCCESS(rc) + && (pv == NULL || cb == 0)) + rc = VERR_NO_DATA; + + if (RT_SUCCESS(rc)) + { + *ppv = pv; + *pcb = cb; + } + + LogFlowFunc(("Returning pv=%p, cb=%RU32, rc=%Rrc\n", pv, cb, rc)); + return rc; +} + +/** + * Satisfies a request from X11 to convert the clipboard text to UTF-8 LF. + * + * @returns VBox status code. VERR_NO_DATA if no data was converted. + * @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. + */ +static int clipConvertUtf16ToX11Data(Display *pDisplay, PRTUTF16 pwszSrc, + size_t cbSrc, Atom *atomTarget, + Atom *atomTypeReturn, + XtPointer *pValReturn, + unsigned long *pcLenReturn, + int *piFormatReturn) +{ + RT_NOREF(pDisplay); + AssertReturn(cbSrc % sizeof(RTUTF16) == 0, VERR_INVALID_PARAMETER); + + const size_t cwcSrc = cbSrc / sizeof(RTUTF16); + if (!cwcSrc) + return VERR_NO_DATA; + + /* This may slightly overestimate the space needed. */ + size_t chDst = 0; + int rc = ShClUtf16LenUtf8(pwszSrc, cwcSrc, &chDst); + if (RT_SUCCESS(rc)) + { + chDst++; /* Add space for terminator. */ + + char *pszDst = (char *)XtMalloc(chDst); + if (pszDst) + { + size_t cbActual = 0; + rc = ShClConvUtf16CRLFToUtf8LF(pwszSrc, cwcSrc, pszDst, chDst, &cbActual); + if (RT_SUCCESS(rc)) + { + *atomTypeReturn = *atomTarget; + *pValReturn = (XtPointer)pszDst; + *pcLenReturn = cbActual + 1 /* Include terminator */; + *piFormatReturn = 8; + } + } + else + rc = VERR_NO_MEMORY; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Satisfies a request from X11 to convert the clipboard HTML fragment to UTF-8. We + * return null-terminated text, but can cope with non-null-terminated input. + * + * @returns VBox status code. + * @param pDisplay An X11 display structure, needed for conversions + * performed by Xlib. + * @param pv The text to be converted (UTF8 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. + */ +static int clipConvertHtmlToX11Data(Display *pDisplay, const char *pszSrc, + size_t cbSrc, Atom *atomTarget, + Atom *atomTypeReturn, + XtPointer *pValReturn, + unsigned long *pcLenReturn, + int *piFormatReturn) +{ + RT_NOREF(pDisplay, pValReturn); + + /* This may slightly overestimate the space needed. */ + LogFlowFunc(("Source: %s", pszSrc)); + + char *pszDest = (char *)XtMalloc(cbSrc); + if (pszDest == NULL) + return VERR_NO_MEMORY; + + memcpy(pszDest, pszSrc, cbSrc); + + *atomTypeReturn = *atomTarget; + *pValReturn = (XtPointer)pszDest; + *pcLenReturn = cbSrc; + *piFormatReturn = 8; + + return VINF_SUCCESS; +} + + +/** + * Does this atom correspond to one of the two selection types we support? + * + * @param pCtx The X11 clipboard context to use. + * @param selType The atom in question. + */ +static bool clipIsSupportedSelectionType(PSHCLX11CTX pCtx, Atom selType) +{ + return( (selType == clipGetAtom(pCtx, "CLIPBOARD")) + || (selType == clipGetAtom(pCtx, "PRIMARY"))); +} + +/** + * Removes a trailing nul character from a string by adjusting the string + * length. Some X11 applications don't like zero-terminated text... + * + * @param pText The text in question. + * @param pcText The length of the text, adjusted on return. + * @param format The format of the text. + */ +static void clipTrimTrailingNul(XtPointer pText, unsigned long *pcText, + SHCLX11FMT format) +{ + AssertPtrReturnVoid(pText); + AssertPtrReturnVoid(pcText); + AssertReturnVoid((format == SHCLX11FMT_UTF8) || (format == SHCLX11FMT_TEXT) || (format == SHCLX11FMT_HTML)); + + if (((char *)pText)[*pcText - 1] == '\0') + --(*pcText); +} + +static int clipConvertToX11Data(PSHCLX11CTX pCtx, Atom *atomTarget, + Atom *atomTypeReturn, + XtPointer *pValReturn, + unsigned long *pcLenReturn, + int *piFormatReturn) +{ + int rc = VERR_NOT_SUPPORTED; /* Play safe by default. */ + + SHCLX11FMTIDX idxFmtX11 = clipFindX11FormatByAtom(pCtx, *atomTarget); + SHCLX11FMT fmtX11 = clipRealFormatForX11Format(idxFmtX11); + + LogFlowFunc(("vboxFormats=0x%x, idxFmtX11=%u ('%s'), fmtX11=%u\n", + pCtx->vboxFormats, idxFmtX11, g_aFormats[idxFmtX11].pcszAtom, fmtX11)); + +#ifdef LOG_ENABLED + char *pszFmts = ShClFormatsToStrA(pCtx->vboxFormats); + AssertPtrReturn(pszFmts, VERR_NO_MEMORY); + LogRel2(("Shared Clipboard: Converting VBox formats '%s' to '%s' for X11\n", + pszFmts, g_aFormats[idxFmtX11].pcszAtom)); + RTStrFree(pszFmts); +#endif + + void *pv = NULL; + uint32_t cb = 0; + + if ( ( (fmtX11 == SHCLX11FMT_UTF8) + || (fmtX11 == SHCLX11FMT_TEXT) + ) + && (pCtx->vboxFormats & VBOX_SHCL_FMT_UNICODETEXT)) + { + rc = shClX11RequestDataForX11CallbackHelper(pCtx, VBOX_SHCL_FMT_UNICODETEXT, &pv, &cb); + if ( RT_SUCCESS(rc) + && ( (fmtX11 == SHCLX11FMT_UTF8) + || (fmtX11 == SHCLX11FMT_TEXT))) + { + rc = clipConvertUtf16ToX11Data(XtDisplay(pCtx->pWidget), + (PRTUTF16)pv, cb, atomTarget, + atomTypeReturn, pValReturn, + pcLenReturn, piFormatReturn); + } + + if (RT_SUCCESS(rc)) + clipTrimTrailingNul(*(XtPointer *)pValReturn, pcLenReturn, fmtX11); + + RTMemFree(pv); + } + else if ( (fmtX11 == SHCLX11FMT_BMP) + && (pCtx->vboxFormats & VBOX_SHCL_FMT_BITMAP)) + { + rc = shClX11RequestDataForX11CallbackHelper(pCtx, VBOX_SHCL_FMT_BITMAP, &pv, &cb); + if ( RT_SUCCESS(rc) + && (fmtX11 == SHCLX11FMT_BMP)) + { + /* Create a full BMP from it. */ + rc = ShClDibToBmp(pv, cb, (void **)pValReturn, + (size_t *)pcLenReturn); + } + + if (RT_SUCCESS(rc)) + { + *atomTypeReturn = *atomTarget; + *piFormatReturn = 8; + } + + RTMemFree(pv); + } + else if ( (fmtX11 == SHCLX11FMT_HTML) + && (pCtx->vboxFormats & VBOX_SHCL_FMT_HTML)) + { + rc = shClX11RequestDataForX11CallbackHelper(pCtx, VBOX_SHCL_FMT_HTML, &pv, &cb); + if (RT_SUCCESS(rc)) + { + /** + * The common VBox HTML encoding will be UTF-8. + * Before sending it to the X11 clipboard we have to convert it to UTF-8 first. + * + * Strange that we get UTF-16 from the X11 clipboard, but + * in same time we send UTF-8 to X11 clipboard and it works. + ** @todo r=andy Verify this. + */ + rc = clipConvertHtmlToX11Data(XtDisplay(pCtx->pWidget), + (const char*)pv, cb, atomTarget, + atomTypeReturn, pValReturn, + pcLenReturn, piFormatReturn); + if (RT_SUCCESS(rc)) + clipTrimTrailingNul(*(XtPointer *)pValReturn, pcLenReturn, fmtX11); + + RTMemFree(pv); + } + } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + else if (fmtX11 == SHCLX11FMT_URI_LIST) + { + if (pCtx->vboxFormats & VBOX_SHCL_FMT_URI_LIST) + { + rc = shClX11RequestDataForX11CallbackHelper(pCtx, VBOX_SHCL_FMT_URI_LIST, &pv, &cb); + if (RT_SUCCESS(rc)) + { + void *pvDst = (void *)XtMalloc(cb); + if (pvDst) + { + memcpy(pvDst, pv, cb); + + *atomTypeReturn = *atomTarget; + *pValReturn = (XtPointer)pvDst; + *pcLenReturn = cb; + *piFormatReturn = 8; + } + else + rc = VERR_NO_MEMORY; + } + } + /* else not supported yet. */ + } +#endif + else + { + *atomTypeReturn = XT_CONVERT_FAIL; + *pValReturn = (XtPointer)NULL; + *pcLenReturn = 0; + *piFormatReturn = 0; + } + + if (RT_FAILURE(rc)) + { + char *pszFmts2 = ShClFormatsToStrA(pCtx->vboxFormats); + char *pszAtomName = XGetAtomName(XtDisplay(pCtx->pWidget), *atomTarget); + + LogRel(("Shared Clipboard: Converting VBox formats '%s' to '%s' for X11 (idxFmtX11=%u, fmtX11=%u, atomTarget='%s') failed, rc=%Rrc\n", + pszFmts2 ? pszFmts2 : "unknown", g_aFormats[idxFmtX11].pcszAtom, idxFmtX11, fmtX11, pszAtomName ? pszAtomName : "unknown", rc)); + + if (pszFmts2) + RTStrFree(pszFmts2); + if (pszAtomName) + XFree(pszAtomName); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Returns VBox's clipboard data for an X11 client. + * + * @note Callback for XtOwnSelection. + */ +static Boolean clipXtConvertSelectionProc(Widget widget, Atom *atomSelection, + Atom *atomTarget, + Atom *atomTypeReturn, + XtPointer *pValReturn, + unsigned long *pcLenReturn, + int *piFormatReturn) +{ + LogFlowFuncEnter(); + + PSHCLX11CTX pCtx = clipLookupContext(widget); + if (!pCtx) + return False; + + /* Is this the rigt selection (clipboard) we were asked for? */ + if (!clipIsSupportedSelectionType(pCtx, *atomSelection)) + return False; + + int rc; + if (*atomTarget == clipGetAtom(pCtx, "TARGETS")) + rc = clipCreateX11Targets(pCtx, atomTypeReturn, pValReturn, + pcLenReturn, piFormatReturn); + else + rc = clipConvertToX11Data(pCtx, atomTarget, atomTypeReturn, + pValReturn, pcLenReturn, piFormatReturn); + +#if 0 /** @todo Disabled -- crashes when running with tstClipboardGH-X11. */ + XSelectionRequestEvent* pReq = + XtGetSelectionRequest(widget, *atomSelection, (XtRequestId)NULL); + LogFlowFunc(("returning pVBoxWnd=%#x, ownerWnd=%#x, reqWnd=%#x, %RTbool, rc=%Rrc\n", + XtWindow(pCtx->pWidget), pReq->owner, pReq->requestor, RT_SUCCESS(rc), rc)); +#endif + return RT_SUCCESS(rc) ? True : False; +} + +static void clipXtConvertSelectionProcLose(Widget widget, Atom *atomSelection) +{ + RT_NOREF(widget, atomSelection); + LogFlowFuncEnter(); +} + +static void clipXtConvertSelectionProcDone(Widget widget, Atom *atomSelection, Atom *atomTarget) +{ + RT_NOREF(widget, atomSelection, atomTarget); + LogFlowFuncEnter(); +} + +/** + * Structure used to pass information about formats that VBox supports. + */ +typedef struct _CLIPNEWVBOXFORMATS +{ + /** Context information for the X11 clipboard. */ + PSHCLX11CTX pCtx; + /** Formats supported by VBox. */ + SHCLFORMATS Formats; +} CLIPNEWVBOXFORMATS, *PCLIPNEWVBOXFORMATS; + + + +/** + * Invalidates the local cache of the data in the VBox clipboard. + * + * @param pCtx The X11 clipboard context to use. + */ +static void clipInvalidateClipboardCache(PSHCLX11CTX pCtx) +{ + if (pCtx->pvUnicodeCache != NULL) + { + RTMemFree(pCtx->pvUnicodeCache); + pCtx->pvUnicodeCache = NULL; + } +} + +/** + * Takes possession of the X11 clipboard (and middle-button selection). + * + * @param pCtx The X11 clipboard context to use. + * @param uFormats Clipboard formats to set. + */ +static void clipGrabX11Clipboard(PSHCLX11CTX pCtx, SHCLFORMATS uFormats) +{ + LogFlowFuncEnter(); + + /** @ŧodo r=andy The docs say: "the value CurrentTime is not acceptable" here!? */ + if (XtOwnSelection(pCtx->pWidget, clipGetAtom(pCtx, "CLIPBOARD"), + CurrentTime, + clipXtConvertSelectionProc, clipXtConvertSelectionProcLose, clipXtConvertSelectionProcDone)) + { + pCtx->vboxFormats = uFormats; + + /* Grab the middle-button paste selection too. */ + XtOwnSelection(pCtx->pWidget, clipGetAtom(pCtx, "PRIMARY"), + CurrentTime, clipXtConvertSelectionProc, NULL, 0); +#ifndef TESTCASE + /* Xt suppresses these if we already own the clipboard, so send them + * ourselves. */ + XSetSelectionOwner(XtDisplay(pCtx->pWidget), + clipGetAtom(pCtx, "CLIPBOARD"), + XtWindow(pCtx->pWidget), CurrentTime); + XSetSelectionOwner(XtDisplay(pCtx->pWidget), + clipGetAtom(pCtx, "PRIMARY"), + XtWindow(pCtx->pWidget), CurrentTime); +#endif + } +} + +/** + * Worker function for ShClX11ReportFormatsToX11 which runs on the + * event thread. + * + * @param pvUserData Pointer to a CLIPNEWVBOXFORMATS structure containing + * information about the VBox formats available and the + * clipboard context data. Must be freed by the worker. + */ +static void ShClX11ReportFormatsToX11Worker(void *pvUserData, void * /* interval */) +{ + AssertPtrReturnVoid(pvUserData); + + CLIPNEWVBOXFORMATS *pFormats = (CLIPNEWVBOXFORMATS *)pvUserData; + + PSHCLX11CTX pCtx = pFormats->pCtx; + SHCLFORMATS fFormats = pFormats->Formats; + + RTMemFree(pFormats); + +#ifdef LOG_ENABLED + char *pszFmts = ShClFormatsToStrA(fFormats); + AssertPtrReturnVoid(pszFmts); + LogRel2(("Shared Clipboard: Reported available VBox formats %s to X11\n", pszFmts)); + RTStrFree(pszFmts); +#endif + + clipInvalidateClipboardCache(pCtx); + clipGrabX11Clipboard(pCtx, fFormats); + clipResetX11Formats(pCtx); + + LogFlowFuncLeave(); +} + +/** + * Announces new clipboard formats to the X11 clipboard. + * + * @returns VBox status code. + * @param pCtx Context data for the clipboard backend. + * @param uFormats Clipboard formats offered. + */ +int ShClX11ReportFormatsToX11(PSHCLX11CTX pCtx, SHCLFORMATS uFormats) +{ + /* + * Immediately return if we are not connected to the X server. + */ + if (!pCtx->fHaveX11) + return VINF_SUCCESS; + + int rc; + + /* This must be free'd by the worker callback. */ + PCLIPNEWVBOXFORMATS pFormats = (PCLIPNEWVBOXFORMATS)RTMemAlloc(sizeof(CLIPNEWVBOXFORMATS)); + if (pFormats) + { + pFormats->pCtx = pCtx; + pFormats->Formats = uFormats; + rc = clipThreadScheduleCall(pCtx, ShClX11ReportFormatsToX11Worker, + (XtPointer)pFormats); + if (RT_FAILURE(rc)) + RTMemFree(pFormats); + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Converts the data obtained from the X11 clipboard to the required format, + * place it in the buffer supplied and signal that data has arrived. + * + * Converts the text obtained UTF-16LE with Windows EOLs. + * Converts full BMP data to DIB format. + */ +SHCL_X11_DECL(void) clipConvertDataFromX11Worker(void *pClient, void *pvSrc, unsigned cbSrc) +{ + CLIPREADX11CBREQ *pReq = (CLIPREADX11CBREQ *)pClient; + AssertPtrReturnVoid(pReq); + + LogFlowFunc(("pReq->uFmtVBox=%#x, pReq->idxFmtX11=%u, pReq->pCtx=%p\n", pReq->uFmtVBox, pReq->idxFmtX11, pReq->pCtx)); + + LogRel2(("Shared Clipboard: Converting X11 format '%s' to VBox format %#x\n", + g_aFormats[pReq->idxFmtX11].pcszAtom, pReq->uFmtVBox)); + + AssertPtr(pReq->pCtx); + Assert(pReq->uFmtVBox != VBOX_SHCL_FMT_NONE); /* Sanity. */ + + int rc = VINF_SUCCESS; + + void *pvDst = NULL; + size_t cbDst = 0; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY + PSHCLX11CTX pCtx = pReq->pCtx; + AssertPtr(pReq->pCtx); + clipSetXtBusy(pCtx, false); + if (clipGetXtNeedsUpdate(pCtx)) + clipQueryX11Targets(pCtx); +#endif + + /* If X11 clipboard buffer has no data, libXt can pass to XtGetSelectionValue() + * callback an empty string, in this case cbSrc is 0. */ + if (pvSrc == NULL || cbSrc == 0) + { + /* The clipboard selection may have changed before we could get it. */ + rc = VERR_NO_DATA; + } + else if (pReq->uFmtVBox == VBOX_SHCL_FMT_UNICODETEXT) + { + /* In which format is the clipboard data? */ + switch (clipRealFormatForX11Format(pReq->idxFmtX11)) + { + case SHCLX11FMT_UTF8: + RT_FALL_THROUGH(); + case SHCLX11FMT_TEXT: + { + size_t cwDst; + /* If we are given broken UTF-8, we treat it as Latin1. */ /** @todo BUGBUG Is this acceptable? */ + if (RT_SUCCESS(RTStrValidateEncodingEx((char *)pvSrc, cbSrc, 0))) + rc = ShClConvUtf8LFToUtf16CRLF((const char *)pvSrc, cbSrc, + (PRTUTF16 *)&pvDst, &cwDst); + else + rc = ShClConvLatin1LFToUtf16CRLF((char *)pvSrc, cbSrc, + (PRTUTF16 *)&pvDst, &cwDst); + if (RT_SUCCESS(rc)) + { + cwDst += 1 /* Include terminator */; + cbDst = cwDst * sizeof(RTUTF16); /* Convert RTUTF16 units to bytes. */ + } + break; + } + + default: + { + rc = VERR_INVALID_PARAMETER; + break; + } + } + } + else if (pReq->uFmtVBox == VBOX_SHCL_FMT_BITMAP) + { + /* In which format is the clipboard data? */ + switch (clipRealFormatForX11Format(pReq->idxFmtX11)) + { + case SHCLX11FMT_BMP: + { + const void *pDib; + size_t cbDibSize; + rc = ShClBmpGetDib((const void *)pvSrc, cbSrc, + &pDib, &cbDibSize); + if (RT_SUCCESS(rc)) + { + pvDst = RTMemAlloc(cbDibSize); + if (!pvDst) + rc = VERR_NO_MEMORY; + else + { + memcpy(pvDst, pDib, cbDibSize); + cbDst = cbDibSize; + } + } + break; + } + + default: + { + rc = VERR_INVALID_PARAMETER; + break; + } + } + } + else if (pReq->uFmtVBox == VBOX_SHCL_FMT_HTML) + { + /* In which format is the clipboard data? */ + switch (clipRealFormatForX11Format(pReq->idxFmtX11)) + { + case SHCLX11FMT_HTML: + { + /* + * The common VBox HTML encoding will be - UTF-8 + * because it more general for HTML formats then UTF-16 + * X11 clipboard returns UTF-16, so before sending it we should + * convert it to UTF-8. + */ + pvDst = NULL; + cbDst = 0; + + /* + * Some applications sends data in UTF-16, some in UTF-8, + * without indication it in MIME. + * + * In case of UTF-16, at least [Open|Libre] Office adds an byte order mark (0xfeff) + * at the start of the clipboard data. + */ + if ( cbSrc >= sizeof(RTUTF16) + && *(PRTUTF16)pvSrc == VBOX_SHCL_UTF16LEMARKER) + { + rc = ShClConvUtf16ToUtf8HTML((PRTUTF16)pvSrc, cbSrc / sizeof(RTUTF16), (char**)&pvDst, &cbDst); + if (RT_SUCCESS(rc)) + { + LogFlowFunc(("UTF-16 Unicode source (%u bytes):\n%ls\n\n", cbSrc, pvSrc)); + LogFlowFunc(("Byte Order Mark = %hx", ((PRTUTF16)pvSrc)[0])); + LogFlowFunc(("UTF-8 Unicode dest (%u bytes):\n%s\n\n", cbDst, pvDst)); + } + else + LogRel(("Shared Clipboard: Converting UTF-16 Unicode failed with %Rrc\n", rc)); + } + else /* Raw data. */ + { + pvDst = RTMemAllocZ(cbSrc + 1 /* '\0' */); + if(pvDst) + { + memcpy(pvDst, pvSrc, cbSrc); + cbDst = cbSrc + 1 /* '\0' */; + } + else + { + rc = VERR_NO_MEMORY; + break; + } + } + + rc = VINF_SUCCESS; + break; + } + + default: + { + rc = VERR_INVALID_PARAMETER; + break; + } + } + } +# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + else if (pReq->uFmtVBox == VBOX_SHCL_FMT_URI_LIST) + { + /* In which format is the clipboard data? */ + switch (clipRealFormatForX11Format(pReq->idxFmtX11)) + { + case SHCLX11FMT_URI_LIST: + { + /* For URI lists we only accept valid UTF-8 encodings. */ + if (RT_SUCCESS(RTStrValidateEncodingEx((char *)pvSrc, cbSrc, 0))) + { + /* URI lists on X are strings separated with "\r\n". */ + RTCList<RTCString> lstRootEntries = RTCString((char *)pvSrc, cbSrc).split("\r\n"); + for (size_t i = 0; i < lstRootEntries.size(); ++i) + { + char *pszEntry = RTUriFilePath(lstRootEntries.at(i).c_str()); + AssertPtrBreakStmt(pszEntry, VERR_INVALID_PARAMETER); + + rc = RTStrAAppend((char **)&pvDst, "http://localhost"); + AssertRCBreakStmt(rc, VERR_NO_MEMORY); + cbDst += (uint32_t)strlen(pszEntry); + + + + /** @todo BUGBUG Fix port! */ + /** @todo Add port + UUID (virtual path). */ + + rc = RTStrAAppend((char **)&pvDst, pszEntry); + AssertRCBreakStmt(rc, VERR_NO_MEMORY); + cbDst += (uint32_t)strlen(pszEntry); + + LogFlowFunc(("URI list entry '%s'\n", (char *)pvDst)); + + rc = RTStrAAppend((char **)&pvDst, "\r\n"); + AssertRCBreakStmt(rc, VERR_NO_MEMORY); + cbDst += (uint32_t)strlen("\r\n"); + + RTStrFree(pszEntry); + } + + if (cbDst) + cbDst++; /* Include final (zero) termination. */ + + LogFlowFunc(("URI list: cbDst=%RU32\n", cbDst)); + } + else + rc = VERR_INVALID_PARAMETER; + break; + } + + default: + { + rc = VERR_INVALID_PARAMETER; + break; + } + } + } +# endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + else + rc = VERR_NOT_SUPPORTED; + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Converting X11 format '%s' (idxFmtX11=%u) to VBox format %#x failed, rc=%Rrc\n", + g_aFormats[pReq->idxFmtX11].pcszAtom, pReq->idxFmtX11, pReq->uFmtVBox, rc)); + + SHCLX11READDATAREQ SendData; + RT_ZERO(SendData); + SendData.pReq = pReq->pReq; + SendData.rcCompletion = rc; + + pCtx->Callbacks.pfnOnSendDataToDest(pReq->pCtx->pFrontend, pvDst, cbDst, &SendData); + + RTMemFree(pvDst); + RTMemFree(pReq); + + LogFlowFuncLeaveRC(rc); +} + +/** + * Converts the data obtained from the X11 clipboard to the required format, + * place it in the buffer supplied and signal that data has arrived. + * + * Converts the text obtained UTF-16LE with Windows EOLs. + * Converts full BMP data to DIB format. + */ +SHCL_X11_DECL(void) clipConvertDataFromX11(Widget widget, XtPointer pClient, + Atom * /* selection */, Atom *atomType, + XtPointer pvSrc, long unsigned int *pcLen, + int *piFormat) +{ + RT_NOREF(widget); + if (*atomType == XT_CONVERT_FAIL) /* Xt timeout */ + clipConvertDataFromX11Worker(pClient, NULL, 0); + else + { + CLIPREADX11CBREQ *pReq = (CLIPREADX11CBREQ *)pClient; + if (pReq->pCtx->Callbacks.pfnOnClipboardRead) + { + void *pvData = NULL; + size_t cbData = 0; + int rc = pReq->pCtx->Callbacks.pfnOnClipboardRead(pReq->pCtx->pFrontend, pReq->uFmtVBox, &pvData, &cbData, NULL); + if (RT_SUCCESS(rc)) + { + /* Feed to conversion worker. */ + clipConvertDataFromX11Worker(pClient, pvData, cbData); + RTMemFree(pvData); + } + else + clipConvertDataFromX11Worker(pClient, NULL, 0); + } + else /* Call with current data provided by X (default). */ + clipConvertDataFromX11Worker(pClient, pvSrc, (*pcLen) * (*piFormat) / 8); + } + + XtFree((char *)pvSrc); +} + +static int clipGetSelectionValue(PSHCLX11CTX pCtx, SHCLX11FMTIDX idxFmt, + CLIPREADX11CBREQ *pReq) +{ +#ifndef TESTCASE + XtGetSelectionValue(pCtx->pWidget, clipGetAtom(pCtx, "CLIPBOARD"), + clipAtomForX11Format(pCtx, idxFmt), + clipConvertDataFromX11, + reinterpret_cast<XtPointer>(pReq), + CurrentTime); +#else + tstClipRequestData(pCtx, idxFmt, (void *)pReq); +#endif + + return VINF_SUCCESS; /** @todo Return real rc. */ +} + +/** + * Worker function for ShClX11ReadDataFromX11 which runs on the event thread. + * + * @param pvUserData Pointer to a CLIPREADX11CBREQ structure containing + * information about the clipboard read request. + * Must be free'd by the worker. + */ +static void ShClX11ReadDataFromX11Worker(void *pvUserData, void * /* interval */) +{ + AssertPtrReturnVoid(pvUserData); + + CLIPREADX11CBREQ *pReq = (CLIPREADX11CBREQ *)pvUserData; + SHCLX11CTX *pCtx = pReq->pCtx; + AssertPtrReturnVoid(pCtx); + + LogFlowFunc(("pReq->uFmtVBox=%#x, idxFmtX11=%#x\n", pReq->uFmtVBox, pReq->idxFmtX11)); + + int rc = VERR_NO_DATA; /* VBox thinks we have data and we don't. */ + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY + const bool fXtBusy = clipGetXtBusy(pCtx); + clipSetXtBusy(pCtx, true); + if (fXtBusy) + { + /* If the clipboard is busy just fend off the request. */ + rc = VERR_TRY_AGAIN; + } + else +#endif + if (pReq->uFmtVBox & VBOX_SHCL_FMT_UNICODETEXT) + { + pReq->idxFmtX11 = pCtx->idxFmtText; + if (pReq->idxFmtX11 != SHCLX11FMT_INVALID) + { + /* Send out a request for the data to the current clipboard owner. */ + rc = clipGetSelectionValue(pCtx, pCtx->idxFmtText, pReq); + } + } + else if (pReq->uFmtVBox & VBOX_SHCL_FMT_BITMAP) + { + pReq->idxFmtX11 = pCtx->idxFmtBmp; + if (pReq->idxFmtX11 != SHCLX11FMT_INVALID) + { + /* Send out a request for the data to the current clipboard owner. */ + rc = clipGetSelectionValue(pCtx, pCtx->idxFmtBmp, pReq); + } + } + else if (pReq->uFmtVBox & VBOX_SHCL_FMT_HTML) + { + pReq->idxFmtX11 = pCtx->idxFmtHTML; + if (pReq->idxFmtX11 != SHCLX11FMT_INVALID) + { + /* Send out a request for the data to the current clipboard owner. */ + rc = clipGetSelectionValue(pCtx, pCtx->idxFmtHTML, pReq); + } + } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + else if (pReq->uFmtVBox & VBOX_SHCL_FMT_URI_LIST) + { + pReq->idxFmtX11 = pCtx->idxFmtURI; + if (pReq->idxFmtX11 != SHCLX11FMT_INVALID) + { + /* Send out a request for the data to the current clipboard owner. */ + rc = clipGetSelectionValue(pCtx, pCtx->idxFmtURI, pReq); + } + } +#endif + else + { +#ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY + clipSetXtBusy(pCtx, false); +#endif + rc = VERR_NOT_IMPLEMENTED; + } + + if (RT_FAILURE(rc)) + { + /* The clipboard callback was never scheduled, so we must signal + * that the request processing is finished and clean up ourselves. */ + SHCLX11READDATAREQ SendData; + RT_ZERO(SendData); + SendData.pReq = pReq->pReq; + SendData.rcCompletion = rc; + + pCtx->Callbacks.pfnOnSendDataToDest(pReq->pCtx->pFrontend, NULL /* pv */ ,0 /* cb */, &SendData); + RTMemFree(pReq); + } + + LogFlowFuncLeaveRC(rc); +} + +/** + * Called when VBox wants to read the X11 clipboard. + * + * @returns VBox status code. + * @retval VERR_NO_DATA if format is supported but no data is available currently. + * @retval VERR_NOT_IMPLEMENTED if the format is not implemented. + * @param pCtx Context data for the clipboard backend. + * @param uFmt The format that the VBox would like to receive the data in. + * @param pReq Read callback request to use. Will be free'd in the callback on success. + * Otherwise the caller has to free this again on error. + * + * @note We allocate a request structure which must be freed by the worker. + */ +int ShClX11ReadDataFromX11(PSHCLX11CTX pCtx, SHCLFORMAT uFmt, CLIPREADCBREQ *pReq) +{ + AssertPtrReturn(pReq, VERR_INVALID_POINTER); + /* + * Immediately return if we are not connected to the X server. + */ + if (!pCtx->fHaveX11) + return VERR_NO_DATA; + + int rc = VINF_SUCCESS; + + CLIPREADX11CBREQ *pX11Req = (CLIPREADX11CBREQ *)RTMemAllocZ(sizeof(CLIPREADX11CBREQ)); + if (pX11Req) + { + pX11Req->pCtx = pCtx; + pX11Req->uFmtVBox = uFmt; + pX11Req->pReq = pReq; + + /* We use this to schedule a worker function on the event thread. */ + rc = clipThreadScheduleCall(pCtx, ShClX11ReadDataFromX11Worker, (XtPointer)pX11Req); + if (RT_FAILURE(rc)) + RTMemFree(pX11Req); + } + else + rc = VERR_NO_MEMORY; + + LogFlowFuncLeaveRC(rc); + return rc; +} |