/** @file * Shared Clipboard: Common X11 code. */ /* * Copyright (C) 2006-2023 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 . * * 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 #include #include #include #ifdef RT_OS_SOLARIS #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS # include # include # include #endif #include #include #include #include #include #include /** 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_GNOME_COPIED_FILES, VBOX_SHCL_FMT_URI_LIST }, { "x-special/mate-copied-files", SHCLX11FMT_URI_LIST_MATE_COPIED_FILES, VBOX_SHCL_FMT_URI_LIST }, { "x-special/nautilus-clipboard", SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD, VBOX_SHCL_FMT_URI_LIST }, { "application/x-kde-cutselection", SHCLX11FMT_URI_LIST_KDE_CUTSELECTION, 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 /* !RT_OS_SOLARIS_10 */ const char XtStrings [] = ""; _WidgetClassRec* applicationShellWidgetClass; const char XtShellStrings [] = ""; # endif /* RT_OS_SOLARIS_10 */ #else /* !TESTCASE */ # ifdef VBOX_WITH_VBOXCLIENT_LAZY_LOAD /* Defines needed for lazy loading global data from the shared objects (.so). * See r157060. */ DECLASM(WidgetClass * ) LazyGetPtr_applicationShellWidgetClass(void); #define applicationShellWidgetClass (*LazyGetPtr_applicationShellWidgetClass()) DECLASM(const char *) LazyGetPtr_XtStrings(void); #define XtStrings (LazyGetPtr_XtStrings()) # endif #endif /* TESTCASE */ /********************************************************************************************************************************* * Defines * *********************************************************************************************************************************/ #define SHCL_MAX_X11_FORMATS RT_ELEMENTS(g_aFormats) /********************************************************************************************************************************* * Internal structures * *********************************************************************************************************************************/ #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; if (!pWidget) return; 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 Log((", idxFmtURI=%u ('%s')", pCtx->idxFmtURI, g_aFormats[pCtx->idxFmtURI].pcszAtom)); #endif Log((" -> 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 if (pCtx->Callbacks.pfnReportFormats) 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(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(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); } /** * Helper function for public X11 Shared Clipboard APIs to know whether we're running in headless mode or not. * * Headless mode either could mean that we don't want to touch the X11 clipboard, or that X simply isn't installed and/or * isn't available (e.g. running on a pure server installation w/o any desktop environment). * * Goal here is to make the X11 API transparent for the caller whether X is available or not. * * @returns \c true if running in headless mode, or \c false if not. * @param pCtx The X11 clipboard context to use. */ DECLINLINE(bool) shClX11HeadlessIsEnabled(PSHCLX11CTX pCtx) { return pCtx->fHeadless; } /** * 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)); RT_BZERO(pCtx, sizeof(SHCLX11CTX)); /* Init clipboard cache. */ ShClCacheInit(&pCtx->Cache); /* Install given callbacks. */ shClX11SetCallbacksInternal(pCtx, pCallbacks); pCtx->fHeadless = fHeadless; pCtx->pFrontend = pParent; #ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY pCtx->fXtBusy = false; pCtx->fXtNeedsUpdate = false; #endif int rc = VINF_SUCCESS; LogRel(("Shared Clipboard: Initializing X11 clipboard (%s mode)\n", fHeadless ? "headless" : "regular")); if (!pCtx->fHeadless) { #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP rc = 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 } if (RT_FAILURE(rc)) LogRel(("Shared Clipboard: Initializing X11 clipboard failed with %Rrc\n", rc)); LogFlowFuncLeaveRC(rc); return rc; } /** * Destroys a Shared Clipboard X11 context. * * @returns VBox status code. * @param pCtx The X11 clipboard context to destroy. */ int ShClX11Destroy(PSHCLX11CTX pCtx) { if (!pCtx) return VINF_SUCCESS; LogFlowFunc(("pCtx=%p\n", pCtx)); /* Destroy clipboard cache. */ ShClCacheDestroy(&pCtx->Cache); int rc = VINF_SUCCESS; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP rc = ShClTransferHttpServerDestroy(&pCtx->HttpCtx.HttpServer); #endif #ifdef TESTCASE /** @todo The testcases currently do not utilize the threading code. So uninit stuff here. */ clipUnregisterContext(pCtx); clipUninitInternal(pCtx); #endif if (!shClX11HeadlessIsEnabled(pCtx)) { /* 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. */ AssertStmt(pCtx->pWidget == NULL, rc = VERR_WRONG_ORDER); } return rc; } #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); if (shClX11HeadlessIsEnabled(pCtx)) 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) { if (shClX11HeadlessIsEnabled(pCtx)) return VINF_SUCCESS; LogRel2(("Shared Clipboard: Signalling the X11 event thread to stop\n")); /* Write to the "stop" pipe. */ int 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 clipConvertToX11Data() that will cache the data returned. * * @returns VBox status code. VERR_SHCLPB_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. * * @thread X11 event thread. */ 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); #ifdef LOG_ENABLED char *pszFmts = ShClFormatsToStrA(uFmt); AssertPtrReturn(pszFmts, VERR_NO_MEMORY); LogRel2(("Shared Clipboard: Requesting data for X11 from source as '%s'\n", pszFmts)); RTStrFree(pszFmts); #endif int rc = VINF_SUCCESS; void *pv = NULL; uint32_t cb = 0; PSHCLCACHEENTRY pCacheEntry = ShClCacheGet(&pCtx->Cache, uFmt); if (!pCacheEntry) /* Cache miss */ { AssertPtrReturn(pCtx->Callbacks.pfnOnRequestDataFromSource, VERR_INVALID_POINTER); rc = pCtx->Callbacks.pfnOnRequestDataFromSource(pCtx->pFrontend, uFmt, &pv, &cb, NULL /* pvUser */); if (RT_SUCCESS(rc)) rc = ShClCacheSet(&pCtx->Cache, uFmt, pv, cb); } else /* Cache hit */ { void *pvCache = NULL; size_t cbCache = 0; ShClCacheEntryGet(pCacheEntry, &pvCache, &cbCache); if ( pvCache && cbCache) { pv = RTMemDup(pvCache, cbCache); if (pv) { cb = cbCache; } else rc = VERR_NO_MEMORY; } } LogFlowFunc(("pCtx=%p, uFmt=%#x -> Cache %s\n", pCtx, uFmt, pCacheEntry ? "HIT" : "MISS")); /* Safey net in case the stuff above misbehaves * (must return VERR_SHCLPB_NO_DATA if no data available). */ if ( RT_SUCCESS(rc) && (pv == NULL || cb == 0)) rc = VERR_SHCLPB_NO_DATA; if (RT_SUCCESS(rc)) { *ppv = pv; *pcb = cb; } if ( RT_FAILURE(rc) && rc != VERR_SHCLPB_NO_DATA) LogRel(("Shared Clipboard: Requesting data for X11 from source failed with %Rrc\n", rc)); LogFlowFunc(("Returning pv=%p, cb=%RU32, rc=%Rrc\n", pv, cb, rc)); return rc; } /** * Free's an allocated SHCLX11RESPONSE struct. * * @param pResp Pointer to response to free. * The pointer will be invalid after return. */ static void shClX11ResponseFree(PSHCLX11RESPONSE pResp) { if (!pResp) return; switch (pResp->enmType) { case SHCLX11EVENTTYPE_READ: { if (pResp->Read.pvData) { Assert(pResp->Read.cbData); RTMemFree(pResp->Read.pvData); } break; } case SHCLX11EVENTTYPE_REPORT_FORMATS: RT_FALL_THROUGH(); case SHCLX11EVENTTYPE_WRITE: RT_FALL_THROUGH(); default: break; } RTMemFree(pResp); } /** * Satisfies a request from X11 to convert the clipboard text to UTF-8 LF. * * @returns VBox status code. VERR_SHCLPB_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_SHCLPB_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)); char *pszFmts = ShClFormatsToStrA(pCtx->vboxFormats); AssertPtrReturn(pszFmts, VERR_NO_MEMORY); LogRel2(("Shared Clipboard: Converting VBox formats '%s' to '%s' for X11\n", pszFmts, fmtX11 == SHCLX11FMT_INVALID ? "" : g_aFormats[idxFmtX11].pcszAtom)); RTStrFree(pszFmts); 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 || fmtX11 == SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES /** @todo BUGBUG Not sure about the following ones; test those. */ || fmtX11 == SHCLX11FMT_URI_LIST_MATE_COPIED_FILES || fmtX11 == SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD || fmtX11 == SHCLX11FMT_URI_LIST_KDE_CUTSELECTION) { if (pCtx->vboxFormats & VBOX_SHCL_FMT_URI_LIST) { rc = shClX11RequestDataForX11CallbackHelper(pCtx, VBOX_SHCL_FMT_URI_LIST, &pv, &cb); if (RT_SUCCESS(rc)) { void *pvX11; size_t cbX11; rc = ShClX11TransferConvertToX11((const char *)pv, cb, fmtX11, &pvX11, &cbX11); if (RT_SUCCESS(rc)) { *atomTypeReturn = *atomTarget; *pValReturn = (XtPointer)pvX11; *pcLenReturn = cbX11; *piFormatReturn = 8; } } RTMemFree(pv); pv = NULL; } /* else not supported yet. */ } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ else { *atomTypeReturn = XT_CONVERT_FAIL; *pValReturn = (XtPointer)NULL; *pcLenReturn = 0; *piFormatReturn = 0; } if ( RT_FAILURE(rc) && rc != VERR_SHCLPB_NO_DATA) { 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(); } /** * Invalidates the local clipboard cache. * * @param pCtx The X11 clipboard context to use. */ static void clipInvalidateClipboardCache(PSHCLX11CTX pCtx) { LogFlowFuncEnter(); ShClCacheInvalidate(&pCtx->Cache); } /** * 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 ShClX11ReportFormatsToX11Async. * * @param pvUserData Pointer to a PSHCLX11REQUEST structure containing * information about the VBox formats available and the * clipboard context data. Must be free'd by the worker. * * @thread X11 event thread. */ static void shClX11ReportFormatsToX11Worker(void *pvUserData, void * /* interval */) { AssertPtrReturnVoid(pvUserData); PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)pvUserData; AssertReturnVoid(pReq->enmType == SHCLX11EVENTTYPE_REPORT_FORMATS); PSHCLX11CTX pCtx = pReq->pCtx; SHCLFORMATS fFormats = pReq->Formats.fFormats; RTMemFree(pReq); #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. * * @note When calling this function, data for the clipboard already has to be available, * as we grab the clipboard, which in turn then calls the X11 data conversion callback. */ int ShClX11ReportFormatsToX11Async(PSHCLX11CTX pCtx, SHCLFORMATS uFormats) { if (shClX11HeadlessIsEnabled(pCtx)) return VINF_SUCCESS; int rc; PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)RTMemAllocZ(sizeof(SHCLX11REQUEST)); if (pReq) { pReq->enmType = SHCLX11EVENTTYPE_REPORT_FORMATS; pReq->pCtx = pCtx; pReq->Formats.fFormats = uFormats; rc = clipThreadScheduleCall(pCtx, shClX11ReportFormatsToX11Worker, (XtPointer)pReq); if (RT_FAILURE(rc)) RTMemFree(pReq); } else rc = VERR_NO_MEMORY; LogFlowFuncLeaveRC(rc); return rc; } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** * Converts transfer data to a format returned back to X11. * * @returns VBox status code. * @param pszSrc Transfer data to convert. * @param cbSrc Size of transfer data (in bytes) to convert. * @param enmFmtX11 X11 format to convert data to. * @param ppvDst Where to return converted data on success. Must be free'd with XtFree(). * @param pcbDst Where to return the bytes of the converted data on success. Optional. */ int ShClX11TransferConvertToX11(const char *pszSrc, size_t cbSrc, SHCLX11FMT enmFmtX11, void **ppvDst, size_t *pcbDst) { AssertPtrReturn(pszSrc, VERR_INVALID_POINTER); AssertReturn(cbSrc, VERR_INVALID_PARAMETER); AssertPtrReturn(ppvDst, VERR_INVALID_POINTER); /* pcbDst is optional. */ int rc = VINF_SUCCESS; char *pszDst = NULL; # ifdef DEBUG_andy LogFlowFunc(("Src:\n%.*RhXd\n", cbSrc, pszSrc)); # endif switch (enmFmtX11) { case SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES: RT_FALL_THROUGH(); case SHCLX11FMT_URI_LIST_MATE_COPIED_FILES: RT_FALL_THROUGH(); case SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD: RT_FALL_THROUGH(); case SHCLX11FMT_URI_LIST_KDE_CUTSELECTION: { const char chSep = '\n'; /* Currently (?) all entries need to be separated by '\n'. */ /* Note: There must be *no* final new line ('\n') at the end, otherwise Nautilus will crash! */ pszDst = RTStrAPrintf2("copy%c%s", chSep, pszSrc); if (!pszDst) rc = VERR_NO_MEMORY; break; } case SHCLX11FMT_URI_LIST: { pszDst = RTStrDup(pszSrc); AssertPtrBreakStmt(pszDst, rc = VERR_NO_MEMORY); break; } default: AssertFailed(); /* Most likely a bug in the code; let me know. */ break; } if (RT_SUCCESS(rc)) { size_t const cbDst = RTStrNLen(pszDst, RTSTR_MAX); void *pvDst = (void *)XtMalloc(cbDst); if (pvDst) { memcpy(pvDst, pszDst, cbDst); # ifdef DEBUG_andy LogFlowFunc(("Dst:\n%.*RhXd\n", cbDst, pvDst)); # endif } else rc = VERR_NO_MEMORY; if (pcbDst) *pcbDst = cbDst; *ppvDst = pvDst; RTStrFree(pszDst); pszDst = NULL; } LogFlowFuncLeaveRC(rc); return rc; } /** * Converts X11 data to a string list usable for transfers. * * @returns VBox status code. * @param pvData Data to conver to a string list. * @param cbData Size (in bytes) of \a pvData. * @param ppszList Where to return the allocated string list on success. * Must be free'd with RTStrFree(). * @param pcbList Size (in bytes) of the returned string list on success. * Includes terminator. */ int ShClX11TransferConvertFromX11(const char *pvData, size_t cbData, char **ppszList, size_t *pcbList) { AssertPtrReturn(pvData, VERR_INVALID_POINTER); AssertReturn(cbData, VERR_INVALID_PARAMETER); AssertPtrNullReturn(ppszList, VERR_INVALID_POINTER); AssertPtrReturn(pcbList, VERR_INVALID_POINTER); /* For URI lists we only accept valid UTF-8 encodings. */ int rc = RTStrValidateEncodingEx((char *)pvData, cbData, 0 /* fFlags */); if (RT_FAILURE(rc)) return rc; /* We might need to skip some prefixes before actually reaching the file list. */ static const char *s_aszPrefixesToSkip[] = { "copy\n" /* Nautilus / Nemo */ }; for (size_t i = 0; i < RT_ELEMENTS(s_aszPrefixesToSkip); i++) { const char *pszNeedle = RTStrStr(pvData, s_aszPrefixesToSkip[i]); if (pszNeedle) { size_t const cbNeedle = strlen(s_aszPrefixesToSkip[i]); pszNeedle += cbNeedle; pvData = pszNeedle; Assert(cbData >= cbNeedle); cbData -= cbNeedle; } } *pcbList = 0; # ifdef DEBUG_andy LogFlowFunc(("Data:\n%.*RhXd\n", cbData, pvData)); # endif char **papszStrings; size_t cStrings; rc = RTStrSplit(pvData, cbData, SHCL_TRANSFER_URI_LIST_SEP_STR, &papszStrings, &cStrings); if (RT_SUCCESS(rc)) { for (size_t i = 0; i < cStrings; i++) { const char *pszString = papszStrings[i]; LogRel2(("Shared Clipboard: Received entry #%zu from X11: '%s'\n", i, pszString)); rc = RTStrAAppend(ppszList, pszString); if (RT_FAILURE(rc)) break; *pcbList += strlen(pszString); } *pcbList++; /* Include terminator. */ } return rc; } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ /** * Worker function for clipConvertDataFromX11. * * Converts the data read from the X11 clipboard to the required format. * Signals the wait event. * * Converts the text obtained UTF-16LE with Windows EOLs. * Converts full BMP data to DIB format. * * @thread X11 event thread. */ SHCL_X11_DECL(void) clipConvertDataFromX11Worker(void *pClient, void *pvSrc, unsigned cbSrc) { PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)pClient; AssertPtrReturnVoid(pReq); LogFlowFunc(("uFmtVBox=%#x, idxFmtX11=%u, pvSrc=%p, cbSrc=%u\n", pReq->Read.uFmtVBox, pReq->Read.idxFmtX11, pvSrc, cbSrc)); /* Sanity. */ AssertReturnVoid(pReq->enmType == SHCLX11EVENTTYPE_READ); AssertReturnVoid(pReq->Read.uFmtVBox != VBOX_SHCL_FMT_NONE); AssertReturnVoid(pReq->Read.idxFmtX11 < SHCL_MAX_X11_FORMATS); AssertPtrReturnVoid(pReq->pCtx); LogRel2(("Shared Clipboard: Converting X11 format index %#x to VBox format %#x (%RU32 bytes max)\n", pReq->Read.idxFmtX11, pReq->Read.uFmtVBox, pReq->Read.cbMax)); int rc = VINF_SUCCESS; void *pvDst = NULL; size_t cbDst = 0; PSHCLX11CTX pCtx = pReq->pCtx; AssertPtr(pReq->pCtx); #ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY 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_SHCLPB_NO_DATA; } else if (pReq->Read.uFmtVBox == VBOX_SHCL_FMT_UNICODETEXT) { /* In which format is the clipboard data? */ switch (clipRealFormatForX11Format(pReq->Read.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. */ LogFlowFunc(("UTF-16 text (%zu bytes):\n%ls\n", cbDst, pvDst)); } break; } default: { rc = VERR_INVALID_PARAMETER; break; } } } else if (pReq->Read.uFmtVBox == VBOX_SHCL_FMT_BITMAP) { /* In which format is the clipboard data? */ switch (clipRealFormatForX11Format(pReq->Read.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->Read.uFmtVBox == VBOX_SHCL_FMT_HTML) { /* In which format is the clipboard data? */ switch (clipRealFormatForX11Format(pReq->Read.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->Read.uFmtVBox == VBOX_SHCL_FMT_URI_LIST) { /* In which format is the clipboard data? */ switch (clipRealFormatForX11Format(pReq->Read.idxFmtX11)) { case SHCLX11FMT_URI_LIST: RT_FALL_THROUGH(); case SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES: RT_FALL_THROUGH(); case SHCLX11FMT_URI_LIST_MATE_COPIED_FILES: RT_FALL_THROUGH(); case SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD: RT_FALL_THROUGH(); case SHCLX11FMT_URI_LIST_KDE_CUTSELECTION: { rc = ShClX11TransferConvertFromX11((const char *)pvSrc, cbSrc, (char **)&pvDst, &cbDst); break; } default: { AssertFailedStmt(rc = VERR_NOT_SUPPORTED); /* Missing code? */ break; } } } # endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ else rc = VERR_NOT_SUPPORTED; LogFlowFunc(("pvDst=%p, cbDst=%RU32\n", pvDst, cbDst)); if (RT_FAILURE(rc)) LogRel(("Shared Clipboard: Converting X11 format index %#x to VBox format %#x failed, rc=%Rrc\n", pReq->Read.idxFmtX11, pReq->Read.uFmtVBox, rc)); PSHCLEVENTPAYLOAD pPayload = NULL; if ( pvDst && cbDst) { size_t cbResp = sizeof(SHCLX11RESPONSE); PSHCLX11RESPONSE pResp = (PSHCLX11RESPONSE)RTMemAllocZ(cbResp); if (pResp) { pResp->enmType = SHCLX11EVENTTYPE_READ; pResp->Read.pvData = pvDst; pResp->Read.cbData = cbDst; pvDst = NULL; /* The response owns the data now. */ rc = ShClPayloadInit(0 /* ID, unused */, pResp, cbResp, &pPayload); } else rc = VERR_NO_MEMORY; } /* Let the caller know in any case. */ int rc2 = ShClEventSignal(pReq->pEvent, pPayload); if (RT_SUCCESS(rc2)) pPayload = NULL; /* The event owns the payload now. */ if (RT_SUCCESS(rc)) rc = rc2; if (pPayload) /* Free payload on error. */ { ShClPayloadFree(pPayload); pPayload = NULL; } LogRel2(("Shared Clipboard: Converting X11 clipboard data completed with %Rrc\n", rc)); RTMemFree(pReq); RTMemFree(pvDst); LogFlowFuncLeaveRC(rc); } /** * Converts the data read from the X11 clipboard to the required format. * * @thread X11 event thread. */ SHCL_X11_DECL(void) clipConvertDataFromX11(Widget widget, XtPointer pClient, Atom * /* selection */, Atom *atomType, XtPointer pvSrc, long unsigned int *pcLen, int *piFormat) { RT_NOREF(widget); int rc = VINF_SUCCESS; if (*atomType == XT_CONVERT_FAIL) /* Xt timeout */ { LogRel(("Shared Clipboard: Reading clipboard data from X11 timed out\n")); rc = VERR_TIMEOUT; } else { PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)pClient; if (pReq) /* Give some more clues, if available. */ { AssertReturnVoid(pReq->enmType == SHCLX11EVENTTYPE_READ); char *pszFmts = ShClFormatsToStrA(pReq->Read.uFmtVBox); AssertPtrReturnVoid(pszFmts); AssertReturnVoid(pReq->Read.idxFmtX11 < SHCL_MAX_X11_FORMATS); /* Paranoia, should be checked already by the caller. */ LogRel2(("Shared Clipboard: Converting X11 format '%s' -> VBox format(s) '%s'\n", g_aFormats[pReq->Read.idxFmtX11].pcszAtom, pszFmts)); RTStrFree(pszFmts); if (pReq->pCtx->Callbacks.pfnOnClipboardRead) /* Usually only used for testcases. */ { void *pvData = NULL; size_t cbData = 0; rc = pReq->pCtx->Callbacks.pfnOnClipboardRead(pReq->pCtx->pFrontend, pReq->Read.uFmtVBox, &pvData, &cbData, NULL); if (RT_SUCCESS(rc)) { /* Feed to conversion worker. */ clipConvertDataFromX11Worker(pClient, pvData, cbData); RTMemFree(pvData); } } else /* Call conversion worker with current data provided by X (default). */ clipConvertDataFromX11Worker(pClient, pvSrc, (*pcLen) * (*piFormat) / 8); } else rc = VERR_INVALID_POINTER; } if (RT_FAILURE(rc)) { LogRel(("Shared Clipboard: Reading clipboard data from X11 failed with %Rrc\n", rc)); /* Make sure to complete the request in any case by calling the conversion worker. */ clipConvertDataFromX11Worker(pClient, NULL, 0); } XtFree((char *)pvSrc); } /** * Requests the current clipboard data from a specific selection. * * @returns VBox status code. * @param pCtx The X11 clipboard context to use. * @param pszWhere Clipboard selection to request the data from. * @param idxFmt The X11 format to request the data in. * @param pReq Where to store the requested data on success. */ static int clipGetSelectionValueEx(PSHCLX11CTX pCtx, const char *pszWhere, SHCLX11FMTIDX idxFmt, PSHCLX11REQUEST pReq) { AssertPtrReturn(pszWhere, VERR_INVALID_POINTER); AssertReturn(idxFmt < SHCL_MAX_X11_FORMATS, VERR_INVALID_PARAMETER); AssertReturn(clipIsSupportedSelectionType(pCtx, clipGetAtom(pCtx, pszWhere)), VERR_INVALID_PARAMETER); AssertPtrReturn(pReq, VERR_INVALID_POINTER); LogRel2(("Shared Clipboard: Requesting X11 selection value in %s for format '%s'\n", pszWhere, g_aFormats[idxFmt].pcszAtom)); #ifndef TESTCASE XtGetSelectionValue(pCtx->pWidget, clipGetAtom(pCtx, pszWhere), clipAtomForX11Format(pCtx, idxFmt), clipConvertDataFromX11, reinterpret_cast(pReq), CurrentTime); #else tstClipRequestData(pCtx, idxFmt, (void *)pReq); #endif return VINF_SUCCESS; /** @todo Return real rc. */ } /** * Requests the current clipboard data from the CLIPBOARD selection. * * @returns VBox status code. * @param pCtx The X11 clipboard context to use. * @param idxFmt The X11 format to request the data in. * @param pReq Where to store the requested data on success. * * @sa clipGetSelectionValueEx() for requesting data for a specific selection. */ static int clipGetSelectionValue(PSHCLX11CTX pCtx, SHCLX11FMTIDX idxFmt, PSHCLX11REQUEST pReq) { return clipGetSelectionValueEx(pCtx, "CLIPBOARD", idxFmt, pReq); } /** * Worker function for ShClX11ReadDataFromX11Async. * * @param pvUserData Pointer to a PSHCLX11REQUEST structure containing * information about the clipboard read request. * Must be free'd by the worker. * @thread X11 event thread. */ static void ShClX11ReadDataFromX11Worker(void *pvUserData, void * /* interval */) { AssertPtrReturnVoid(pvUserData); PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)pvUserData; AssertReturnVoid(pReq->enmType == SHCLX11EVENTTYPE_READ); SHCLX11CTX *pCtx = pReq->pCtx; AssertPtrReturnVoid(pCtx); LogFlowFunc(("pReq->uFmtVBox=%#x, idxFmtX11=%#x\n", pReq->Read.uFmtVBox, pReq->Read.idxFmtX11)); int rc = VERR_SHCLPB_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->Read.uFmtVBox & VBOX_SHCL_FMT_UNICODETEXT) { pReq->Read.idxFmtX11 = pCtx->idxFmtText; if (pReq->Read.idxFmtX11 != SHCLX11FMT_INVALID) { /* Send out a request for the data to the current clipboard owner. */ rc = clipGetSelectionValue(pCtx, pCtx->idxFmtText, pReq); } } else if (pReq->Read.uFmtVBox & VBOX_SHCL_FMT_BITMAP) { pReq->Read.idxFmtX11 = pCtx->idxFmtBmp; if (pReq->Read.idxFmtX11 != SHCLX11FMT_INVALID) { /* Send out a request for the data to the current clipboard owner. */ rc = clipGetSelectionValue(pCtx, pCtx->idxFmtBmp, pReq); } } else if (pReq->Read.uFmtVBox & VBOX_SHCL_FMT_HTML) { pReq->Read.idxFmtX11 = pCtx->idxFmtHTML; if (pReq->Read.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->Read.uFmtVBox & VBOX_SHCL_FMT_URI_LIST) { pReq->Read.idxFmtX11 = pCtx->idxFmtURI; if (pReq->Read.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 the above stuff fails, make sure to let the waiters know. * * Getting the actual selection value via clipGetSelectionValue[Ex]() above will happen in the X event thread, * which has its own signalling then. So this check only handles errors which happens before we put anything * onto the X event thread. */ if (RT_FAILURE(rc)) { int rc2 = ShClEventSignalEx(pReq->pEvent, rc, NULL /* Payload */); AssertRC(rc2); RTMemFree(pReq); } LogRel2(("Shared Clipboard: Reading X11 clipboard data completed with %Rrc\n", rc)); LogFlowFuncLeaveRC(rc); } /** * Reads from the X11 clipboard (asynchronously). * * @returns VBox status code. * @param pCtx Context data for the clipboard backend. * @param uFmt The format that the VBox would like to receive the data in. * @param cbMax Maximum data to read (in bytes). * Specify UINT32_MAX to read as much as available. * @param pEvent Event to use for waiting for data to arrive. * The event's payload will contain the data read. Needs to be free'd with ShClEventRelease(). */ int ShClX11ReadDataFromX11Async(PSHCLX11CTX pCtx, SHCLFORMAT uFmt, uint32_t cbMax, PSHCLEVENT pEvent) { AssertPtrReturn(pEvent, VERR_INVALID_POINTER); if (shClX11HeadlessIsEnabled(pCtx)) return VINF_SUCCESS; int rc = VINF_SUCCESS; PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)RTMemAllocZ(sizeof(SHCLX11REQUEST)); if (pReq) { pReq->enmType = SHCLX11EVENTTYPE_READ; pReq->pCtx = pCtx; pReq->Read.uFmtVBox = uFmt; pReq->Read.cbMax = cbMax; pReq->pEvent = pEvent; /* We use this to schedule a worker function on the event thread. */ rc = clipThreadScheduleCall(pCtx, ShClX11ReadDataFromX11Worker, (XtPointer)pReq); if (RT_FAILURE(rc)) RTMemFree(pReq); } else rc = VERR_NO_MEMORY; LogFlowFuncLeaveRC(rc); return rc; } /** * Reads from the X11 clipboard, internal version. * * @returns VBox status code. * @retval VERR_SHCLPB_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 pEventSource Event source to use. * @param msTimeout Timeout (in ms) for waiting. * @param uFmt The format that the VBox would like to receive the data in. * @param cbMax Maximum size (in bytes) to read. * @param pResp Where to return the allocated SHCLX11RESPONSE on success. * Must be free'd via shClX11ResponseFree() by the caller. */ static int shClX11ReadDataFromX11Internal(PSHCLX11CTX pCtx, PSHCLEVENTSOURCE pEventSource, RTMSINTERVAL msTimeout, SHCLFORMAT uFmt, uint32_t cbMax, PSHCLX11RESPONSE *ppResp) { PSHCLEVENT pEvent; int rc = ShClEventSourceGenerateAndRegisterEvent(pEventSource, &pEvent); if (RT_SUCCESS(rc)) { rc = ShClX11ReadDataFromX11Async(pCtx, uFmt, cbMax, pEvent); if (RT_SUCCESS(rc)) { PSHCLEVENTPAYLOAD pPayload; int rcEvent; rc = ShClEventWaitEx(pEvent, msTimeout, &rcEvent, &pPayload); if (RT_SUCCESS(rc)) { if (pPayload) { AssertReturn(pPayload->cbData == sizeof(SHCLX11RESPONSE), VERR_INVALID_PARAMETER); AssertPtrReturn(pPayload->pvData, VERR_INVALID_POINTER); PSHCLX11RESPONSE pResp = (PSHCLX11RESPONSE)pPayload->pvData; AssertReturn(pResp->enmType == SHCLX11EVENTTYPE_READ, VERR_INVALID_PARAMETER); AssertReturn(pResp->Read.cbData <= cbMax, VERR_BUFFER_OVERFLOW); /* Paranoia. */ pPayload->pvData = NULL; /* pvData (pResp) is owned by ppResp now. */ pPayload->cbData = 0; ShClPayloadFree(pPayload); *ppResp = pResp; } else /* No payload given; could happen on invalid / not-expected formats. */ rc = VERR_SHCLPB_NO_DATA; } else if (rc == VERR_SHCLPB_EVENT_FAILED) rc = rcEvent; } ShClEventRelease(pEvent); } LogFlowFuncLeaveRC(rc); return rc; } /** * Reads from the X11 clipboard, extended version. * * @returns VBox status code. * @retval VERR_SHCLPB_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 pEventSource Event source to use. * @param msTimeout Timeout (in ms) for waiting. * @param uFmt The format that the VBox would like to receive the data in. * @param ppvBuf Where to return the allocated received data on success. * Must be free'd by the caller. * @param pcbBuf Where to return the size (in bytes) of \a ppvBuf. */ int ShClX11ReadDataFromX11Ex(PSHCLX11CTX pCtx, PSHCLEVENTSOURCE pEventSource, RTMSINTERVAL msTimeout, SHCLFORMAT uFmt, void **ppvBuf, uint32_t *pcbBuf) { AssertPtrReturn(pCtx, VERR_INVALID_POINTER); AssertPtrReturn(pEventSource, VERR_INVALID_POINTER); AssertPtrReturn(ppvBuf, VERR_INVALID_POINTER); AssertPtrReturn(pcbBuf, VERR_INVALID_POINTER); if (shClX11HeadlessIsEnabled(pCtx)) { *pcbBuf = 0; return VINF_SUCCESS; } PSHCLX11RESPONSE pResp; int rc = shClX11ReadDataFromX11Internal(pCtx, pEventSource, msTimeout, uFmt, UINT32_MAX, &pResp); if (RT_SUCCESS(rc)) { *ppvBuf = pResp->Read.pvData; *pcbBuf = pResp->Read.cbData; pResp->Read.pvData = NULL; /* Is owned by ppvBuf now. */ pResp->Read.cbData = 0; shClX11ResponseFree(pResp); } LogFlowFuncLeaveRC(rc); return rc; } /** * Reads from the X11 clipboard. * * @returns VBox status code. * @retval VERR_SHCLPB_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 pEventSource Event source to use. * @param msTimeout Timeout (in ms) for waiting. * @param uFmt The format that the VBox would like to receive the data in. * @param pvBuf Where to store the received data on success. * @param cbBuf Size (in bytes) of \a pvBuf. Also marks maximum data to read (in bytes). * @param pcbRead Where to return the read bytes on success. Optional. */ int ShClX11ReadDataFromX11(PSHCLX11CTX pCtx, PSHCLEVENTSOURCE pEventSource, RTMSINTERVAL msTimeout, SHCLFORMAT uFmt, void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) { AssertPtrReturn(pCtx, VERR_INVALID_POINTER); AssertPtrReturn(pEventSource, VERR_INVALID_POINTER); AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); AssertReturn(cbBuf, VERR_INVALID_PARAMETER); /* pcbRead is optional. */ if (shClX11HeadlessIsEnabled(pCtx)) { if (pcbRead) *pcbRead = 0; return VINF_SUCCESS; } PSHCLX11RESPONSE pResp; int rc = shClX11ReadDataFromX11Internal(pCtx, pEventSource, msTimeout, uFmt, cbBuf, &pResp); if (RT_SUCCESS(rc)) { memcpy(pvBuf, pResp->Read.pvData, RT_MIN(cbBuf, pResp->Read.cbData)); if (pcbRead) *pcbRead = pResp->Read.cbData; shClX11ResponseFree(pResp); } LogFlowFuncLeaveRC(rc); return rc; } /** * Writes to the X11 clipboard (asynchronously). * * @returns VBox status code. * @param pCtx Context data for the clipboard backend. * @param uFmts The format(s) to write. * Conversions might be performed, if available. * @param pvBuf Pointer to data to write. * @param cbBuf Size (in bytes) of data to write. * @param pEvent Event to use for waiting for data to get written. * The event's payload will contain the amount of data written. * Needs to be free'd with ShClEventRelease(). */ int ShClX11WriteDataToX11Async(PSHCLX11CTX pCtx, SHCLFORMATS uFmts, const void *pvBuf, uint32_t cbBuf, PSHCLEVENT pEvent) { AssertPtrReturn(pCtx, VERR_INVALID_POINTER); AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); AssertReturn(cbBuf, VERR_INVALID_PARAMETER); /* pEvent not used yet. */ RT_NOREF(pEvent); if (shClX11HeadlessIsEnabled(pCtx)) return VINF_SUCCESS; int rc = ShClCacheSetMultiple(&pCtx->Cache, uFmts, pvBuf, cbBuf); if (RT_SUCCESS(rc)) { clipResetX11Formats(pCtx); clipGrabX11Clipboard(pCtx, uFmts); } return VINF_SUCCESS; } /** * Writes to the X11 clipboard. * * This function currently only is implemented as asynchronous version. * * @returns VBox status code. * @retval VERR_NOT_AVAILABLE the the X11 clipboard is not available. * @retval VERR_TRY_AGAIN if format is supported but data could not be written. * @retval VERR_NOT_IMPLEMENTED if the format is not implemented. * @param pCtx Context data for the clipboard backend. * @param uFmt The format to write. * @param pvBuf Pointer to data to write. Must match format to write. * @param cbBuf Size (in bytes) of data to write. * @param pcbWritten Where to return the written bytes on success. Optional. * Currently always returns the value of \a cbBuf on success. * * @note Text data must be in UTF-8, always. */ int ShClX11WriteDataToX11(PSHCLX11CTX pCtx, PSHCLEVENTSOURCE pEventSource, RTMSINTERVAL msTimeout, SHCLFORMAT uFmt, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) { AssertPtrReturn(pCtx, VERR_INVALID_POINTER); AssertPtrReturn(pEventSource, VERR_INVALID_POINTER); AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); AssertReturn(cbBuf, VERR_INVALID_PARAMETER); /* pcbWritetn is optional. */ RT_NOREF(msTimeout); /* Not used yet. */ int rc = ShClX11WriteDataToX11Async(pCtx, uFmt, pvBuf, cbBuf, NULL /* pEvent */); if (RT_SUCCESS(rc)) { if (pcbWritten) *pcbWritten = cbBuf; } LogFlowFuncLeaveRC(rc); return rc; }