summaryrefslogtreecommitdiffstats
path: root/src/VBox/GuestHost/SharedClipboard
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/GuestHost/SharedClipboard
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp978
-rw-r--r--src/VBox/GuestHost/SharedClipboard/ClipboardEnumFormatEtcImpl-win.cpp205
-rw-r--r--src/VBox/GuestHost/SharedClipboard/ClipboardMIME.cpp53
-rw-r--r--src/VBox/GuestHost/SharedClipboard/ClipboardPath.cpp92
-rw-r--r--src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp382
-rw-r--r--src/VBox/GuestHost/SharedClipboard/Makefile.kmk36
-rw-r--r--src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp1226
-rw-r--r--src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp862
-rw-r--r--src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp3132
-rw-r--r--src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp1322
-rw-r--r--src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp2428
-rw-r--r--src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk76
-rw-r--r--src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp941
-rw-r--r--src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp103
-rw-r--r--src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardQt/Makefile.kmk52
-rw-r--r--src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardQt/tstClipboardQt.cpp47
16 files changed, 11935 insertions, 0 deletions
diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp
new file mode 100644
index 00000000..edf9d6e7
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp
@@ -0,0 +1,978 @@
+/* $Id: ClipboardDataObjectImpl-win.cpp $ */
+/** @file
+ * ClipboardDataObjectImpl-win.cpp - Shared Clipboard IDataObject implementation.
+ */
+
+/*
+ * Copyright (C) 2019-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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <VBox/GuestHost/SharedClipboard-win.h>
+#include <VBox/GuestHost/SharedClipboard-transfers.h>
+
+#include <iprt/win/windows.h>
+#include <iprt/win/shlobj.h>
+#include <iprt/win/shlwapi.h>
+
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/path.h>
+#include <iprt/semaphore.h>
+#include <iprt/uri.h>
+#include <iprt/utf16.h>
+
+#include <iprt/errcore.h>
+#include <VBox/log.h>
+
+/** @todo Also handle Unicode entries.
+ * !!! WARNING: Buggy, doesn't work yet (some memory corruption / garbage in the file name descriptions) !!! */
+//#define VBOX_CLIPBOARD_WITH_UNICODE_SUPPORT 1
+
+SharedClipboardWinDataObject::SharedClipboardWinDataObject(PSHCLTRANSFER pTransfer,
+ LPFORMATETC pFormatEtc, LPSTGMEDIUM pStgMed, ULONG cFormats)
+ : m_enmStatus(Uninitialized)
+ , m_lRefCount(0)
+ , m_cFormats(0)
+ , m_pTransfer(pTransfer)
+ , m_pStream(NULL)
+ , m_uObjIdx(0)
+ , m_fRunning(false)
+ , m_EventListComplete(NIL_RTSEMEVENT)
+ , m_EventTransferComplete(NIL_RTSEMEVENT)
+{
+ AssertPtr(m_pTransfer);
+
+ HRESULT hr;
+
+ ULONG cFixedFormats = 3; /* CFSTR_FILEDESCRIPTORA + CFSTR_FILECONTENTS + CFSTR_PERFORMEDDROPEFFECT */
+#ifdef VBOX_CLIPBOARD_WITH_UNICODE_SUPPORT
+ cFixedFormats++; /* CFSTR_FILEDESCRIPTORW */
+#endif
+ const ULONG cAllFormats = cFormats + cFixedFormats;
+
+ try
+ {
+ m_pFormatEtc = new FORMATETC[cAllFormats];
+ RT_BZERO(m_pFormatEtc, sizeof(FORMATETC) * cAllFormats);
+ m_pStgMedium = new STGMEDIUM[cAllFormats];
+ RT_BZERO(m_pStgMedium, sizeof(STGMEDIUM) * cAllFormats);
+
+ /** @todo Do we need CFSTR_FILENAME / CFSTR_SHELLIDLIST here? */
+
+ /*
+ * Register fixed formats.
+ */
+ unsigned uIdx = 0;
+
+ LogFlowFunc(("Registering CFSTR_FILEDESCRIPTORA ...\n"));
+ m_cfFileDescriptorA = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
+ registerFormat(&m_pFormatEtc[uIdx++], m_cfFileDescriptorA);
+#ifdef VBOX_CLIPBOARD_WITH_UNICODE_SUPPORT
+ LogFlowFunc(("Registering CFSTR_FILEDESCRIPTORW ...\n"));
+ m_cfFileDescriptorW = RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
+ registerFormat(&m_pFormatEtc[uIdx++], m_cfFileDescriptorW);
+#endif
+
+ /* IStream interface, implemented in ClipboardStreamImpl-win.cpp. */
+ LogFlowFunc(("Registering CFSTR_FILECONTENTS ...\n"));
+ m_cfFileContents = RegisterClipboardFormat(CFSTR_FILECONTENTS);
+ registerFormat(&m_pFormatEtc[uIdx++], m_cfFileContents, TYMED_ISTREAM, 0 /* lIndex */);
+
+ /* We want to know from the target what the outcome of the operation was to react accordingly (e.g. abort a transfer). */
+ LogFlowFunc(("Registering CFSTR_PERFORMEDDROPEFFECT ...\n"));
+ m_cfPerformedDropEffect = RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT);
+ registerFormat(&m_pFormatEtc[uIdx++], m_cfPerformedDropEffect, TYMED_HGLOBAL, -1 /* lIndex */, DVASPECT_CONTENT);
+
+ /*
+ * Registration of dynamic formats needed?
+ */
+ LogFlowFunc(("%RU32 dynamic formats\n", cFormats));
+ if (cFormats)
+ {
+ AssertPtr(pFormatEtc);
+ AssertPtr(pStgMed);
+
+ for (ULONG i = 0; i < cFormats; i++)
+ {
+ LogFlowFunc(("Format %RU32: cfFormat=%RI16, tyMed=%RU32, dwAspect=%RU32\n",
+ i, pFormatEtc[i].cfFormat, pFormatEtc[i].tymed, pFormatEtc[i].dwAspect));
+ m_pFormatEtc[cFixedFormats + i] = pFormatEtc[i];
+ m_pStgMedium[cFixedFormats + i] = pStgMed[i];
+ }
+ }
+
+ hr = S_OK;
+ }
+ catch (std::bad_alloc &)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ m_cFormats = cAllFormats;
+ m_enmStatus = Initialized;
+
+ int rc2 = RTSemEventCreate(&m_EventListComplete);
+ AssertRC(rc2);
+ rc2 = RTSemEventCreate(&m_EventTransferComplete);
+ AssertRC(rc2);
+ }
+
+ LogFlowFunc(("cAllFormats=%RU32, hr=%Rhrc\n", cAllFormats, hr));
+}
+
+SharedClipboardWinDataObject::~SharedClipboardWinDataObject(void)
+{
+ LogFlowFuncEnter();
+
+ RTSemEventDestroy(m_EventListComplete);
+ m_EventListComplete = NIL_RTSEMEVENT;
+
+ RTSemEventDestroy(m_EventTransferComplete);
+ m_EventTransferComplete = NIL_RTSEMEVENT;
+
+ if (m_pStream)
+ m_pStream->Release();
+
+ if (m_pFormatEtc)
+ delete[] m_pFormatEtc;
+
+ if (m_pStgMedium)
+ delete[] m_pStgMedium;
+
+ LogFlowFunc(("mRefCount=%RI32\n", m_lRefCount));
+}
+
+/*
+ * IUnknown methods.
+ */
+
+STDMETHODIMP_(ULONG) SharedClipboardWinDataObject::AddRef(void)
+{
+ LONG lCount = InterlockedIncrement(&m_lRefCount);
+ LogFlowFunc(("lCount=%RI32\n", lCount));
+ return lCount;
+}
+
+STDMETHODIMP_(ULONG) SharedClipboardWinDataObject::Release(void)
+{
+ LONG lCount = InterlockedDecrement(&m_lRefCount);
+ LogFlowFunc(("lCount=%RI32\n", m_lRefCount));
+ if (lCount == 0)
+ {
+ delete this;
+ return 0;
+ }
+
+ return lCount;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::QueryInterface(REFIID iid, void **ppvObject)
+{
+ AssertPtrReturn(ppvObject, E_INVALIDARG);
+
+ if ( iid == IID_IDataObject
+ || iid == IID_IUnknown)
+ {
+ AddRef();
+ *ppvObject = this;
+ return S_OK;
+ }
+
+ *ppvObject = 0;
+ return E_NOINTERFACE;
+}
+
+/**
+ * Copies a chunk of data into a HGLOBAL object.
+ *
+ * @returns VBox status code.
+ * @param pvData Data to copy.
+ * @param cbData Size (in bytes) to copy.
+ * @param fFlags GlobalAlloc flags, used for allocating the HGLOBAL block.
+ * @param phGlobal Where to store the allocated HGLOBAL object.
+ */
+int SharedClipboardWinDataObject::copyToHGlobal(const void *pvData, size_t cbData, UINT fFlags, HGLOBAL *phGlobal)
+{
+ AssertPtrReturn(phGlobal, VERR_INVALID_POINTER);
+
+ HGLOBAL hGlobal = GlobalAlloc(fFlags, cbData);
+ if (!hGlobal)
+ return VERR_NO_MEMORY;
+
+ void *pvAlloc = GlobalLock(hGlobal);
+ if (pvAlloc)
+ {
+ CopyMemory(pvAlloc, pvData, cbData);
+ GlobalUnlock(hGlobal);
+
+ *phGlobal = hGlobal;
+
+ return VINF_SUCCESS;
+ }
+
+ GlobalFree(hGlobal);
+ return VERR_ACCESS_DENIED;
+}
+
+/**
+ * Reads (handles) a specific directory reursively and inserts its entry into the
+ * objects's entry list.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Shared Clipboard transfer object to handle.
+ * @param strDir Directory path to handle.
+ */
+int SharedClipboardWinDataObject::readDir(PSHCLTRANSFER pTransfer, const Utf8Str &strDir)
+{
+ LogFlowFunc(("strDir=%s\n", strDir.c_str()));
+
+ SHCLLISTOPENPARMS openParmsList;
+ int rc = ShClTransferListOpenParmsInit(&openParmsList);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrCopy(openParmsList.pszPath, openParmsList.cbPath, strDir.c_str());
+ if (RT_SUCCESS(rc))
+ {
+ SHCLLISTHANDLE hList;
+ rc = ShClTransferListOpen(pTransfer, &openParmsList, &hList);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("strDir=%s -> hList=%RU64\n", strDir.c_str(), hList));
+
+ SHCLLISTHDR hdrList;
+ rc = ShClTransferListGetHeader(pTransfer, hList, &hdrList);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("cTotalObjects=%RU64, cbTotalSize=%RU64\n\n",
+ hdrList.cTotalObjects, hdrList.cbTotalSize));
+
+ for (uint64_t o = 0; o < hdrList.cTotalObjects; o++)
+ {
+ SHCLLISTENTRY entryList;
+ rc = ShClTransferListEntryInit(&entryList);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ShClTransferListRead(pTransfer, hList, &entryList);
+ if (RT_SUCCESS(rc))
+ {
+ if (ShClTransferListEntryIsValid(&entryList))
+ {
+ PSHCLFSOBJINFO pFsObjInfo = (PSHCLFSOBJINFO)entryList.pvInfo;
+ Assert(entryList.cbInfo == sizeof(SHCLFSOBJINFO));
+
+ Utf8Str strPath = strDir + Utf8Str("\\") + Utf8Str(entryList.pszName);
+
+ LogFlowFunc(("\t%s (%RU64 bytes) -> %s\n",
+ entryList.pszName, pFsObjInfo->cbObject, strPath.c_str()));
+
+ if (RTFS_IS_DIRECTORY(pFsObjInfo->Attr.fMode))
+ {
+ FSOBJENTRY objEntry = { strPath.c_str(), *pFsObjInfo };
+
+ m_lstEntries.push_back(objEntry); /** @todo Can this throw? */
+
+ rc = readDir(pTransfer, strPath.c_str());
+ }
+ else if (RTFS_IS_FILE(pFsObjInfo->Attr.fMode))
+ {
+ FSOBJENTRY objEntry = { strPath.c_str(), *pFsObjInfo };
+
+ m_lstEntries.push_back(objEntry); /** @todo Can this throw? */
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ /** @todo Handle symlinks. */
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ ShClTransferListEntryDestroy(&entryList);
+ }
+
+ if ( RT_FAILURE(rc)
+ && pTransfer->Thread.fStop)
+ break;
+ }
+ }
+
+ ShClTransferListClose(pTransfer, hList);
+ }
+ }
+
+ ShClTransferListOpenParmsDestroy(&openParmsList);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Thread for reading transfer data.
+ * The data object needs the (high level, root) transfer listing at the time of ::GetData(), so we need
+ * to block and wait until we have this data (via this thread) and continue.
+ *
+ * @returns VBox status code.
+ * @param ThreadSelf Thread handle. Unused at the moment.
+ * @param pvUser Pointer to user-provided data. Of type SharedClipboardWinDataObject.
+ */
+/* static */
+DECLCALLBACK(int) SharedClipboardWinDataObject::readThread(RTTHREAD ThreadSelf, void *pvUser)
+{
+ RT_NOREF(ThreadSelf);
+
+ LogFlowFuncEnter();
+
+ SharedClipboardWinDataObject *pThis = (SharedClipboardWinDataObject *)pvUser;
+
+ PSHCLTRANSFER pTransfer = pThis->m_pTransfer;
+ AssertPtr(pTransfer);
+
+ pTransfer->Thread.fStarted = true;
+ pTransfer->Thread.fStop = false;
+
+ RTThreadUserSignal(RTThreadSelf());
+
+ LogRel2(("Shared Clipboard: Calculating transfer ...\n"));
+
+ PSHCLROOTLIST pRootList;
+ int rc = ShClTransferRootsGet(pTransfer, &pRootList);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("cRoots=%RU32\n\n", pRootList->Hdr.cRoots));
+
+ for (uint32_t i = 0; i < pRootList->Hdr.cRoots; i++)
+ {
+ PSHCLLISTENTRY pRootEntry = &pRootList->paEntries[i];
+ AssertPtr(pRootEntry);
+
+ Assert(pRootEntry->cbInfo == sizeof(SHCLFSOBJINFO));
+ PSHCLFSOBJINFO pFsObjInfo = (PSHCLFSOBJINFO)pRootEntry->pvInfo;
+
+ LogFlowFunc(("pszRoot=%s, fMode=0x%x\n", pRootEntry->pszName, pFsObjInfo->Attr.fMode));
+
+ if (RTFS_IS_DIRECTORY(pFsObjInfo->Attr.fMode))
+ {
+ FSOBJENTRY objEntry = { pRootEntry->pszName, *pFsObjInfo };
+
+ pThis->m_lstEntries.push_back(objEntry); /** @todo Can this throw? */
+
+ rc = pThis->readDir(pTransfer, pRootEntry->pszName);
+ }
+ else if (RTFS_IS_FILE(pFsObjInfo->Attr.fMode))
+ {
+ FSOBJENTRY objEntry = { pRootEntry->pszName, *pFsObjInfo };
+
+ pThis->m_lstEntries.push_back(objEntry); /** @todo Can this throw? */
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ if (ASMAtomicReadBool(&pTransfer->Thread.fStop))
+ {
+ LogRel2(("Shared Clipboard: Stopping transfer calculation ...\n"));
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ ShClTransferRootListFree(pRootList);
+ pRootList = NULL;
+
+ if ( RT_SUCCESS(rc)
+ && !ASMAtomicReadBool(&pTransfer->Thread.fStop))
+ {
+ LogRel2(("Shared Clipboard: Transfer calculation complete (%zu root entries)\n", pThis->m_lstEntries.size()));
+
+ /*
+ * Signal the "list complete" event so that this data object can return (valid) data via ::GetData().
+ * This in turn then will create IStream instances (by the OS) for each file system object to handle.
+ */
+ int rc2 = RTSemEventSignal(pThis->m_EventListComplete);
+ AssertRC(rc2);
+
+ if (pThis->m_lstEntries.size())
+ {
+ LogRel2(("Shared Clipboard: Waiting for transfer to complete ...\n"));
+
+ LogFlowFunc(("Waiting for transfer to complete ...\n"));
+
+ /* Transferring stuff can take a while, so don't use any timeout here. */
+ rc2 = RTSemEventWait(pThis->m_EventTransferComplete, RT_INDEFINITE_WAIT);
+ AssertRC(rc2);
+
+ switch (pThis->m_enmStatus)
+ {
+ case Completed:
+ LogRel2(("Shared Clipboard: Transfer complete\n"));
+ break;
+
+ case Canceled:
+ LogRel2(("Shared Clipboard: Transfer canceled\n"));
+ break;
+
+ case Error:
+ LogRel2(("Shared Clipboard: Transfer error occurred\n"));
+ break;
+
+ default:
+ break;
+ }
+ }
+ else
+ LogRel(("Shared Clipboard: No transfer root entries found -- should not happen, please file a bug report\n"));
+ }
+ else if (RT_FAILURE(rc))
+ LogRel(("Shared Clipboard: Transfer failed with %Rrc\n", rc));
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Creates a FILEGROUPDESCRIPTOR object from a given Shared Clipboard transfer and stores the result into an HGLOBAL object.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Shared Clipboard transfer to create file grou desciprtor for.
+ * @param fUnicode Whether the FILEGROUPDESCRIPTOR object shall contain Unicode data or not.
+ * @param phGlobal Where to store the allocated HGLOBAL object on success.
+ */
+int SharedClipboardWinDataObject::createFileGroupDescriptorFromTransfer(PSHCLTRANSFER pTransfer,
+ bool fUnicode, HGLOBAL *phGlobal)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(phGlobal, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ const size_t cbFileGroupDescriptor = fUnicode ? sizeof(FILEGROUPDESCRIPTORW) : sizeof(FILEGROUPDESCRIPTORA);
+ const size_t cbFileDescriptor = fUnicode ? sizeof(FILEDESCRIPTORW) : sizeof(FILEDESCRIPTORA);
+
+ const UINT cItems = (UINT)m_lstEntries.size(); /** UINT vs. size_t. */
+ if (!cItems)
+ return VERR_NOT_FOUND;
+
+ UINT curIdx = 0; /* Current index of the handled file group descriptor (FGD). */
+
+ const size_t cbFGD = cbFileGroupDescriptor + (cbFileDescriptor * (cItems - 1));
+
+ LogFunc(("fUnicode=%RTbool, cItems=%u, cbFileDescriptor=%zu\n", fUnicode, cItems, cbFileDescriptor));
+
+ /* FILEGROUPDESCRIPTORA / FILEGROUPDESCRIPTOR matches except the cFileName member (TCHAR vs. WCHAR). */
+ FILEGROUPDESCRIPTOR *pFGD = (FILEGROUPDESCRIPTOR *)RTMemAllocZ(cbFGD);
+ if (!pFGD)
+ return VERR_NO_MEMORY;
+
+ int rc = VINF_SUCCESS;
+
+ pFGD->cItems = cItems;
+
+ char *pszFileSpec = NULL;
+
+ FsObjEntryList::const_iterator itRoot = m_lstEntries.begin();
+ while (itRoot != m_lstEntries.end())
+ {
+ FILEDESCRIPTOR *pFD = &pFGD->fgd[curIdx];
+ RT_BZERO(pFD, cbFileDescriptor);
+
+ const char *pszFile = itRoot->strPath.c_str();
+ AssertPtr(pszFile);
+
+ pszFileSpec = RTStrDup(pszFile);
+ AssertBreakStmt(pszFileSpec != NULL, rc = VERR_NO_MEMORY);
+
+ if (fUnicode)
+ {
+ PRTUTF16 pwszFileSpec;
+ rc = RTStrToUtf16(pszFileSpec, &pwszFileSpec);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTUtf16CopyEx((PRTUTF16 )pFD->cFileName, sizeof(pFD->cFileName) / sizeof(WCHAR),
+ pwszFileSpec, RTUtf16Len(pwszFileSpec));
+ RTUtf16Free(pwszFileSpec);
+
+ LogFlowFunc(("pFD->cFileNameW=%ls\n", pFD->cFileName));
+ }
+ }
+ else
+ {
+ rc = RTStrCopy(pFD->cFileName, sizeof(pFD->cFileName), pszFileSpec);
+ LogFlowFunc(("pFD->cFileNameA=%s\n", pFD->cFileName));
+ }
+
+ RTStrFree(pszFileSpec);
+ pszFileSpec = NULL;
+
+ if (RT_FAILURE(rc))
+ break;
+
+ pFD->dwFlags = FD_PROGRESSUI | FD_ATTRIBUTES;
+ if (fUnicode) /** @todo Only >= Vista. */
+ pFD->dwFlags |= FD_UNICODE;
+ pFD->dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
+
+ const SHCLFSOBJINFO *pObjInfo = &itRoot->objInfo;
+
+ if (RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode))
+ {
+ pFD->dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
+ }
+ else if (RTFS_IS_FILE(pObjInfo->Attr.fMode))
+ {
+ pFD->dwFlags |= FD_FILESIZE;
+
+ const uint64_t cbObjSize = pObjInfo->cbObject;
+
+ pFD->nFileSizeHigh = RT_HI_U32(cbObjSize);
+ pFD->nFileSizeLow = RT_LO_U32(cbObjSize);
+ }
+ else if (RTFS_IS_SYMLINK(pObjInfo->Attr.fMode))
+ {
+ /** @todo Implement. */
+ }
+#if 0 /** @todo Implement this. */
+ pFD->dwFlags = FD_ATTRIBUTES | FD_CREATETIME | FD_ACCESSTIME | FD_WRITESTIME | FD_FILESIZE;
+ pFD->dwFileAttributes =
+ pFD->ftCreationTime =
+ pFD->ftLastAccessTime =
+ pFD->ftLastWriteTime =
+#endif
+ ++curIdx;
+ ++itRoot;
+ }
+
+ if (pszFileSpec)
+ RTStrFree(pszFileSpec);
+
+ if (RT_SUCCESS(rc))
+ rc = copyToHGlobal(pFGD, cbFGD, GMEM_MOVEABLE, phGlobal);
+
+ RTMemFree(pFGD);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Retrieves the data stored in this object and store the result in
+ * pMedium.
+ *
+ * @return IPRT status code.
+ * @return HRESULT
+ * @param pFormatEtc
+ * @param pMedium
+ */
+STDMETHODIMP SharedClipboardWinDataObject::GetData(LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium)
+{
+ AssertPtrReturn(pFormatEtc, DV_E_FORMATETC);
+ AssertPtrReturn(pMedium, DV_E_FORMATETC);
+
+ LogFlowFuncEnter();
+
+ LogFlowFunc(("lIndex=%RI32\n", pFormatEtc->lindex));
+
+ /*
+ * Initialize default values.
+ */
+ RT_BZERO(pMedium, sizeof(STGMEDIUM));
+
+ HRESULT hr = DV_E_FORMATETC; /* Play safe. */
+
+ if ( pFormatEtc->cfFormat == m_cfFileDescriptorA
+#ifdef VBOX_CLIPBOARD_WITH_UNICODE_SUPPORT
+ || pFormatEtc->cfFormat == m_cfFileDescriptorW
+#endif
+ )
+ {
+ const bool fUnicode = pFormatEtc->cfFormat == m_cfFileDescriptorW;
+
+ const uint32_t enmTransferStatus = ShClTransferGetStatus(m_pTransfer);
+ RT_NOREF(enmTransferStatus);
+
+ LogFlowFunc(("FormatIndex_FileDescriptor%s, enmTransferStatus=%s, m_fRunning=%RTbool\n",
+ fUnicode ? "W" : "A", ShClTransferStatusToStr(enmTransferStatus), m_fRunning));
+
+ int rc;
+
+ /* The caller can call GetData() several times, so make sure we don't do the same transfer multiple times. */
+ if (!m_fRunning)
+ {
+ /* Start the transfer asynchronously in a separate thread. */
+ rc = ShClTransferRun(m_pTransfer, &SharedClipboardWinDataObject::readThread, this);
+ if (RT_SUCCESS(rc))
+ {
+ m_fRunning = true;
+
+ /* Don't block for too long here, as this also will screw other apps running on the OS. */
+ LogFunc(("Waiting for listing to arrive ...\n"));
+ rc = RTSemEventWait(m_EventListComplete, 30 * 1000 /* 30s timeout */);
+ if (RT_SUCCESS(rc))
+ {
+ LogFunc(("Listing complete\n"));
+ }
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ HGLOBAL hGlobal;
+ rc = createFileGroupDescriptorFromTransfer(m_pTransfer, fUnicode, &hGlobal);
+ if (RT_SUCCESS(rc))
+ {
+ pMedium->tymed = TYMED_HGLOBAL;
+ pMedium->hGlobal = hGlobal;
+ /* Note: hGlobal now is being owned by pMedium / the caller. */
+
+ hr = S_OK;
+ }
+ else /* We can't tell any better to the caller, unfortunately. */
+ hr = E_UNEXPECTED;
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("Shared Clipboard: Data object unable to get data, rc=%Rrc\n", rc));
+ }
+
+ if (pFormatEtc->cfFormat == m_cfFileContents)
+ {
+ if ( pFormatEtc->lindex >= 0
+ && (ULONG)pFormatEtc->lindex < m_lstEntries.size())
+ {
+ m_uObjIdx = pFormatEtc->lindex; /* lIndex of FormatEtc contains the actual index to the object being handled. */
+
+ FSOBJENTRY &fsObjEntry = m_lstEntries.at(m_uObjIdx);
+
+ LogFlowFunc(("FormatIndex_FileContents: m_uObjIdx=%u (entry '%s')\n", m_uObjIdx, fsObjEntry.strPath.c_str()));
+
+ LogRel2(("Shared Clipboard: Receiving object '%s' ...\n", fsObjEntry.strPath.c_str()));
+
+ /* Hand-in the provider so that our IStream implementation can continue working with it. */
+ hr = SharedClipboardWinStreamImpl::Create(this /* pParent */, m_pTransfer,
+ fsObjEntry.strPath.c_str()/* File name */, &fsObjEntry.objInfo /* PSHCLFSOBJINFO */,
+ &m_pStream);
+ if (SUCCEEDED(hr))
+ {
+ /* Hand over the stream to the caller. */
+ pMedium->tymed = TYMED_ISTREAM;
+ pMedium->pstm = m_pStream;
+ }
+ }
+ }
+ else if (pFormatEtc->cfFormat == m_cfPerformedDropEffect)
+ {
+ HGLOBAL hGlobal = GlobalAlloc(GHND, sizeof(DWORD));
+
+ DWORD* pdwDropEffect = (DWORD*)GlobalLock(hGlobal);
+ *pdwDropEffect = DROPEFFECT_COPY;
+
+ GlobalUnlock(hGlobal);
+
+ pMedium->tymed = TYMED_HGLOBAL;
+ pMedium->hGlobal = hGlobal;
+ pMedium->pUnkForRelease = NULL;
+ }
+
+ if ( FAILED(hr)
+ && hr != DV_E_FORMATETC) /* Can happen if the caller queries unknown / unhandled formats. */
+ {
+ LogRel(("Shared Clipboard: Error returning data from data object (%Rhrc)\n", hr));
+ }
+
+ LogFlowFunc(("hr=%Rhrc\n", hr));
+ return hr;
+}
+
+/**
+ * Only required for IStream / IStorage interfaces.
+ *
+ * @return IPRT status code.
+ * @return HRESULT
+ * @param pFormatEtc
+ * @param pMedium
+ */
+STDMETHODIMP SharedClipboardWinDataObject::GetDataHere(LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium)
+{
+ RT_NOREF(pFormatEtc, pMedium);
+ LogFlowFunc(("\n"));
+ return E_NOTIMPL;
+}
+
+/**
+ * Query if this objects supports a specific format.
+ *
+ * @return IPRT status code.
+ * @return HRESULT
+ * @param pFormatEtc
+ */
+STDMETHODIMP SharedClipboardWinDataObject::QueryGetData(LPFORMATETC pFormatEtc)
+{
+ LogFlowFunc(("\n"));
+ return lookupFormatEtc(pFormatEtc, NULL /* puIndex */) ? S_OK : DV_E_FORMATETC;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::GetCanonicalFormatEtc(LPFORMATETC pFormatEtc, LPFORMATETC pFormatEtcOut)
+{
+ RT_NOREF(pFormatEtc);
+ LogFlowFunc(("\n"));
+
+ /* Set this to NULL in any case. */
+ pFormatEtcOut->ptd = NULL;
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::SetData(LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium, BOOL fRelease)
+{
+ if ( pFormatEtc == NULL
+ || pMedium == NULL)
+ return E_INVALIDARG;
+
+ if (pFormatEtc->lindex != -1)
+ return DV_E_LINDEX;
+
+ if (pFormatEtc->tymed != TYMED_HGLOBAL)
+ return DV_E_TYMED;
+
+ if (pFormatEtc->dwAspect != DVASPECT_CONTENT)
+ return DV_E_DVASPECT;
+
+ LogFlowFunc(("cfFormat=%RU16, lookupFormatEtc=%RTbool\n",
+ pFormatEtc->cfFormat, lookupFormatEtc(pFormatEtc, NULL /* puIndex */)));
+
+ /* CFSTR_PERFORMEDDROPEFFECT is used by the drop target (caller of this IDataObject) to communicate
+ * the outcome of the overall operation. */
+ if ( pFormatEtc->cfFormat == m_cfPerformedDropEffect
+ && pMedium->tymed == TYMED_HGLOBAL)
+ {
+ DWORD dwEffect = *(DWORD *)GlobalLock(pMedium->hGlobal);
+ GlobalUnlock(pMedium->hGlobal);
+
+ LogFlowFunc(("dwEffect=%RI32\n", dwEffect));
+
+ /* Did the user cancel the operation via UI (shell)? This also might happen when overwriting an existing file
+ * and the user doesn't want to allow this. */
+ if (dwEffect == DROPEFFECT_NONE)
+ {
+ LogRel2(("Shared Clipboard: Transfer canceled by user interaction\n"));
+
+ OnTransferCanceled();
+ }
+ /** @todo Detect move / overwrite actions here. */
+
+ if (fRelease)
+ ReleaseStgMedium(pMedium);
+
+ return S_OK;
+ }
+
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppEnumFormatEtc)
+{
+ LogFlowFunc(("dwDirection=%RI32, mcFormats=%RI32, mpFormatEtc=%p\n", dwDirection, m_cFormats, m_pFormatEtc));
+
+ HRESULT hr;
+ if (dwDirection == DATADIR_GET)
+ hr = SharedClipboardWinEnumFormatEtc::CreateEnumFormatEtc(m_cFormats, m_pFormatEtc, ppEnumFormatEtc);
+ else
+ hr = E_NOTIMPL;
+
+ LogFlowFunc(("hr=%Rhrc\n", hr));
+ return hr;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::DAdvise(LPFORMATETC pFormatEtc, DWORD fAdvise, IAdviseSink *pAdvSink, DWORD *pdwConnection)
+{
+ RT_NOREF(pFormatEtc, fAdvise, pAdvSink, pdwConnection);
+ return OLE_E_ADVISENOTSUPPORTED;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::DUnadvise(DWORD dwConnection)
+{
+ RT_NOREF(dwConnection);
+ return OLE_E_ADVISENOTSUPPORTED;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::EnumDAdvise(IEnumSTATDATA **ppEnumAdvise)
+{
+ RT_NOREF(ppEnumAdvise);
+ return OLE_E_ADVISENOTSUPPORTED;
+}
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_WIN_ASYNC
+/*
+ * IDataObjectAsyncCapability methods.
+ */
+
+STDMETHODIMP SharedClipboardWinDataObject::EndOperation(HRESULT hResult, IBindCtx *pbcReserved, DWORD dwEffects)
+{
+ RT_NOREF(hResult, pbcReserved, dwEffects);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::GetAsyncMode(BOOL *pfIsOpAsync)
+{
+ RT_NOREF(pfIsOpAsync);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::InOperation(BOOL *pfInAsyncOp)
+{
+ RT_NOREF(pfInAsyncOp);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::SetAsyncMode(BOOL fDoOpAsync)
+{
+ RT_NOREF(fDoOpAsync);
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinDataObject::StartOperation(IBindCtx *pbcReserved)
+{
+ RT_NOREF(pbcReserved);
+ return E_NOTIMPL;
+}
+#endif /* VBOX_WITH_SHARED_CLIPBOARD_WIN_ASYNC */
+
+/*
+ * Own stuff.
+ */
+
+int SharedClipboardWinDataObject::Init(void)
+{
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+void SharedClipboardWinDataObject::OnTransferComplete(int rc /* = VINF_SUCESS */)
+{
+ RT_NOREF(rc);
+
+ LogFlowFunc(("m_uObjIdx=%RU32 (total: %zu)\n", m_uObjIdx, m_lstEntries.size()));
+
+ if (RT_SUCCESS(rc))
+ {
+ const bool fComplete = m_uObjIdx == m_lstEntries.size() - 1 /* Object index is zero-based */;
+ if (fComplete)
+ {
+ m_enmStatus = Completed;
+ }
+ }
+ else
+ m_enmStatus = Error;
+
+ if (m_enmStatus != Initialized)
+ {
+ if (m_EventTransferComplete != NIL_RTSEMEVENT)
+ {
+ int rc2 = RTSemEventSignal(m_EventTransferComplete);
+ AssertRC(rc2);
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+}
+
+void SharedClipboardWinDataObject::OnTransferCanceled(void)
+{
+ LogFlowFuncEnter();
+
+ m_enmStatus = Canceled;
+
+ if (m_EventTransferComplete != NIL_RTSEMEVENT)
+ {
+ int rc2 = RTSemEventSignal(m_EventTransferComplete);
+ AssertRC(rc2);
+ }
+
+ LogFlowFuncLeave();
+}
+
+/* static */
+void SharedClipboardWinDataObject::logFormat(CLIPFORMAT fmt)
+{
+ char szFormat[128];
+ if (GetClipboardFormatName(fmt, szFormat, sizeof(szFormat)))
+ {
+ LogFlowFunc(("clipFormat=%RI16 -> %s\n", fmt, szFormat));
+ }
+ else
+ LogFlowFunc(("clipFormat=%RI16 is unknown\n", fmt));
+}
+
+bool SharedClipboardWinDataObject::lookupFormatEtc(LPFORMATETC pFormatEtc, ULONG *puIndex)
+{
+ AssertReturn(pFormatEtc, false);
+ /* puIndex is optional. */
+
+ for (ULONG i = 0; i < m_cFormats; i++)
+ {
+ if( (pFormatEtc->tymed & m_pFormatEtc[i].tymed)
+ && pFormatEtc->cfFormat == m_pFormatEtc[i].cfFormat)
+ /* Note: Do *not* compare dwAspect here, as this can be dynamic, depending on how the object should be represented. */
+ //&& pFormatEtc->dwAspect == m_pFormatEtc[i].dwAspect)
+ {
+ LogRel2(("Shared Clipboard: Format found: tyMed=%RI32, cfFormat=%RI16, dwAspect=%RI32, ulIndex=%RU32\n",
+ pFormatEtc->tymed, pFormatEtc->cfFormat, pFormatEtc->dwAspect, i));
+ if (puIndex)
+ *puIndex = i;
+ return true;
+ }
+ }
+
+ LogRel2(("Shared Clipboard: Format NOT found: tyMed=%RI32, cfFormat=%RI16, dwAspect=%RI32\n",
+ pFormatEtc->tymed, pFormatEtc->cfFormat, pFormatEtc->dwAspect));
+
+ logFormat(pFormatEtc->cfFormat);
+
+ return false;
+}
+
+void SharedClipboardWinDataObject::registerFormat(LPFORMATETC pFormatEtc, CLIPFORMAT clipFormat,
+ TYMED tyMed, LONG lIndex, DWORD dwAspect,
+ DVTARGETDEVICE *pTargetDevice)
+{
+ AssertPtr(pFormatEtc);
+
+ pFormatEtc->cfFormat = clipFormat;
+ pFormatEtc->tymed = tyMed;
+ pFormatEtc->lindex = lIndex;
+ pFormatEtc->dwAspect = dwAspect;
+ pFormatEtc->ptd = pTargetDevice;
+
+ LogFlowFunc(("Registered format=%ld\n", pFormatEtc->cfFormat));
+
+ logFormat(pFormatEtc->cfFormat);
+}
diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardEnumFormatEtcImpl-win.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardEnumFormatEtcImpl-win.cpp
new file mode 100644
index 00000000..3e517a90
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/ClipboardEnumFormatEtcImpl-win.cpp
@@ -0,0 +1,205 @@
+/* $Id: ClipboardEnumFormatEtcImpl-win.cpp $ */
+/** @file
+ * ClipboardEnumFormatEtcImpl-win.cpp - Shared Clipboard IEnumFORMATETC ("Format et cetera") implementation.
+ */
+
+/*
+ * Copyright (C) 2013-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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <new> /* For bad_alloc. */
+
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <VBox/GuestHost/SharedClipboard-win.h>
+
+#include <iprt/win/windows.h>
+
+#include <VBox/log.h>
+
+
+
+SharedClipboardWinEnumFormatEtc::SharedClipboardWinEnumFormatEtc(LPFORMATETC pFormatEtc, ULONG cFormats)
+ : m_lRefCount(1),
+ m_nIndex(0)
+{
+ HRESULT hr;
+
+ try
+ {
+ LogFlowFunc(("pFormatEtc=%p, cFormats=%RU32\n", pFormatEtc, cFormats));
+ m_pFormatEtc = new FORMATETC[cFormats];
+
+ for (ULONG i = 0; i < cFormats; i++)
+ {
+ LogFlowFunc(("Format %RU32: cfFormat=%RI16, tyMed=%RU32, dwAspect=%RU32\n",
+ i, pFormatEtc[i].cfFormat, pFormatEtc[i].tymed, pFormatEtc[i].dwAspect));
+
+ SharedClipboardWinDataObject::logFormat(pFormatEtc[i].cfFormat);
+
+ SharedClipboardWinEnumFormatEtc::CopyFormat(&m_pFormatEtc[i], &pFormatEtc[i]);
+ }
+
+ m_nNumFormats = cFormats;
+ hr = S_OK;
+ }
+ catch (std::bad_alloc &)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+
+ LogFlowFunc(("hr=%Rhrc\n", hr));
+}
+
+SharedClipboardWinEnumFormatEtc::~SharedClipboardWinEnumFormatEtc(void)
+{
+ if (m_pFormatEtc)
+ {
+ for (ULONG i = 0; i < m_nNumFormats; i++)
+ {
+ if(m_pFormatEtc[i].ptd)
+ CoTaskMemFree(m_pFormatEtc[i].ptd);
+ }
+
+ delete[] m_pFormatEtc;
+ m_pFormatEtc = NULL;
+ }
+
+ LogFlowFunc(("m_lRefCount=%RI32\n", m_lRefCount));
+}
+
+/*
+ * IUnknown methods.
+ */
+
+STDMETHODIMP_(ULONG) SharedClipboardWinEnumFormatEtc::AddRef(void)
+{
+ return InterlockedIncrement(&m_lRefCount);
+}
+
+STDMETHODIMP_(ULONG) SharedClipboardWinEnumFormatEtc::Release(void)
+{
+ LONG lCount = InterlockedDecrement(&m_lRefCount);
+ if (lCount == 0)
+ {
+ LogFlowFunc(("Delete\n"));
+ delete this;
+ return 0;
+ }
+
+ return lCount;
+}
+
+STDMETHODIMP SharedClipboardWinEnumFormatEtc::QueryInterface(REFIID iid, void **ppvObject)
+{
+ if ( iid == IID_IEnumFORMATETC
+ || iid == IID_IUnknown)
+ {
+ AddRef();
+ *ppvObject = this;
+ return S_OK;
+ }
+
+ *ppvObject = 0;
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP SharedClipboardWinEnumFormatEtc::Next(ULONG cFormats, LPFORMATETC pFormatEtc, ULONG *pcFetched)
+{
+ ULONG ulCopied = 0;
+
+ if(cFormats == 0 || pFormatEtc == 0)
+ return E_INVALIDARG;
+
+ while ( m_nIndex < m_nNumFormats
+ && ulCopied < cFormats)
+ {
+ SharedClipboardWinEnumFormatEtc::CopyFormat(&pFormatEtc[ulCopied], &m_pFormatEtc[m_nIndex]);
+ ulCopied++;
+ m_nIndex++;
+ }
+
+ if (pcFetched)
+ *pcFetched = ulCopied;
+
+ return (ulCopied == cFormats) ? S_OK : S_FALSE;
+}
+
+STDMETHODIMP SharedClipboardWinEnumFormatEtc::Skip(ULONG cFormats)
+{
+ m_nIndex += cFormats;
+ return (m_nIndex <= m_nNumFormats) ? S_OK : S_FALSE;
+}
+
+STDMETHODIMP SharedClipboardWinEnumFormatEtc::Reset(void)
+{
+ m_nIndex = 0;
+ return S_OK;
+}
+
+STDMETHODIMP SharedClipboardWinEnumFormatEtc::Clone(IEnumFORMATETC **ppEnumFormatEtc)
+{
+ HRESULT hResult = CreateEnumFormatEtc(m_nNumFormats, m_pFormatEtc, ppEnumFormatEtc);
+ if (hResult == S_OK)
+ ((SharedClipboardWinEnumFormatEtc *) *ppEnumFormatEtc)->m_nIndex = m_nIndex;
+
+ return hResult;
+}
+
+/* static */
+void SharedClipboardWinEnumFormatEtc::CopyFormat(LPFORMATETC pDest, LPFORMATETC pSource)
+{
+ AssertPtrReturnVoid(pDest);
+ AssertPtrReturnVoid(pSource);
+
+ *pDest = *pSource;
+
+ if (pSource->ptd)
+ {
+ pDest->ptd = (DVTARGETDEVICE*)CoTaskMemAlloc(sizeof(DVTARGETDEVICE));
+ *(pDest->ptd) = *(pSource->ptd);
+ }
+}
+
+/* static */
+HRESULT SharedClipboardWinEnumFormatEtc::CreateEnumFormatEtc(UINT nNumFormats, LPFORMATETC pFormatEtc, IEnumFORMATETC **ppEnumFormatEtc)
+{
+ AssertReturn(nNumFormats, E_INVALIDARG);
+ AssertPtrReturn(pFormatEtc, E_INVALIDARG);
+ AssertPtrReturn(ppEnumFormatEtc, E_INVALIDARG);
+
+ HRESULT hr;
+ try
+ {
+ *ppEnumFormatEtc = new SharedClipboardWinEnumFormatEtc(pFormatEtc, nNumFormats);
+ hr = S_OK;
+ }
+ catch(std::bad_alloc &)
+ {
+ hr = E_OUTOFMEMORY;
+ }
+
+ return hr;
+}
+
diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardMIME.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardMIME.cpp
new file mode 100644
index 00000000..e8c800b8
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/ClipboardMIME.cpp
@@ -0,0 +1,53 @@
+/* $Id: ClipboardMIME.cpp $ */
+/** @file
+ * Shared Clipboard - Path list class.
+ */
+
+/*
+ * Copyright (C) 2019-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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <VBox/GuestHost/SharedClipboard-transfers.h>
+
+#include <iprt/string.h>
+
+
+bool ShClMIMEHasFileURLs(const char *pcszFormat, size_t cchFormatMax)
+{
+ /** @todo "text/uri" also an official variant? */
+ return ( RTStrNICmp(pcszFormat, "text/uri-list", cchFormatMax) == 0
+ || RTStrNICmp(pcszFormat, "x-special/gnome-icon-list", cchFormatMax) == 0);
+}
+
+bool ShClMIMENeedsCache(const char *pcszFormat, size_t cchFormatMax)
+{
+ bool fNeedsDropDir = false;
+ if (!RTStrNICmp(pcszFormat, "text/uri-list", cchFormatMax)) /** @todo Add "x-special/gnome-icon-list"? */
+ fNeedsDropDir = true;
+
+ return fNeedsDropDir;
+}
+
diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardPath.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardPath.cpp
new file mode 100644
index 00000000..bb9cc3d3
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/ClipboardPath.cpp
@@ -0,0 +1,92 @@
+/* $Id: ClipboardPath.cpp $ */
+/** @file
+ * Shared Clipboard - Path handling.
+ */
+
+/*
+ * Copyright (C) 2019-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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <VBox/GuestHost/SharedClipboard-transfers.h>
+
+#include <iprt/err.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+
+/**
+ * Sanitizes the file name component so that unsupported characters
+ * will be replaced by an underscore ("_").
+ *
+ * @return IPRT status code.
+ * @param pszPath Path to sanitize.
+ * @param cbPath Size (in bytes) of path to sanitize.
+ */
+int ShClPathSanitizeFilename(char *pszPath, size_t cbPath)
+{
+ int rc = VINF_SUCCESS;
+#ifdef RT_OS_WINDOWS
+ RT_NOREF1(cbPath);
+ /* Replace out characters not allowed on Windows platforms, put in by RTTimeSpecToString(). */
+ /** @todo Use something like RTPathSanitize() if available later some time. */
+ static const RTUNICP s_uszValidRangePairs[] =
+ {
+ ' ', ' ',
+ '(', ')',
+ '-', '.',
+ '0', '9',
+ 'A', 'Z',
+ 'a', 'z',
+ '_', '_',
+ 0xa0, 0xd7af,
+ '\0'
+ };
+ ssize_t cReplaced = RTStrPurgeComplementSet(pszPath, s_uszValidRangePairs, '_' /* chReplacement */);
+ if (cReplaced < 0)
+ rc = VERR_INVALID_UTF8_ENCODING;
+#else
+ RT_NOREF2(pszPath, cbPath);
+#endif
+ return rc;
+}
+
+/**
+ * Sanitizes a given path regarding invalid / unhandled characters.
+ * Currently not implemented.
+ *
+ * @returns VBox status code.
+ * @param pszPath Path to sanitize. UTF-8.
+ * @param cbPath Size (in bytes) of the path to sanitize.
+ */
+int ShClPathSanitize(char *pszPath, size_t cbPath)
+{
+ RT_NOREF(pszPath, cbPath);
+
+ /** @todo */
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp
new file mode 100644
index 00000000..38626873
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp
@@ -0,0 +1,382 @@
+/* $Id: ClipboardStreamImpl-win.cpp $ */
+/** @file
+ * ClipboardStreamImpl-win.cpp - Shared Clipboard IStream object implementation (guest and host side).
+ */
+
+/*
+ * Copyright (C) 2019-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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <VBox/GuestHost/SharedClipboard-win.h>
+
+#include <iprt/asm.h>
+#include <iprt/ldr.h>
+#include <iprt/thread.h>
+
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/GuestHost/SharedClipboard-win.h>
+#include <strsafe.h>
+
+#include <VBox/log.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+
+
+/*********************************************************************************************************************************
+* Static variables *
+*********************************************************************************************************************************/
+
+
+
+SharedClipboardWinStreamImpl::SharedClipboardWinStreamImpl(SharedClipboardWinDataObject *pParent, PSHCLTRANSFER pTransfer,
+ const Utf8Str &strPath, PSHCLFSOBJINFO pObjInfo)
+ : m_pParent(pParent)
+ , m_lRefCount(1) /* Our IDataObjct *always* holds the last reference to this object; needed for the callbacks. */
+ , m_pTransfer(pTransfer)
+ , m_hObj(SHCLOBJHANDLE_INVALID)
+ , m_strPath(strPath)
+ , m_objInfo(*pObjInfo)
+ , m_cbProcessed(0)
+ , m_fIsComplete(false)
+{
+ AssertPtr(m_pTransfer);
+
+ LogFunc(("m_strPath=%s\n", m_strPath.c_str()));
+}
+
+SharedClipboardWinStreamImpl::~SharedClipboardWinStreamImpl(void)
+{
+ LogFlowThisFuncEnter();
+}
+
+/*
+ * IUnknown methods.
+ */
+
+STDMETHODIMP SharedClipboardWinStreamImpl::QueryInterface(REFIID iid, void **ppvObject)
+{
+ AssertPtrReturn(ppvObject, E_INVALIDARG);
+
+ if (iid == IID_IUnknown)
+ {
+ LogFlowFunc(("IID_IUnknown\n"));
+ *ppvObject = (IUnknown *)(ISequentialStream *)this;
+ }
+ else if (iid == IID_ISequentialStream)
+ {
+ LogFlowFunc(("IID_ISequentialStream\n"));
+ *ppvObject = (ISequentialStream *)this;
+ }
+ else if (iid == IID_IStream)
+ {
+ LogFlowFunc(("IID_IStream\n"));
+ *ppvObject = (IStream *)this;
+ }
+ else
+ {
+ *ppvObject = NULL;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+}
+
+STDMETHODIMP_(ULONG) SharedClipboardWinStreamImpl::AddRef(void)
+{
+ LONG lCount = InterlockedIncrement(&m_lRefCount);
+ LogFlowFunc(("lCount=%RI32\n", lCount));
+ return lCount;
+}
+
+STDMETHODIMP_(ULONG) SharedClipboardWinStreamImpl::Release(void)
+{
+ LONG lCount = InterlockedDecrement(&m_lRefCount);
+ LogFlowFunc(("lCount=%RI32\n", m_lRefCount));
+ if (lCount == 0)
+ {
+ delete this;
+ return 0;
+ }
+
+ return lCount;
+}
+
+/*
+ * IStream methods.
+ */
+
+STDMETHODIMP SharedClipboardWinStreamImpl::Clone(IStream** ppStream)
+{
+ RT_NOREF(ppStream);
+
+ LogFlowFuncEnter();
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::Commit(DWORD dwFrags)
+{
+ RT_NOREF(dwFrags);
+
+ LogFlowThisFuncEnter();
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::CopyTo(IStream *pDestStream, ULARGE_INTEGER nBytesToCopy, ULARGE_INTEGER *nBytesRead,
+ ULARGE_INTEGER *nBytesWritten)
+{
+ RT_NOREF(pDestStream, nBytesToCopy, nBytesRead, nBytesWritten);
+
+ LogFlowThisFuncEnter();
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::LockRegion(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes,DWORD dwFlags)
+{
+ RT_NOREF(nStart, nBytes, dwFlags);
+
+ LogFlowThisFuncEnter();
+ return STG_E_INVALIDFUNCTION;
+}
+
+/* Note: Windows seems to assume EOF if nBytesRead < nBytesToRead. */
+STDMETHODIMP SharedClipboardWinStreamImpl::Read(void *pvBuffer, ULONG nBytesToRead, ULONG *nBytesRead)
+{
+ LogFlowThisFunc(("Enter: m_cbProcessed=%RU64\n", m_cbProcessed));
+
+ /** @todo Is there any locking required so that parallel reads aren't possible? */
+
+ if (!pvBuffer)
+ return STG_E_INVALIDPOINTER;
+
+ if ( nBytesToRead == 0
+ || m_fIsComplete)
+ {
+ if (nBytesRead)
+ *nBytesRead = 0;
+ return S_OK;
+ }
+
+ int rc;
+
+ try
+ {
+ if ( m_hObj == SHCLOBJHANDLE_INVALID
+ && m_pTransfer->ProviderIface.pfnObjOpen)
+ {
+ SHCLOBJOPENCREATEPARMS openParms;
+ rc = ShClTransferObjOpenParmsInit(&openParms);
+ if (RT_SUCCESS(rc))
+ {
+ openParms.fCreate = SHCL_OBJ_CF_ACCESS_READ
+ | SHCL_OBJ_CF_ACCESS_DENYWRITE;
+
+ rc = RTStrCopy(openParms.pszPath, openParms.cbPath, m_strPath.c_str());
+ if (RT_SUCCESS(rc))
+ {
+ rc = m_pTransfer->ProviderIface.pfnObjOpen(&m_pTransfer->ProviderCtx, &openParms, &m_hObj);
+ }
+
+ ShClTransferObjOpenParmsDestroy(&openParms);
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ uint32_t cbRead = 0;
+
+ const uint64_t cbSize = (uint64_t)m_objInfo.cbObject;
+ const uint32_t cbToRead = RT_MIN(cbSize - m_cbProcessed, nBytesToRead);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (cbToRead)
+ {
+ rc = m_pTransfer->ProviderIface.pfnObjRead(&m_pTransfer->ProviderCtx, m_hObj,
+ pvBuffer, cbToRead, 0 /* fFlags */, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ m_cbProcessed += cbRead;
+ Assert(m_cbProcessed <= cbSize);
+ }
+ }
+
+ /* Transfer complete? Make sure to close the object again. */
+ m_fIsComplete = m_cbProcessed == cbSize;
+
+ if (m_fIsComplete)
+ {
+ if (m_pTransfer->ProviderIface.pfnObjClose)
+ {
+ int rc2 = m_pTransfer->ProviderIface.pfnObjClose(&m_pTransfer->ProviderCtx, m_hObj);
+ AssertRC(rc2);
+ }
+
+ if (m_pParent)
+ m_pParent->OnTransferComplete();
+ }
+ }
+
+ LogFlowThisFunc(("Leave: rc=%Rrc, cbSize=%RU64, cbProcessed=%RU64 -> nBytesToRead=%RU32, cbToRead=%RU32, cbRead=%RU32\n",
+ rc, cbSize, m_cbProcessed, nBytesToRead, cbToRead, cbRead));
+
+ if (nBytesRead)
+ *nBytesRead = (ULONG)cbRead;
+
+ if (nBytesToRead != cbRead)
+ return S_FALSE;
+
+ return S_OK;
+ }
+ catch (...)
+ {
+ LogFunc(("Caught exception\n"));
+ }
+
+ return E_FAIL;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::Revert(void)
+{
+ LogFlowThisFuncEnter();
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::Seek(LARGE_INTEGER nMove, DWORD dwOrigin, ULARGE_INTEGER* nNewPos)
+{
+ RT_NOREF(nMove, dwOrigin, nNewPos);
+
+ LogFlowThisFunc(("nMove=%RI64, dwOrigin=%RI32\n", nMove, dwOrigin));
+
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::SetSize(ULARGE_INTEGER nNewSize)
+{
+ RT_NOREF(nNewSize);
+
+ LogFlowThisFuncEnter();
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::Stat(STATSTG *pStatStg, DWORD dwFlags)
+{
+ HRESULT hr = S_OK;
+
+ if (pStatStg)
+ {
+ RT_ZERO(*pStatStg);
+
+ switch (dwFlags)
+ {
+ case STATFLAG_NONAME:
+ pStatStg->pwcsName = NULL;
+ break;
+
+ case STATFLAG_DEFAULT:
+ {
+ /** @todo r=bird: This is using the wrong allocator. According to MSDN the
+ * caller will pass this to CoTaskMemFree, so we should use CoTaskMemAlloc to
+ * allocate it. */
+ int rc2 = RTStrToUtf16(m_strPath.c_str(), &pStatStg->pwcsName);
+ if (RT_FAILURE(rc2))
+ hr = E_FAIL;
+ break;
+ }
+
+ default:
+ hr = STG_E_INVALIDFLAG;
+ break;
+ }
+
+ if (SUCCEEDED(hr))
+ {
+ pStatStg->type = STGTY_STREAM;
+ pStatStg->grfMode = STGM_READ;
+ pStatStg->grfLocksSupported = 0;
+ pStatStg->cbSize.QuadPart = (uint64_t)m_objInfo.cbObject;
+ }
+ }
+ else
+ hr = STG_E_INVALIDPOINTER;
+
+ LogFlowThisFunc(("hr=%Rhrc\n", hr));
+ return hr;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::UnlockRegion(ULARGE_INTEGER nStart, ULARGE_INTEGER nBytes, DWORD dwFlags)
+{
+ RT_NOREF(nStart, nBytes, dwFlags);
+
+ LogFlowThisFuncEnter();
+ return E_NOTIMPL;
+}
+
+STDMETHODIMP SharedClipboardWinStreamImpl::Write(const void *pvBuffer, ULONG nBytesToRead, ULONG *nBytesRead)
+{
+ RT_NOREF(pvBuffer, nBytesToRead, nBytesRead);
+
+ LogFlowThisFuncEnter();
+ return E_NOTIMPL;
+}
+
+/*
+ * Own stuff.
+ */
+
+/**
+ * Factory to create our own IStream implementation.
+ *
+ * @returns HRESULT
+ * @param pParent Pointer to the parent data object.
+ * @param pTransfer Pointer to Shared Clipboard transfer object to use.
+ * @param strPath Path of object to handle for the stream.
+ * @param pObjInfo Pointer to object information.
+ * @param ppStream Where to return the created stream object on success.
+ */
+/* static */
+HRESULT SharedClipboardWinStreamImpl::Create(SharedClipboardWinDataObject *pParent, PSHCLTRANSFER pTransfer,
+ const Utf8Str &strPath, PSHCLFSOBJINFO pObjInfo,
+ IStream **ppStream)
+{
+ AssertPtrReturn(pTransfer, E_POINTER);
+
+ SharedClipboardWinStreamImpl *pStream = new SharedClipboardWinStreamImpl(pParent, pTransfer, strPath, pObjInfo);
+ if (pStream)
+ {
+ pStream->AddRef();
+
+ *ppStream = pStream;
+ return S_OK;
+ }
+
+ return E_FAIL;
+}
+
diff --git a/src/VBox/GuestHost/SharedClipboard/Makefile.kmk b/src/VBox/GuestHost/SharedClipboard/Makefile.kmk
new file mode 100644
index 00000000..aa3cdc96
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/Makefile.kmk
@@ -0,0 +1,36 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the VirtualBox Shared Clipboard guest/host code.
+#
+
+#
+# Copyright (C) 2020-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 <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+ifdef VBOX_WITH_TESTCASES
+ include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp
new file mode 100644
index 00000000..c1886d31
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp
@@ -0,0 +1,1226 @@
+/* $Id: clipboard-common.cpp $ */
+/** @file
+ * Shared Clipboard: Some helper function for converting between the various eol.
+ */
+
+/*
+ * Includes contributions from François Revol
+ *
+ * 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/semaphore.h>
+#include <iprt/path.h>
+#include <iprt/rand.h>
+#include <iprt/utf16.h>
+
+#include <iprt/formats/bmp.h>
+
+#include <iprt/errcore.h>
+#include <VBox/log.h>
+#include <VBox/GuestHost/clipboard-helper.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+
+
+/*********************************************************************************************************************************
+* Prototypes *
+*********************************************************************************************************************************/
+static void shClEventSourceResetInternal(PSHCLEVENTSOURCE pSource);
+
+static void shClEventDestroy(PSHCLEVENT pEvent);
+DECLINLINE(PSHCLEVENT) shclEventGet(PSHCLEVENTSOURCE pSource, SHCLEVENTID idEvent);
+
+
+/*********************************************************************************************************************************
+* Implementation *
+*********************************************************************************************************************************/
+
+/**
+ * Allocates a new event payload.
+ *
+ * @returns VBox status code.
+ * @param uID Payload ID to set for this payload. Useful for consequtive payloads.
+ * @param pvData Data block to associate to this payload.
+ * @param cbData Size (in bytes) of data block to associate.
+ * @param ppPayload Where to store the allocated event payload on success.
+ */
+int ShClPayloadAlloc(uint32_t uID, const void *pvData, uint32_t cbData,
+ PSHCLEVENTPAYLOAD *ppPayload)
+{
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData > 0, VERR_INVALID_PARAMETER);
+
+ PSHCLEVENTPAYLOAD pPayload = (PSHCLEVENTPAYLOAD)RTMemAlloc(sizeof(SHCLEVENTPAYLOAD));
+ if (pPayload)
+ {
+ pPayload->pvData = RTMemDup(pvData, cbData);
+ if (pPayload->pvData)
+ {
+ pPayload->cbData = cbData;
+ pPayload->uID = uID;
+
+ *ppPayload = pPayload;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pPayload);
+ }
+ return VERR_NO_MEMORY;
+}
+
+/**
+ * Frees an event payload.
+ *
+ * @returns VBox status code.
+ * @param pPayload Event payload to free.
+ */
+void ShClPayloadFree(PSHCLEVENTPAYLOAD pPayload)
+{
+ if (!pPayload)
+ return;
+
+ if (pPayload->pvData)
+ {
+ Assert(pPayload->cbData);
+ RTMemFree(pPayload->pvData);
+ pPayload->pvData = NULL;
+ }
+
+ pPayload->cbData = 0;
+ pPayload->uID = UINT32_MAX;
+
+ RTMemFree(pPayload);
+}
+
+/**
+ * Creates a new event source.
+ *
+ * @returns VBox status code.
+ * @param pSource Event source to create.
+ * @param uID ID to use for event source.
+ */
+int ShClEventSourceCreate(PSHCLEVENTSOURCE pSource, SHCLEVENTSOURCEID uID)
+{
+ LogFlowFunc(("pSource=%p, uID=%RU16\n", pSource, uID));
+ AssertPtrReturn(pSource, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectInit(&pSource->CritSect);
+ AssertRCReturn(rc, rc);
+
+ RTListInit(&pSource->lstEvents);
+
+ pSource->uID = uID;
+ /* Choose a random event ID starting point. */
+ pSource->idNextEvent = RTRandU32Ex(1, VBOX_SHCL_MAX_EVENTS - 1);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys an event source.
+ *
+ * @returns VBox status code.
+ * @param pSource Event source to destroy.
+ */
+int ShClEventSourceDestroy(PSHCLEVENTSOURCE pSource)
+{
+ if (!pSource)
+ return VINF_SUCCESS;
+
+ LogFlowFunc(("ID=%RU32\n", pSource->uID));
+
+ int rc = RTCritSectEnter(&pSource->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ shClEventSourceResetInternal(pSource);
+
+ rc = RTCritSectLeave(&pSource->CritSect);
+ AssertRC(rc);
+
+ RTCritSectDelete(&pSource->CritSect);
+
+ pSource->uID = UINT16_MAX;
+ pSource->idNextEvent = UINT32_MAX;
+ }
+
+ return rc;
+}
+
+/**
+ * Resets an event source, internal version.
+ *
+ * @param pSource Event source to reset.
+ */
+static void shClEventSourceResetInternal(PSHCLEVENTSOURCE pSource)
+{
+ LogFlowFunc(("ID=%RU32\n", pSource->uID));
+
+ PSHCLEVENT pEvIt;
+ PSHCLEVENT pEvItNext;
+ RTListForEachSafe(&pSource->lstEvents, pEvIt, pEvItNext, SHCLEVENT, Node)
+ {
+ RTListNodeRemove(&pEvIt->Node);
+
+ shClEventDestroy(pEvIt);
+
+ RTMemFree(pEvIt);
+ pEvIt = NULL;
+ }
+}
+
+/**
+ * Resets an event source.
+ *
+ * @param pSource Event source to reset.
+ */
+void ShClEventSourceReset(PSHCLEVENTSOURCE pSource)
+{
+ int rc2 = RTCritSectEnter(&pSource->CritSect);
+ if (RT_SUCCESS(rc2))
+ {
+ shClEventSourceResetInternal(pSource);
+
+ rc2 = RTCritSectLeave(&pSource->CritSect);
+ AssertRC(rc2);
+ }
+}
+
+/**
+ * Generates a new event ID for a specific event source and registers it.
+ *
+ * @returns VBox status code.
+ * @param pSource Event source to generate event for.
+ * @param ppEvent Where to return the new event generated on success.
+ */
+int ShClEventSourceGenerateAndRegisterEvent(PSHCLEVENTSOURCE pSource, PSHCLEVENT *ppEvent)
+{
+ AssertPtrReturn(pSource, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppEvent, VERR_INVALID_POINTER);
+
+ PSHCLEVENT pEvent = (PSHCLEVENT)RTMemAllocZ(sizeof(SHCLEVENT));
+ AssertReturn(pEvent, VERR_NO_MEMORY);
+ int rc = RTSemEventMultiCreate(&pEvent->hEvtMulSem);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectEnter(&pSource->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate an unique event ID.
+ */
+ for (uint32_t cTries = 0;; cTries++)
+ {
+ SHCLEVENTID idEvent = ++pSource->idNextEvent;
+ if (idEvent < VBOX_SHCL_MAX_EVENTS)
+ { /* likely */ }
+ else
+ pSource->idNextEvent = idEvent = 1; /* zero == error, remember! */
+
+ if (shclEventGet(pSource, idEvent) == NULL)
+ {
+ pEvent->pParent = pSource;
+ pEvent->idEvent = idEvent;
+ RTListAppend(&pSource->lstEvents, &pEvent->Node);
+
+ rc = RTCritSectLeave(&pSource->CritSect);
+ AssertRC(rc);
+
+ LogFlowFunc(("uSource=%RU16: New event: %#x\n", pSource->uID, idEvent));
+
+ ShClEventRetain(pEvent);
+ *ppEvent = pEvent;
+
+ return VINF_SUCCESS;
+ }
+
+ AssertBreak(cTries < 4096);
+ }
+
+ rc = RTCritSectLeave(&pSource->CritSect);
+ AssertRC(rc);
+ }
+ }
+
+ AssertMsgFailed(("Unable to register a new event ID for event source %RU16\n", pSource->uID));
+
+ RTSemEventMultiDestroy(pEvent->hEvtMulSem);
+ pEvent->hEvtMulSem = NIL_RTSEMEVENTMULTI;
+ RTMemFree(pEvent);
+ return rc;
+}
+
+/**
+ * Destroys an event.
+ *
+ * @param pEvent Event to destroy.
+ */
+static void shClEventDestroy(PSHCLEVENT pEvent)
+{
+ if (!pEvent)
+ return;
+
+ AssertMsgReturnVoid(pEvent->cRefs == 0, ("Event %RU32 still has %RU32 references\n",
+ pEvent->idEvent, pEvent->cRefs));
+
+ LogFlowFunc(("Event %RU32\n", pEvent->idEvent));
+
+ if (pEvent->hEvtMulSem != NIL_RTSEMEVENT)
+ {
+ RTSemEventMultiDestroy(pEvent->hEvtMulSem);
+ pEvent->hEvtMulSem = NIL_RTSEMEVENT;
+ }
+
+ ShClPayloadFree(pEvent->pPayload);
+
+ pEvent->idEvent = NIL_SHCLEVENTID;
+}
+
+/**
+ * Unregisters an event.
+ *
+ * @returns VBox status code.
+ * @param pSource Event source to unregister event for.
+ * @param pEvent Event to unregister. On success the pointer will be invalid.
+ */
+static int shClEventSourceUnregisterEventInternal(PSHCLEVENTSOURCE pSource, PSHCLEVENT pEvent)
+{
+ LogFlowFunc(("idEvent=%RU32, cRefs=%RU32\n", pEvent->idEvent, pEvent->cRefs));
+
+ AssertReturn(pEvent->cRefs == 0, VERR_WRONG_ORDER);
+
+ int rc = RTCritSectEnter(&pSource->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ RTListNodeRemove(&pEvent->Node);
+
+ shClEventDestroy(pEvent);
+
+ rc = RTCritSectLeave(&pSource->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ RTMemFree(pEvent);
+ pEvent = NULL;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Returns a specific event of a event source. Inlined version.
+ *
+ * @returns Pointer to event if found, or NULL if not found.
+ * @param pSource Event source to get event from.
+ * @param uID Event ID to get.
+ */
+DECLINLINE(PSHCLEVENT) shclEventGet(PSHCLEVENTSOURCE pSource, SHCLEVENTID idEvent)
+{
+ PSHCLEVENT pEvent;
+ RTListForEach(&pSource->lstEvents, pEvent, SHCLEVENT, Node)
+ {
+ if (pEvent->idEvent == idEvent)
+ return pEvent;
+ }
+
+ return NULL;
+}
+
+/**
+ * Returns a specific event of a event source.
+ *
+ * @returns Pointer to event if found, or NULL if not found.
+ * @param pSource Event source to get event from.
+ * @param idEvent ID of event to return.
+ */
+PSHCLEVENT ShClEventSourceGetFromId(PSHCLEVENTSOURCE pSource, SHCLEVENTID idEvent)
+{
+ AssertPtrReturn(pSource, NULL);
+
+ int rc = RTCritSectEnter(&pSource->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ PSHCLEVENT pEvent = shclEventGet(pSource, idEvent);
+
+ rc = RTCritSectLeave(&pSource->CritSect);
+ AssertRC(rc);
+
+ return pEvent;
+ }
+
+ return NULL;
+}
+
+/**
+ * Returns the last (newest) event ID which has been registered for an event source.
+ *
+ * @returns Pointer to last registered event, or NULL if not found.
+ * @param pSource Event source to get last registered event from.
+ */
+PSHCLEVENT ShClEventSourceGetLast(PSHCLEVENTSOURCE pSource)
+{
+ AssertPtrReturn(pSource, NULL);
+
+ int rc = RTCritSectEnter(&pSource->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ PSHCLEVENT pEvent = RTListGetLast(&pSource->lstEvents, SHCLEVENT, Node);
+
+ rc = RTCritSectLeave(&pSource->CritSect);
+ AssertRC(rc);
+
+ return pEvent;
+ }
+
+ return NULL;
+}
+
+/**
+ * Returns the current reference count for a specific event.
+ *
+ * @returns Reference count.
+ * @param pSource Event source the specific event is part of.
+ * @param idEvent Event ID to return reference count for.
+ */
+uint32_t ShClEventGetRefs(PSHCLEVENT pEvent)
+{
+ AssertPtrReturn(pEvent, 0);
+
+ return ASMAtomicReadU32(&pEvent->cRefs);
+}
+
+/**
+ * Detaches a payload from an event, internal version.
+ *
+ * @returns Pointer to the detached payload. Can be NULL if the payload has no payload.
+ * @param pEvent Event to detach payload for.
+ */
+static PSHCLEVENTPAYLOAD shclEventPayloadDetachInternal(PSHCLEVENT pEvent)
+{
+#ifdef VBOX_STRICT
+ AssertPtrReturn(pEvent, NULL);
+#endif
+
+ PSHCLEVENTPAYLOAD pPayload = pEvent->pPayload;
+
+ pEvent->pPayload = NULL;
+
+ return pPayload;
+}
+
+/**
+ * Waits for an event to get signalled.
+ *
+ * @returns VBox status code.
+ * @param pEvent Event to wait for.
+ * @param uTimeoutMs Timeout (in ms) to wait.
+ * @param ppPayload Where to store the (allocated) event payload on success. Needs to be free'd with
+ * SharedClipboardPayloadFree(). Optional.
+ */
+int ShClEventWait(PSHCLEVENT pEvent, RTMSINTERVAL uTimeoutMs, PSHCLEVENTPAYLOAD *ppPayload)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(ppPayload, VERR_INVALID_POINTER);
+ LogFlowFuncEnter();
+
+ int rc = RTSemEventMultiWait(pEvent->hEvtMulSem, uTimeoutMs);
+ if (RT_SUCCESS(rc))
+ {
+ if (ppPayload)
+ {
+ /* Make sure to detach payload here, as the caller now owns the data. */
+ *ppPayload = shclEventPayloadDetachInternal(pEvent);
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel2(("Shared Clipboard: Waiting for event %RU32 failed, rc=%Rrc\n", pEvent->idEvent, rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Retains an event by increasing its reference count.
+ *
+ * @returns New reference count, or UINT32_MAX if failed.
+ * @param pEvent Event to retain.
+ */
+uint32_t ShClEventRetain(PSHCLEVENT pEvent)
+{
+ AssertPtrReturn(pEvent, UINT32_MAX);
+ AssertReturn(ASMAtomicReadU32(&pEvent->cRefs) < 64, UINT32_MAX);
+ return ASMAtomicIncU32(&pEvent->cRefs);
+}
+
+/**
+ * Releases an event by decreasing its reference count.
+ *
+ * @returns New reference count, or UINT32_MAX if failed.
+ * @param pEvent Event to release.
+ * If the reference count reaches 0, the event will
+ * be destroyed and \a pEvent will be invalid.
+ */
+uint32_t ShClEventRelease(PSHCLEVENT pEvent)
+{
+ if (!pEvent)
+ return 0;
+
+ AssertReturn(ASMAtomicReadU32(&pEvent->cRefs) > 0, UINT32_MAX);
+
+ uint32_t const cRefs = ASMAtomicDecU32(&pEvent->cRefs);
+ if (cRefs == 0)
+ {
+ AssertPtr(pEvent->pParent);
+ int rc2 = shClEventSourceUnregisterEventInternal(pEvent->pParent, pEvent);
+ AssertRC(rc2);
+
+ return RT_SUCCESS(rc2) ? 0 : UINT32_MAX;
+ }
+
+ return cRefs;
+}
+
+/**
+ * Signals an event.
+ *
+ * @returns VBox status code.
+ * @param pEvent Event to signal.
+ * @param pPayload Event payload to associate. Takes ownership on
+ * success. Optional.
+ */
+int ShClEventSignal(PSHCLEVENT pEvent, PSHCLEVENTPAYLOAD pPayload)
+{
+ AssertPtrReturn(pEvent, VERR_INVALID_POINTER);
+
+ Assert(pEvent->pPayload == NULL);
+
+ pEvent->pPayload = pPayload;
+
+ int rc = RTSemEventMultiSignal(pEvent->hEvtMulSem);
+ if (RT_FAILURE(rc))
+ pEvent->pPayload = NULL; /* (no race condition if consumer also enters the critical section) */
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+int ShClUtf16LenUtf8(PCRTUTF16 pcwszSrc, size_t cwcSrc, size_t *pchLen)
+{
+ AssertPtrReturn(pcwszSrc, VERR_INVALID_POINTER);
+ AssertPtrReturn(pchLen, VERR_INVALID_POINTER);
+
+ size_t chLen = 0;
+ int rc = RTUtf16CalcUtf8LenEx(pcwszSrc, cwcSrc, &chLen);
+ if (RT_SUCCESS(rc))
+ *pchLen = chLen;
+ return rc;
+}
+
+int ShClConvUtf16CRLFToUtf8LF(PCRTUTF16 pcwszSrc, size_t cwcSrc,
+ char *pszBuf, size_t cbBuf, size_t *pcbLen)
+{
+ AssertPtrReturn(pcwszSrc, VERR_INVALID_POINTER);
+ AssertReturn (cwcSrc, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbLen, VERR_INVALID_POINTER);
+
+ int rc;
+
+ PRTUTF16 pwszTmp = NULL;
+ size_t cchTmp = 0;
+
+ size_t cbLen = 0;
+
+ /* How long will the converted text be? */
+ rc = ShClUtf16CRLFLenUtf8(pcwszSrc, cwcSrc, &cchTmp);
+ if (RT_SUCCESS(rc))
+ {
+ cchTmp++; /* Add space for terminator. */
+
+ pwszTmp = (PRTUTF16)RTMemAlloc(cchTmp * sizeof(RTUTF16));
+ if (pwszTmp)
+ {
+ rc = ShClConvUtf16CRLFToLF(pcwszSrc, cwcSrc, pwszTmp, cchTmp);
+ if (RT_SUCCESS(rc))
+ rc = RTUtf16ToUtf8Ex(pwszTmp + 1, cchTmp - 1, &pszBuf, cbBuf, &cbLen);
+
+ RTMemFree(reinterpret_cast<void *>(pwszTmp));
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *pcbLen = cbLen;
+ }
+
+ return rc;
+}
+
+int ShClConvUtf16LFToCRLFA(PCRTUTF16 pcwszSrc, size_t cwcSrc,
+ PRTUTF16 *ppwszDst, size_t *pcwDst)
+{
+ AssertPtrReturn(pcwszSrc, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppwszDst, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcwDst, VERR_INVALID_POINTER);
+
+ PRTUTF16 pwszDst = NULL;
+ size_t cchDst;
+
+ int rc = ShClUtf16LFLenUtf8(pcwszSrc, cwcSrc, &cchDst);
+ if (RT_SUCCESS(rc))
+ {
+ pwszDst = (PRTUTF16)RTMemAlloc((cchDst + 1 /* Leave space for terminator */) * sizeof(RTUTF16));
+ if (pwszDst)
+ {
+ rc = ShClConvUtf16LFToCRLF(pcwszSrc, cwcSrc, pwszDst, cchDst + 1 /* Include terminator */);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppwszDst = pwszDst;
+ *pcwDst = cchDst;
+ }
+ else
+ RTMemFree(pwszDst);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+int ShClConvUtf8LFToUtf16CRLF(const char *pcszSrc, size_t cbSrc,
+ PRTUTF16 *ppwszDst, size_t *pcwDst)
+{
+ AssertPtrReturn(pcszSrc, VERR_INVALID_POINTER);
+ AssertReturn(cbSrc, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ppwszDst, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcwDst, VERR_INVALID_POINTER);
+
+ /* Intermediate conversion to UTF-16. */
+ size_t cwcTmp;
+ PRTUTF16 pwcTmp = NULL;
+ int rc = RTStrToUtf16Ex(pcszSrc, cbSrc, &pwcTmp, 0, &cwcTmp);
+ if (RT_SUCCESS(rc))
+ {
+ rc = ShClConvUtf16LFToCRLFA(pwcTmp, cwcTmp, ppwszDst, pcwDst);
+ RTUtf16Free(pwcTmp);
+ }
+
+ return rc;
+}
+
+/**
+ * Converts a Latin-1 string with LF line endings into an UTF-16 string with CRLF endings.
+ *
+ * @returns VBox status code.
+ * @param pcszSrc Latin-1 string to convert.
+ * @param cbSrc Size (in bytes) of Latin-1 string to convert.
+ * @param ppwszDst Where to return the converted UTF-16 string on success.
+ * @param pcwDst Where to return the length (in UTF-16 characters) on success.
+ *
+ * @note Only converts the source until the string terminator is found (or length limit is hit).
+ */
+int ShClConvLatin1LFToUtf16CRLF(const char *pcszSrc, size_t cbSrc,
+ PRTUTF16 *ppwszDst, size_t *pcwDst)
+{
+ AssertPtrReturn(pcszSrc, VERR_INVALID_POINTER);
+ AssertReturn(cbSrc, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ppwszDst, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcwDst, VERR_INVALID_POINTER);
+
+ size_t chSrc = 0;
+
+ PRTUTF16 pwszDst = NULL;
+
+ /* Calculate the space needed. */
+ size_t cwDst = 0;
+ for (size_t i = 0; i < cbSrc && pcszSrc[i] != '\0'; ++i)
+ {
+ if (pcszSrc[i] == VBOX_SHCL_LINEFEED)
+ cwDst += 2; /* Space for VBOX_SHCL_CARRIAGERETURN + VBOX_SHCL_LINEFEED. */
+ else
+ ++cwDst;
+ chSrc++;
+ }
+
+ pwszDst = (PRTUTF16)RTMemAlloc((cwDst + 1 /* Leave space for the terminator */) * sizeof(RTUTF16));
+ AssertPtrReturn(pwszDst, VERR_NO_MEMORY);
+
+ /* Do the conversion, bearing in mind that Latin-1 expands "naturally" to UTF-16. */
+ for (size_t i = 0, j = 0; i < chSrc; ++i, ++j)
+ {
+ AssertMsg(j <= cwDst, ("cbSrc=%zu, j=%u vs. cwDst=%u\n", cbSrc, j, cwDst));
+ if (pcszSrc[i] != VBOX_SHCL_LINEFEED)
+ pwszDst[j] = pcszSrc[i];
+ else
+ {
+ pwszDst[j] = VBOX_SHCL_CARRIAGERETURN;
+ pwszDst[j + 1] = VBOX_SHCL_LINEFEED;
+ ++j;
+ }
+ }
+
+ pwszDst[cwDst] = '\0'; /* Make sure we are zero-terminated. */
+
+ *ppwszDst = pwszDst;
+ *pcwDst = cwDst;
+
+ return VINF_SUCCESS;
+}
+
+int ShClConvUtf16ToUtf8HTML(PCRTUTF16 pcwszSrc, size_t cwcSrc, char **ppszDst, size_t *pcbDst)
+{
+ AssertPtrReturn(pcwszSrc, VERR_INVALID_POINTER);
+ AssertReturn (cwcSrc, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ppszDst, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbDst, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ size_t cwTmp = cwcSrc;
+ PCRTUTF16 pwTmp = pcwszSrc;
+
+ char *pchDst = NULL;
+ size_t cbDst = 0;
+
+ size_t i = 0;
+ while (i < cwTmp)
+ {
+ /* Find zero symbol (end of string). */
+ for (; i < cwTmp && pcwszSrc[i] != 0; i++)
+ ;
+
+ /* Convert found string. */
+ char *psz = NULL;
+ size_t cch = 0;
+ rc = RTUtf16ToUtf8Ex(pwTmp, cwTmp, &psz, pwTmp - pcwszSrc, &cch);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Append new substring. */
+ char *pchNew = (char *)RTMemRealloc(pchDst, cbDst + cch + 1);
+ if (!pchNew)
+ {
+ RTStrFree(psz);
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ pchDst = pchNew;
+ memcpy(pchDst + cbDst, psz, cch + 1);
+
+ RTStrFree(psz);
+
+ cbDst += cch + 1;
+
+ /* Skip zero symbols. */
+ for (; i < cwTmp && pcwszSrc[i] == 0; i++)
+ ;
+
+ /* Remember start of string. */
+ pwTmp += i;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppszDst = pchDst;
+ *pcbDst = cbDst;
+
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pchDst);
+
+ return rc;
+}
+
+int ShClUtf16LFLenUtf8(PCRTUTF16 pcwszSrc, size_t cwSrc, size_t *pchLen)
+{
+ AssertPtrReturn(pcwszSrc, VERR_INVALID_POINTER);
+ AssertPtrReturn(pchLen, VERR_INVALID_POINTER);
+
+ AssertMsgReturn(pcwszSrc[0] != VBOX_SHCL_UTF16BEMARKER,
+ ("Big endian UTF-16 not supported yet\n"), VERR_NOT_SUPPORTED);
+
+ size_t cLen = 0;
+
+ /* Don't copy the endian marker. */
+ size_t i = pcwszSrc[0] == VBOX_SHCL_UTF16LEMARKER ? 1 : 0;
+
+ /* Calculate the size of the destination text string. */
+ /* Is this Utf16 or Utf16-LE? */
+ for (; i < cwSrc; ++i, ++cLen)
+ {
+ /* Check for a single line feed */
+ if (pcwszSrc[i] == VBOX_SHCL_LINEFEED)
+ ++cLen;
+#ifdef RT_OS_DARWIN
+ /* Check for a single carriage return (MacOS) */
+ if (pcwszSrc[i] == VBOX_SHCL_CARRIAGERETURN)
+ ++cLen;
+#endif
+ if (pcwszSrc[i] == 0)
+ {
+ /* Don't count this, as we do so below. */
+ break;
+ }
+ }
+
+ *pchLen = cLen;
+
+ return VINF_SUCCESS;
+}
+
+int ShClUtf16CRLFLenUtf8(PCRTUTF16 pcwszSrc, size_t cwSrc, size_t *pchLen)
+{
+ AssertPtrReturn(pcwszSrc, VERR_INVALID_POINTER);
+ AssertReturn(cwSrc, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pchLen, VERR_INVALID_POINTER);
+
+ AssertMsgReturn(pcwszSrc[0] != VBOX_SHCL_UTF16BEMARKER,
+ ("Big endian UTF-16 not supported yet\n"), VERR_NOT_SUPPORTED);
+
+ size_t cLen = 0;
+
+ /* Calculate the size of the destination text string. */
+ /* Is this Utf16 or Utf16-LE? */
+ if (pcwszSrc[0] == VBOX_SHCL_UTF16LEMARKER)
+ cLen = 0;
+ else
+ cLen = 1;
+
+ for (size_t i = 0; i < cwSrc; ++i, ++cLen)
+ {
+ if ( (i + 1 < cwSrc)
+ && (pcwszSrc[i] == VBOX_SHCL_CARRIAGERETURN)
+ && (pcwszSrc[i + 1] == VBOX_SHCL_LINEFEED))
+ {
+ ++i;
+ }
+ if (pcwszSrc[i] == 0)
+ break;
+ }
+
+ *pchLen = cLen;
+
+ return VINF_SUCCESS;
+}
+
+int ShClConvUtf16LFToCRLF(PCRTUTF16 pcwszSrc, size_t cwcSrc, PRTUTF16 pu16Dst, size_t cwDst)
+{
+ AssertPtrReturn(pcwszSrc, VERR_INVALID_POINTER);
+ AssertPtrReturn(pu16Dst, VERR_INVALID_POINTER);
+ AssertReturn(cwDst, VERR_INVALID_PARAMETER);
+
+ AssertMsgReturn(pcwszSrc[0] != VBOX_SHCL_UTF16BEMARKER,
+ ("Big endian UTF-16 not supported yet\n"), VERR_NOT_SUPPORTED);
+
+ int rc = VINF_SUCCESS;
+
+ /* Don't copy the endian marker. */
+ size_t i = pcwszSrc[0] == VBOX_SHCL_UTF16LEMARKER ? 1 : 0;
+ size_t j = 0;
+
+ for (; i < cwcSrc; ++i, ++j)
+ {
+ /* Don't copy the null byte, as we add it below. */
+ if (pcwszSrc[i] == 0)
+ break;
+
+ /* Not enough space in destination? */
+ if (j == cwDst)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ if (pcwszSrc[i] == VBOX_SHCL_LINEFEED)
+ {
+ pu16Dst[j] = VBOX_SHCL_CARRIAGERETURN;
+ ++j;
+
+ /* Not enough space in destination? */
+ if (j == cwDst)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ }
+#ifdef RT_OS_DARWIN
+ /* Check for a single carriage return (MacOS) */
+ else if (pcwszSrc[i] == VBOX_SHCL_CARRIAGERETURN)
+ {
+ /* Set CR.r */
+ pu16Dst[j] = VBOX_SHCL_CARRIAGERETURN;
+ ++j;
+
+ /* Not enough space in destination? */
+ if (j == cwDst)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ /* Add line feed. */
+ pu16Dst[j] = VBOX_SHCL_LINEFEED;
+ continue;
+ }
+#endif
+ pu16Dst[j] = pcwszSrc[i];
+ }
+
+ if (j == cwDst)
+ rc = VERR_BUFFER_OVERFLOW;
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Add terminator. */
+ pu16Dst[j] = 0;
+ }
+
+ return rc;
+}
+
+int ShClConvUtf16CRLFToLF(PCRTUTF16 pcwszSrc, size_t cwcSrc, PRTUTF16 pu16Dst, size_t cwDst)
+{
+ AssertPtrReturn(pcwszSrc, VERR_INVALID_POINTER);
+ AssertReturn(cwcSrc, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pu16Dst, VERR_INVALID_POINTER);
+ AssertReturn(cwDst, VERR_INVALID_PARAMETER);
+
+ AssertMsgReturn(pcwszSrc[0] != VBOX_SHCL_UTF16BEMARKER,
+ ("Big endian UTF-16 not supported yet\n"), VERR_NOT_SUPPORTED);
+
+ /* Prepend the Utf16 byte order marker if it is missing. */
+ size_t cwDstPos;
+ if (pcwszSrc[0] == VBOX_SHCL_UTF16LEMARKER)
+ {
+ cwDstPos = 0;
+ }
+ else
+ {
+ pu16Dst[0] = VBOX_SHCL_UTF16LEMARKER;
+ cwDstPos = 1;
+ }
+
+ for (size_t i = 0; i < cwcSrc; ++i, ++cwDstPos)
+ {
+ if (pcwszSrc[i] == 0)
+ break;
+
+ if (cwDstPos == cwDst)
+ return VERR_BUFFER_OVERFLOW;
+
+ if ( (i + 1 < cwcSrc)
+ && (pcwszSrc[i] == VBOX_SHCL_CARRIAGERETURN)
+ && (pcwszSrc[i + 1] == VBOX_SHCL_LINEFEED))
+ {
+ ++i;
+ }
+
+ pu16Dst[cwDstPos] = pcwszSrc[i];
+ }
+
+ if (cwDstPos == cwDst)
+ return VERR_BUFFER_OVERFLOW;
+
+ /* Add terminating zero. */
+ pu16Dst[cwDstPos] = 0;
+
+ return VINF_SUCCESS;
+}
+
+int ShClDibToBmp(const void *pvSrc, size_t cbSrc, void **ppvDest, size_t *pcbDest)
+{
+ AssertPtrReturn(pvSrc, VERR_INVALID_POINTER);
+ AssertReturn(cbSrc, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ppvDest, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbDest, VERR_INVALID_POINTER);
+
+ PBMPWIN3XINFOHDR coreHdr = (PBMPWIN3XINFOHDR)pvSrc;
+ /** @todo Support all the many versions of the DIB headers. */
+ if ( cbSrc < sizeof(BMPWIN3XINFOHDR)
+ || RT_LE2H_U32(coreHdr->cbSize) < sizeof(BMPWIN3XINFOHDR)
+ || RT_LE2H_U32(coreHdr->cbSize) != sizeof(BMPWIN3XINFOHDR))
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ size_t offPixel = sizeof(BMPFILEHDR)
+ + RT_LE2H_U32(coreHdr->cbSize)
+ + RT_LE2H_U32(coreHdr->cClrUsed) * sizeof(uint32_t);
+ if (cbSrc < offPixel)
+ return VERR_INVALID_PARAMETER;
+
+ size_t cbDst = sizeof(BMPFILEHDR) + cbSrc;
+
+ void *pvDest = RTMemAlloc(cbDst);
+ if (!pvDest)
+ return VERR_NO_MEMORY;
+
+ PBMPFILEHDR fileHdr = (PBMPFILEHDR)pvDest;
+
+ fileHdr->uType = BMP_HDR_MAGIC;
+ fileHdr->cbFileSize = (uint32_t)RT_H2LE_U32(cbDst);
+ fileHdr->Reserved1 = 0;
+ fileHdr->Reserved2 = 0;
+ fileHdr->offBits = (uint32_t)RT_H2LE_U32(offPixel);
+
+ memcpy((uint8_t *)pvDest + sizeof(BMPFILEHDR), pvSrc, cbSrc);
+
+ *ppvDest = pvDest;
+ *pcbDest = cbDst;
+
+ return VINF_SUCCESS;
+}
+
+int ShClBmpGetDib(const void *pvSrc, size_t cbSrc, const void **ppvDest, size_t *pcbDest)
+{
+ AssertPtrReturn(pvSrc, VERR_INVALID_POINTER);
+ AssertReturn(cbSrc, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ppvDest, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbDest, VERR_INVALID_POINTER);
+
+ PBMPFILEHDR pBmpHdr = (PBMPFILEHDR)pvSrc;
+ if ( cbSrc < sizeof(BMPFILEHDR)
+ || pBmpHdr->uType != BMP_HDR_MAGIC
+ || RT_LE2H_U32(pBmpHdr->cbFileSize) != cbSrc)
+ {
+ return VERR_INVALID_PARAMETER;
+ }
+
+ *ppvDest = ((uint8_t *)pvSrc) + sizeof(BMPFILEHDR);
+ *pcbDest = cbSrc - sizeof(BMPFILEHDR);
+
+ return VINF_SUCCESS;
+}
+
+#ifdef LOG_ENABLED
+
+int ShClDbgDumpHtml(const char *pcszSrc, size_t cbSrc)
+{
+ int rc = VINF_SUCCESS;
+ char *pszBuf = (char *)RTMemTmpAllocZ(cbSrc + 1);
+ if (pszBuf)
+ {
+ memcpy(pszBuf, pcszSrc, cbSrc);
+ pszBuf[cbSrc] = '\0';
+ for (size_t off = 0; off < cbSrc; ++off)
+ if (pszBuf[off] == '\n' || pszBuf[off] == '\r')
+ pszBuf[off] = ' ';
+ LogFunc(("Removed \\r\\n: %s\n", pszBuf));
+ RTMemTmpFree(pszBuf);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+void ShClDbgDumpData(const void *pv, size_t cb, SHCLFORMAT uFormat)
+{
+ if (LogIsEnabled())
+ {
+ if (uFormat & VBOX_SHCL_FMT_UNICODETEXT)
+ {
+ LogFunc(("VBOX_SHCL_FMT_UNICODETEXT:\n"));
+ if (pv && cb)
+ LogFunc(("%ls\n", pv));
+ else
+ LogFunc(("%p %zu\n", pv, cb));
+ }
+ else if (uFormat & VBOX_SHCL_FMT_BITMAP)
+ LogFunc(("VBOX_SHCL_FMT_BITMAP\n"));
+ else if (uFormat & VBOX_SHCL_FMT_HTML)
+ {
+ LogFunc(("VBOX_SHCL_FMT_HTML:\n"));
+ if (pv && cb)
+ {
+ LogFunc(("%s\n", pv));
+ ShClDbgDumpHtml((const char *)pv, cb);
+ }
+ else
+ LogFunc(("%p %zu\n", pv, cb));
+ }
+ else
+ LogFunc(("Invalid format %02X\n", uFormat));
+ }
+}
+
+#endif /* LOG_ENABLED */
+
+/**
+ * Translates a Shared Clipboard host function number to a string.
+ *
+ * @returns Function ID string name.
+ * @param uFn The function to translate.
+ */
+const char *ShClHostFunctionToStr(uint32_t uFn)
+{
+ switch (uFn)
+ {
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_MODE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_HEADLESS);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_CANCEL);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_ERROR);
+ }
+ return "Unknown";
+}
+
+/**
+ * Translates a Shared Clipboard host message enum to a string.
+ *
+ * @returns Message ID string name.
+ * @param uMsg The message to translate.
+ */
+const char *ShClHostMsgToStr(uint32_t uMsg)
+{
+ switch (uMsg)
+ {
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_QUIT);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_READ_DATA);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_FORMATS_REPORT);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_CANCELED);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_READ_DATA_CID);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_STATUS);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_OPEN);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_CLOSE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_OPEN);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_CLOSE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_CANCEL);
+ RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ERROR);
+ }
+ return "Unknown";
+}
+
+/**
+ * Translates a Shared Clipboard guest message enum to a string.
+ *
+ * @returns Message ID string name.
+ * @param uMsg The message to translate.
+ */
+const char *ShClGuestMsgToStr(uint32_t uMsg)
+{
+ switch (uMsg)
+ {
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPORT_FORMATS);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_DATA_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_DATA_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_CONNECT);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPORT_FEATURES);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_QUERY_FEATURES);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_GET);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_CANCEL);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPLY);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_OPEN);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_CLOSE);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_HDR_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_HDR_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_ENTRY_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_ENTRY_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_OPEN);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_CLOSE);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_READ);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_WRITE);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ERROR);
+ RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_NEGOTIATE_CHUNK_SIZE);
+ }
+ return "Unknown";
+}
+
+/**
+ * Converts Shared Clipboard formats to a string.
+ *
+ * @returns Stringified Shared Clipboard formats, or NULL on failure. Must be free'd with RTStrFree().
+ * @param fFormats Shared Clipboard formats to convert.
+ *
+ */
+char *ShClFormatsToStrA(SHCLFORMATS fFormats)
+{
+#define APPEND_FMT_TO_STR(_aFmt) \
+ if (fFormats & VBOX_SHCL_FMT_##_aFmt) \
+ { \
+ if (pszFmts) \
+ { \
+ rc2 = RTStrAAppend(&pszFmts, ", "); \
+ if (RT_FAILURE(rc2)) \
+ break; \
+ } \
+ \
+ rc2 = RTStrAAppend(&pszFmts, #_aFmt); \
+ if (RT_FAILURE(rc2)) \
+ break; \
+ }
+
+ char *pszFmts = NULL;
+ int rc2 = VINF_SUCCESS;
+
+ do
+ {
+ APPEND_FMT_TO_STR(UNICODETEXT);
+ APPEND_FMT_TO_STR(BITMAP);
+ APPEND_FMT_TO_STR(HTML);
+# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ APPEND_FMT_TO_STR(URI_LIST);
+# endif
+
+ } while (0);
+
+ if (!pszFmts)
+ rc2 = RTStrAAppend(&pszFmts, "NONE");
+
+ if ( RT_FAILURE(rc2)
+ && pszFmts)
+ {
+ RTStrFree(pszFmts);
+ pszFmts = NULL;
+ }
+
+#undef APPEND_FMT_TO_STR
+
+ return pszFmts;
+}
+
diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp
new file mode 100644
index 00000000..71504151
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp
@@ -0,0 +1,862 @@
+/* $Id: clipboard-transfers-http.cpp $ */
+/** @file
+ * Shared Clipboard: HTTP server implementation for Shared Clipboard transfers on UNIX-y guests / hosts.
+ */
+
+/*
+ * Copyright (C) 2020-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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <signal.h>
+
+#include <iprt/http.h>
+#include <iprt/http-server.h>
+
+#include <iprt/net.h> /* To make use of IPv4Addr in RTGETOPTUNION. */
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/list.h>
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/rand.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/uuid.h>
+#include <iprt/vfs.h>
+
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/GuestHost/SharedClipboard-x11.h>
+#include <VBox/GuestHost/SharedClipboard-transfers.h>
+
+
+/*********************************************************************************************************************************
+* Definitations *
+*********************************************************************************************************************************/
+
+typedef struct _SHCLHTTPSERVERTRANSFER
+{
+ /** The node list. */
+ RTLISTNODE Node;
+ /** Pointer to associated transfer. */
+ PSHCLTRANSFER pTransfer;
+ /** The (cached) root list of the transfer. NULL if not cached yet. */
+ PSHCLROOTLIST pRootList;
+ /** Critical section for serializing access. */
+ RTCRITSECT CritSect;
+ /** The handle we're going to use for this HTTP transfer. */
+ SHCLOBJHANDLE hObj;
+ /** The virtual path of the HTTP server's root directory for this transfer. */
+ char szPathVirtual[RTPATH_MAX];
+} SHCLHTTPSERVERTRANSFER;
+typedef SHCLHTTPSERVERTRANSFER *PSHCLHTTPSERVERTRANSFER;
+
+
+/*********************************************************************************************************************************
+* Prototypes *
+*********************************************************************************************************************************/
+static int shClTransferHttpServerDestroyInternal(PSHCLHTTPSERVER pThis);
+static const char *shClTransferHttpServerGetHost(PSHCLHTTPSERVER pSrv);
+
+
+/*********************************************************************************************************************************
+* Public Shared Clipboard HTTP transfer functions *
+*********************************************************************************************************************************/
+
+/**
+ * Registers a Shared Clipboard transfer to a HTTP context.
+ *
+ * @returns VBox status code.
+ * @param pCtx HTTP context to register transfer for.
+ * @param pTransfer Transfer to register.
+ */
+int ShClHttpTransferRegister(PSHCLHTTPCONTEXT pCtx, PSHCLTRANSFER pTransfer)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Start the built-in HTTP server to serve file(s). */
+ if (!ShClTransferHttpServerIsRunning(&pCtx->HttpServer)) /* Only one HTTP server per transfer context. */
+ rc = ShClTransferHttpServerCreate(&pCtx->HttpServer, NULL /* puPort */);
+
+ if (RT_SUCCESS(rc))
+ rc = ShClTransferHttpServerRegisterTransfer(&pCtx->HttpServer, pTransfer);
+
+ return rc;
+}
+
+/**
+ * Unregisters a formerly registered Shared Clipboard transfer.
+ *
+ * @returns VBox status code.
+ * @param pCtx HTTP context to unregister transfer from.
+ * @param pTransfer Transfer to unregister.
+ */
+int ShClHttpTransferUnregister(PSHCLHTTPCONTEXT pCtx, PSHCLTRANSFER pTransfer)
+{
+ int rc = VINF_SUCCESS;
+
+ if (ShClTransferHttpServerIsRunning(&pCtx->HttpServer))
+ {
+ /* Try unregistering transfer (if it was registered before). */
+ rc = ShClTransferHttpServerUnregisterTransfer(&pCtx->HttpServer, pTransfer);
+ if (RT_SUCCESS(rc))
+ {
+ /* No more registered transfers left? Tear down the HTTP server instance then. */
+ if (ShClTransferHttpServerGetTransferCount(&pCtx->HttpServer) == 0)
+ rc = ShClTransferHttpServerDestroy(&pCtx->HttpServer);
+ }
+ AssertRC(rc);
+ }
+
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* Internal Shared Clipboard HTTP transfer functions *
+*********************************************************************************************************************************/
+
+DECLINLINE(void) shClHttpTransferLock(PSHCLHTTPSERVERTRANSFER pSrvTx)
+{
+ int rc2 = RTCritSectEnter(&pSrvTx->CritSect);
+ AssertRC(rc2);
+}
+
+DECLINLINE(void) shClHttpTransferUnlock(PSHCLHTTPSERVERTRANSFER pSrvTx)
+{
+ int rc2 = RTCritSectEnter(&pSrvTx->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Return the HTTP server transfer for a specific transfer ID.
+ *
+ * @returns Pointer to HTTP server transfer if found, NULL if not found.
+ * @param pSrv HTTP server instance.
+ * @param idTransfer Transfer ID to return HTTP server transfer for.
+ */
+static PSHCLHTTPSERVERTRANSFER shClTransferHttpServerGetTransferById(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer)
+{
+ PSHCLHTTPSERVERTRANSFER pSrvTx;
+ RTListForEach(&pSrv->lstTransfers, pSrvTx, SHCLHTTPSERVERTRANSFER, Node) /** @todo Slow O(n) lookup, but does it for now. */
+ {
+ if (pSrvTx->pTransfer->State.uID == idTransfer)
+ return pSrvTx;
+ }
+
+ return NULL;
+}
+
+/**
+ * Returns a HTTP server transfer from a given URL.
+ *
+ * @returns Pointer to HTTP server transfer if found, NULL if not found.
+ * @param pThis HTTP server instance data.
+ * @param pszUrl URL to validate.
+ */
+DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromUrl(PSHCLHTTPSERVER pThis, const char *pszUrl)
+{
+ AssertPtrReturn(pszUrl, NULL);
+
+ PSHCLHTTPSERVERTRANSFER pSrvTx = NULL;
+
+ PSHCLHTTPSERVERTRANSFER pSrvTxCur;
+ RTListForEach(&pThis->lstTransfers, pSrvTxCur, SHCLHTTPSERVERTRANSFER, Node)
+ {
+ AssertPtr(pSrvTxCur->pTransfer);
+
+ LogFlowFunc(("pSrvTxCur=%s\n", pSrvTxCur->szPathVirtual));
+
+ /* Be picky here, do a case sensitive comparison. */
+ if (RTStrStartsWith(pszUrl, pSrvTxCur->szPathVirtual))
+ {
+ pSrvTx = pSrvTxCur;
+ break;
+ }
+ }
+
+ if (!pSrvTx)
+ LogRel2(("Shared Clipboard: HTTP URL '%s' not valid\n", pszUrl));
+
+ LogFlowFunc(("pszUrl=%s, pSrvTx=%p\n", pszUrl, pSrvTx));
+ return pSrvTx;
+}
+
+/**
+ * Returns a HTTP server transfer from an internal HTTP handle.
+ *
+ * @returns Pointer to HTTP server transfer if found, NULL if not found.
+ * @param pThis HTTP server instance data.
+ * @param pvHandle Handle to return transfer for.
+ */
+DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromHandle(PSHCLHTTPSERVER pThis, void *pvHandle)
+{
+ AssertPtrReturn(pvHandle, NULL);
+
+ const SHCLTRANSFERID uHandle = *(uint16_t *)pvHandle;
+
+ /** @ŧodo Use a handle lookup table (map) later. */
+ PSHCLHTTPSERVERTRANSFER pSrvTxCur;
+ RTListForEach(&pThis->lstTransfers, pSrvTxCur, SHCLHTTPSERVERTRANSFER, Node)
+ {
+ AssertPtr(pSrvTxCur->pTransfer);
+
+ if (pSrvTxCur->pTransfer->State.uID == uHandle) /** @ŧodo We're using the transfer ID as handle for now. */
+ return pSrvTxCur;
+ }
+
+ return NULL;
+}
+
+static int shClTransferHttpGetTransferRoots(PSHCLHTTPSERVER pThis, PSHCLHTTPSERVERTRANSFER pSrvTx)
+{
+ RT_NOREF(pThis);
+
+ int rc = VINF_SUCCESS;
+
+ if (pSrvTx->pRootList == NULL)
+ {
+ AssertPtr(pSrvTx->pTransfer);
+ rc = ShClTransferRootsGet(pSrvTx->pTransfer, &pSrvTx->pRootList);
+ }
+
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* HTTP server callback implementations *
+*********************************************************************************************************************************/
+
+/** @copydoc RTHTTPSERVERCALLBACKS::pfnOpen */
+static DECLCALLBACK(int) shClTransferHttpOpen(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, void **ppvHandle)
+{
+ PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser;
+ Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
+
+ int rc;
+
+ PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpGetTransferFromUrl(pThis, pReq->pszUrl);
+ if (pSrvTx)
+ {
+ shClHttpTransferLock(pSrvTx);
+
+ AssertPtr(pSrvTx->pTransfer);
+
+ SHCLOBJOPENCREATEPARMS openParms;
+ rc = ShClTransferObjOpenParmsInit(&openParms);
+ if (RT_SUCCESS(rc))
+ {
+ openParms.fCreate = SHCL_OBJ_CF_ACCESS_READ
+ | SHCL_OBJ_CF_ACCESS_DENYWRITE;
+
+ rc = RTStrCopy(openParms.pszPath, openParms.cbPath, "foo"); /** @ŧodo BUGBUG !!!! */
+ if (RT_SUCCESS(rc))
+ {
+ rc = ShClTransferObjOpen(pSrvTx->pTransfer, &openParms, &pSrvTx->hObj);
+ if (RT_SUCCESS(rc))
+ {
+ *ppvHandle = &pSrvTx->hObj;
+ LogRel2(("Shared Clipboard: HTTP transfer (handle %RU64) started ...\n", pSrvTx->hObj));
+ }
+ }
+
+ ShClTransferObjOpenParmsDestroy(&openParms);
+ }
+
+ shClHttpTransferUnlock(pSrvTx);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ if (RT_FAILURE(rc))
+ LogRel(("Shared Clipboard: Error starting HTTP transfer for '%s', rc=%Rrc\n", pReq->pszUrl, rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/** @copydoc RTHTTPSERVERCALLBACKS::pfnRead */
+static DECLCALLBACK(int) shClTransferHttpRead(PRTHTTPCALLBACKDATA pData, void *pvHandle, void *pvBuf, size_t cbBuf, size_t *pcbRead)
+{
+ PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser;
+ Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
+
+ RT_NOREF(pvBuf, cbBuf, pcbRead);
+
+ int rc;
+
+ PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpGetTransferFromHandle(pThis, pvHandle);
+ if (pSrvTx)
+ {
+ Assert(pSrvTx->hObj != SHCLOBJHANDLE_INVALID);
+
+ uint32_t cbRead;
+ rc = ShClTransferObjRead(pSrvTx->pTransfer, pSrvTx->hObj, pvBuf, cbBuf, 0 /* fFlags */, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbRead = (uint32_t)cbRead;
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("Shared Clipboard: Error reading HTTP transfer (handle %RU64), rc=%Rrc\n", pSrvTx->hObj, rc));
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/** @copydoc RTHTTPSERVERCALLBACKS::pfnClose */
+static DECLCALLBACK(int) shClTransferHttpClose(PRTHTTPCALLBACKDATA pData, void *pvHandle)
+{
+ PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser;
+ Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
+
+ int rc;
+
+ PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpGetTransferFromHandle(pThis, pvHandle);
+ if (pSrvTx)
+ {
+ shClHttpTransferLock(pSrvTx);
+
+ Assert(pSrvTx->hObj != SHCLOBJHANDLE_INVALID);
+ rc = ShClTransferObjClose(pSrvTx->pTransfer, pSrvTx->hObj);
+ if (RT_SUCCESS(rc))
+ {
+ pSrvTx->hObj = SHCLOBJHANDLE_INVALID;
+ LogRel2(("Shared Clipboard: HTTP transfer %RU16 done\n", pSrvTx->pTransfer->State.uID));
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("Shared Clipboard: Error closing HTTP transfer (handle %RU64), rc=%Rrc\n", pSrvTx->hObj, rc));
+
+ shClHttpTransferUnlock(pSrvTx);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/** @copydoc RTHTTPSERVERCALLBACKS::pfnQueryInfo */
+static DECLCALLBACK(int) shClTransferHttpQueryInfo(PRTHTTPCALLBACKDATA pData,
+ PRTHTTPSERVERREQ pReq, PRTFSOBJINFO pObjInfo, char **ppszMIMEHint)
+{
+ PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser;
+ Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
+
+ int rc;
+
+ PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpGetTransferFromUrl(pThis, pReq->pszUrl);
+ if (pSrvTx)
+ {
+ shClHttpTransferLock(pSrvTx);
+
+ rc = shClTransferHttpGetTransferRoots(pThis, pSrvTx);
+
+ shClHttpTransferUnlock(pSrvTx);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ RT_NOREF(pObjInfo, ppszMIMEHint);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/** @copydoc RTHTTPSERVERCALLBACKS::pfnDestroy */
+static DECLCALLBACK(int) shClTransferHttpDestroy(PRTHTTPCALLBACKDATA pData)
+{
+ PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser;
+ Assert(pData->cbUser == sizeof(SHCLHTTPSERVER));
+
+ return shClTransferHttpServerDestroyInternal(pThis);
+}
+
+
+/*********************************************************************************************************************************
+* Internal Shared Clipboard HTTP server functions *
+*********************************************************************************************************************************/
+
+/**
+ * Destroys a Shared Clipboard HTTP server instance, internal version.
+ *
+ * @returns VBox status code.
+ * @param pSrv Shared Clipboard HTTP server instance to destroy.
+ */
+static int shClTransferHttpServerDestroyInternal(PSHCLHTTPSERVER pSrv)
+{
+ PSHCLHTTPSERVERTRANSFER pSrvTx, pSrvTxNext;
+ RTListForEachSafe(&pSrv->lstTransfers, pSrvTx, pSrvTxNext, SHCLHTTPSERVERTRANSFER, Node)
+ {
+ RTListNodeRemove(&pSrvTx->Node);
+
+ RTMemFree(pSrvTx);
+ pSrvTx = NULL;
+ }
+
+ RTHttpServerResponseDestroy(&pSrv->Resp);
+
+ pSrv->hHTTPServer = NIL_RTHTTPSERVER;
+
+ int rc = VINF_SUCCESS;
+
+ if (RTCritSectIsInitialized(&pSrv->CritSect))
+ rc = RTCritSectDelete(&pSrv->CritSect);
+
+ return rc;
+}
+
+/**
+ * Locks the critical section of a Shared Clipboard HTTP server instance.
+ *
+ * @param pSrv Shared Clipboard HTTP server instance to lock.
+ */
+DECLINLINE(void) shClTransferHttpServerLock(PSHCLHTTPSERVER pSrv)
+{
+ int rc2 = RTCritSectEnter(&pSrv->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Unlocks the critical section of a Shared Clipboard HTTP server instance.
+ *
+ * @param pSrv Shared Clipboard HTTP server instance to unlock.
+ */
+DECLINLINE(void) shClTransferHttpServerUnlock(PSHCLHTTPSERVER pSrv)
+{
+ int rc2 = RTCritSectLeave(&pSrv->CritSect);
+ AssertRC(rc2);
+}
+
+/**
+ * Initializes a new Shared Clipboard HTTP server instance.
+ *
+ * @param pSrv HTTP server instance to initialize.
+ */
+static void shClTransferHttpServerInitInternal(PSHCLHTTPSERVER pSrv)
+{
+ pSrv->hHTTPServer = NIL_RTHTTPSERVER;
+ pSrv->uPort = 0;
+ RTListInit(&pSrv->lstTransfers);
+ pSrv->cTransfers = 0;
+ int rc2 = RTHttpServerResponseInit(&pSrv->Resp);
+ AssertRC(rc2);
+}
+
+
+/*********************************************************************************************************************************
+* Public Shared Clipboard HTTP server functions *
+*********************************************************************************************************************************/
+
+/**
+ * Initializes a new Shared Clipboard HTTP server instance.
+ *
+ * @param pSrv HTTP server instance to initialize.
+ */
+void ShClTransferHttpServerInit(PSHCLHTTPSERVER pSrv)
+{
+ AssertPtrReturnVoid(pSrv);
+
+ shClTransferHttpServerInitInternal(pSrv);
+}
+
+/**
+ * Creates a new Shared Clipboard HTTP server instance, extended version.
+ *
+ * @returns VBox status code.
+ * @param pSrv HTTP server instance to create.
+ * @param uPort TCP port number to use.
+ */
+int ShClTransferHttpServerCreateEx(PSHCLHTTPSERVER pSrv, uint16_t uPort)
+{
+ AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
+
+ RTHTTPSERVERCALLBACKS Callbacks;
+ RT_ZERO(Callbacks);
+
+ Callbacks.pfnOpen = shClTransferHttpOpen;
+ Callbacks.pfnRead = shClTransferHttpRead;
+ Callbacks.pfnClose = shClTransferHttpClose;
+ Callbacks.pfnQueryInfo = shClTransferHttpQueryInfo;
+ Callbacks.pfnDestroy = shClTransferHttpDestroy;
+
+ /* Note: The server always and *only* runs against the localhost interface. */
+ int rc = RTHttpServerCreate(&pSrv->hHTTPServer, "localhost", uPort, &Callbacks,
+ pSrv, sizeof(SHCLHTTPSERVER));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectInit(&pSrv->CritSect);
+ AssertRCReturn(rc, rc);
+
+ pSrv->uPort = uPort;
+
+ LogRel2(("Shared Clipboard: HTTP server running at port %RU16\n", pSrv->uPort));
+ }
+ else
+ {
+ int rc2 = shClTransferHttpServerDestroyInternal(pSrv);
+ AssertRC(rc2);
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("Shared Clipboard: HTTP server failed to run, rc=%Rrc\n", rc));
+
+ return rc;
+}
+
+/**
+ * Creates a new Shared Clipboard HTTP server instance.
+ *
+ * This does automatic probing of TCP ports if one already is being used.
+ *
+ * @returns VBox status code.
+ * @param pSrv HTTP server instance to create.
+ * @param puPort Where to return the TCP port number being used on success. Optional.
+ */
+int ShClTransferHttpServerCreate(PSHCLHTTPSERVER pSrv, uint16_t *puPort)
+{
+ AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
+ /* puPort is optional. */
+
+ /** @todo Try favorite ports first (e.g. 8080, 8000, ...)? */
+
+ RTRAND hRand;
+ int rc = RTRandAdvCreateSystemFaster(&hRand); /* Should be good enough for this task. */
+ if (RT_SUCCESS(rc))
+ {
+ uint16_t uPort;
+ for (int i = 0; i < 32; i++)
+ {
+#ifdef DEBUG_andy
+ uPort = 8080; /* Make the port predictable, but only for me, mwahaha! :-). */
+#else
+ uPort = RTRandAdvU32Ex(hRand, 1024, UINT16_MAX);
+#endif
+ rc = ShClTransferHttpServerCreateEx(pSrv, (uint32_t)uPort);
+ if (RT_SUCCESS(rc))
+ {
+ if (puPort)
+ *puPort = uPort;
+ break;
+ }
+ }
+
+ RTRandAdvDestroy(hRand);
+ }
+
+ return rc;
+}
+
+/**
+ * Destroys a Shared Clipboard HTTP server instance.
+ *
+ * @returns VBox status code.
+ * @param pSrv HTTP server instance to destroy.
+ */
+int ShClTransferHttpServerDestroy(PSHCLHTTPSERVER pSrv)
+{
+ AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
+
+ if (pSrv->hHTTPServer == NIL_RTHTTPSERVER)
+ return VINF_SUCCESS;
+
+ Assert(pSrv->cTransfers == 0); /* Sanity. */
+
+ int rc = RTHttpServerDestroy(pSrv->hHTTPServer);
+ if (RT_SUCCESS(rc))
+ rc = shClTransferHttpServerDestroyInternal(pSrv);
+
+ if (RT_SUCCESS(rc))
+ LogRel2(("Shared Clipboard: HTTP server stopped\n"));
+ else
+ LogRel(("Shared Clipboard: HTTP server failed to stop, rc=%Rrc\n", rc));
+
+ return rc;
+}
+
+/**
+ * Registers a Shared Clipboard transfer to a HTTP server instance.
+ *
+ * @returns VBox status code.
+ * @param pSrv HTTP server instance to register transfer for.
+ * @param pTransfer Transfer to register.
+ */
+int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ AssertReturn(pTransfer->State.uID, VERR_INVALID_PARAMETER); /* Paranoia. */
+
+ shClTransferHttpServerLock(pSrv);
+
+ PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)RTMemAllocZ(sizeof(SHCLHTTPSERVERTRANSFER));
+ AssertPtrReturn(pSrvTx, VERR_NO_MEMORY);
+
+ RTUUID Uuid;
+ int rc = RTUuidCreate(&Uuid);
+ if (RT_SUCCESS(rc))
+ {
+ char szUuid[64];
+ rc = RTUuidToStr(&Uuid, szUuid, sizeof(szUuid));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectInit(&pSrvTx->CritSect);
+ AssertRC(rc);
+
+ /* Create the virtual HTTP path for the transfer.
+ * Every transfer has a dedicated HTTP path. */
+#ifdef DEBUG_andy
+ ssize_t cch = RTStrPrintf2(pSrvTx->szPathVirtual, sizeof(pSrvTx->szPathVirtual), "/d1bbda60-80b7-45dc-a41c-ac4686c1d988/10664");
+#else
+ ssize_t cch = RTStrPrintf2(pSrvTx->szPathVirtual, sizeof(pSrvTx->szPathVirtual), "%s/%RU16/", szUuid, pTransfer->State.uID);
+#endif
+ AssertReturn(cch, VERR_BUFFER_OVERFLOW);
+
+ pSrvTx->pTransfer = pTransfer;
+ pSrvTx->pRootList = NULL;
+ pSrvTx->hObj = SHCLOBJHANDLE_INVALID;
+
+ RTListAppend(&pSrv->lstTransfers, &pSrvTx->Node);
+ pSrv->cTransfers++;
+
+ LogFunc(("pTransfer=%p, idTransfer=%RU16, szPath=%s -> %RU32 transfers\n",
+ pSrvTx->pTransfer, pSrvTx->pTransfer->State.uID, pSrvTx->szPathVirtual, pSrv->cTransfers));
+
+ LogRel2(("Shared Clipboard: Registered HTTP transfer %RU16, now %RU32 HTTP transfers total\n",
+ pTransfer->State.uID, pSrv->cTransfers));
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pSrvTx);
+
+ shClTransferHttpServerUnlock(pSrv);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Unregisters a formerly registered Shared Clipboard transfer.
+ *
+ * @returns VBox status code.
+ * @param pSrv HTTP server instance to unregister transfer from.
+ * @param pTransfer Transfer to unregister.
+ */
+int ShClTransferHttpServerUnregisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pSrv, VERR_INVALID_POINTER);
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ shClTransferHttpServerLock(pSrv);
+
+ AssertReturn(pSrv->cTransfers, VERR_WRONG_ORDER);
+
+ int rc = VINF_SUCCESS;
+
+ PSHCLHTTPSERVERTRANSFER pSrvTx;
+ RTListForEach(&pSrv->lstTransfers, pSrvTx, SHCLHTTPSERVERTRANSFER, Node)
+ {
+ AssertPtr(pSrvTx->pTransfer);
+ if (pSrvTx->pTransfer->State.uID == pTransfer->State.uID)
+ {
+ RTListNodeRemove(&pSrvTx->Node);
+
+ Assert(pSrv->cTransfers);
+ pSrv->cTransfers--;
+
+ LogFunc(("pTransfer=%p, idTransfer=%RU16, szPath=%s -> %RU32 transfers\n",
+ pSrvTx->pTransfer, pSrvTx->pTransfer->State.uID, pSrvTx->szPathVirtual, pSrv->cTransfers));
+
+ LogRel2(("Shared Clipboard: Unregistered HTTP transfer %RU16, now %RU32 HTTP transfers total\n",
+ pTransfer->State.uID, pSrv->cTransfers));
+
+ rc = RTCritSectDelete(&pSrvTx->CritSect);
+ AssertRC(rc);
+
+ RTMemFree(pSrvTx);
+ pSrvTx = NULL;
+
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+
+ shClTransferHttpServerUnlock(pSrv);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Returns whether a specific transfer ID is registered with a HTTP server instance or not.
+ *
+ * @returns \c true if the transfer ID is registered, \c false if not.
+ * @param pSrv HTTP server instance.
+ * @param idTransfer Transfer ID to check for.
+ */
+bool ShClTransferHttpServerHasTransfer(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer)
+{
+ AssertPtrReturn(pSrv, false);
+
+ shClTransferHttpServerLock(pSrv);
+
+ const bool fRc = shClTransferHttpServerGetTransferById(pSrv, idTransfer) != NULL;
+
+ shClTransferHttpServerUnlock(pSrv);
+
+ return fRc;
+}
+
+/**
+ * Returns the used TCP port number of a HTTP server instance.
+ *
+ * @returns TCP port number. 0 if not specified yet.
+ * @param pSrv HTTP server instance to return port for.
+ */
+uint16_t ShClTransferHttpServerGetPort(PSHCLHTTPSERVER pSrv)
+{
+ AssertPtrReturn(pSrv, 0);
+
+ shClTransferHttpServerLock(pSrv);
+
+ const uint16_t uPort = pSrv->uPort;
+
+ shClTransferHttpServerUnlock(pSrv);
+
+ return uPort;
+}
+
+/**
+ * Returns the number of registered HTTP server transfers of a HTTP server instance.
+ *
+ * @returns Number of registered transfers.
+ * @param pSrv HTTP server instance to return registered transfers for.
+ */
+uint32_t ShClTransferHttpServerGetTransferCount(PSHCLHTTPSERVER pSrv)
+{
+ AssertPtrReturn(pSrv, 0);
+
+ shClTransferHttpServerLock(pSrv);
+
+ const uint32_t cTransfers = pSrv->cTransfers;
+
+ shClTransferHttpServerUnlock(pSrv);
+
+ return cTransfers;
+}
+
+/**
+ * Returns the host name (scheme) of a HTTP server instance.
+ *
+ * @param pSrv HTTP server instance to return host name (scheme) for.
+ *
+ * @returns Host name (scheme).
+ */
+static const char *shClTransferHttpServerGetHost(PSHCLHTTPSERVER pSrv)
+{
+ RT_NOREF(pSrv);
+ return "http://localhost"; /* Hardcoded for now. */
+}
+
+/**
+ * Returns an allocated string with a HTTP server instance's address.
+ *
+ * @returns Allocated string with a HTTP server instance's address, or NULL on OOM.
+ * Needs to be free'd by the caller using RTStrFree().
+ * @param pSrv HTTP server instance to return address for.
+ */
+char *ShClTransferHttpServerGetAddressA(PSHCLHTTPSERVER pSrv)
+{
+ AssertPtrReturn(pSrv, NULL);
+
+ shClTransferHttpServerLock(pSrv);
+
+ char *pszAddress = RTStrAPrintf2("%s:%RU16", shClTransferHttpServerGetHost(pSrv), pSrv->uPort);
+ AssertPtr(pszAddress);
+
+ shClTransferHttpServerUnlock(pSrv);
+
+ return pszAddress;
+}
+
+/**
+ * Returns an allocated string with the URL of a given Shared Clipboard transfer ID.
+ *
+ * @returns Allocated string with the URL of a given Shared Clipboard transfer ID, or NULL if not found.
+ * Needs to be free'd by the caller using RTStrFree().
+ * @param pSrv HTTP server instance to return URL for.
+ */
+char *ShClTransferHttpServerGetUrlA(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer)
+{
+ AssertPtrReturn(pSrv, NULL);
+ AssertReturn(idTransfer != NIL_SHCLTRANSFERID, NULL);
+
+ shClTransferHttpServerLock(pSrv);
+
+ PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpServerGetTransferById(pSrv, idTransfer);
+ if (!pSrvTx)
+ {
+ AssertFailed();
+ shClTransferHttpServerUnlock(pSrv);
+ return NULL;
+ }
+
+ AssertReturn(RTStrNLen(pSrvTx->szPathVirtual, RTPATH_MAX), NULL);
+ char *pszUrl = RTStrAPrintf2("%s:%RU16/%s", shClTransferHttpServerGetHost(pSrv), pSrv->uPort, pSrvTx->szPathVirtual);
+ AssertPtr(pszUrl);
+
+ shClTransferHttpServerUnlock(pSrv);
+
+ return pszUrl;
+}
+
+/**
+ * Returns whether a given HTTP server instance is running or not.
+ *
+ * @returns \c true if running, or \c false if not.
+ * @param pSrv HTTP server instance to check running state for.
+ */
+bool ShClTransferHttpServerIsRunning(PSHCLHTTPSERVER pSrv)
+{
+ AssertPtrReturn(pSrv, false);
+
+ return (pSrv->hHTTPServer != NIL_RTHTTPSERVER); /* Seems enough for now. */
+}
diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp
new file mode 100644
index 00000000..f0a0453d
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp
@@ -0,0 +1,3132 @@
+/* $Id: clipboard-transfers.cpp $ */
+/** @file
+ * Shared Clipboard: Common Shared Clipboard transfer handling code.
+ */
+
+/*
+ * Copyright (C) 2019-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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <VBox/log.h>
+
+#include <iprt/dir.h>
+#include <iprt/file.h>
+#include <iprt/list.h>
+#include <iprt/path.h>
+#include <iprt/rand.h>
+#include <iprt/semaphore.h>
+
+#include <VBox/err.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#include <VBox/GuestHost/SharedClipboard-transfers.h>
+
+
+static int shClTransferThreadCreate(PSHCLTRANSFER pTransfer, PFNRTTHREAD pfnThreadFunc, void *pvUser);
+static int shClTransferThreadDestroy(PSHCLTRANSFER pTransfer, RTMSINTERVAL uTimeoutMs);
+
+static void shclTransferCtxTransferRemoveAndUnregister(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer);
+static PSHCLTRANSFER shClTransferCtxGetTransferByIdInternal(PSHCLTRANSFERCTX pTransferCtx, uint32_t uId);
+static PSHCLTRANSFER shClTransferCtxGetTransferByIndexInternal(PSHCLTRANSFERCTX pTransferCtx, uint32_t uIdx);
+static int shClConvertFileCreateFlags(uint32_t fShClFlags, uint64_t *pfOpen);
+static int shClTransferResolvePathAbs(PSHCLTRANSFER pTransfer, const char *pszPath, uint32_t fFlags, char **ppszResolved);
+
+/** @todo Split this file up in different modules. */
+
+/**
+ * Allocates a new transfer root list.
+ *
+ * @returns Allocated transfer root list on success, or NULL on failure.
+ */
+PSHCLROOTLIST ShClTransferRootListAlloc(void)
+{
+ PSHCLROOTLIST pRootList = (PSHCLROOTLIST)RTMemAllocZ(sizeof(SHCLROOTLIST));
+
+ return pRootList;
+}
+
+/**
+ * Frees a transfer root list.
+ *
+ * @param pRootList transfer root list to free. The pointer will be
+ * invalid after returning from this function.
+ */
+void ShClTransferRootListFree(PSHCLROOTLIST pRootList)
+{
+ if (!pRootList)
+ return;
+
+ for (uint32_t i = 0; i < pRootList->Hdr.cRoots; i++)
+ ShClTransferListEntryInit(&pRootList->paEntries[i]);
+
+ RTMemFree(pRootList);
+ pRootList = NULL;
+}
+
+/**
+ * Initializes a transfer root list header.
+ *
+ * @returns VBox status code.
+ * @param pRootLstHdr Root list header to initialize.
+ */
+int ShClTransferRootListHdrInit(PSHCLROOTLISTHDR pRootLstHdr)
+{
+ AssertPtrReturn(pRootLstHdr, VERR_INVALID_POINTER);
+
+ RT_BZERO(pRootLstHdr, sizeof(SHCLROOTLISTHDR));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a transfer root list header.
+ *
+ * @param pRootLstHdr Root list header to destroy.
+ */
+void ShClTransferRootListHdrDestroy(PSHCLROOTLISTHDR pRootLstHdr)
+{
+ if (!pRootLstHdr)
+ return;
+
+ pRootLstHdr->fRoots = 0;
+ pRootLstHdr->cRoots = 0;
+}
+
+/**
+ * Duplicates a transfer list header.
+ *
+ * @returns Duplicated transfer list header on success, or NULL on failure.
+ * @param pRootLstHdr Root list header to duplicate.
+ */
+PSHCLROOTLISTHDR ShClTransferRootListHdrDup(PSHCLROOTLISTHDR pRootLstHdr)
+{
+ AssertPtrReturn(pRootLstHdr, NULL);
+
+ int rc = VINF_SUCCESS;
+
+ PSHCLROOTLISTHDR pRootsDup = (PSHCLROOTLISTHDR)RTMemAllocZ(sizeof(SHCLROOTLISTHDR));
+ if (pRootsDup)
+ {
+ *pRootsDup = *pRootLstHdr;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (RT_FAILURE(rc))
+ {
+ ShClTransferRootListHdrDestroy(pRootsDup);
+ pRootsDup = NULL;
+ }
+
+ return pRootsDup;
+}
+
+/**
+ * (Deep) Copies a clipboard root list entry structure.
+ *
+ * @returns VBox status code.
+ * @param pDst Where to copy the source root list entry to.
+ * @param pSrc Source root list entry to copy.
+ */
+int ShClTransferRootListEntryCopy(PSHCLROOTLISTENTRY pDst, PSHCLROOTLISTENTRY pSrc)
+{
+ return ShClTransferListEntryCopy(pDst, pSrc);
+}
+
+/**
+ * Initializes a clipboard root list entry structure.
+ *
+ * @param pRootListEntry Clipboard root list entry structure to destroy.
+ */
+int ShClTransferRootListEntryInit(PSHCLROOTLISTENTRY pRootListEntry)
+{
+ return ShClTransferListEntryInit(pRootListEntry);
+}
+
+/**
+ * Destroys a clipboard root list entry structure.
+ *
+ * @param pRootListEntry Clipboard root list entry structure to destroy.
+ */
+void ShClTransferRootListEntryDestroy(PSHCLROOTLISTENTRY pRootListEntry)
+{
+ return ShClTransferListEntryDestroy(pRootListEntry);
+}
+
+/**
+ * Duplicates (allocates) a clipboard root list entry structure.
+ *
+ * @returns Duplicated clipboard root list entry structure on success.
+ * @param pRootListEntry Clipboard root list entry to duplicate.
+ */
+PSHCLROOTLISTENTRY ShClTransferRootListEntryDup(PSHCLROOTLISTENTRY pRootListEntry)
+{
+ return ShClTransferListEntryDup(pRootListEntry);
+}
+
+/**
+ * Initializes an list handle info structure.
+ *
+ * @returns VBox status code.
+ * @param pInfo List handle info structure to initialize.
+ */
+int ShClTransferListHandleInfoInit(PSHCLLISTHANDLEINFO pInfo)
+{
+ AssertPtrReturn(pInfo, VERR_INVALID_POINTER);
+
+ pInfo->hList = SHCLLISTHANDLE_INVALID;
+ pInfo->enmType = SHCLOBJTYPE_INVALID;
+
+ pInfo->pszPathLocalAbs = NULL;
+
+ RT_ZERO(pInfo->u);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a list handle info structure.
+ *
+ * @param pInfo List handle info structure to destroy.
+ */
+void ShClTransferListHandleInfoDestroy(PSHCLLISTHANDLEINFO pInfo)
+{
+ if (!pInfo)
+ return;
+
+ if (pInfo->pszPathLocalAbs)
+ {
+ RTStrFree(pInfo->pszPathLocalAbs);
+ pInfo->pszPathLocalAbs = NULL;
+ }
+}
+
+/**
+ * Allocates a transfer list header structure.
+ *
+ * @returns VBox status code.
+ * @param ppListHdr Where to store the allocated transfer list header structure on success.
+ */
+int ShClTransferListHdrAlloc(PSHCLLISTHDR *ppListHdr)
+{
+ int rc;
+
+ PSHCLLISTHDR pListHdr = (PSHCLLISTHDR)RTMemAllocZ(sizeof(SHCLLISTHDR));
+ if (pListHdr)
+ {
+ *ppListHdr = pListHdr;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Frees a transfer list header structure.
+ *
+ * @param pListEntry Transfer list header structure to free.
+ */
+void ShClTransferListHdrFree(PSHCLLISTHDR pListHdr)
+{
+ if (!pListHdr)
+ return;
+
+ LogFlowFuncEnter();
+
+ ShClTransferListHdrDestroy(pListHdr);
+
+ RTMemFree(pListHdr);
+ pListHdr = NULL;
+}
+
+/**
+ * Duplicates (allocates) a transfer list header structure.
+ *
+ * @returns Duplicated transfer list header structure on success.
+ * @param pListHdr Transfer list header to duplicate.
+ */
+PSHCLLISTHDR ShClTransferListHdrDup(PSHCLLISTHDR pListHdr)
+{
+ AssertPtrReturn(pListHdr, NULL);
+
+ PSHCLLISTHDR pListHdrDup = (PSHCLLISTHDR)RTMemAlloc(sizeof(SHCLLISTHDR));
+ if (pListHdrDup)
+ {
+ *pListHdrDup = *pListHdr;
+ }
+
+ return pListHdrDup;
+}
+
+/**
+ * Initializes a transfer list header structure.
+ *
+ * @returns VBox status code.
+ * @param pListHdr Transfer list header struct to initialize.
+ */
+int ShClTransferListHdrInit(PSHCLLISTHDR pListHdr)
+{
+ AssertPtrReturn(pListHdr, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ ShClTransferListHdrReset(pListHdr);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a transfer list header structure.
+ *
+ * @param pListHdr Transfer list header struct to destroy.
+ */
+void ShClTransferListHdrDestroy(PSHCLLISTHDR pListHdr)
+{
+ if (!pListHdr)
+ return;
+
+ LogFlowFuncEnter();
+}
+
+/**
+ * Resets a transfer list header structure.
+ *
+ * @returns VBox status code.
+ * @param pListHdr Transfer list header struct to reset.
+ */
+void ShClTransferListHdrReset(PSHCLLISTHDR pListHdr)
+{
+ AssertPtrReturnVoid(pListHdr);
+
+ LogFlowFuncEnter();
+
+ RT_BZERO(pListHdr, sizeof(SHCLLISTHDR));
+}
+
+/**
+ * Returns whether a given transfer list header is valid or not.
+ *
+ * @returns \c true if valid, \c false if not.
+ * @param pListHdr Transfer list header to validate.
+ */
+bool ShClTransferListHdrIsValid(PSHCLLISTHDR pListHdr)
+{
+ RT_NOREF(pListHdr);
+ return true; /** @todo Implement this. */
+}
+
+int ShClTransferListOpenParmsCopy(PSHCLLISTOPENPARMS pDst, PSHCLLISTOPENPARMS pSrc)
+{
+ AssertPtrReturn(pDst, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSrc, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ if (pSrc->pszFilter)
+ {
+ pDst->pszFilter = RTStrDup(pSrc->pszFilter);
+ if (!pDst->pszFilter)
+ rc = VERR_NO_MEMORY;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pSrc->pszPath)
+ {
+ pDst->pszPath = RTStrDup(pSrc->pszPath);
+ if (!pDst->pszPath)
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pDst->fList = pDst->fList;
+ pDst->cbFilter = pSrc->cbFilter;
+ pDst->cbPath = pSrc->cbPath;
+ }
+
+ return rc;
+}
+
+/**
+ * Duplicates a transfer list open parameters structure.
+ *
+ * @returns Duplicated transfer list open parameters structure on success, or NULL on failure.
+ * @param pParms Transfer list open parameters structure to duplicate.
+ */
+PSHCLLISTOPENPARMS ShClTransferListOpenParmsDup(PSHCLLISTOPENPARMS pParms)
+{
+ AssertPtrReturn(pParms, NULL);
+
+ PSHCLLISTOPENPARMS pParmsDup = (PSHCLLISTOPENPARMS)RTMemAllocZ(sizeof(SHCLLISTOPENPARMS));
+ if (!pParmsDup)
+ return NULL;
+
+ int rc = ShClTransferListOpenParmsCopy(pParmsDup, pParms);
+ if (RT_FAILURE(rc))
+ {
+ ShClTransferListOpenParmsDestroy(pParmsDup);
+
+ RTMemFree(pParmsDup);
+ pParmsDup = NULL;
+ }
+
+ return pParmsDup;
+}
+
+/**
+ * Initializes a transfer list open parameters structure.
+ *
+ * @returns VBox status code.
+ * @param pParms Transfer list open parameters structure to initialize.
+ */
+int ShClTransferListOpenParmsInit(PSHCLLISTOPENPARMS pParms)
+{
+ AssertPtrReturn(pParms, VERR_INVALID_POINTER);
+
+ RT_BZERO(pParms, sizeof(SHCLLISTOPENPARMS));
+
+ pParms->cbFilter = SHCL_TRANSFER_PATH_MAX; /** @todo Make this dynamic. */
+ pParms->pszFilter = RTStrAlloc(pParms->cbFilter);
+
+ pParms->cbPath = SHCL_TRANSFER_PATH_MAX; /** @todo Make this dynamic. */
+ pParms->pszPath = RTStrAlloc(pParms->cbPath);
+
+ LogFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a transfer list open parameters structure.
+ *
+ * @param pParms Transfer list open parameters structure to destroy.
+ */
+void ShClTransferListOpenParmsDestroy(PSHCLLISTOPENPARMS pParms)
+{
+ if (!pParms)
+ return;
+
+ if (pParms->pszFilter)
+ {
+ RTStrFree(pParms->pszFilter);
+ pParms->pszFilter = NULL;
+ }
+
+ if (pParms->pszPath)
+ {
+ RTStrFree(pParms->pszPath);
+ pParms->pszPath = NULL;
+ }
+}
+
+/**
+ * Creates (allocates) and initializes a clipboard list entry structure.
+ *
+ * @param ppDirData Where to return the created clipboard list entry structure on success.
+ */
+int ShClTransferListEntryAlloc(PSHCLLISTENTRY *ppListEntry)
+{
+ PSHCLLISTENTRY pListEntry = (PSHCLLISTENTRY)RTMemAlloc(sizeof(SHCLLISTENTRY));
+ if (!pListEntry)
+ return VERR_NO_MEMORY;
+
+ int rc = ShClTransferListEntryInit(pListEntry);
+ if (RT_SUCCESS(rc))
+ *ppListEntry = pListEntry;
+
+ return rc;
+}
+
+/**
+ * Frees a clipboard list entry structure.
+ *
+ * @param pListEntry Clipboard list entry structure to free.
+ */
+void ShClTransferListEntryFree(PSHCLLISTENTRY pListEntry)
+{
+ if (!pListEntry)
+ return;
+
+ ShClTransferListEntryDestroy(pListEntry);
+ RTMemFree(pListEntry);
+}
+
+/**
+ * (Deep) Copies a clipboard list entry structure.
+ *
+ * @returns VBox status code.
+ * @param pListEntry Clipboard list entry to copy.
+ */
+int ShClTransferListEntryCopy(PSHCLLISTENTRY pDst, PSHCLLISTENTRY pSrc)
+{
+ AssertPtrReturn(pDst, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSrc, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ *pDst = *pSrc;
+
+ if (pSrc->pszName)
+ {
+ pDst->pszName = RTStrDup(pSrc->pszName);
+ if (!pDst->pszName)
+ rc = VERR_NO_MEMORY;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pSrc->pvInfo)
+ {
+ pDst->pvInfo = RTMemDup(pSrc->pvInfo, pSrc->cbInfo);
+ if (pDst->pvInfo)
+ {
+ pDst->cbInfo = pSrc->cbInfo;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ if (pDst->pvInfo)
+ {
+ RTMemFree(pDst->pvInfo);
+ pDst->pvInfo = NULL;
+ pDst->cbInfo = 0;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Duplicates (allocates) a clipboard list entry structure.
+ *
+ * @returns Duplicated clipboard list entry structure on success.
+ * @param pListEntry Clipboard list entry to duplicate.
+ */
+PSHCLLISTENTRY ShClTransferListEntryDup(PSHCLLISTENTRY pListEntry)
+{
+ AssertPtrReturn(pListEntry, NULL);
+
+ int rc = VINF_SUCCESS;
+
+ PSHCLLISTENTRY pListEntryDup = (PSHCLLISTENTRY)RTMemAllocZ(sizeof(SHCLLISTENTRY));
+ if (pListEntryDup)
+ rc = ShClTransferListEntryCopy(pListEntryDup, pListEntry);
+
+ if (RT_FAILURE(rc))
+ {
+ ShClTransferListEntryDestroy(pListEntryDup);
+
+ RTMemFree(pListEntryDup);
+ pListEntryDup = NULL;
+ }
+
+ return pListEntryDup;
+}
+
+/**
+ * Initializes a clipboard list entry structure.
+ *
+ * @returns VBox status code.
+ * @param pListEntry Clipboard list entry structure to initialize.
+ */
+int ShClTransferListEntryInit(PSHCLLISTENTRY pListEntry)
+{
+ AssertPtrReturn(pListEntry, VERR_INVALID_POINTER);
+
+ RT_BZERO(pListEntry, sizeof(SHCLLISTENTRY));
+
+ pListEntry->pszName = RTStrAlloc(SHCLLISTENTRY_MAX_NAME);
+ if (!pListEntry->pszName)
+ return VERR_NO_MEMORY;
+
+ pListEntry->cbName = SHCLLISTENTRY_MAX_NAME;
+
+ pListEntry->pvInfo = (PSHCLFSOBJINFO)RTMemAlloc(sizeof(SHCLFSOBJINFO));
+ if (pListEntry->pvInfo)
+ {
+ pListEntry->cbInfo = sizeof(SHCLFSOBJINFO);
+ pListEntry->fInfo = VBOX_SHCL_INFO_FLAG_FSOBJINFO;
+
+ return VINF_SUCCESS;
+ }
+
+ return VERR_NO_MEMORY;
+}
+
+/**
+ * Destroys a clipboard list entry structure.
+ *
+ * @param pListEntry Clipboard list entry structure to destroy.
+ */
+void ShClTransferListEntryDestroy(PSHCLLISTENTRY pListEntry)
+{
+ if (!pListEntry)
+ return;
+
+ if (pListEntry->pszName)
+ {
+ RTStrFree(pListEntry->pszName);
+
+ pListEntry->pszName = NULL;
+ pListEntry->cbName = 0;
+ }
+
+ if (pListEntry->pvInfo)
+ {
+ RTMemFree(pListEntry->pvInfo);
+ pListEntry->pvInfo = NULL;
+ pListEntry->cbInfo = 0;
+ }
+}
+
+/**
+ * Returns whether a given clipboard list entry is valid or not.
+ *
+ * @returns \c true if valid, \c false if not.
+ * @param pListEntry Clipboard list entry to validate.
+ */
+bool ShClTransferListEntryIsValid(PSHCLLISTENTRY pListEntry)
+{
+ AssertPtrReturn(pListEntry, false);
+
+ if ( !pListEntry->pszName
+ || !pListEntry->cbName
+ || strlen(pListEntry->pszName) == 0
+ || strlen(pListEntry->pszName) > pListEntry->cbName /* Includes zero termination */ - 1)
+ {
+ return false;
+ }
+
+ if (pListEntry->cbInfo) /* cbInfo / pvInfo is optional. */
+ {
+ if (!pListEntry->pvInfo)
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Initializes a transfer object context.
+ *
+ * @returns VBox status code.
+ * @param pObjCtx transfer object context to initialize.
+ */
+int ShClTransferObjCtxInit(PSHCLCLIENTTRANSFEROBJCTX pObjCtx)
+{
+ AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ pObjCtx->uHandle = SHCLOBJHANDLE_INVALID;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a transfer object context.
+ *
+ * @param pObjCtx transfer object context to destroy.
+ */
+void ShClTransferObjCtxDestroy(PSHCLCLIENTTRANSFEROBJCTX pObjCtx)
+{
+ AssertPtrReturnVoid(pObjCtx);
+
+ LogFlowFuncEnter();
+}
+
+/**
+ * Returns if a transfer object context is valid or not.
+ *
+ * @returns \c true if valid, \c false if not.
+ * @param pObjCtx transfer object context to check.
+ */
+bool ShClTransferObjCtxIsValid(PSHCLCLIENTTRANSFEROBJCTX pObjCtx)
+{
+ return ( pObjCtx
+ && pObjCtx->uHandle != SHCLOBJHANDLE_INVALID);
+}
+
+/**
+ * Initializes an object handle info structure.
+ *
+ * @returns VBox status code.
+ * @param pInfo Object handle info structure to initialize.
+ */
+int ShClTransferObjHandleInfoInit(PSHCLOBJHANDLEINFO pInfo)
+{
+ AssertPtrReturn(pInfo, VERR_INVALID_POINTER);
+
+ pInfo->hObj = SHCLOBJHANDLE_INVALID;
+ pInfo->enmType = SHCLOBJTYPE_INVALID;
+
+ pInfo->pszPathLocalAbs = NULL;
+
+ RT_ZERO(pInfo->u);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys an object handle info structure.
+ *
+ * @param pInfo Object handle info structure to destroy.
+ */
+void ShClTransferObjHandleInfoDestroy(PSHCLOBJHANDLEINFO pInfo)
+{
+ if (!pInfo)
+ return;
+
+ if (pInfo->pszPathLocalAbs)
+ {
+ RTStrFree(pInfo->pszPathLocalAbs);
+ pInfo->pszPathLocalAbs = NULL;
+ }
+}
+
+/**
+ * Initializes a transfer object open parameters structure.
+ *
+ * @returns VBox status code.
+ * @param pParms transfer object open parameters structure to initialize.
+ */
+int ShClTransferObjOpenParmsInit(PSHCLOBJOPENCREATEPARMS pParms)
+{
+ AssertPtrReturn(pParms, VERR_INVALID_POINTER);
+
+ int rc;
+
+ RT_BZERO(pParms, sizeof(SHCLOBJOPENCREATEPARMS));
+
+ pParms->cbPath = RTPATH_MAX; /** @todo Make this dynamic. */
+ pParms->pszPath = RTStrAlloc(pParms->cbPath);
+ if (pParms->pszPath)
+ {
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Copies a transfer object open parameters structure from source to destination.
+ *
+ * @returns VBox status code.
+ * @param pParmsDst Where to copy the source transfer object open parameters to.
+ * @param pParmsSrc Which source transfer object open parameters to copy.
+ */
+int ShClTransferObjOpenParmsCopy(PSHCLOBJOPENCREATEPARMS pParmsDst, PSHCLOBJOPENCREATEPARMS pParmsSrc)
+{
+ int rc;
+
+ *pParmsDst = *pParmsSrc;
+
+ if (pParmsSrc->pszPath)
+ {
+ Assert(pParmsSrc->cbPath);
+ pParmsDst->pszPath = RTStrDup(pParmsSrc->pszPath);
+ if (pParmsDst->pszPath)
+ {
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Destroys a transfer object open parameters structure.
+ *
+ * @param pParms transfer object open parameters structure to destroy.
+ */
+void ShClTransferObjOpenParmsDestroy(PSHCLOBJOPENCREATEPARMS pParms)
+{
+ if (!pParms)
+ return;
+
+ if (pParms->pszPath)
+ {
+ RTStrFree(pParms->pszPath);
+ pParms->pszPath = NULL;
+ }
+}
+
+/**
+ * Returns a specific object handle info of a transfer.
+ *
+ * @returns Pointer to object handle info if found, or NULL if not found.
+ * @param pTransfer Clipboard transfer to get object handle info from.
+ * @param hObj Object handle of the object to get handle info for.
+ */
+DECLINLINE(PSHCLOBJHANDLEINFO) shClTransferObjGet(PSHCLTRANSFER pTransfer, SHCLOBJHANDLE hObj)
+{
+ PSHCLOBJHANDLEINFO pIt;
+ RTListForEach(&pTransfer->lstObj, pIt, SHCLOBJHANDLEINFO, Node) /** @todo Slooow ...but works for now. */
+ {
+ if (pIt->hObj == hObj)
+ return pIt;
+ }
+
+ return NULL;
+}
+
+/**
+ * Opens a transfer object.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to open the object for.
+ * @param pOpenCreateParms Open / create parameters of transfer object to open / create.
+ * @param phObj Where to store the handle of transfer object opened on success.
+ */
+int ShClTransferObjOpen(PSHCLTRANSFER pTransfer, PSHCLOBJOPENCREATEPARMS pOpenCreateParms, PSHCLOBJHANDLE phObj)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pOpenCreateParms, VERR_INVALID_POINTER);
+ AssertPtrReturn(phObj, VERR_INVALID_POINTER);
+ AssertMsgReturn(pTransfer->pszPathRootAbs, ("Transfer has no root path set\n"), VERR_INVALID_PARAMETER);
+ AssertMsgReturn(pOpenCreateParms->pszPath, ("No path in open/create params set\n"), VERR_INVALID_PARAMETER);
+
+ if (pTransfer->cObjHandles >= pTransfer->cMaxObjHandles)
+ return VERR_SHCLPB_MAX_OBJECTS_REACHED;
+
+ LogFlowFunc(("pszPath=%s, fCreate=0x%x\n", pOpenCreateParms->pszPath, pOpenCreateParms->fCreate));
+
+ int rc;
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ PSHCLOBJHANDLEINFO pInfo = (PSHCLOBJHANDLEINFO)RTMemAllocZ(sizeof(SHCLOBJHANDLEINFO));
+ if (pInfo)
+ {
+ rc = ShClTransferObjHandleInfoInit(pInfo);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t fOpen;
+ rc = shClConvertFileCreateFlags(pOpenCreateParms->fCreate, &fOpen);
+ if (RT_SUCCESS(rc))
+ {
+ rc = shClTransferResolvePathAbs(pTransfer, pOpenCreateParms->pszPath, 0 /* fFlags */,
+ &pInfo->pszPathLocalAbs);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileOpen(&pInfo->u.Local.hFile, pInfo->pszPathLocalAbs, fOpen);
+ if (RT_SUCCESS(rc))
+ LogRel2(("Shared Clipboard: Opened file '%s'\n", pInfo->pszPathLocalAbs));
+ else
+ LogRel(("Shared Clipboard: Error opening file '%s': rc=%Rrc\n", pInfo->pszPathLocalAbs, rc));
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pInfo->hObj = pTransfer->uObjHandleNext++;
+ pInfo->enmType = SHCLOBJTYPE_FILE;
+
+ RTListAppend(&pTransfer->lstObj, &pInfo->Node);
+ pTransfer->cObjHandles++;
+
+ LogFlowFunc(("cObjHandles=%RU32\n", pTransfer->cObjHandles));
+
+ *phObj = pInfo->hObj;
+ }
+ else
+ {
+ ShClTransferObjHandleInfoDestroy(pInfo);
+ RTMemFree(pInfo);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnObjOpen)
+ rc = pTransfer->ProviderIface.pfnObjOpen(&pTransfer->ProviderCtx, pOpenCreateParms, phObj);
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Closes a transfer object.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer that contains the object to close.
+ * @param hObj Handle of transfer object to close.
+ */
+int ShClTransferObjClose(PSHCLTRANSFER pTransfer, SHCLOBJHANDLE hObj)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ PSHCLOBJHANDLEINFO pInfo = shClTransferObjGet(pTransfer, hObj);
+ if (pInfo)
+ {
+ switch (pInfo->enmType)
+ {
+ case SHCLOBJTYPE_DIRECTORY:
+ {
+ rc = RTDirClose(pInfo->u.Local.hDir);
+ if (RT_SUCCESS(rc))
+ {
+ pInfo->u.Local.hDir = NIL_RTDIR;
+
+ LogRel2(("Shared Clipboard: Closed directory '%s'\n", pInfo->pszPathLocalAbs));
+ }
+ else
+ LogRel(("Shared Clipboard: Closing directory '%s' failed with %Rrc\n", pInfo->pszPathLocalAbs, rc));
+ break;
+ }
+
+ case SHCLOBJTYPE_FILE:
+ {
+ rc = RTFileClose(pInfo->u.Local.hFile);
+ if (RT_SUCCESS(rc))
+ {
+ pInfo->u.Local.hFile = NIL_RTFILE;
+
+ LogRel2(("Shared Clipboard: Closed file '%s'\n", pInfo->pszPathLocalAbs));
+ }
+ else
+ LogRel(("Shared Clipboard: Closing file '%s' failed with %Rrc\n", pInfo->pszPathLocalAbs, rc));
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ RTListNodeRemove(&pInfo->Node);
+
+ Assert(pTransfer->cObjHandles);
+ pTransfer->cObjHandles--;
+
+ ShClTransferObjHandleInfoDestroy(pInfo);
+
+ RTMemFree(pInfo);
+ pInfo = NULL;
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnObjClose)
+ {
+ rc = pTransfer->ProviderIface.pfnObjClose(&pTransfer->ProviderCtx, hObj);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Reads from a transfer object.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer that contains the object to read from.
+ * @param hObj Handle of transfer object to read from.
+ * @param pvBuf Buffer for where to store the read data.
+ * @param cbBuf Size (in bytes) of buffer.
+ * @param fFlags Read flags. Optional.
+ * @param pcbRead Where to return how much bytes were read on success. Optional.
+ */
+int ShClTransferObjRead(PSHCLTRANSFER pTransfer,
+ SHCLOBJHANDLE hObj, void *pvBuf, uint32_t cbBuf, uint32_t fFlags, uint32_t *pcbRead)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn (cbBuf, VERR_INVALID_PARAMETER);
+ /* pcbRead is optional. */
+ /** @todo Validate fFlags. */
+
+ int rc = VINF_SUCCESS;
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ PSHCLOBJHANDLEINFO pInfo = shClTransferObjGet(pTransfer, hObj);
+ if (pInfo)
+ {
+ switch (pInfo->enmType)
+ {
+ case SHCLOBJTYPE_FILE:
+ {
+ size_t cbRead;
+ rc = RTFileRead(pInfo->u.Local.hFile, pvBuf, cbBuf, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ if (pcbRead)
+ *pcbRead = (uint32_t)cbRead;
+ }
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnObjRead)
+ {
+ rc = pTransfer->ProviderIface.pfnObjRead(&pTransfer->ProviderCtx, hObj, pvBuf, cbBuf, fFlags, pcbRead);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Writes to a transfer object.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer that contains the object to write to.
+ * @param hObj Handle of transfer object to write to.
+ * @param pvBuf Buffer of data to write.
+ * @param cbBuf Size (in bytes) of buffer to write.
+ * @param fFlags Write flags. Optional.
+ * @param pcbWritten How much bytes were writtenon success. Optional.
+ */
+int ShClTransferObjWrite(PSHCLTRANSFER pTransfer,
+ SHCLOBJHANDLE hObj, void *pvBuf, uint32_t cbBuf, uint32_t fFlags, uint32_t *pcbWritten)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn (cbBuf, VERR_INVALID_PARAMETER);
+ /* pcbWritten is optional. */
+
+ int rc = VINF_SUCCESS;
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ PSHCLOBJHANDLEINFO pInfo = shClTransferObjGet(pTransfer, hObj);
+ if (pInfo)
+ {
+ switch (pInfo->enmType)
+ {
+ case SHCLOBJTYPE_FILE:
+ {
+ rc = RTFileWrite(pInfo->u.Local.hFile, pvBuf, cbBuf, (size_t *)pcbWritten);
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnObjWrite)
+ {
+ rc = pTransfer->ProviderIface.pfnObjWrite(&pTransfer->ProviderCtx, hObj, pvBuf, cbBuf, fFlags, pcbWritten);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Duplicates a transfer object data chunk.
+ *
+ * @returns Duplicated object data chunk on success, or NULL on failure.
+ * @param pDataChunk transfer object data chunk to duplicate.
+ */
+PSHCLOBJDATACHUNK ShClTransferObjDataChunkDup(PSHCLOBJDATACHUNK pDataChunk)
+{
+ if (!pDataChunk)
+ return NULL;
+
+ PSHCLOBJDATACHUNK pDataChunkDup = (PSHCLOBJDATACHUNK)RTMemAllocZ(sizeof(SHCLOBJDATACHUNK));
+ if (!pDataChunkDup)
+ return NULL;
+
+ if (pDataChunk->pvData)
+ {
+ Assert(pDataChunk->cbData);
+
+ pDataChunkDup->uHandle = pDataChunk->uHandle;
+ pDataChunkDup->pvData = RTMemDup(pDataChunk->pvData, pDataChunk->cbData);
+ pDataChunkDup->cbData = pDataChunk->cbData;
+ }
+
+ return pDataChunkDup;
+}
+
+/**
+ * Destroys a transfer object data chunk.
+ *
+ * @param pDataChunk transfer object data chunk to destroy.
+ */
+void ShClTransferObjDataChunkDestroy(PSHCLOBJDATACHUNK pDataChunk)
+{
+ if (!pDataChunk)
+ return;
+
+ if (pDataChunk->pvData)
+ {
+ Assert(pDataChunk->cbData);
+
+ RTMemFree(pDataChunk->pvData);
+
+ pDataChunk->pvData = NULL;
+ pDataChunk->cbData = 0;
+ }
+
+ pDataChunk->uHandle = 0;
+}
+
+/**
+ * Frees a transfer object data chunk.
+ *
+ * @param pDataChunk transfer object data chunk to free. The handed-in pointer will
+ * be invalid after calling this function.
+ */
+void ShClTransferObjDataChunkFree(PSHCLOBJDATACHUNK pDataChunk)
+{
+ if (!pDataChunk)
+ return;
+
+ ShClTransferObjDataChunkDestroy(pDataChunk);
+
+ RTMemFree(pDataChunk);
+ pDataChunk = NULL;
+}
+
+/**
+ * Creates a clipboard transfer.
+ *
+ * @returns VBox status code.
+ * @param ppTransfer Where to return the created Shared Clipboard transfer struct.
+ * Must be destroyed by ShClTransferDestroy().
+ */
+int ShClTransferCreate(PSHCLTRANSFER *ppTransfer)
+{
+ AssertPtrReturn(ppTransfer, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ PSHCLTRANSFER pTransfer = (PSHCLTRANSFER)RTMemAllocZ(sizeof(SHCLTRANSFER));
+ AssertPtrReturn(pTransfer, VERR_NO_MEMORY);
+
+ pTransfer->State.uID = 0;
+ pTransfer->State.enmStatus = SHCLTRANSFERSTATUS_NONE;
+ pTransfer->State.enmDir = SHCLTRANSFERDIR_UNKNOWN;
+ pTransfer->State.enmSource = SHCLSOURCE_INVALID;
+
+ pTransfer->Thread.hThread = NIL_RTTHREAD;
+ pTransfer->Thread.fCancelled = false;
+ pTransfer->Thread.fStarted = false;
+ pTransfer->Thread.fStop = false;
+
+ pTransfer->pszPathRootAbs = NULL;
+
+#ifdef DEBUG_andy
+ pTransfer->uTimeoutMs = RT_MS_5SEC;
+#else
+ pTransfer->uTimeoutMs = RT_MS_30SEC;
+#endif
+ pTransfer->cbMaxChunkSize = _64K; /** @todo Make this configurable. */
+ pTransfer->cMaxListHandles = _4K; /** @todo Ditto. */
+ pTransfer->cMaxObjHandles = _4K; /** @todo Ditto. */
+
+ pTransfer->pvUser = NULL;
+ pTransfer->cbUser = 0;
+
+ RTListInit(&pTransfer->lstList);
+ RTListInit(&pTransfer->lstObj);
+
+ pTransfer->cRoots = 0;
+ RTListInit(&pTransfer->lstRoots);
+
+ int rc = ShClEventSourceCreate(&pTransfer->Events, 0 /* uID */);
+ if (RT_SUCCESS(rc))
+ {
+ *ppTransfer = pTransfer;
+ }
+ else
+ {
+ if (pTransfer)
+ {
+ ShClTransferDestroy(pTransfer);
+ RTMemFree(pTransfer);
+ }
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Destroys a clipboard transfer context struct.
+ *
+ * @returns VBox status code.
+ * @param pTransferCtx Clipboard transfer to destroy.
+ */
+int ShClTransferDestroy(PSHCLTRANSFER pTransfer)
+{
+ if (!pTransfer)
+ return VINF_SUCCESS;
+
+ LogFlowFuncEnter();
+
+ int rc = shClTransferThreadDestroy(pTransfer, 30 * 1000 /* Timeout in ms */);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ ShClTransferReset(pTransfer);
+
+ ShClEventSourceDestroy(&pTransfer->Events);
+
+ LogFlowFuncLeave();
+ return VINF_SUCCESS;
+}
+
+/**
+ * Initializes a Shared Clipboard transfer object.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Transfer to initialize.
+ * @param enmDir Specifies the transfer direction of this transfer.
+ * @param enmSource Specifies the data source of the transfer.
+ */
+int ShClTransferInit(PSHCLTRANSFER pTransfer, SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource)
+{
+ pTransfer->State.enmDir = enmDir;
+ pTransfer->State.enmSource = enmSource;
+
+ LogFlowFunc(("uID=%RU32, enmDir=%RU32, enmSource=%RU32\n",
+ pTransfer->State.uID, pTransfer->State.enmDir, pTransfer->State.enmSource));
+
+ pTransfer->State.enmStatus = SHCLTRANSFERSTATUS_INITIALIZED; /* Now we're ready to run. */
+
+ pTransfer->cListHandles = 0;
+ pTransfer->uListHandleNext = 1;
+
+ pTransfer->cObjHandles = 0;
+ pTransfer->uObjHandleNext = 1;
+
+ int rc = VINF_SUCCESS;
+
+ if (pTransfer->Callbacks.pfnOnInitialize)
+ rc = pTransfer->Callbacks.pfnOnInitialize(&pTransfer->CallbackCtx);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Returns a specific list handle info of a transfer.
+ *
+ * @returns Pointer to list handle info if found, or NULL if not found.
+ * @param pTransfer Clipboard transfer to get list handle info from.
+ * @param hList List handle of the list to get handle info for.
+ */
+DECLINLINE(PSHCLLISTHANDLEINFO) shClTransferListGetByHandle(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList)
+{
+ PSHCLLISTHANDLEINFO pIt;
+ RTListForEach(&pTransfer->lstList, pIt, SHCLLISTHANDLEINFO, Node) /** @todo Sloooow ... improve this. */
+ {
+ if (pIt->hList == hList)
+ return pIt;
+ }
+
+ return NULL;
+}
+
+/**
+ * Creates a new list handle (local only).
+ *
+ * @returns New List handle on success, or SHCLLISTHANDLE_INVALID on error.
+ * @param pTransfer Clipboard transfer to create new list handle for.
+ */
+DECLINLINE(SHCLLISTHANDLE) shClTransferListHandleNew(PSHCLTRANSFER pTransfer)
+{
+ return pTransfer->uListHandleNext++; /** @todo Good enough for now. Improve this later. */
+}
+
+/**
+ * Validates whether a given path matches our set of rules or not.
+ *
+ * @returns VBox status code.
+ * @param pcszPath Path to validate.
+ * @param fMustExist Whether the path to validate also must exist.
+ */
+static int shClTransferValidatePath(const char *pcszPath, bool fMustExist)
+{
+ int rc = VINF_SUCCESS;
+
+ if (!strlen(pcszPath))
+ rc = VERR_INVALID_PARAMETER;
+
+ if ( RT_SUCCESS(rc)
+ && !RTStrIsValidEncoding(pcszPath))
+ {
+ rc = VERR_INVALID_UTF8_ENCODING;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && RTStrStr(pcszPath, ".."))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && fMustExist)
+ {
+ RTFSOBJINFO objInfo;
+ rc = RTPathQueryInfo(pcszPath, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ if (!RTDirExists(pcszPath)) /* Path must exist. */
+ rc = VERR_PATH_NOT_FOUND;
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ {
+ if (!RTFileExists(pcszPath)) /* File must exist. */
+ rc = VERR_FILE_NOT_FOUND;
+ }
+ else /* Everything else (e.g. symbolic links) are not supported. */
+ {
+ LogRel2(("Shared Clipboard: Path '%s' contains a symbolic link or junktion, which are not supported\n", pcszPath));
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel2(("Shared Clipboard: Validating path '%s' failed: %Rrc\n", pcszPath, rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Resolves a relative path of a specific transfer to its absolute path.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to resolve path for.
+ * @param pszPath Path to resolve.
+ * @param fFlags Resolve flags. Currently not used and must be 0.
+ * @param ppszResolved Where to store the allocated resolved path. Must be free'd by the called using RTStrFree().
+ */
+static int shClTransferResolvePathAbs(PSHCLTRANSFER pTransfer, const char *pszPath, uint32_t fFlags,
+ char **ppszResolved)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn (fFlags == 0, VERR_INVALID_PARAMETER);
+
+ LogFlowFunc(("pszPathRootAbs=%s, pszPath=%s\n", pTransfer->pszPathRootAbs, pszPath));
+
+ int rc = shClTransferValidatePath(pszPath, false /* fMustExist */);
+ if (RT_SUCCESS(rc))
+ {
+ char *pszPathAbs = RTPathJoinA(pTransfer->pszPathRootAbs, pszPath);
+ if (pszPathAbs)
+ {
+ char szResolved[RTPATH_MAX];
+ size_t cbResolved = sizeof(szResolved);
+ rc = RTPathAbsEx(pTransfer->pszPathRootAbs, pszPathAbs, RTPATH_STR_F_STYLE_HOST, szResolved, &cbResolved);
+
+ RTStrFree(pszPathAbs);
+
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("pszResolved=%s\n", szResolved));
+
+ rc = VERR_PATH_NOT_FOUND; /* Play safe by default. */
+
+ /* Make sure the resolved path is part of the set of root entries. */
+ PSHCLLISTROOT pListRoot;
+ RTListForEach(&pTransfer->lstRoots, pListRoot, SHCLLISTROOT, Node)
+ {
+ if (RTPathStartsWith(szResolved, pListRoot->pszPathAbs))
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppszResolved = RTStrDup(szResolved);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(rc))
+ LogRel(("Shared Clipboard: Resolving absolute path '%s' failed, rc=%Rrc\n", pszPath, rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Opens a list.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to handle.
+ * @param pOpenParms List open parameters to use for opening.
+ * @param phList Where to store the List handle of opened list on success.
+ */
+int ShClTransferListOpen(PSHCLTRANSFER pTransfer, PSHCLLISTOPENPARMS pOpenParms,
+ PSHCLLISTHANDLE phList)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pOpenParms, VERR_INVALID_POINTER);
+ AssertPtrReturn(phList, VERR_INVALID_POINTER);
+
+ int rc;
+
+ if (pTransfer->cListHandles == pTransfer->cMaxListHandles)
+ return VERR_SHCLPB_MAX_LISTS_REACHED;
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ LogFlowFunc(("pszPath=%s\n", pOpenParms->pszPath));
+
+ PSHCLLISTHANDLEINFO pInfo
+ = (PSHCLLISTHANDLEINFO)RTMemAllocZ(sizeof(SHCLLISTHANDLEINFO));
+ if (pInfo)
+ {
+ rc = ShClTransferListHandleInfoInit(pInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = shClTransferResolvePathAbs(pTransfer, pOpenParms->pszPath, 0 /* fFlags */, &pInfo->pszPathLocalAbs);
+ if (RT_SUCCESS(rc))
+ {
+ RTFSOBJINFO objInfo;
+ rc = RTPathQueryInfo(pInfo->pszPathLocalAbs, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ rc = RTDirOpen(&pInfo->u.Local.hDir, pInfo->pszPathLocalAbs);
+ if (RT_SUCCESS(rc))
+ {
+ pInfo->enmType = SHCLOBJTYPE_DIRECTORY;
+
+ LogRel2(("Shared Clipboard: Opening directory '%s'\n", pInfo->pszPathLocalAbs));
+ }
+ else
+ LogRel(("Shared Clipboard: Opening directory '%s' failed with %Rrc\n", pInfo->pszPathLocalAbs, rc));
+
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ {
+ rc = RTFileOpen(&pInfo->u.Local.hFile, pInfo->pszPathLocalAbs,
+ RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ pInfo->enmType = SHCLOBJTYPE_FILE;
+
+ LogRel2(("Shared Clipboard: Opening file '%s'\n", pInfo->pszPathLocalAbs));
+ }
+ else
+ LogRel(("Shared Clipboard: Opening file '%s' failed with %Rrc\n", pInfo->pszPathLocalAbs, rc));
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ if (RT_SUCCESS(rc))
+ {
+ pInfo->hList = shClTransferListHandleNew(pTransfer);
+
+ RTListAppend(&pTransfer->lstList, &pInfo->Node);
+ pTransfer->cListHandles++;
+
+ if (phList)
+ *phList = pInfo->hList;
+
+ LogFlowFunc(("pszPathLocalAbs=%s, hList=%RU64, cListHandles=%RU32\n",
+ pInfo->pszPathLocalAbs, pInfo->hList, pTransfer->cListHandles));
+ }
+ else
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ if (RTDirIsValid(pInfo->u.Local.hDir))
+ RTDirClose(pInfo->u.Local.hDir);
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ {
+ if (RTFileIsValid(pInfo->u.Local.hFile))
+ RTFileClose(pInfo->u.Local.hFile);
+ }
+ }
+ }
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ ShClTransferListHandleInfoDestroy(pInfo);
+
+ RTMemFree(pInfo);
+ pInfo = NULL;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnListOpen)
+ {
+ rc = pTransfer->ProviderIface.pfnListOpen(&pTransfer->ProviderCtx, pOpenParms, phList);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Closes a list.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to handle.
+ * @param hList Handle of list to close.
+ */
+int ShClTransferListClose(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ if (hList == SHCLLISTHANDLE_INVALID)
+ return VINF_SUCCESS;
+
+ int rc = VINF_SUCCESS;
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ PSHCLLISTHANDLEINFO pInfo = shClTransferListGetByHandle(pTransfer, hList);
+ if (pInfo)
+ {
+ switch (pInfo->enmType)
+ {
+ case SHCLOBJTYPE_DIRECTORY:
+ {
+ if (RTDirIsValid(pInfo->u.Local.hDir))
+ {
+ RTDirClose(pInfo->u.Local.hDir);
+ pInfo->u.Local.hDir = NIL_RTDIR;
+ }
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+
+ RTListNodeRemove(&pInfo->Node);
+
+ Assert(pTransfer->cListHandles);
+ pTransfer->cListHandles--;
+
+ RTMemFree(pInfo);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnListClose)
+ {
+ rc = pTransfer->ProviderIface.pfnListClose(&pTransfer->ProviderCtx, hList);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Adds a file to a list heaer.
+ *
+ * @returns VBox status code.
+ * @param pHdr List header to add file to.
+ * @param pszPath Path of file to add.
+ */
+static int shclTransferListHdrAddFile(PSHCLLISTHDR pHdr, const char *pszPath)
+{
+ uint64_t cbSize = 0;
+ int rc = RTFileQuerySizeByPath(pszPath, &cbSize);
+ if (RT_SUCCESS(rc))
+ {
+ pHdr->cbTotalSize += cbSize;
+ pHdr->cTotalObjects++;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Builds a list header, internal version.
+ *
+ * @returns VBox status code.
+ * @param pHdr Where to store the build list header.
+ * @param pcszSrcPath Source path of list.
+ * @param pcszDstPath Destination path of list.
+ * @param pcszDstBase Destination base path.
+ * @param cchDstBase Number of charaters of destination base path.
+ */
+static int shclTransferListHdrFromDir(PSHCLLISTHDR pHdr, const char *pcszPathAbs)
+{
+ AssertPtrReturn(pcszPathAbs, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("pcszPathAbs=%s\n", pcszPathAbs));
+
+ RTFSOBJINFO objInfo;
+ int rc = RTPathQueryInfo(pcszPathAbs, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(objInfo.Attr.fMode))
+ {
+ RTDIR hDir;
+ rc = RTDirOpen(&hDir, pcszPathAbs);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbDirEntry = 0;
+ PRTDIRENTRYEX pDirEntry = NULL;
+ do
+ {
+ /* Retrieve the next directory entry. */
+ rc = RTDirReadExA(hDir, &pDirEntry, &cbDirEntry, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ /* Skip "." and ".." entries. */
+ if (RTDirEntryExIsStdDotLink(pDirEntry))
+ break;
+
+ pHdr->cTotalObjects++;
+ break;
+ }
+ case RTFS_TYPE_FILE:
+ {
+ char *pszSrc = RTPathJoinA(pcszPathAbs, pDirEntry->szName);
+ if (pszSrc)
+ {
+ rc = shclTransferListHdrAddFile(pHdr, pszSrc);
+ RTStrFree(pszSrc);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ case RTFS_TYPE_SYMLINK:
+ {
+ /** @todo Not implemented yet. */
+ }
+
+ default:
+ break;
+ }
+
+ } while (RT_SUCCESS(rc));
+
+ RTDirReadExAFree(&pDirEntry, &cbDirEntry);
+ RTDirClose(hDir);
+ }
+ }
+ else if (RTFS_IS_FILE(objInfo.Attr.fMode))
+ {
+ rc = shclTransferListHdrAddFile(pHdr, pcszPathAbs);
+ }
+ else if (RTFS_IS_SYMLINK(objInfo.Attr.fMode))
+ {
+ /** @todo Not implemented yet. */
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Retrieves the header of a Shared Clipboard list.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to handle.
+ * @param hList Handle of list to get header for.
+ * @param pHdr Where to store the returned list header information.
+ */
+int ShClTransferListGetHeader(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList,
+ PSHCLLISTHDR pHdr)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pHdr, VERR_INVALID_POINTER);
+
+ int rc;
+
+ LogFlowFunc(("hList=%RU64\n", hList));
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ PSHCLLISTHANDLEINFO pInfo = shClTransferListGetByHandle(pTransfer, hList);
+ if (pInfo)
+ {
+ rc = ShClTransferListHdrInit(pHdr);
+ if (RT_SUCCESS(rc))
+ {
+ switch (pInfo->enmType)
+ {
+ case SHCLOBJTYPE_DIRECTORY:
+ {
+ LogFlowFunc(("DirAbs: %s\n", pInfo->pszPathLocalAbs));
+
+ rc = shclTransferListHdrFromDir(pHdr, pInfo->pszPathLocalAbs);
+ break;
+ }
+
+ case SHCLOBJTYPE_FILE:
+ {
+ LogFlowFunc(("FileAbs: %s\n", pInfo->pszPathLocalAbs));
+
+ pHdr->cTotalObjects = 1;
+
+ RTFSOBJINFO objInfo;
+ rc = RTFileQueryInfo(pInfo->u.Local.hFile, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ pHdr->cbTotalSize = objInfo.cbObject;
+ }
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+
+ LogFlowFunc(("cTotalObj=%RU64, cbTotalSize=%RU64\n", pHdr->cTotalObjects, pHdr->cbTotalSize));
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnListHdrRead)
+ {
+ rc = pTransfer->ProviderIface.pfnListHdrRead(&pTransfer->ProviderCtx, hList, pHdr);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Returns the current transfer object for a Shared Clipboard transfer list.
+ *
+ * Currently not implemented and wil return NULL.
+ *
+ * @returns Pointer to transfer object, or NULL if not found / invalid.
+ * @param pTransfer Clipboard transfer to return transfer object for.
+ * @param hList Handle of Shared Clipboard transfer list to get object for.
+ * @param uIdx Index of object to get.
+ */
+PSHCLTRANSFEROBJ ShClTransferListGetObj(PSHCLTRANSFER pTransfer,
+ SHCLLISTHANDLE hList, uint64_t uIdx)
+{
+ AssertPtrReturn(pTransfer, NULL);
+
+ RT_NOREF(hList, uIdx);
+
+ LogFlowFunc(("hList=%RU64\n", hList));
+
+ return NULL;
+}
+
+/**
+ * Reads a single Shared Clipboard list entry.
+ *
+ * @returns VBox status code or VERR_NO_MORE_FILES if the end of the list has been reached.
+ * @param pTransfer Clipboard transfer to handle.
+ * @param hList List handle of list to read from.
+ * @param pEntry Where to store the read information.
+ */
+int ShClTransferListRead(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList,
+ PSHCLLISTENTRY pEntry)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pEntry, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("hList=%RU64\n", hList));
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ PSHCLLISTHANDLEINFO pInfo = shClTransferListGetByHandle(pTransfer, hList);
+ if (pInfo)
+ {
+ switch (pInfo->enmType)
+ {
+ case SHCLOBJTYPE_DIRECTORY:
+ {
+ LogFlowFunc(("\tDirectory: %s\n", pInfo->pszPathLocalAbs));
+
+ for (;;)
+ {
+ bool fSkipEntry = false; /* Whether to skip an entry in the enumeration. */
+
+ size_t cbDirEntry = 0;
+ PRTDIRENTRYEX pDirEntry = NULL;
+ rc = RTDirReadExA(pInfo->u.Local.hDir, &pDirEntry, &cbDirEntry, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_SUCCESS(rc))
+ {
+ switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ /* Skip "." and ".." entries. */
+ if (RTDirEntryExIsStdDotLink(pDirEntry))
+ {
+ fSkipEntry = true;
+ break;
+ }
+
+ LogFlowFunc(("Directory: %s\n", pDirEntry->szName));
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ LogFlowFunc(("File: %s\n", pDirEntry->szName));
+ break;
+ }
+
+ case RTFS_TYPE_SYMLINK:
+ {
+ rc = VERR_NOT_IMPLEMENTED; /** @todo Not implemented yet. */
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && !fSkipEntry)
+ {
+ rc = RTStrCopy(pEntry->pszName, pEntry->cbName, pDirEntry->szName);
+ if (RT_SUCCESS(rc))
+ {
+ pEntry->cbName = (uint32_t)strlen(pEntry->pszName) + 1; /* Include termination. */
+
+ AssertPtr(pEntry->pvInfo);
+ Assert (pEntry->cbInfo == sizeof(SHCLFSOBJINFO));
+
+ ShClFsObjFromIPRT(PSHCLFSOBJINFO(pEntry->pvInfo), &pDirEntry->Info);
+
+ LogFlowFunc(("Entry pszName=%s, pvInfo=%p, cbInfo=%RU32\n",
+ pEntry->pszName, pEntry->pvInfo, pEntry->cbInfo));
+ }
+ }
+
+ RTDirReadExAFree(&pDirEntry, &cbDirEntry);
+ }
+
+ if ( !fSkipEntry /* Do we have a valid entry? Bail out. */
+ || RT_FAILURE(rc))
+ {
+ break;
+ }
+ }
+
+ break;
+ }
+
+ case SHCLOBJTYPE_FILE:
+ {
+ LogFlowFunc(("\tSingle file: %s\n", pInfo->pszPathLocalAbs));
+
+ RTFSOBJINFO objInfo;
+ rc = RTFileQueryInfo(pInfo->u.Local.hFile, &objInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ pEntry->pvInfo = (PSHCLFSOBJINFO)RTMemAlloc(sizeof(SHCLFSOBJINFO));
+ if (pEntry->pvInfo)
+ {
+ rc = RTStrCopy(pEntry->pszName, pEntry->cbName, pInfo->pszPathLocalAbs);
+ if (RT_SUCCESS(rc))
+ {
+ ShClFsObjFromIPRT(PSHCLFSOBJINFO(pEntry->pvInfo), &objInfo);
+
+ pEntry->cbInfo = sizeof(SHCLFSOBJINFO);
+ pEntry->fInfo = VBOX_SHCL_INFO_FLAG_FSOBJINFO;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ break;
+ }
+
+ default:
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnListEntryRead)
+ rc = pTransfer->ProviderIface.pfnListEntryRead(&pTransfer->ProviderCtx, hList, pEntry);
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+int ShClTransferListWrite(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList,
+ PSHCLLISTENTRY pEntry)
+{
+ RT_NOREF(pTransfer, hList, pEntry);
+
+ int rc = VINF_SUCCESS;
+
+#if 0
+ if (pTransfer->ProviderIface.pfnListEntryWrite)
+ rc = pTransfer->ProviderIface.pfnListEntryWrite(&pTransfer->ProviderCtx, hList, pEntry);
+#endif
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Returns whether a given list handle is valid or not.
+ *
+ * @returns \c true if list handle is valid, \c false if not.
+ * @param pTransfer Clipboard transfer to handle.
+ * @param hList List handle to check.
+ */
+bool ShClTransferListHandleIsValid(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList)
+{
+ bool fIsValid = false;
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ fIsValid = shClTransferListGetByHandle(pTransfer, hList) != NULL;
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ AssertFailed(); /** @todo Implement. */
+ }
+ else
+ AssertFailedStmt(fIsValid = false);
+
+ return fIsValid;
+}
+
+/**
+ * Copies a transfer callback table from source to destination.
+ *
+ * @param pCallbacksDst Callback destination.
+ * @param pCallbacksSrc Callback source. If set to NULL, the
+ * destination callback table will be unset.
+ */
+void ShClTransferCopyCallbacks(PSHCLTRANSFERCALLBACKTABLE pCallbacksDst,
+ PSHCLTRANSFERCALLBACKTABLE pCallbacksSrc)
+{
+ AssertPtrReturnVoid(pCallbacksDst);
+
+ if (pCallbacksSrc) /* Set */
+ {
+#define SET_CALLBACK(a_pfnCallback) \
+ if (pCallbacksSrc->a_pfnCallback) \
+ pCallbacksDst->a_pfnCallback = pCallbacksSrc->a_pfnCallback
+
+ SET_CALLBACK(pfnOnInitialize);
+ SET_CALLBACK(pfnOnStart);
+ SET_CALLBACK(pfnOnCompleted);
+ SET_CALLBACK(pfnOnError);
+ SET_CALLBACK(pfnOnRegistered);
+ SET_CALLBACK(pfnOnUnregistered);
+
+#undef SET_CALLBACK
+
+ pCallbacksDst->pvUser = pCallbacksSrc->pvUser;
+ pCallbacksDst->cbUser = pCallbacksSrc->cbUser;
+ }
+ else /* Unset */
+ RT_BZERO(pCallbacksDst, sizeof(SHCLTRANSFERCALLBACKTABLE));
+}
+
+/**
+ * Sets or unsets the callback table to be used for a Shared Clipboard transfer.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to set callbacks for.
+ * @param pCallbacks Pointer to callback table to set. If set to NULL,
+ * existing callbacks for this transfer will be unset.
+ */
+void ShClTransferSetCallbacks(PSHCLTRANSFER pTransfer,
+ PSHCLTRANSFERCALLBACKTABLE pCallbacks)
+{
+ AssertPtrReturnVoid(pTransfer);
+ /* pCallbacks can be NULL. */
+
+ ShClTransferCopyCallbacks(&pTransfer->Callbacks, pCallbacks);
+}
+
+/**
+ * Sets the transfer provider interface for a given transfer.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Transfer to create transfer provider for.
+ * @param pCreationCtx Provider creation context to use for provider creation.
+ */
+int ShClTransferSetProviderIface(PSHCLTRANSFER pTransfer,
+ PSHCLTXPROVIDERCREATIONCTX pCreationCtx)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pCreationCtx, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ int rc = VINF_SUCCESS;
+
+ pTransfer->ProviderIface = pCreationCtx->Interface;
+ pTransfer->ProviderCtx.pTransfer = pTransfer;
+ pTransfer->ProviderCtx.pvUser = pCreationCtx->pvUser;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Clears (resets) the root list of a Shared Clipboard transfer.
+ *
+ * @param pTransfer Transfer to clear transfer root list for.
+ */
+static void shClTransferListRootsClear(PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturnVoid(pTransfer);
+
+ if (pTransfer->pszPathRootAbs)
+ {
+ RTStrFree(pTransfer->pszPathRootAbs);
+ pTransfer->pszPathRootAbs = NULL;
+ }
+
+ PSHCLLISTROOT pListRoot, pListRootNext;
+ RTListForEachSafe(&pTransfer->lstRoots, pListRoot, pListRootNext, SHCLLISTROOT, Node)
+ {
+ RTStrFree(pListRoot->pszPathAbs);
+
+ RTListNodeRemove(&pListRoot->Node);
+
+ RTMemFree(pListRoot);
+ pListRoot = NULL;
+ }
+
+ pTransfer->cRoots = 0;
+}
+
+/**
+ * Resets a Shared Clipboard transfer.
+ *
+ * @param pTransfer Clipboard transfer to reset.
+ */
+void ShClTransferReset(PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturnVoid(pTransfer);
+
+ LogFlowFuncEnter();
+
+ shClTransferListRootsClear(pTransfer);
+
+ PSHCLLISTHANDLEINFO pItList, pItListNext;
+ RTListForEachSafe(&pTransfer->lstList, pItList, pItListNext, SHCLLISTHANDLEINFO, Node)
+ {
+ ShClTransferListHandleInfoDestroy(pItList);
+
+ RTListNodeRemove(&pItList->Node);
+
+ RTMemFree(pItList);
+ }
+
+ PSHCLOBJHANDLEINFO pItObj, pItObjNext;
+ RTListForEachSafe(&pTransfer->lstObj, pItObj, pItObjNext, SHCLOBJHANDLEINFO, Node)
+ {
+ ShClTransferObjHandleInfoDestroy(pItObj);
+
+ RTListNodeRemove(&pItObj->Node);
+
+ RTMemFree(pItObj);
+ }
+}
+
+/**
+ * Returns the number of transfer root list entries.
+ *
+ * @returns Root list entry count.
+ * @param pTransfer Clipboard transfer to return root entry count for.
+ */
+uint32_t ShClTransferRootsCount(PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pTransfer, 0);
+
+ LogFlowFunc(("[Transfer %RU32] cRoots=%RU64\n", pTransfer->State.uID, pTransfer->cRoots));
+ return (uint32_t)pTransfer->cRoots;
+}
+
+/**
+ * Returns a specific root list entry of a transfer.
+ *
+ * @returns Pointer to root list entry if found, or NULL if not found.
+ * @param pTransfer Clipboard transfer to get root list entry from.
+ * @param uIdx Index of root list entry to return.
+ */
+DECLINLINE(PSHCLLISTROOT) shClTransferRootsGetInternal(PSHCLTRANSFER pTransfer, uint32_t uIdx)
+{
+ if (uIdx >= pTransfer->cRoots)
+ return NULL;
+
+ PSHCLLISTROOT pIt = RTListGetFirst(&pTransfer->lstRoots, SHCLLISTROOT, Node);
+ while (uIdx--) /** @todo Slow, but works for now. */
+ pIt = RTListGetNext(&pTransfer->lstRoots, pIt, SHCLLISTROOT, Node);
+
+ return pIt;
+}
+
+/**
+ * Get a specific root list entry.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to get root list entry of.
+ * @param uIndex Index (zero-based) of entry to get.
+ * @param pEntry Where to store the returned entry on success.
+ */
+int ShClTransferRootsEntry(PSHCLTRANSFER pTransfer,
+ uint64_t uIndex, PSHCLROOTLISTENTRY pEntry)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pEntry, VERR_INVALID_POINTER);
+
+ if (uIndex >= pTransfer->cRoots)
+ return VERR_INVALID_PARAMETER;
+
+ int rc;
+
+ PSHCLLISTROOT pRoot = shClTransferRootsGetInternal(pTransfer, uIndex);
+ AssertPtrReturn(pRoot, VERR_INVALID_PARAMETER);
+
+ /* Make sure that we only advertise relative source paths, not absolute ones. */
+ const char *pcszSrcPath = pRoot->pszPathAbs;
+
+ char *pszFileName = RTPathFilename(pcszSrcPath);
+ if (pszFileName)
+ {
+ Assert(pszFileName >= pcszSrcPath);
+ size_t cchDstBase = pszFileName - pcszSrcPath;
+ const char *pszDstPath = &pcszSrcPath[cchDstBase];
+
+ LogFlowFunc(("pcszSrcPath=%s, pszDstPath=%s\n", pcszSrcPath, pszDstPath));
+
+ rc = ShClTransferListEntryInit(pEntry);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrCopy(pEntry->pszName, pEntry->cbName, pszDstPath);
+ if (RT_SUCCESS(rc))
+ {
+ pEntry->cbInfo = sizeof(SHCLFSOBJINFO);
+ pEntry->pvInfo = (PSHCLFSOBJINFO)RTMemAlloc(pEntry->cbInfo);
+ if (pEntry->pvInfo)
+ {
+ RTFSOBJINFO fsObjInfo;
+ rc = RTPathQueryInfo(pcszSrcPath, &fsObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ ShClFsObjFromIPRT(PSHCLFSOBJINFO(pEntry->pvInfo), &fsObjInfo);
+
+ pEntry->fInfo = VBOX_SHCL_INFO_FLAG_FSOBJINFO;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ }
+ else
+ rc = VERR_INVALID_POINTER;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Returns the root entries of a Shared Clipboard transfer.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to return root entries for.
+ * @param ppRootList Where to store the root list on success.
+ */
+int ShClTransferRootsGet(PSHCLTRANSFER pTransfer, PSHCLROOTLIST *ppRootList)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppRootList, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ int rc = VINF_SUCCESS;
+
+ if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL)
+ {
+ PSHCLROOTLIST pRootList = ShClTransferRootListAlloc();
+ if (!pRootList)
+ return VERR_NO_MEMORY;
+
+ const uint64_t cRoots = (uint32_t)pTransfer->cRoots;
+
+ LogFlowFunc(("cRoots=%RU64\n", cRoots));
+
+ if (cRoots)
+ {
+ PSHCLROOTLISTENTRY paRootListEntries
+ = (PSHCLROOTLISTENTRY)RTMemAllocZ(cRoots * sizeof(SHCLROOTLISTENTRY));
+ if (paRootListEntries)
+ {
+ for (uint64_t i = 0; i < cRoots; ++i)
+ {
+ rc = ShClTransferRootsEntry(pTransfer, i, &paRootListEntries[i]);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ pRootList->paEntries = paRootListEntries;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ if (RT_SUCCESS(rc))
+ {
+ pRootList->Hdr.cRoots = cRoots;
+ pRootList->Hdr.fRoots = 0; /** @todo Implement this. */
+
+ *ppRootList = pRootList;
+ }
+ }
+ else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE)
+ {
+ if (pTransfer->ProviderIface.pfnRootsGet)
+ rc = pTransfer->ProviderIface.pfnRootsGet(&pTransfer->ProviderCtx, ppRootList);
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Sets transfer root list entries for a given transfer.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Transfer to set transfer list entries for.
+ * @param pszRoots String list (separated by CRLF) of root entries to set.
+ * All entries must have the same root path.
+ * @param cbRoots Size (in bytes) of string list.
+ */
+int ShClTransferRootsSet(PSHCLTRANSFER pTransfer, const char *pszRoots, size_t cbRoots)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszRoots, VERR_INVALID_POINTER);
+ AssertReturn(cbRoots, VERR_INVALID_PARAMETER);
+
+ if (!RTStrIsValidEncoding(pszRoots))
+ return VERR_INVALID_UTF8_ENCODING;
+
+ int rc = VINF_SUCCESS;
+
+ shClTransferListRootsClear(pTransfer);
+
+ char *pszPathRootAbs = NULL;
+
+ RTCList<RTCString> lstRootEntries = RTCString(pszRoots, cbRoots - 1).split("\r\n");
+ for (size_t i = 0; i < lstRootEntries.size(); ++i)
+ {
+ PSHCLLISTROOT pListRoot = (PSHCLLISTROOT)RTMemAlloc(sizeof(SHCLLISTROOT));
+ AssertPtrBreakStmt(pListRoot, rc = VERR_NO_MEMORY);
+
+ const char *pszPathCur = RTStrDup(lstRootEntries.at(i).c_str());
+
+ LogFlowFunc(("pszPathCur=%s\n", pszPathCur));
+
+ /* No root path determined yet? */
+ if (!pszPathRootAbs)
+ {
+ pszPathRootAbs = RTStrDup(pszPathCur);
+ if (pszPathRootAbs)
+ {
+ RTPathStripFilename(pszPathRootAbs);
+
+ LogFlowFunc(("pszPathRootAbs=%s\n", pszPathRootAbs));
+
+ /* We don't want to have a relative directory here. */
+ if (RTPathStartsWithRoot(pszPathRootAbs))
+ {
+ rc = shClTransferValidatePath(pszPathRootAbs, true /* Path must exist */);
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_FAILURE(rc))
+ break;
+
+ pListRoot->pszPathAbs = RTStrDup(pszPathCur);
+ if (!pListRoot->pszPathAbs)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ RTListAppend(&pTransfer->lstRoots, &pListRoot->Node);
+
+ pTransfer->cRoots++;
+ }
+
+ /* No (valid) root directory found? Bail out early. */
+ if (!pszPathRootAbs)
+ rc = VERR_PATH_NOT_FOUND;
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Step 2:
+ * Go through the created list and make sure all entries have the same root path.
+ */
+ PSHCLLISTROOT pListRoot;
+ RTListForEach(&pTransfer->lstRoots, pListRoot, SHCLLISTROOT, Node)
+ {
+ if (!RTStrStartsWith(pListRoot->pszPathAbs, pszPathRootAbs))
+ {
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+
+ rc = shClTransferValidatePath(pListRoot->pszPathAbs, true /* Path must exist */);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+
+ /** @todo Entry rollback on failure? */
+
+ if (RT_SUCCESS(rc))
+ {
+ pTransfer->pszPathRootAbs = pszPathRootAbs;
+ LogFlowFunc(("pszPathRootAbs=%s, cRoots=%zu\n", pTransfer->pszPathRootAbs, pTransfer->cRoots));
+
+ LogRel2(("Shared Clipboard: Transfer uses root '%s'\n", pTransfer->pszPathRootAbs));
+ }
+ else
+ {
+ LogRel(("Shared Clipboard: Unable to set roots for transfer, rc=%Rrc\n", rc));
+ RTStrFree(pszPathRootAbs);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Returns the transfer's ID.
+ *
+ * @returns The transfer's ID.
+ * @param pTransfer Clipboard transfer to return ID for.
+ */
+SHCLTRANSFERID ShClTransferGetID(PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pTransfer, 0);
+
+ return pTransfer->State.uID;
+}
+
+/**
+ * Returns the transfer's direction.
+ *
+ * @returns The transfer's direction.
+ * @param pTransfer Clipboard transfer to return direction for.
+ */
+SHCLTRANSFERDIR ShClTransferGetDir(PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pTransfer, SHCLTRANSFERDIR_UNKNOWN);
+
+ LogFlowFunc(("[Transfer %RU32] enmDir=%RU32\n", pTransfer->State.uID, pTransfer->State.enmDir));
+ return pTransfer->State.enmDir;
+}
+
+/**
+ * Returns the transfer's source.
+ *
+ * @returns The transfer's source.
+ * @param pTransfer Clipboard transfer to return source for.
+ */
+SHCLSOURCE ShClTransferGetSource(PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pTransfer, SHCLSOURCE_INVALID);
+
+ LogFlowFunc(("[Transfer %RU32] enmSource=%RU32\n", pTransfer->State.uID, pTransfer->State.enmSource));
+ return pTransfer->State.enmSource;
+}
+
+/**
+ * Returns the current transfer status.
+ *
+ * @returns Current transfer status.
+ * @param pTransfer Clipboard transfer to return status for.
+ */
+SHCLTRANSFERSTATUS ShClTransferGetStatus(PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pTransfer, SHCLTRANSFERSTATUS_NONE);
+
+ LogFlowFunc(("[Transfer %RU32] enmStatus=%RU32\n", pTransfer->State.uID, pTransfer->State.enmStatus));
+ return pTransfer->State.enmStatus;
+}
+
+/**
+ * Runs a started Shared Clipboard transfer in a dedicated thread.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to run.
+ * @param pfnThreadFunc Pointer to thread function to use.
+ * @param pvUser Pointer to user-provided data. Optional.
+ */
+int ShClTransferRun(PSHCLTRANSFER pTransfer, PFNRTTHREAD pfnThreadFunc, void *pvUser)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfnThreadFunc, VERR_INVALID_POINTER);
+ /* pvUser is optional. */
+
+ AssertMsgReturn(pTransfer->State.enmStatus == SHCLTRANSFERSTATUS_STARTED,
+ ("Wrong status (currently is %s)\n", ShClTransferStatusToStr(pTransfer->State.enmStatus)),
+ VERR_WRONG_ORDER);
+
+ int rc = shClTransferThreadCreate(pTransfer, pfnThreadFunc, pvUser);
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Starts an initialized transfer.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to start.
+ */
+int ShClTransferStart(PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ LogFlowFuncEnter();
+
+ /* Ready to start? */
+ AssertMsgReturn(pTransfer->State.enmStatus == SHCLTRANSFERSTATUS_INITIALIZED,
+ ("Wrong status (currently is %s)\n", ShClTransferStatusToStr(pTransfer->State.enmStatus)),
+ VERR_WRONG_ORDER);
+
+ int rc;
+
+ if (pTransfer->Callbacks.pfnOnStart)
+ {
+ rc = pTransfer->Callbacks.pfnOnStart(&pTransfer->CallbackCtx);
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ pTransfer->State.enmStatus = SHCLTRANSFERSTATUS_STARTED;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Creates a thread for a Shared Clipboard transfer.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to create thread for.
+ * @param pfnThreadFunc Thread function to use for this transfer.
+ * @param pvUser Pointer to user-provided data.
+ */
+static int shClTransferThreadCreate(PSHCLTRANSFER pTransfer, PFNRTTHREAD pfnThreadFunc, void *pvUser)
+
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ /* Already marked for stopping? */
+ AssertMsgReturn(pTransfer->Thread.fStop == false,
+ ("Transfer thread already marked for stopping"), VERR_WRONG_ORDER);
+ /* Already started? */
+ AssertMsgReturn(pTransfer->Thread.fStarted == false,
+ ("Transfer thread already started"), VERR_WRONG_ORDER);
+
+ /* Spawn a worker thread, so that we don't block the window thread for too long. */
+ int rc = RTThreadCreate(&pTransfer->Thread.hThread, pfnThreadFunc,
+ pvUser, 0, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE,
+ "shclp");
+ if (RT_SUCCESS(rc))
+ {
+ int rc2 = RTThreadUserWait(pTransfer->Thread.hThread, 30 * 1000 /* Timeout in ms */);
+ AssertRC(rc2);
+
+ if (pTransfer->Thread.fStarted) /* Did the thread indicate that it started correctly? */
+ {
+ /* Nothing to do in here. */
+ }
+ else
+ rc = VERR_GENERAL_FAILURE; /** @todo Find a better rc. */
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Destroys a thread of a Shared Clipboard transfer.
+ *
+ * @returns VBox status code.
+ * @param pTransfer Clipboard transfer to destroy thread for.
+ * @param uTimeoutMs Timeout (in ms) to wait for thread creation.
+ */
+static int shClTransferThreadDestroy(PSHCLTRANSFER pTransfer, RTMSINTERVAL uTimeoutMs)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ if (pTransfer->Thread.hThread == NIL_RTTHREAD)
+ return VINF_SUCCESS;
+
+ LogFlowFuncEnter();
+
+ /* Set stop indicator. */
+ pTransfer->Thread.fStop = true;
+
+ int rcThread = VERR_WRONG_ORDER;
+ int rc = RTThreadWait(pTransfer->Thread.hThread, uTimeoutMs, &rcThread);
+
+ LogFlowFunc(("Waiting for thread resulted in %Rrc (thread exited with %Rrc)\n", rc, rcThread));
+
+ return rc;
+}
+
+/**
+ * Initializes a Shared Clipboard transfer context.
+ *
+ * @returns VBox status code.
+ * @param pTransferCtx Transfer context to initialize.
+ */
+int ShClTransferCtxInit(PSHCLTRANSFERCTX pTransferCtx)
+{
+ AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("pTransferCtx=%p\n", pTransferCtx));
+
+ int rc = RTCritSectInit(&pTransferCtx->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ RTListInit(&pTransferCtx->List);
+
+ pTransferCtx->cTransfers = 0;
+ pTransferCtx->cRunning = 0;
+ pTransferCtx->cMaxRunning = 64; /** @todo Make this configurable? */
+
+ RT_ZERO(pTransferCtx->bmTransferIds);
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
+ ShClTransferHttpServerInit(&pTransferCtx->HttpServer);
+#endif
+ ShClTransferCtxReset(pTransferCtx);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Destroys a Shared Clipboard transfer context struct.
+ *
+ * @param pTransferCtx Transfer context to destroy.
+ */
+void ShClTransferCtxDestroy(PSHCLTRANSFERCTX pTransferCtx)
+{
+ if (!pTransferCtx)
+ return;
+
+ LogFlowFunc(("pTransferCtx=%p\n", pTransferCtx));
+
+ if (RTCritSectIsInitialized(&pTransferCtx->CritSect))
+ RTCritSectDelete(&pTransferCtx->CritSect);
+
+ PSHCLTRANSFER pTransfer, pTransferNext;
+ RTListForEachSafe(&pTransferCtx->List, pTransfer, pTransferNext, SHCLTRANSFER, Node)
+ {
+ ShClTransferDestroy(pTransfer);
+
+ shclTransferCtxTransferRemoveAndUnregister(pTransferCtx, pTransfer);
+
+ RTMemFree(pTransfer);
+ pTransfer = NULL;
+ }
+
+ pTransferCtx->cRunning = 0;
+ pTransferCtx->cTransfers = 0;
+}
+
+/**
+ * Resets a Shared Clipboard transfer.
+ *
+ * @param pTransferCtx Transfer context to reset.
+ */
+void ShClTransferCtxReset(PSHCLTRANSFERCTX pTransferCtx)
+{
+ AssertPtrReturnVoid(pTransferCtx);
+
+ LogFlowFuncEnter();
+
+ PSHCLTRANSFER pTransfer;
+ RTListForEach(&pTransferCtx->List, pTransfer, SHCLTRANSFER, Node)
+ ShClTransferReset(pTransfer);
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP
+ /** @todo Anything to do here? */
+#endif
+}
+
+/**
+ * Returns a specific Shared Clipboard transfer, internal version.
+ *
+ * @returns Shared Clipboard transfer, or NULL if not found.
+ * @param pTransferCtx Transfer context to return transfer for.
+ * @param uID ID of the transfer to return.
+ */
+static PSHCLTRANSFER shClTransferCtxGetTransferByIdInternal(PSHCLTRANSFERCTX pTransferCtx, uint32_t uID)
+{
+ PSHCLTRANSFER pTransfer;
+ RTListForEach(&pTransferCtx->List, pTransfer, SHCLTRANSFER, Node) /** @todo Slow, but works for now. */
+ {
+ if (pTransfer->State.uID == uID)
+ return pTransfer;
+ }
+
+ return NULL;
+}
+
+/**
+ * Returns a specific Shared Clipboard transfer by index, internal version.
+ *
+ * @returns Shared Clipboard transfer, or NULL if not found.
+ * @param pTransferCtx Transfer context to return transfer for.
+ * @param uIdx Index of the transfer to return.
+ */
+static PSHCLTRANSFER shClTransferCtxGetTransferByIndexInternal(PSHCLTRANSFERCTX pTransferCtx, uint32_t uIdx)
+{
+ uint32_t idx = 0;
+
+ PSHCLTRANSFER pTransfer;
+ RTListForEach(&pTransferCtx->List, pTransfer, SHCLTRANSFER, Node) /** @todo Slow, but works for now. */
+ {
+ if (uIdx == idx)
+ return pTransfer;
+ idx++;
+ }
+
+ return NULL;
+}
+
+/**
+ * Returns a Shared Clipboard transfer for a specific transfer ID.
+ *
+ * @returns Shared Clipboard transfer, or NULL if not found.
+ * @param pTransferCtx Transfer context to return transfer for.
+ * @param uID ID of the transfer to return.
+ */
+PSHCLTRANSFER ShClTransferCtxGetTransferById(PSHCLTRANSFERCTX pTransferCtx, uint32_t uID)
+{
+ return shClTransferCtxGetTransferByIdInternal(pTransferCtx, uID);
+}
+
+/**
+ * Returns a Shared Clipboard transfer for a specific list index.
+ *
+ * @returns Shared Clipboard transfer, or NULL if not found.
+ * @param pTransferCtx Transfer context to return transfer for.
+ * @param uIdx List index of the transfer to return.
+ */
+PSHCLTRANSFER ShClTransferCtxGetTransferByIndex(PSHCLTRANSFERCTX pTransferCtx, uint32_t uIdx)
+{
+ return shClTransferCtxGetTransferByIndexInternal(pTransferCtx, uIdx);
+}
+
+/**
+ * Returns the number of running Shared Clipboard transfers.
+ *
+ * @returns Number of running transfers.
+ * @param pTransferCtx Transfer context to return number for.
+ */
+uint32_t ShClTransferCtxGetRunningTransfers(PSHCLTRANSFERCTX pTransferCtx)
+{
+ AssertPtrReturn(pTransferCtx, 0);
+ return pTransferCtx->cRunning;
+}
+
+/**
+ * Returns the number of total Shared Clipboard transfers.
+ *
+ * @returns Number of total transfers.
+ * @param pTransferCtx Transfer context to return number for.
+ */
+uint32_t ShClTransferCtxGetTotalTransfers(PSHCLTRANSFERCTX pTransferCtx)
+{
+ AssertPtrReturn(pTransferCtx, 0);
+ return pTransferCtx->cTransfers;
+}
+
+/**
+ * Registers a Shared Clipboard transfer with a transfer context, i.e. allocates a transfer ID.
+ *
+ * @return VBox status code.
+ * @retval VERR_SHCLPB_MAX_TRANSFERS_REACHED if the maximum of concurrent transfers
+ * is reached.
+ * @param pTransferCtx Transfer context to register transfer to.
+ * @param pTransfer Transfer to register.
+ * @param pidTransfer Where to return the transfer ID on success. Optional.
+ */
+int ShClTransferCtxTransferRegister(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer, SHCLTRANSFERID *pidTransfer)
+{
+ AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+ /* pidTransfer is optional. */
+
+ /*
+ * Pick a random bit as starting point. If it's in use, search forward
+ * for a free one, wrapping around. We've reserved both the zero'th and
+ * max-1 IDs.
+ */
+ SHCLTRANSFERID idTransfer = RTRandU32Ex(1, VBOX_SHCL_MAX_TRANSFERS - 2);
+
+ if (!ASMBitTestAndSet(&pTransferCtx->bmTransferIds[0], idTransfer))
+ { /* likely */ }
+ else if (pTransferCtx->cTransfers < VBOX_SHCL_MAX_TRANSFERS - 2 /* First and last are not used */)
+ {
+ /* Forward search. */
+ int iHit = ASMBitNextClear(&pTransferCtx->bmTransferIds[0], VBOX_SHCL_MAX_TRANSFERS, idTransfer);
+ if (iHit < 0)
+ iHit = ASMBitFirstClear(&pTransferCtx->bmTransferIds[0], VBOX_SHCL_MAX_TRANSFERS);
+ AssertLogRelMsgReturn(iHit >= 0, ("Transfer count: %RU16\n", pTransferCtx->cTransfers), VERR_SHCLPB_MAX_TRANSFERS_REACHED);
+ idTransfer = iHit;
+ AssertLogRelMsgReturn(!ASMBitTestAndSet(&pTransferCtx->bmTransferIds[0], idTransfer), ("idObject=%#x\n", idTransfer), VERR_INTERNAL_ERROR_2);
+ }
+ else
+ {
+ LogFunc(("Maximum number of transfers reached (%RU16 transfers)\n", pTransferCtx->cTransfers));
+ return VERR_SHCLPB_MAX_TRANSFERS_REACHED;
+ }
+
+ Log2Func(("pTransfer=%p, idTransfer=%RU32 (%RU16 transfers)\n", pTransfer, idTransfer, pTransferCtx->cTransfers));
+
+ pTransfer->State.uID = idTransfer;
+
+ RTListAppend(&pTransferCtx->List, &pTransfer->Node);
+
+ pTransferCtx->cTransfers++;
+
+ if (pTransfer->Callbacks.pfnOnRegistered)
+ pTransfer->Callbacks.pfnOnRegistered(&pTransfer->CallbackCtx, pTransferCtx);
+
+ if (pidTransfer)
+ *pidTransfer = idTransfer;
+
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Registers a Shared Clipboard transfer with a transfer context by specifying an ID for the transfer.
+ *
+ * @return VBox status code.
+ * @retval VERR_ALREADY_EXISTS if a transfer with the given ID already exists.
+ * @retval VERR_SHCLPB_MAX_TRANSFERS_REACHED if the maximum of concurrent transfers for this context has been reached.
+ * @param pTransferCtx Transfer context to register transfer to.
+ * @param pTransfer Transfer to register.
+ * @param idTransfer Transfer ID to use for registration.
+ */
+int ShClTransferCtxTransferRegisterById(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer, SHCLTRANSFERID idTransfer)
+{
+ LogFlowFunc(("cTransfers=%RU16, idTransfer=%RU32\n", pTransferCtx->cTransfers, idTransfer));
+
+ if (pTransferCtx->cTransfers < VBOX_SHCL_MAX_TRANSFERS - 2 /* First and last are not used */)
+ {
+ if (!ASMBitTestAndSet(&pTransferCtx->bmTransferIds[0], idTransfer))
+ {
+ RTListAppend(&pTransferCtx->List, &pTransfer->Node);
+
+ pTransfer->State.uID = idTransfer;
+
+ if (pTransfer->Callbacks.pfnOnRegistered)
+ pTransfer->Callbacks.pfnOnRegistered(&pTransfer->CallbackCtx, pTransferCtx);
+
+ pTransferCtx->cTransfers++;
+ return VINF_SUCCESS;
+ }
+
+ return VERR_ALREADY_EXISTS;
+ }
+
+ LogFunc(("Maximum number of transfers reached (%RU16 transfers)\n", pTransferCtx->cTransfers));
+ return VERR_SHCLPB_MAX_TRANSFERS_REACHED;
+}
+
+/**
+ * Removes and unregisters a transfer from a transfer context.
+ *
+ * @param pTransferCtx Transfer context to remove transfer from.
+ * @param pTransfer Transfer to remove.
+ */
+static void shclTransferCtxTransferRemoveAndUnregister(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer)
+{
+ RTListNodeRemove(&pTransfer->Node);
+
+ Assert(pTransferCtx->cTransfers);
+ pTransferCtx->cTransfers--;
+
+ Assert(pTransferCtx->cTransfers >= pTransferCtx->cRunning);
+
+ if (pTransfer->Callbacks.pfnOnUnregistered)
+ pTransfer->Callbacks.pfnOnUnregistered(&pTransfer->CallbackCtx, pTransferCtx);
+
+ LogFlowFunc(("Now %RU32 transfers left\n", pTransferCtx->cTransfers));
+}
+
+/**
+ * Unregisters a transfer from an Transfer context.
+ *
+ * @retval VINF_SUCCESS on success.
+ * @retval VERR_NOT_FOUND if the transfer ID was not found.
+ * @param pTransferCtx Transfer context to unregister transfer from.
+ * @param idTransfer Transfer ID to unregister.
+ */
+int ShClTransferCtxTransferUnregister(PSHCLTRANSFERCTX pTransferCtx, SHCLTRANSFERID idTransfer)
+{
+ int rc = VINF_SUCCESS;
+ AssertMsgStmt(ASMBitTestAndClear(&pTransferCtx->bmTransferIds, idTransfer), ("idTransfer=%#x\n", idTransfer), rc = VERR_NOT_FOUND);
+
+ LogFlowFunc(("idTransfer=%RU32\n", idTransfer));
+
+ PSHCLTRANSFER pTransfer = shClTransferCtxGetTransferByIdInternal(pTransferCtx, idTransfer);
+ if (pTransfer)
+ {
+ shclTransferCtxTransferRemoveAndUnregister(pTransferCtx, pTransfer);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Cleans up all associated transfers which are not needed (anymore).
+ * This can be due to transfers which only have been announced but not / never being run.
+ *
+ * @param pTransferCtx Transfer context to cleanup transfers for.
+ */
+void ShClTransferCtxCleanup(PSHCLTRANSFERCTX pTransferCtx)
+{
+ AssertPtrReturnVoid(pTransferCtx);
+
+ LogFlowFunc(("pTransferCtx=%p, cTransfers=%RU16 cRunning=%RU16\n",
+ pTransferCtx, pTransferCtx->cTransfers, pTransferCtx->cRunning));
+
+ if (pTransferCtx->cTransfers == 0)
+ return;
+
+ /* Remove all transfers which are not in a running state (e.g. only announced). */
+ PSHCLTRANSFER pTransfer, pTransferNext;
+ RTListForEachSafe(&pTransferCtx->List, pTransfer, pTransferNext, SHCLTRANSFER, Node)
+ {
+ if (ShClTransferGetStatus(pTransfer) != SHCLTRANSFERSTATUS_STARTED)
+ {
+ shclTransferCtxTransferRemoveAndUnregister(pTransferCtx, pTransfer);
+
+ ShClTransferDestroy(pTransfer);
+
+ RTMemFree(pTransfer);
+ pTransfer = NULL;
+ }
+ }
+}
+
+/**
+ * Returns whether the maximum of concurrent transfers of a specific transfer contexthas been reached or not.
+ *
+ * @returns \c if maximum has been reached, \c false if not.
+ * @param pTransferCtx Transfer context to determine value for.
+ */
+bool ShClTransferCtxTransfersMaximumReached(PSHCLTRANSFERCTX pTransferCtx)
+{
+ AssertPtrReturn(pTransferCtx, true);
+
+ LogFlowFunc(("cRunning=%RU32, cMaxRunning=%RU32\n", pTransferCtx->cRunning, pTransferCtx->cMaxRunning));
+
+ Assert(pTransferCtx->cRunning <= pTransferCtx->cMaxRunning);
+ return pTransferCtx->cRunning == pTransferCtx->cMaxRunning;
+}
+
+/**
+ * Copies file system objinfo from IPRT to Shared Clipboard format.
+ *
+ * @param pDst The Shared Clipboard structure to convert data to.
+ * @param pSrc The IPRT structure to convert data from.
+ */
+void ShClFsObjFromIPRT(PSHCLFSOBJINFO pDst, PCRTFSOBJINFO pSrc)
+{
+ pDst->cbObject = pSrc->cbObject;
+ pDst->cbAllocated = pSrc->cbAllocated;
+ pDst->AccessTime = pSrc->AccessTime;
+ pDst->ModificationTime = pSrc->ModificationTime;
+ pDst->ChangeTime = pSrc->ChangeTime;
+ pDst->BirthTime = pSrc->BirthTime;
+ pDst->Attr.fMode = pSrc->Attr.fMode;
+ /* Clear bits which we don't pass through for security reasons. */
+ pDst->Attr.fMode &= ~(RTFS_UNIX_ISUID | RTFS_UNIX_ISGID | RTFS_UNIX_ISTXT);
+ RT_ZERO(pDst->Attr.u);
+ switch (pSrc->Attr.enmAdditional)
+ {
+ default:
+ case RTFSOBJATTRADD_NOTHING:
+ pDst->Attr.enmAdditional = SHCLFSOBJATTRADD_NOTHING;
+ break;
+
+ case RTFSOBJATTRADD_UNIX:
+ pDst->Attr.enmAdditional = SHCLFSOBJATTRADD_UNIX;
+ pDst->Attr.u.Unix.uid = pSrc->Attr.u.Unix.uid;
+ pDst->Attr.u.Unix.gid = pSrc->Attr.u.Unix.gid;
+ pDst->Attr.u.Unix.cHardlinks = pSrc->Attr.u.Unix.cHardlinks;
+ pDst->Attr.u.Unix.INodeIdDevice = pSrc->Attr.u.Unix.INodeIdDevice;
+ pDst->Attr.u.Unix.INodeId = pSrc->Attr.u.Unix.INodeId;
+ pDst->Attr.u.Unix.fFlags = pSrc->Attr.u.Unix.fFlags;
+ pDst->Attr.u.Unix.GenerationId = pSrc->Attr.u.Unix.GenerationId;
+ pDst->Attr.u.Unix.Device = pSrc->Attr.u.Unix.Device;
+ break;
+
+ case RTFSOBJATTRADD_EASIZE:
+ pDst->Attr.enmAdditional = SHCLFSOBJATTRADD_EASIZE;
+ pDst->Attr.u.EASize.cb = pSrc->Attr.u.EASize.cb;
+ break;
+ }
+}
+
+/**
+ * Converts Shared Clipboard create flags (see SharedClipboard-transfers.h) into IPRT create flags.
+ *
+ * @returns IPRT status code.
+ * @param fShClFlags Shared clipboard create flags.
+ * @param[out] pfOpen Where to store the RTFILE_O_XXX flags for
+ * RTFileOpen.
+ *
+ * @sa Initially taken from vbsfConvertFileOpenFlags().
+ */
+static int shClConvertFileCreateFlags(uint32_t fShClFlags, uint64_t *pfOpen)
+{
+ AssertMsgReturnStmt(!(fShClFlags & ~SHCL_OBJ_CF_VALID_MASK), ("%#x4\n", fShClFlags), *pfOpen = 0, VERR_INVALID_FLAGS);
+
+ uint64_t fOpen = 0;
+
+ switch (fShClFlags & SHCL_OBJ_CF_ACCESS_MASK_RW)
+ {
+ case SHCL_OBJ_CF_ACCESS_NONE:
+ {
+#ifdef RT_OS_WINDOWS
+ if ((fShClFlags & SHCL_OBJ_CF_ACCESS_MASK_ATTR) != SHCL_OBJ_CF_ACCESS_ATTR_NONE)
+ fOpen |= RTFILE_O_OPEN | RTFILE_O_ATTR_ONLY;
+ else
+#endif
+ fOpen |= RTFILE_O_OPEN | RTFILE_O_READ;
+ LogFlowFunc(("SHCL_OBJ_CF_ACCESS_NONE\n"));
+ break;
+ }
+
+ case SHCL_OBJ_CF_ACCESS_READ:
+ {
+ fOpen |= RTFILE_O_OPEN | RTFILE_O_READ;
+ LogFlowFunc(("SHCL_OBJ_CF_ACCESS_READ\n"));
+ break;
+ }
+
+ default:
+ AssertFailedReturn(VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ }
+
+ switch (fShClFlags & SHCL_OBJ_CF_ACCESS_MASK_ATTR)
+ {
+ case SHCL_OBJ_CF_ACCESS_ATTR_NONE:
+ {
+ fOpen |= RTFILE_O_ACCESS_ATTR_DEFAULT;
+ LogFlowFunc(("SHCL_OBJ_CF_ACCESS_ATTR_NONE\n"));
+ break;
+ }
+
+ case SHCL_OBJ_CF_ACCESS_ATTR_READ:
+ {
+ fOpen |= RTFILE_O_ACCESS_ATTR_READ;
+ LogFlowFunc(("SHCL_OBJ_CF_ACCESS_ATTR_READ\n"));
+ break;
+ }
+
+ default:
+ AssertFailedReturn(VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ }
+
+ /* Sharing mask */
+ switch (fShClFlags & SHCL_OBJ_CF_ACCESS_MASK_DENY)
+ {
+ case SHCL_OBJ_CF_ACCESS_DENYNONE:
+ fOpen |= RTFILE_O_DENY_NONE;
+ LogFlowFunc(("SHCL_OBJ_CF_ACCESS_DENYNONE\n"));
+ break;
+
+ case SHCL_OBJ_CF_ACCESS_DENYWRITE:
+ fOpen |= RTFILE_O_DENY_WRITE;
+ LogFlowFunc(("SHCL_OBJ_CF_ACCESS_DENYWRITE\n"));
+ break;
+
+ default:
+ AssertFailedReturn(VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ }
+
+ *pfOpen = fOpen;
+
+ LogFlowFuncLeaveRC(VINF_SUCCESS);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Translates a Shared Clipboard transfer status (SHCLTRANSFERSTATUS_XXX) into a string.
+ *
+ * @returns Transfer status string name.
+ * @param enmStatus The transfer status to translate.
+ */
+const char *ShClTransferStatusToStr(SHCLTRANSFERSTATUS enmStatus)
+{
+ switch (enmStatus)
+ {
+ RT_CASE_RET_STR(SHCLTRANSFERSTATUS_NONE);
+ RT_CASE_RET_STR(SHCLTRANSFERSTATUS_INITIALIZED);
+ RT_CASE_RET_STR(SHCLTRANSFERSTATUS_STARTED);
+ RT_CASE_RET_STR(SHCLTRANSFERSTATUS_STOPPED);
+ RT_CASE_RET_STR(SHCLTRANSFERSTATUS_CANCELED);
+ RT_CASE_RET_STR(SHCLTRANSFERSTATUS_KILLED);
+ RT_CASE_RET_STR(SHCLTRANSFERSTATUS_ERROR);
+ }
+ return "Unknown";
+}
diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp
new file mode 100644
index 00000000..bd2a50f1
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp
@@ -0,0 +1,1322 @@
+/* $Id: clipboard-win.cpp $ */
+/** @file
+ * Shared Clipboard: Windows-specific functions for clipboard handling.
+ */
+
+/*
+ * 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD
+#include <VBox/GuestHost/SharedClipboard.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/ldr.h>
+#include <iprt/mem.h>
+#include <iprt/thread.h>
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+# include <iprt/win/windows.h>
+# include <iprt/win/shlobj.h> /* For CFSTR_FILEDESCRIPTORXXX + CFSTR_FILECONTENTS. */
+# include <iprt/utf16.h>
+#endif
+
+#include <VBox/log.h>
+
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+# include <VBox/GuestHost/SharedClipboard-transfers.h>
+#endif
+#include <VBox/GuestHost/SharedClipboard-win.h>
+#include <VBox/GuestHost/clipboard-helper.h>
+
+
+/**
+ * Opens the clipboard of a specific window.
+ *
+ * @returns VBox status code.
+ * @param hWnd Handle of window to open clipboard for.
+ */
+int SharedClipboardWinOpen(HWND hWnd)
+{
+ /* "OpenClipboard fails if another window has the clipboard open."
+ * So try a few times and wait up to 1 second.
+ */
+ BOOL fOpened = FALSE;
+
+ LogFlowFunc(("hWnd=%p\n", hWnd));
+
+ int i = 0;
+ for (;;)
+ {
+ if (OpenClipboard(hWnd))
+ {
+ fOpened = TRUE;
+ break;
+ }
+
+ if (i >= 10) /* sleep interval = [1..512] ms */
+ break;
+
+ RTThreadSleep(1 << i);
+ ++i;
+ }
+
+#ifdef LOG_ENABLED
+ if (i > 0)
+ LogFlowFunc(("%d times tried to open clipboard\n", i + 1));
+#endif
+
+ int rc;
+ if (fOpened)
+ rc = VINF_SUCCESS;
+ else
+ {
+ const DWORD dwLastErr = GetLastError();
+ rc = RTErrConvertFromWin32(dwLastErr);
+ LogRel(("Failed to open clipboard, rc=%Rrc (0x%x)\n", rc, dwLastErr));
+ }
+
+ return rc;
+}
+
+/**
+ * Closes the clipboard for the current thread.
+ *
+ * @returns VBox status code.
+ */
+int SharedClipboardWinClose(void)
+{
+ int rc;
+
+ const BOOL fRc = CloseClipboard();
+ if (RT_UNLIKELY(!fRc))
+ {
+ const DWORD dwLastErr = GetLastError();
+ if (dwLastErr == ERROR_CLIPBOARD_NOT_OPEN)
+ {
+ rc = VINF_SUCCESS; /* Not important, so just report success instead. */
+ }
+ else
+ {
+ rc = RTErrConvertFromWin32(dwLastErr);
+ LogFunc(("Failed with %Rrc (0x%x)\n", rc, dwLastErr));
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Clears the clipboard for the current thread.
+ *
+ * @returns VBox status code.
+ */
+int SharedClipboardWinClear(void)
+{
+ LogFlowFuncEnter();
+ if (EmptyClipboard())
+ return VINF_SUCCESS;
+
+ const DWORD dwLastErr = GetLastError();
+ AssertReturn(dwLastErr != ERROR_CLIPBOARD_NOT_OPEN, VERR_INVALID_STATE);
+
+ int rc = RTErrConvertFromWin32(dwLastErr);
+ LogFunc(("Failed with %Rrc (0x%x)\n", rc, dwLastErr));
+ return rc;
+}
+
+/**
+ * Initializes a Shared Clipboard Windows context.
+ *
+ * @returns VBox status code.
+ * @param pWinCtx Shared Clipboard Windows context to initialize.
+ */
+int SharedClipboardWinCtxInit(PSHCLWINCTX pWinCtx)
+{
+ int rc = RTCritSectInit(&pWinCtx->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ /* Check that new Clipboard API is available. */
+ SharedClipboardWinCheckAndInitNewAPI(&pWinCtx->newAPI);
+ /* Do *not* check the rc, as the call might return VERR_SYMBOL_NOT_FOUND is the new API isn't available. */
+
+ pWinCtx->hWnd = NULL;
+ pWinCtx->hWndClipboardOwnerUs = NULL;
+ pWinCtx->hWndNextInChain = NULL;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Destroys a Shared Clipboard Windows context.
+ *
+ * @param pWinCtx Shared Clipboard Windows context to destroy.
+ */
+void SharedClipboardWinCtxDestroy(PSHCLWINCTX pWinCtx)
+{
+ if (!pWinCtx)
+ return;
+
+ LogFlowFuncEnter();
+
+ if (RTCritSectIsInitialized(&pWinCtx->CritSect))
+ {
+ int rc2 = RTCritSectDelete(&pWinCtx->CritSect);
+ AssertRC(rc2);
+ }
+}
+
+/**
+ * Checks and initializes function pointer which are required for using
+ * the new clipboard API.
+ *
+ * @returns VBox status code, or VERR_SYMBOL_NOT_FOUND if the new API is not available.
+ * @param pAPI Where to store the retrieved function pointers.
+ * Will be set to NULL if the new API is not available.
+ */
+int SharedClipboardWinCheckAndInitNewAPI(PSHCLWINAPINEW pAPI)
+{
+ RTLDRMOD hUser32 = NIL_RTLDRMOD;
+ int rc = RTLdrLoadSystem("User32.dll", /* fNoUnload = */ true, &hUser32);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrGetSymbol(hUser32, "AddClipboardFormatListener", (void **)&pAPI->pfnAddClipboardFormatListener);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrGetSymbol(hUser32, "RemoveClipboardFormatListener", (void **)&pAPI->pfnRemoveClipboardFormatListener);
+ }
+
+ RTLdrClose(hUser32);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ LogRel(("Shared Clipboard: New Clipboard API enabled\n"));
+ }
+ else
+ {
+ RT_BZERO(pAPI, sizeof(SHCLWINAPINEW));
+ LogRel(("Shared Clipboard: New Clipboard API not available (%Rrc)\n", rc));
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Returns if the new clipboard API is available or not.
+ *
+ * @returns @c true if the new API is available, or @c false if not.
+ * @param pAPI Structure used for checking if the new clipboard API is available or not.
+ */
+bool SharedClipboardWinIsNewAPI(PSHCLWINAPINEW pAPI)
+{
+ if (!pAPI)
+ return false;
+ return pAPI->pfnAddClipboardFormatListener != NULL;
+}
+
+/**
+ * Adds ourselves into the chain of cliboard listeners.
+ *
+ * @returns VBox status code.
+ * @param pCtx Windows clipboard context to use to add ourselves.
+ */
+int SharedClipboardWinChainAdd(PSHCLWINCTX pCtx)
+{
+ const PSHCLWINAPINEW pAPI = &pCtx->newAPI;
+
+ BOOL fRc;
+ if (SharedClipboardWinIsNewAPI(pAPI))
+ fRc = pAPI->pfnAddClipboardFormatListener(pCtx->hWnd);
+ else
+ {
+ SetLastError(NO_ERROR);
+ pCtx->hWndNextInChain = SetClipboardViewer(pCtx->hWnd);
+ fRc = pCtx->hWndNextInChain != NULL || GetLastError() == NO_ERROR;
+ }
+
+ int rc = VINF_SUCCESS;
+
+ if (!fRc)
+ {
+ const DWORD dwLastErr = GetLastError();
+ rc = RTErrConvertFromWin32(dwLastErr);
+ LogFunc(("Failed with %Rrc (0x%x)\n", rc, dwLastErr));
+ }
+
+ return rc;
+}
+
+/**
+ * Remove ourselves from the chain of cliboard listeners
+ *
+ * @returns VBox status code.
+ * @param pCtx Windows clipboard context to use to remove ourselves.
+ */
+int SharedClipboardWinChainRemove(PSHCLWINCTX pCtx)
+{
+ if (!pCtx->hWnd)
+ return VINF_SUCCESS;
+
+ const PSHCLWINAPINEW pAPI = &pCtx->newAPI;
+
+ BOOL fRc;
+ if (SharedClipboardWinIsNewAPI(pAPI))
+ {
+ fRc = pAPI->pfnRemoveClipboardFormatListener(pCtx->hWnd);
+ }
+ else
+ {
+ fRc = ChangeClipboardChain(pCtx->hWnd, pCtx->hWndNextInChain);
+ if (fRc)
+ pCtx->hWndNextInChain = NULL;
+ }
+
+ int rc = VINF_SUCCESS;
+
+ if (!fRc)
+ {
+ const DWORD dwLastErr = GetLastError();
+ rc = RTErrConvertFromWin32(dwLastErr);
+ LogFunc(("Failed with %Rrc (0x%x)\n", rc, dwLastErr));
+ }
+
+ return rc;
+}
+
+/**
+ * Callback which is invoked when we have successfully pinged ourselves down the
+ * clipboard chain. We simply unset a boolean flag to say that we are responding.
+ * There is a race if a ping returns after the next one is initiated, but nothing
+ * very bad is likely to happen.
+ *
+ * @param hWnd Window handle to use for this callback. Not used currently.
+ * @param uMsg Message to handle. Not used currently.
+ * @param dwData Pointer to user-provided data. Contains our Windows clipboard context.
+ * @param lResult Additional data to pass. Not used currently.
+ */
+VOID CALLBACK SharedClipboardWinChainPingProc(HWND hWnd, UINT uMsg, ULONG_PTR dwData, LRESULT lResult) RT_NOTHROW_DEF
+{
+ RT_NOREF(hWnd);
+ RT_NOREF(uMsg);
+ RT_NOREF(lResult);
+
+ /** @todo r=andy Why not using SetWindowLongPtr for keeping the context? */
+ PSHCLWINCTX pCtx = (PSHCLWINCTX)dwData;
+ AssertPtrReturnVoid(pCtx);
+
+ pCtx->oldAPI.fCBChainPingInProcess = FALSE;
+}
+
+/**
+ * Passes a window message to the next window in the clipboard chain.
+ *
+ * @returns LRESULT
+ * @param pWinCtx Window context to use.
+ * @param msg Window message to pass.
+ * @param wParam WPARAM to pass.
+ * @param lParam LPARAM to pass.
+ */
+LRESULT SharedClipboardWinChainPassToNext(PSHCLWINCTX pWinCtx,
+ UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ LogFlowFuncEnter();
+
+ LRESULT lresultRc = 0;
+
+ if (pWinCtx->hWndNextInChain)
+ {
+ LogFunc(("hWndNextInChain=%p\n", pWinCtx->hWndNextInChain));
+
+ /* Pass the message to next window in the clipboard chain. */
+ DWORD_PTR dwResult;
+ lresultRc = SendMessageTimeout(pWinCtx->hWndNextInChain, msg, wParam, lParam, 0,
+ SHCL_WIN_CBCHAIN_TIMEOUT_MS, &dwResult);
+ if (!lresultRc)
+ lresultRc = dwResult;
+ }
+
+ LogFlowFunc(("lresultRc=%ld\n", lresultRc));
+ return lresultRc;
+}
+
+/**
+ * Converts a (registered or standard) Windows clipboard format to a VBox clipboard format.
+ *
+ * @returns Converted VBox clipboard format, or VBOX_SHCL_FMT_NONE if not found.
+ * @param uFormat Windows clipboard format to convert.
+ */
+SHCLFORMAT SharedClipboardWinClipboardFormatToVBox(UINT uFormat)
+{
+ /* Insert the requested clipboard format data into the clipboard. */
+ SHCLFORMAT vboxFormat = VBOX_SHCL_FMT_NONE;
+
+ switch (uFormat)
+ {
+ case CF_UNICODETEXT:
+ vboxFormat = VBOX_SHCL_FMT_UNICODETEXT;
+ break;
+
+ case CF_DIB:
+ vboxFormat = VBOX_SHCL_FMT_BITMAP;
+ break;
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ /* CF_HDROP handles file system entries which are locally present
+ * on source for transferring to the target.
+ *
+ * This does *not* invoke any IDataObject / IStream implementations! */
+ case CF_HDROP:
+ vboxFormat = VBOX_SHCL_FMT_URI_LIST;
+ break;
+#endif
+
+ default:
+ if (uFormat >= 0xC000) /** Formats registered with RegisterClipboardFormat() start at this index. */
+ {
+ TCHAR szFormatName[256]; /** @todo r=andy Do we need Unicode support here as well? */
+ int cActual = GetClipboardFormatName(uFormat, szFormatName, sizeof(szFormatName) / sizeof(TCHAR));
+ if (cActual)
+ {
+ LogFlowFunc(("uFormat=%u -> szFormatName=%s\n", uFormat, szFormatName));
+
+ if (RTStrCmp(szFormatName, SHCL_WIN_REGFMT_HTML) == 0)
+ vboxFormat = VBOX_SHCL_FMT_HTML;
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+ /* These types invoke our IDataObject / IStream implementations. */
+ else if ( (RTStrCmp(szFormatName, CFSTR_FILEDESCRIPTORA) == 0)
+ || (RTStrCmp(szFormatName, CFSTR_FILECONTENTS) == 0))
+ vboxFormat = VBOX_SHCL_FMT_URI_LIST;
+ /** @todo Do we need to handle CFSTR_FILEDESCRIPTORW here as well? */
+#endif
+ }
+ }
+ break;
+ }
+
+ LogFlowFunc(("uFormat=%u -> vboxFormat=0x%x\n", uFormat, vboxFormat));
+ return vboxFormat;
+}
+
+/**
+ * Retrieves all supported clipboard formats of a specific clipboard.
+ *
+ * @returns VBox status code.
+ * @param pCtx Windows clipboard context to retrieve formats for.
+ * @param pfFormats Where to store the retrieved formats.
+ */
+int SharedClipboardWinGetFormats(PSHCLWINCTX pCtx, PSHCLFORMATS pfFormats)
+{
+ AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfFormats, VERR_INVALID_POINTER);
+
+ SHCLFORMATS fFormats = VBOX_SHCL_FMT_NONE;
+
+ /* Query list of available formats and report to host. */
+ int rc = SharedClipboardWinOpen(pCtx->hWnd);
+ if (RT_SUCCESS(rc))
+ {
+ UINT uCurFormat = 0; /* Must be set to zero for EnumClipboardFormats(). */
+ while ((uCurFormat = EnumClipboardFormats(uCurFormat)) != 0)
+ fFormats |= SharedClipboardWinClipboardFormatToVBox(uCurFormat);
+
+ int rc2 = SharedClipboardWinClose();
+ AssertRC(rc2);
+ LogFlowFunc(("fFormats=%#x\n", fFormats));
+ }
+ else
+ LogFunc(("Failed with rc=%Rrc (fFormats=%#x)\n", rc, fFormats));
+
+ *pfFormats = fFormats;
+ return rc;
+}
+
+/**
+ * Extracts a field value from CF_HTML data.
+ *
+ * @returns VBox status code.
+ * @param pszSrc source in CF_HTML format.
+ * @param pszOption Name of CF_HTML field.
+ * @param puValue Where to return extracted value of CF_HTML field.
+ */
+int SharedClipboardWinGetCFHTMLHeaderValue(const char *pszSrc, const char *pszOption, uint32_t *puValue)
+{
+ AssertPtrReturn(pszSrc, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszOption, VERR_INVALID_POINTER);
+
+ int rc = VERR_INVALID_PARAMETER;
+
+ const char *pszOptionValue = RTStrStr(pszSrc, pszOption);
+ if (pszOptionValue)
+ {
+ size_t cchOption = strlen(pszOption);
+ Assert(cchOption);
+
+ rc = RTStrToUInt32Ex(pszOptionValue + cchOption, NULL, 10, puValue);
+ }
+ return rc;
+}
+
+/**
+ * Check that the source string contains CF_HTML struct.
+ *
+ * @returns @c true if the @a pszSource string is in CF_HTML format.
+ * @param pszSource Source string to check.
+ */
+bool SharedClipboardWinIsCFHTML(const char *pszSource)
+{
+ return RTStrStr(pszSource, "Version:") != NULL
+ && RTStrStr(pszSource, "StartHTML:") != NULL;
+}
+
+/**
+ * Converts clipboard data from CF_HTML format to MIME clipboard format.
+ *
+ * Returns allocated buffer that contains html converted to text/html mime type
+ *
+ * @returns VBox status code.
+ * @param pszSource The input.
+ * @param cch The length of the input.
+ * @param ppszOutput Where to return the result. Free using RTMemFree.
+ * @param pcbOutput Where to the return length of the result (bytes/chars).
+ */
+int SharedClipboardWinConvertCFHTMLToMIME(const char *pszSource, const uint32_t cch, char **ppszOutput, uint32_t *pcbOutput)
+{
+ Assert(pszSource);
+ Assert(cch);
+ Assert(ppszOutput);
+ Assert(pcbOutput);
+
+ uint32_t offStart;
+ int rc = SharedClipboardWinGetCFHTMLHeaderValue(pszSource, "StartFragment:", &offStart);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t offEnd;
+ rc = SharedClipboardWinGetCFHTMLHeaderValue(pszSource, "EndFragment:", &offEnd);
+ if (RT_SUCCESS(rc))
+ {
+ if ( offStart > 0
+ && offEnd > 0
+ && offEnd >= offStart
+ && offEnd <= cch)
+ {
+ uint32_t cchSubStr = offEnd - offStart;
+ char *pszResult = (char *)RTMemAlloc(cchSubStr + 1);
+ if (pszResult)
+ {
+ rc = RTStrCopyEx(pszResult, cchSubStr + 1, pszSource + offStart, cchSubStr);
+ if (RT_SUCCESS(rc))
+ {
+ *ppszOutput = pszResult;
+ *pcbOutput = (uint32_t)(cchSubStr + 1);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected EndFragment. rc = %Rrc\n", rc));
+ RTMemFree(pszResult);
+ }
+ }
+ else
+ {
+ LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected EndFragment\n"));
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ {
+ LogRelFlowFunc(("Error: CF_HTML out of bounds - offStart=%#x offEnd=%#x cch=%#x\n", offStart, offEnd, cch));
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected EndFragment. rc = %Rrc\n", rc));
+ rc = VERR_INVALID_PARAMETER;
+ }
+ }
+ else
+ {
+ LogRelFlowFunc(("Error: Unknown CF_HTML format. Expected StartFragment. rc = %Rrc\n", rc));
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+/**
+ * Converts source UTF-8 MIME HTML clipboard data to UTF-8 CF_HTML format.
+ *
+ * This is just encapsulation work, slapping a header on the data.
+ *
+ * It allocates [..]
+ *
+ * Calculations:
+ * Header length = format Length + (2*(10 - 5('%010d'))('digits')) - 2('%s') = format length + 8
+ * EndHtml = Header length + fragment length
+ * StartHtml = 105(constant)
+ * StartFragment = 141(constant) may vary if the header html content will be extended
+ * EndFragment = Header length + fragment length - 38(ending length)
+ *
+ * For more format details, check out:
+ * https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767917(v=vs.85)
+ *
+ * @returns VBox status code.
+ * @param pszSource Source buffer that contains utf-16 string in mime html format
+ * @param cb Size of source buffer in bytes
+ * @param ppszOutput Where to return the allocated output buffer to put converted UTF-8
+ * CF_HTML clipboard data. This function allocates memory for this.
+ * @param pcbOutput Where to return the size of allocated result buffer in bytes/chars, including zero terminator
+ *
+ * @note output buffer should be free using RTMemFree()
+ * @note Everything inside of fragment can be UTF8. Windows allows it. Everything in header should be Latin1.
+ */
+int SharedClipboardWinConvertMIMEToCFHTML(const char *pszSource, size_t cb, char **ppszOutput, uint32_t *pcbOutput)
+{
+ Assert(ppszOutput);
+ Assert(pcbOutput);
+ Assert(pszSource);
+ Assert(cb);
+
+ /*
+ * Check that input UTF-8 and properly zero terminated.
+ * Note! The zero termination may come earlier than 'cb' - 1, that's fine.
+ */
+ int rc = RTStrValidateEncodingEx(pszSource, cb, RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else
+ {
+ LogRelFlowFunc(("Error: invalid source fragment. rc = %Rrc\n", rc));
+ return rc;
+ }
+ size_t const cchFragment = strlen(pszSource); /* Unfortunately the validator doesn't return the length. */
+
+ /*
+ * @StartHtml - Absolute offset of <html>
+ * @EndHtml - Size of the whole resulting text (excluding ending zero char)
+ * @StartFragment - Absolute position after <!--StartFragment-->
+ * @EndFragment - Absolute position of <!--EndFragment-->
+ *
+ * Note! The offset are zero padded to max width so we don't have any variations due to those.
+ * Note! All values includes CRLFs inserted into text.
+ *
+ * Calculations:
+ * Header length = Format sample length - 2 ('%s')
+ * EndHtml = Header length + fragment length
+ * StartHtml = 101(constant)
+ * StartFragment = 137(constant)
+ * EndFragment = Header length + fragment length - 38 (ending length)
+ */
+ static const char s_szFormatSample[] =
+ /* 0: */ "Version:1.0\r\n"
+ /* 13: */ "StartHTML:000000101\r\n"
+ /* 34: */ "EndHTML:%0000009u\r\n" // END HTML = Header length + fragment length
+ /* 53: */ "StartFragment:000000137\r\n"
+ /* 78: */ "EndFragment:%0000009u\r\n"
+ /* 101: */ "<html>\r\n"
+ /* 109: */ "<body>\r\n"
+ /* 117: */ "<!--StartFragment-->"
+ /* 137: */ "%s"
+ /* 137+2: */ "<!--EndFragment-->\r\n"
+ /* 157+2: */ "</body>\r\n"
+ /* 166+2: */ "</html>\r\n"
+ /* 175+2: */ ;
+ AssertCompile(sizeof(s_szFormatSample) == 175 + 2 + 1);
+
+ /* Calculate parameters of the CF_HTML header */
+ size_t const cchHeader = sizeof(s_szFormatSample) - 2 /*%s*/ - 1 /*'\0'*/;
+ size_t const offEndHtml = cchHeader + cchFragment;
+ size_t const offEndFragment = cchHeader + cchFragment - 38; /* 175-137 = 38 */
+ char *pszResult = (char *)RTMemAlloc(offEndHtml + 1);
+ AssertLogRelReturn(pszResult, VERR_NO_MEMORY);
+
+ /* Format resulting CF_HTML string: */
+ size_t cchFormatted = RTStrPrintf(pszResult, offEndHtml + 1, s_szFormatSample, offEndHtml, offEndFragment, pszSource);
+ Assert(offEndHtml == cchFormatted);
+
+#ifdef VBOX_STRICT
+ /*
+ * Check the calculations.
+ */
+
+ /* check 'StartFragment:' value */
+ static const char s_szStartFragment[] = "<!--StartFragment-->";
+ const char *pszRealStartFragment = RTStrStr(pszResult, s_szStartFragment);
+ Assert(&pszRealStartFragment[sizeof(s_szStartFragment) - 1] - pszResult == 137);
+
+ /* check 'EndFragment:' value */
+ static const char s_szEndFragment[] = "<!--EndFragment-->";
+ const char *pszRealEndFragment = RTStrStr(pszResult, s_szEndFragment);
+ Assert((size_t)(pszRealEndFragment - pszResult) == offEndFragment);
+#endif
+
+ *ppszOutput = pszResult;
+ *pcbOutput = (uint32_t)cchFormatted + 1;
+ Assert(*pcbOutput == cchFormatted + 1);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Handles the WM_CHANGECBCHAIN code.
+ *
+ * @returns LRESULT
+ * @param pWinCtx Windows context to use.
+ * @param hWnd Window handle to use.
+ * @param msg Message ID to pass on.
+ * @param wParam wParam to pass on
+ * @param lParam lParam to pass on.
+ */
+LRESULT SharedClipboardWinHandleWMChangeCBChain(PSHCLWINCTX pWinCtx,
+ HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ LRESULT lresultRc = 0;
+
+ LogFlowFuncEnter();
+
+ if (SharedClipboardWinIsNewAPI(&pWinCtx->newAPI))
+ {
+ lresultRc = DefWindowProc(hWnd, msg, wParam, lParam);
+ }
+ else /* Old API */
+ {
+ HWND hwndRemoved = (HWND)wParam;
+ HWND hwndNext = (HWND)lParam;
+
+ if (hwndRemoved == pWinCtx->hWndNextInChain)
+ {
+ /* The window that was next to our in the chain is being removed.
+ * Relink to the new next window.
+ */
+ pWinCtx->hWndNextInChain = hwndNext;
+ }
+ else
+ {
+ if (pWinCtx->hWndNextInChain)
+ {
+ /* Pass the message further. */
+ DWORD_PTR dwResult;
+ lresultRc = SendMessageTimeout(pWinCtx->hWndNextInChain, WM_CHANGECBCHAIN, wParam, lParam, 0,
+ SHCL_WIN_CBCHAIN_TIMEOUT_MS,
+ &dwResult);
+ if (!lresultRc)
+ lresultRc = (LRESULT)dwResult;
+ }
+ }
+ }
+
+ LogFlowFunc(("lresultRc=%ld\n", lresultRc));
+ return lresultRc;
+}
+
+/**
+ * Handles the WM_DESTROY code.
+ *
+ * @returns VBox status code.
+ * @param pWinCtx Windows context to use.
+ */
+int SharedClipboardWinHandleWMDestroy(PSHCLWINCTX pWinCtx)
+{
+ LogFlowFuncEnter();
+
+ int rc = VINF_SUCCESS;
+
+ /* MS recommends to remove from Clipboard chain in this callback. */
+ SharedClipboardWinChainRemove(pWinCtx);
+
+ if (pWinCtx->oldAPI.timerRefresh)
+ {
+ Assert(pWinCtx->hWnd);
+ KillTimer(pWinCtx->hWnd, 0);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Handles the WM_RENDERALLFORMATS message.
+ *
+ * @returns VBox status code.
+ * @param pWinCtx Windows context to use.
+ * @param hWnd Window handle to use.
+ */
+int SharedClipboardWinHandleWMRenderAllFormats(PSHCLWINCTX pWinCtx, HWND hWnd)
+{
+ RT_NOREF(pWinCtx);
+
+ LogFlowFuncEnter();
+
+ /* Do nothing. The clipboard formats will be unavailable now, because the
+ * windows is to be destroyed and therefore the guest side becomes inactive.
+ */
+ int rc = SharedClipboardWinOpen(hWnd);
+ if (RT_SUCCESS(rc))
+ {
+ SharedClipboardWinClear();
+ SharedClipboardWinClose();
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Handles the WM_TIMER code, which is needed if we're running with the so-called "old" Windows clipboard API.
+ * Does nothing if we're running with the "new" Windows API.
+ *
+ * @returns VBox status code.
+ * @param pWinCtx Windows context to use.
+ */
+int SharedClipboardWinHandleWMTimer(PSHCLWINCTX pWinCtx)
+{
+ int rc = VINF_SUCCESS;
+
+ if (!SharedClipboardWinIsNewAPI(&pWinCtx->newAPI)) /* Only run when using the "old" Windows API. */
+ {
+ LogFlowFuncEnter();
+
+ HWND hViewer = GetClipboardViewer();
+
+ /* Re-register ourselves in the clipboard chain if our last ping
+ * timed out or there seems to be no valid chain. */
+ if (!hViewer || pWinCtx->oldAPI.fCBChainPingInProcess)
+ {
+ SharedClipboardWinChainRemove(pWinCtx);
+ SharedClipboardWinChainAdd(pWinCtx);
+ }
+
+ /* Start a new ping by passing a dummy WM_CHANGECBCHAIN to be
+ * processed by ourselves to the chain. */
+ pWinCtx->oldAPI.fCBChainPingInProcess = TRUE;
+
+ hViewer = GetClipboardViewer();
+ if (hViewer)
+ SendMessageCallback(hViewer, WM_CHANGECBCHAIN, (WPARAM)pWinCtx->hWndNextInChain, (LPARAM)pWinCtx->hWndNextInChain,
+ SharedClipboardWinChainPingProc, (ULONG_PTR)pWinCtx);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Announces a clipboard format to the Windows clipboard.
+ *
+ * The actual rendering (setting) of the clipboard data will be done later with
+ * a separate WM_RENDERFORMAT message.
+ *
+ * @returns VBox status code. VERR_NOT_SUPPORTED if the format is not supported / handled.
+ * @param pWinCtx Windows context to use.
+ * @param fFormats Clipboard format(s) to announce.
+ */
+static int sharedClipboardWinAnnounceFormats(PSHCLWINCTX pWinCtx, SHCLFORMATS fFormats)
+{
+ LogFunc(("fFormats=0x%x\n", fFormats));
+
+ /*
+ * Set the clipboard formats.
+ */
+ static struct
+ {
+ uint32_t fVBoxFormat;
+ UINT uWinFormat;
+ const char *pszWinFormat;
+ const char *pszLog;
+ } s_aFormats[] =
+ {
+ { VBOX_SHCL_FMT_UNICODETEXT, CF_UNICODETEXT, NULL, "CF_UNICODETEXT" },
+ { VBOX_SHCL_FMT_BITMAP, CF_DIB, NULL, "CF_DIB" },
+ { VBOX_SHCL_FMT_HTML, 0, SHCL_WIN_REGFMT_HTML, "SHCL_WIN_REGFMT_HTML" },
+ };
+ unsigned cSuccessfullySet = 0;
+ SHCLFORMATS fFormatsLeft = fFormats;
+ int rc = VINF_SUCCESS;
+ for (uintptr_t i = 0; i < RT_ELEMENTS(s_aFormats) && fFormatsLeft != 0; i++)
+ {
+ if (fFormatsLeft & s_aFormats[i].fVBoxFormat)
+ {
+ LogFunc(("%s\n", s_aFormats[i].pszLog));
+ fFormatsLeft &= ~s_aFormats[i].fVBoxFormat;
+
+ /* Reg format if needed: */
+ UINT uWinFormat = s_aFormats[i].uWinFormat;
+ if (!uWinFormat)
+ {
+ uWinFormat = RegisterClipboardFormat(s_aFormats[i].pszWinFormat);
+ AssertContinue(uWinFormat != 0);
+ }
+
+ /* Tell the clipboard we've got data upon a request. We check the
+ last error here as hClip will be NULL even on success (despite
+ what MSDN says). */
+ SetLastError(NO_ERROR);
+ HANDLE hClip = SetClipboardData(uWinFormat, NULL);
+ DWORD dwErr = GetLastError();
+ if (dwErr == NO_ERROR || hClip != NULL)
+ cSuccessfullySet++;
+ else
+ {
+ AssertMsgFailed(("%s/%u: %u\n", s_aFormats[i].pszLog, uWinFormat, dwErr));
+ rc = RTErrConvertFromWin32(dwErr);
+ }
+ }
+ }
+
+ /*
+ * Consider setting anything a success, converting any error into
+ * informational status. Unsupport error only happens if all formats
+ * were unsupported.
+ */
+ if (cSuccessfullySet > 0)
+ {
+ pWinCtx->hWndClipboardOwnerUs = GetClipboardOwner();
+ if (RT_FAILURE(rc))
+ rc = -rc;
+ }
+ else if (RT_SUCCESS(rc) && fFormatsLeft != 0)
+ {
+ LogFunc(("Unsupported formats: %#x (%#x)\n", fFormatsLeft, fFormats));
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Opens the clipboard, clears it, announces @a fFormats and closes it.
+ *
+ * The actual rendering (setting) of the clipboard data will be done later with
+ * a separate WM_RENDERFORMAT message.
+ *
+ * @returns VBox status code. VERR_NOT_SUPPORTED if the format is not supported / handled.
+ * @param pWinCtx Windows context to use.
+ * @param fFormats Clipboard format(s) to announce.
+ * @param hWnd The window handle to use as owner.
+ */
+int SharedClipboardWinClearAndAnnounceFormats(PSHCLWINCTX pWinCtx, SHCLFORMATS fFormats, HWND hWnd)
+{
+ int rc = SharedClipboardWinOpen(hWnd);
+ if (RT_SUCCESS(rc))
+ {
+ SharedClipboardWinClear();
+
+ rc = sharedClipboardWinAnnounceFormats(pWinCtx, fFormats);
+ Assert(pWinCtx->hWndClipboardOwnerUs == hWnd || pWinCtx->hWndClipboardOwnerUs == NULL);
+
+ SharedClipboardWinClose();
+ }
+ return rc;
+}
+
+/**
+ * Writes (places) clipboard data into the Windows clipboard.
+ *
+ * @returns VBox status code.
+ * @param cfFormat Windows clipboard format to write data for.
+ * @param pvData Pointer to actual clipboard data to write.
+ * @param cbData Size (in bytes) of actual clipboard data to write.
+ *
+ * @note ASSUMES that the clipboard has already been opened.
+ */
+int SharedClipboardWinDataWrite(UINT cfFormat, void *pvData, uint32_t cbData)
+{
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn (cbData, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+
+ HANDLE hMem = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, cbData);
+
+ LogFlowFunc(("hMem=%p\n", hMem));
+
+ if (hMem)
+ {
+ void *pMem = GlobalLock(hMem);
+
+ LogFlowFunc(("pMem=%p, GlobalSize=%zu\n", pMem, GlobalSize(hMem)));
+
+ if (pMem)
+ {
+ LogFlowFunc(("Setting data\n"));
+
+ memcpy(pMem, pvData, cbData);
+
+ /* The memory must be unlocked before inserting to the Clipboard. */
+ GlobalUnlock(hMem);
+
+ /* 'hMem' contains the host clipboard data.
+ * size is 'cb' and format is 'format'.
+ */
+ HANDLE hClip = SetClipboardData(cfFormat, hMem);
+
+ LogFlowFunc(("hClip=%p\n", hClip));
+
+ if (hClip)
+ {
+ /* The hMem ownership has gone to the system. Nothing to do. */
+ }
+ else
+ rc = RTErrConvertFromWin32(GetLastError());
+ }
+ else
+ rc = VERR_ACCESS_DENIED;
+
+ GlobalFree(hMem);
+ }
+ else
+ rc = RTErrConvertFromWin32(GetLastError());
+
+ if (RT_FAILURE(rc))
+ LogFunc(("Setting clipboard data failed with %Rrc\n", rc));
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS
+
+/**
+ * Creates an Shared Clipboard transfer by announcing transfer data (via IDataObject) to Windows.
+ *
+ * This creates the necessary IDataObject + IStream implementations and initiates the actual transfers required for getting
+ * the meta data. Whether or not the actual (file++) transfer(s) are happening is up to the user (at some point) later then.
+ *
+ * @returns VBox status code.
+ * @param pWinCtx Windows context to use.
+ * @param pTransferCtxCtx Transfer contextto use.
+ * @param pTransfer Shared Clipboard transfer to use.
+ */
+int SharedClipboardWinTransferCreate(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ LogFlowFunc(("pWinCtx=%p\n", pWinCtx));
+
+ AssertReturn(pTransfer->pvUser == NULL, VERR_WRONG_ORDER);
+
+ /* Make sure to enter the critical section before setting the clipboard data, as otherwise WM_CLIPBOARDUPDATE
+ * might get called *before* we had the opportunity to set pWinCtx->hWndClipboardOwnerUs below. */
+ int rc = RTCritSectEnter(&pWinCtx->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ SharedClipboardWinTransferCtx *pWinURITransferCtx = new SharedClipboardWinTransferCtx();
+ if (pWinURITransferCtx)
+ {
+ pTransfer->pvUser = pWinURITransferCtx;
+ pTransfer->cbUser = sizeof(SharedClipboardWinTransferCtx);
+
+ pWinURITransferCtx->pDataObj = new SharedClipboardWinDataObject(pTransfer);
+ if (pWinURITransferCtx->pDataObj)
+ {
+ rc = pWinURITransferCtx->pDataObj->Init();
+ if (RT_SUCCESS(rc))
+ {
+ SharedClipboardWinClose();
+ /* Note: Clipboard must be closed first before calling OleSetClipboard(). */
+
+ /** @todo There is a potential race between SharedClipboardWinClose() and OleSetClipboard(),
+ * where another application could own the clipboard (open), and thus the call to
+ * OleSetClipboard() will fail. Needs (better) fixing. */
+ HRESULT hr = S_OK;
+
+ for (unsigned uTries = 0; uTries < 3; uTries++)
+ {
+ hr = OleSetClipboard(pWinURITransferCtx->pDataObj);
+ if (SUCCEEDED(hr))
+ {
+ Assert(OleIsCurrentClipboard(pWinURITransferCtx->pDataObj) == S_OK); /* Sanity. */
+
+ /*
+ * Calling OleSetClipboard() changed the clipboard owner, which in turn will let us receive
+ * a WM_CLIPBOARDUPDATE message. To not confuse ourselves with our own clipboard owner changes,
+ * save a new window handle and deal with it in WM_CLIPBOARDUPDATE.
+ */
+ pWinCtx->hWndClipboardOwnerUs = GetClipboardOwner();
+
+ LogFlowFunc(("hWndClipboardOwnerUs=%p\n", pWinCtx->hWndClipboardOwnerUs));
+ break;
+ }
+
+ LogFlowFunc(("Failed with %Rhrc (try %u/3)\n", hr, uTries + 1));
+ RTThreadSleep(500); /* Wait a bit. */
+ }
+
+ if (FAILED(hr))
+ {
+ rc = VERR_ACCESS_DENIED; /** @todo Fudge; fix this. */
+ LogRel(("Shared Clipboard: Failed with %Rhrc when setting data object to clipboard\n", hr));
+ }
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ int rc2 = RTCritSectLeave(&pWinCtx->CritSect);
+ AssertRC(rc2);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Destroys implementation-specific data for an Shared Clipboard transfer.
+ *
+ * @param pWinCtx Windows context to use.
+ * @param pTransfer Shared Clipboard transfer to create implementation-specific data for.
+ */
+void SharedClipboardWinTransferDestroy(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer)
+{
+ RT_NOREF(pWinCtx);
+
+ if (!pTransfer)
+ return;
+
+ LogFlowFuncEnter();
+
+ if (pTransfer->pvUser)
+ {
+ Assert(pTransfer->cbUser == sizeof(SharedClipboardWinTransferCtx));
+ SharedClipboardWinTransferCtx *pWinURITransferCtx = (SharedClipboardWinTransferCtx *)pTransfer->pvUser;
+ Assert(pWinURITransferCtx);
+
+ if (pWinURITransferCtx->pDataObj)
+ {
+ delete pWinURITransferCtx->pDataObj;
+ pWinURITransferCtx->pDataObj = NULL;
+ }
+
+ delete pWinURITransferCtx;
+
+ pTransfer->pvUser = NULL;
+ pTransfer->cbUser = 0;
+ }
+}
+
+/**
+ * Retrieves the roots for a transfer by opening the clipboard and getting the clipboard data
+ * as string list (CF_HDROP), assigning it to the transfer as roots then.
+ *
+ * @returns VBox status code.
+ * @param pWinCtx Windows context to use.
+ * @param pTransfer Transfer to get roots for.
+ */
+int SharedClipboardWinGetRoots(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer)
+{
+ AssertPtrReturn(pWinCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pTransfer, VERR_INVALID_POINTER);
+
+ Assert(ShClTransferGetSource(pTransfer) == SHCLSOURCE_LOCAL); /* Sanity. */
+
+ int rc = SharedClipboardWinOpen(pWinCtx->hWnd);
+ if (RT_SUCCESS(rc))
+ {
+ /* The data data in CF_HDROP format, as the files are locally present and don't need to be
+ * presented as a IDataObject or IStream. */
+ HANDLE hClip = hClip = GetClipboardData(CF_HDROP);
+ if (hClip)
+ {
+ HDROP hDrop = (HDROP)GlobalLock(hClip);
+ if (hDrop)
+ {
+ char *papszList = NULL;
+ uint32_t cbList;
+ rc = SharedClipboardWinDropFilesToStringList((DROPFILES *)hDrop, &papszList, &cbList);
+
+ GlobalUnlock(hClip);
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = ShClTransferRootsSet(pTransfer,
+ papszList, cbList + 1 /* Include termination */);
+ RTStrFree(papszList);
+ }
+ }
+ else
+ LogRel(("Shared Clipboard: Unable to lock clipboard data, last error: %ld\n", GetLastError()));
+ }
+ else
+ LogRel(("Shared Clipboard: Unable to retrieve clipboard data from clipboard (CF_HDROP), last error: %ld\n",
+ GetLastError()));
+
+ SharedClipboardWinClose();
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+/**
+ * Converts a DROPFILES (HDROP) structure to a string list, separated by \r\n.
+ * Does not do any locking on the input data.
+ *
+ * @returns VBox status code.
+ * @param pDropFiles Pointer to DROPFILES structure to convert.
+ * @param papszList Where to store the allocated string list.
+ * @param pcbList Where to store the size (in bytes) of the allocated string list.
+ */
+int SharedClipboardWinDropFilesToStringList(DROPFILES *pDropFiles, char **papszList, uint32_t *pcbList)
+{
+ AssertPtrReturn(pDropFiles, VERR_INVALID_POINTER);
+ AssertPtrReturn(papszList, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbList, VERR_INVALID_POINTER);
+
+ /* Do we need to do Unicode stuff? */
+ const bool fUnicode = RT_BOOL(pDropFiles->fWide);
+
+ /* Get the offset of the file list. */
+ Assert(pDropFiles->pFiles >= sizeof(DROPFILES));
+
+ /* Note: This is *not* pDropFiles->pFiles! DragQueryFile only
+ * will work with the plain storage medium pointer! */
+ HDROP hDrop = (HDROP)(pDropFiles);
+
+ int rc = VINF_SUCCESS;
+
+ /* First, get the file count. */
+ /** @todo Does this work on Windows 2000 / NT4? */
+ char *pszFiles = NULL;
+ uint32_t cchFiles = 0;
+ UINT cFiles = DragQueryFile(hDrop, UINT32_MAX /* iFile */, NULL /* lpszFile */, 0 /* cchFile */);
+
+ LogFlowFunc(("Got %RU16 file(s), fUnicode=%RTbool\n", cFiles, fUnicode));
+
+ for (UINT i = 0; i < cFiles; i++)
+ {
+ UINT cchFile = DragQueryFile(hDrop, i /* File index */, NULL /* Query size first */, 0 /* cchFile */);
+ Assert(cchFile);
+
+ if (RT_FAILURE(rc))
+ break;
+
+ char *pszFileUtf8 = NULL; /* UTF-8 version. */
+ UINT cchFileUtf8 = 0;
+ if (fUnicode)
+ {
+ /* Allocate enough space (including terminator). */
+ WCHAR *pwszFile = (WCHAR *)RTMemAlloc((cchFile + 1) * sizeof(WCHAR));
+ if (pwszFile)
+ {
+ const UINT cwcFileUtf16 = DragQueryFileW(hDrop, i /* File index */,
+ pwszFile, cchFile + 1 /* Include terminator */);
+
+ AssertMsg(cwcFileUtf16 == cchFile, ("cchFileUtf16 (%RU16) does not match cchFile (%RU16)\n",
+ cwcFileUtf16, cchFile));
+ RT_NOREF(cwcFileUtf16);
+
+ rc = RTUtf16ToUtf8(pwszFile, &pszFileUtf8);
+ if (RT_SUCCESS(rc))
+ {
+ cchFileUtf8 = (UINT)strlen(pszFileUtf8);
+ Assert(cchFileUtf8);
+ }
+
+ RTMemFree(pwszFile);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else /* ANSI */
+ {
+ /* Allocate enough space (including terminator). */
+ char *pszFileANSI = (char *)RTMemAlloc((cchFile + 1) * sizeof(char));
+ UINT cchFileANSI = 0;
+ if (pszFileANSI)
+ {
+ cchFileANSI = DragQueryFileA(hDrop, i /* File index */,
+ pszFileANSI, cchFile + 1 /* Include terminator */);
+
+ AssertMsg(cchFileANSI == cchFile, ("cchFileANSI (%RU16) does not match cchFile (%RU16)\n",
+ cchFileANSI, cchFile));
+
+ /* Convert the ANSI codepage to UTF-8. */
+ rc = RTStrCurrentCPToUtf8(&pszFileUtf8, pszFileANSI);
+ if (RT_SUCCESS(rc))
+ {
+ cchFileUtf8 = (UINT)strlen(pszFileUtf8);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ LogFlowFunc(("\tFile: %s (cchFile=%RU16)\n", pszFileUtf8, cchFileUtf8));
+
+ LogRel2(("Shared Clipboard: Adding file '%s' to transfer\n", pszFileUtf8));
+
+ rc = RTStrAAppendExN(&pszFiles, 1 /* cPairs */, pszFileUtf8, strlen(pszFileUtf8));
+ cchFiles += (uint32_t)strlen(pszFileUtf8);
+ }
+
+ if (pszFileUtf8)
+ RTStrFree(pszFileUtf8);
+
+ if (RT_FAILURE(rc))
+ {
+ LogFunc(("Error handling file entry #%u, rc=%Rrc\n", i, rc));
+ break;
+ }
+
+ /* Add separation between filenames.
+ * Note: Also do this for the last element of the list. */
+ rc = RTStrAAppendExN(&pszFiles, 1 /* cPairs */, "\r\n", 2 /* Bytes */);
+ if (RT_SUCCESS(rc))
+ cchFiles += 2; /* Include \r\n */
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ cchFiles += 1; /* Add string termination. */
+ uint32_t cbFiles = cchFiles * sizeof(char); /* UTF-8. */
+
+ LogFlowFunc(("cFiles=%u, cchFiles=%RU32, cbFiles=%RU32, pszFiles=0x%p\n",
+ cFiles, cchFiles, cbFiles, pszFiles));
+
+ *papszList = pszFiles;
+ *pcbList = cbFiles;
+ }
+ else
+ {
+ if (pszFiles)
+ RTStrFree(pszFiles);
+ }
+
+ LogFlowFuncLeaveRC(rc);
+ return rc;
+}
+
+#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */
+
diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp
new file mode 100644
index 00000000..5e57d85e
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp
@@ -0,0 +1,2428 @@
+/** @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 <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;
+}
diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk b/src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk
new file mode 100644
index 00000000..5b073ea1
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk
@@ -0,0 +1,76 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for the Shared Clipboard Guest/Host testcases.
+#
+
+#
+# Copyright (C) 2011-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 <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK)
+ if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris)
+
+ PROGRAMS += tstClipboardGH-X11 tstClipboardGH-X11Smoke
+ TESTING += \
+ $(tstClipboardGH-X11_0_OUTDIR)/tstClipboardGH-X11.run \
+ $(tstClipboardGH-X11_0_OUTDIR)/tstClipboardGH-X11Smoke.run
+
+ tstClipboardGH-X11_TEMPLATE = VBoxR3TstExe
+ tstClipboardGH-X11_DEFS = VBOX_WITH_HGCM UNIT_TEST TESTCASE
+ tstClipboardGH-X11_SOURCES = \
+ tstClipboardGH-X11.cpp \
+ ../clipboard-x11.cpp \
+ ../clipboard-common.cpp
+ tstClipboardGH-X11_CXXFLAGS += -Wno-array-bounds
+ tstClipboardGH-X11_LIBS = X11 Xt
+ tstClipboardGH-X11_CLEAN = $(tstClipboardGH-X11_0_OUTDIR)/tstClipboardGH-X11.run
+
+ tstClipboardGH-X11Smoke_TEMPLATE = VBoxR3TstExe
+ tstClipboardGH-X11Smoke_DEFS = VBOX_WITH_HGCM SMOKETEST
+ tstClipboardGH-X11Smoke_SOURCES = \
+ tstClipboardGH-X11Smoke.cpp \
+ ../clipboard-x11.cpp \
+ ../clipboard-common.cpp
+ tstClipboardGH-X11Smoke_LIBPATH = $(VBOX_LIBPATH_X11)
+ tstClipboardGH-X11Smoke_LIBS = X11 Xt
+ tstClipboardGH-X11Smoke_CLEAN = $(tstClipboardGH-X11Smoke_0_OUTDIR)/tstClipboardGH-X11Smoke.run
+
+ $$(tstClipboardGH-X11_0_OUTDIR)/tstClipboardGH-X11.run: $$(tstClipboardGH-X11_1_STAGE_TARGET)
+ export VBOX_LOG_DEST=nofile; $(tstClipboardGH-X11_1_STAGE_TARGET) quiet
+ $(QUIET)$(APPEND) -t "$@" "done"
+
+ $$(tstClipboardGH-X11Smoke_0_OUTDIR)/tstClipboardGH-X11Smoke.run: $$(tstClipboardGH-X11Smoke_1_STAGE_TARGET)
+ export VBOX_LOG_DEST=nofile; $(tstClipboardGH-X11Smoke_1_STAGE_TARGET) quiet
+ $(QUIET)$(APPEND) -t "$@" "done"
+
+ if defined(VBOX_WITH_QTGUI)
+ include $(PATH_SUB_CURRENT)/tstClipboardQt/Makefile.kmk
+ endif
+
+
+ endif
+endif
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp
new file mode 100644
index 00000000..f9bc19c1
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp
@@ -0,0 +1,941 @@
+/* $Id: tstClipboardGH-X11.cpp $ */
+/** @file
+ * Shared Clipboard guest/host X11 code test cases.
+ */
+
+/*
+ * Copyright (C) 2011-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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/GuestHost/SharedClipboard-x11.h>
+#include <VBox/GuestHost/clipboard-helper.h>
+#include <VBox/HostServices/VBoxClipboardSvc.h>
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/test.h>
+#include <iprt/utf16.h>
+
+#include <poll.h>
+#include <X11/Xatom.h>
+
+
+/*********************************************************************************************************************************
+* Externals *
+*********************************************************************************************************************************/
+extern SHCLX11FMTTABLE g_aFormats[];
+
+extern void clipUpdateX11Targets(PSHCLX11CTX pCtx, SHCLX11FMTIDX *pTargets, size_t cTargets);
+extern void clipReportEmpty(PSHCLX11CTX pCtx);
+extern void clipConvertDataFromX11Worker(void *pClient, void *pvSrc, unsigned cbSrc);
+extern SHCLX11FMTIDX clipGetTextFormatFromTargets(PSHCLX11CTX pCtx, SHCLX11FMTIDX *pTargets, size_t cTargets);
+extern SHCLX11FMT clipRealFormatForX11Format(SHCLX11FMTIDX uFmtIdx);
+extern Atom clipGetAtom(PSHCLX11CTX pCtx, const char *pcszName);
+extern void clipQueryX11Targets(PSHCLX11CTX pCtx);
+extern size_t clipReportMaxX11Formats(void);
+
+
+/*********************************************************************************************************************************
+* Internal prototypes *
+*********************************************************************************************************************************/
+static SHCLX11FMTIDX tstClipFindX11FormatByAtomText(const char *pcszAtom);
+
+
+/*********************************************************************************************************************************
+* Prototypes, used for testcases by clipboard-x11.cpp *
+*********************************************************************************************************************************/
+void tstRequestTargets(SHCLX11CTX* pCtx);
+void tstClipRequestData(PSHCLX11CTX pCtx, SHCLX11FMTIDX target, void *closure);
+void tstThreadScheduleCall(void (*proc)(void *, void *), void *client_data);
+
+
+/*********************************************************************************************************************************
+* Own callback implementations *
+*********************************************************************************************************************************/
+extern DECLCALLBACK(void) clipQueryX11TargetsCallback(Widget widget, XtPointer pClient,
+ Atom * /* selection */, Atom *atomType,
+ XtPointer pValue, long unsigned int *pcLen,
+ int *piFormat);
+
+
+/*********************************************************************************************************************************
+* Defines *
+*********************************************************************************************************************************/
+#define TESTCASE_WIDGET_ID (Widget)0xffff
+
+
+/* For the purpose of the test case, we just execute the procedure to be
+ * scheduled, as we are running single threaded. */
+void tstThreadScheduleCall(void (*proc)(void *, void *), void *client_data)
+{
+ proc(client_data, NULL);
+}
+
+/* The data in the simulated VBox clipboard. */
+static int g_tst_rcDataVBox = VINF_SUCCESS;
+static void *g_tst_pvDataVBox = NULL;
+static uint32_t g_tst_cbDataVBox = 0;
+
+/* Set empty data in the simulated VBox clipboard. */
+static void tstClipEmptyVBox(PSHCLX11CTX pCtx, int retval)
+{
+ g_tst_rcDataVBox = retval;
+ RTMemFree(g_tst_pvDataVBox);
+ g_tst_pvDataVBox = NULL;
+ g_tst_cbDataVBox = 0;
+ ShClX11ReportFormatsToX11(pCtx, 0);
+}
+
+/* Set the data in the simulated VBox clipboard. */
+static int tstClipSetVBoxUtf16(PSHCLX11CTX pCtx, int retval,
+ const char *pcszData, size_t cb)
+{
+ PRTUTF16 pwszData = NULL;
+ size_t cwData = 0;
+ int rc = RTStrToUtf16Ex(pcszData, RTSTR_MAX, &pwszData, 0, &cwData);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertReturn(cb <= cwData * 2 + 2, VERR_BUFFER_OVERFLOW);
+ void *pv = RTMemDup(pwszData, cb);
+ RTUtf16Free(pwszData);
+ if (pv == NULL)
+ return VERR_NO_MEMORY;
+ if (g_tst_pvDataVBox)
+ RTMemFree(g_tst_pvDataVBox);
+ g_tst_rcDataVBox = retval;
+ g_tst_pvDataVBox = pv;
+ g_tst_cbDataVBox = cb;
+ ShClX11ReportFormatsToX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT);
+ return VINF_SUCCESS;
+}
+
+Display *XtDisplay(Widget w) { NOREF(w); return (Display *) 0xffff; }
+
+void XtAppSetExitFlag(XtAppContext app_context) { NOREF(app_context); }
+
+void XtDestroyWidget(Widget w) { NOREF(w); }
+
+XtAppContext XtCreateApplicationContext(void) { return (XtAppContext)0xffff; }
+
+void XtDestroyApplicationContext(XtAppContext app_context) { NOREF(app_context); }
+
+void XtToolkitInitialize(void) {}
+
+Boolean XtToolkitThreadInitialize(void) { return True; }
+
+Display *XtOpenDisplay(XtAppContext app_context,
+ _Xconst _XtString display_string,
+ _Xconst _XtString application_name,
+ _Xconst _XtString application_class,
+ XrmOptionDescRec *options, Cardinal num_options,
+ int *argc, char **argv)
+{
+ RT_NOREF8(app_context, display_string, application_name, application_class, options, num_options, argc, argv);
+ return (Display *)0xffff;
+}
+
+Widget XtVaAppCreateShell(_Xconst _XtString application_name, _Xconst _XtString application_class,
+ WidgetClass widget_class, Display *display, ...)
+{
+ RT_NOREF(application_name, application_class, widget_class, display);
+ return TESTCASE_WIDGET_ID;
+}
+
+void XtSetMappedWhenManaged(Widget widget, _XtBoolean mapped_when_managed) { RT_NOREF(widget, mapped_when_managed); }
+
+void XtRealizeWidget(Widget widget) { NOREF(widget); }
+
+XtInputId XtAppAddInput(XtAppContext app_context, int source, XtPointer condition, XtInputCallbackProc proc, XtPointer closure)
+{
+ RT_NOREF(app_context, source, condition, proc, closure);
+ return 0xffff;
+}
+
+/* Atoms we need other than the formats we support. */
+static const char *g_tst_apszSupAtoms[] =
+{
+ "PRIMARY", "CLIPBOARD", "TARGETS", "MULTIPLE", "TIMESTAMP"
+};
+
+/* This just looks for the atom names in a couple of tables and returns an
+ * index with an offset added. */
+Atom XInternAtom(Display *, const char *pcsz, int)
+{
+ Atom atom = 0;
+ size_t const cFormats = clipReportMaxX11Formats();
+ size_t i;
+ for (i = 0; i < cFormats; ++i)
+ {
+ if (!strcmp(pcsz, g_aFormats[i].pcszAtom))
+ atom = (Atom) (i + 0x1000);
+ }
+ for (i = 0; i < RT_ELEMENTS(g_tst_apszSupAtoms); ++i)
+ if (!strcmp(pcsz, g_tst_apszSupAtoms[i]))
+ atom = (Atom) (i + 0x2000);
+ Assert(atom); /* Have we missed any atoms? */
+ return atom;
+}
+
+/* Take a request for the targets we are currently offering. */
+static SHCLX11FMTIDX g_tst_aSelTargetsIdx[10] = { 0 };
+static size_t g_tst_cTargets = 0;
+
+void tstRequestTargets(SHCLX11CTX* pCtx)
+{
+ clipUpdateX11Targets(pCtx, g_tst_aSelTargetsIdx, g_tst_cTargets);
+}
+
+/* The current values of the X selection, which will be returned to the
+ * XtGetSelectionValue callback. */
+static Atom g_tst_atmSelType = 0;
+static const void *g_tst_pSelData = NULL;
+static unsigned long g_tst_cSelData = 0;
+static int g_tst_selFormat = 0;
+
+void tstClipRequestData(PSHCLX11CTX pCtx, SHCLX11FMTIDX target, void *closure)
+{
+ RT_NOREF(pCtx);
+ unsigned long count = 0;
+ int format = 0;
+ if (target != g_tst_aSelTargetsIdx[0])
+ {
+ clipConvertDataFromX11Worker(closure, NULL, 0); /* Could not convert to target. */
+ return;
+ }
+ void *pValue = NULL;
+ pValue = g_tst_pSelData ? RTMemDup(g_tst_pSelData, g_tst_cSelData) : NULL;
+ count = g_tst_pSelData ? g_tst_cSelData : 0;
+ format = g_tst_selFormat;
+ if (!pValue)
+ {
+ count = 0;
+ format = 0;
+ }
+ clipConvertDataFromX11Worker(closure, pValue, count * format / 8);
+ if (pValue)
+ RTMemFree(pValue);
+}
+
+/* The formats currently on offer from X11 via the shared clipboard. */
+static uint32_t g_tst_uX11Formats = 0;
+
+static uint32_t tstClipQueryFormats(void)
+{
+ return g_tst_uX11Formats;
+}
+
+static void tstClipInvalidateFormats(void)
+{
+ g_tst_uX11Formats = ~0;
+}
+
+/* Does our clipboard code currently own the selection? */
+static bool g_tst_fOwnsSel = false;
+/* The procedure that is called when we should convert the selection to a
+ * given format. */
+static XtConvertSelectionProc g_tst_pfnSelConvert = NULL;
+/* The procedure which is called when we lose the selection. */
+static XtLoseSelectionProc g_tst_pfnSelLose = NULL;
+/* The procedure which is called when the selection transfer has completed. */
+static XtSelectionDoneProc g_tst_pfnSelDone = NULL;
+
+Boolean XtOwnSelection(Widget widget, Atom selection, Time time,
+ XtConvertSelectionProc convert,
+ XtLoseSelectionProc lose,
+ XtSelectionDoneProc done)
+{
+ RT_NOREF(widget, time);
+ if (selection != XInternAtom(NULL, "CLIPBOARD", 0))
+ return True; /* We don't really care about this. */
+ g_tst_fOwnsSel = true; /* Always succeed. */
+ g_tst_pfnSelConvert = convert;
+ g_tst_pfnSelLose = lose;
+ g_tst_pfnSelDone = done;
+ return True;
+}
+
+void XtDisownSelection(Widget widget, Atom selection, Time time)
+{
+ RT_NOREF(widget, time, selection);
+ g_tst_fOwnsSel = false;
+ g_tst_pfnSelConvert = NULL;
+ g_tst_pfnSelLose = NULL;
+ g_tst_pfnSelDone = NULL;
+}
+
+/* Request the shared clipboard to convert its data to a given format. */
+static bool tstClipConvertSelection(const char *pcszTarget, Atom *type,
+ XtPointer *value, unsigned long *length,
+ int *format)
+{
+ Atom target = XInternAtom(NULL, pcszTarget, 0);
+ if (target == 0)
+ return false;
+ /* Initialise all return values in case we make a quick exit. */
+ *type = XA_STRING;
+ *value = NULL;
+ *length = 0;
+ *format = 0;
+ if (!g_tst_fOwnsSel)
+ return false;
+ if (!g_tst_pfnSelConvert)
+ return false;
+ Atom clipAtom = XInternAtom(NULL, "CLIPBOARD", 0);
+ if (!g_tst_pfnSelConvert(TESTCASE_WIDGET_ID, &clipAtom, &target, type,
+ value, length, format))
+ return false;
+ if (g_tst_pfnSelDone)
+ g_tst_pfnSelDone(TESTCASE_WIDGET_ID, &clipAtom, &target);
+ return true;
+}
+
+/* Set the current X selection data */
+static void tstClipSetSelectionValues(const char *pcszTarget, Atom type,
+ const void *data,
+ unsigned long count, int format)
+{
+ Atom clipAtom = XInternAtom(NULL, "CLIPBOARD", 0);
+ g_tst_aSelTargetsIdx[0] = tstClipFindX11FormatByAtomText(pcszTarget);
+ g_tst_cTargets = 1;
+ g_tst_atmSelType = type;
+ g_tst_pSelData = data;
+ g_tst_cSelData = count;
+ g_tst_selFormat = format;
+ if (g_tst_pfnSelLose)
+ g_tst_pfnSelLose(TESTCASE_WIDGET_ID, &clipAtom);
+ g_tst_fOwnsSel = false;
+}
+
+static void tstClipSendTargetUpdate(PSHCLX11CTX pCtx)
+{
+ clipQueryX11Targets(pCtx);
+}
+
+/* Configure if and how the X11 TARGETS clipboard target will fail. */
+static void tstClipSetTargetsFailure(void)
+{
+ g_tst_cTargets = 0;
+}
+
+char *XtMalloc(Cardinal size)
+{
+ return (char *) RTMemAlloc(size);
+}
+
+void XtFree(char *ptr)
+{
+ RTMemFree((void *)ptr);
+}
+
+char *XGetAtomName(Display *display, Atom atom)
+{
+ RT_NOREF(display);
+ const char *pcszName = NULL;
+ if (atom < 0x1000)
+ return NULL;
+ if (0x1000 <= atom && atom < 0x2000)
+ {
+ unsigned index = atom - 0x1000;
+ AssertReturn(index < clipReportMaxX11Formats(), NULL);
+ pcszName = g_aFormats[index].pcszAtom;
+ }
+ else
+ {
+ unsigned index = atom - 0x2000;
+ AssertReturn(index < RT_ELEMENTS(g_tst_apszSupAtoms), NULL);
+ pcszName = g_tst_apszSupAtoms[index];
+ }
+ return (char *)RTMemDup(pcszName, sizeof(pcszName) + 1);
+}
+
+int XFree(void *data)
+{
+ RTMemFree(data);
+ return 0;
+}
+
+void XFreeStringList(char **list)
+{
+ if (list)
+ RTMemFree(*list);
+ RTMemFree(list);
+}
+
+#define TESTCASE_MAX_BUF_SIZE 256
+
+static int g_tst_rcCompleted = VINF_SUCCESS;
+static int g_tst_cbCompleted = 0;
+static CLIPREADCBREQ *g_tst_pCompletedReq = NULL;
+static char g_tst_abCompletedBuf[TESTCASE_MAX_BUF_SIZE];
+
+static DECLCALLBACK(int) tstShClReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser)
+{
+ RT_NOREF(pCtx, pvUser);
+ g_tst_uX11Formats = fFormats;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstShClOnRequestDataFromSourceCallback(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
+{
+ RT_NOREF(pCtx, uFmt, pvUser);
+ *pcb = g_tst_cbDataVBox;
+ if (g_tst_pvDataVBox != NULL)
+ {
+ void *pv = RTMemDup(g_tst_pvDataVBox, g_tst_cbDataVBox);
+ *ppv = pv;
+ return pv != NULL ? g_tst_rcDataVBox : VERR_NO_MEMORY;
+ }
+ *ppv = NULL;
+ return g_tst_rcDataVBox;
+}
+
+static DECLCALLBACK(int) tstShClOnSendDataToDestCallback(PSHCLCONTEXT pCtx, void *pv, uint32_t cb, void *pvUser)
+{
+ RT_NOREF(pCtx);
+
+ PSHCLX11READDATAREQ pData = (PSHCLX11READDATAREQ)pvUser;
+
+ if (cb <= TESTCASE_MAX_BUF_SIZE)
+ {
+ g_tst_rcCompleted = pData->rcCompletion;
+ if (cb != 0)
+ memcpy(g_tst_abCompletedBuf, pv, cb);
+ }
+ else
+ g_tst_rcCompleted = VERR_BUFFER_OVERFLOW;
+ g_tst_cbCompleted = cb;
+ g_tst_pCompletedReq = pData->pReq;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Looks up the X11 format matching a given X11 atom text.
+ *
+ * @returns the format on success, NIL_CLIPX11FORMAT on failure
+ * @param pcszAtom Atom text to look up format for.
+ */
+static SHCLX11FMTIDX tstClipFindX11FormatByAtomText(const char *pcszAtom)
+{
+ const size_t j = clipReportMaxX11Formats();
+
+ for (unsigned i = 0; i < j; ++i)
+ {
+ if (!strcmp(g_aFormats[i].pcszAtom, pcszAtom))
+ return i;
+ }
+ return NIL_CLIPX11FORMAT;
+}
+
+static bool tstClipTextFormatConversion(PSHCLX11CTX pCtx)
+{
+ bool fSuccess = true;
+ SHCLX11FMTIDX targets[2];
+ SHCLX11FMTIDX x11Format;
+ targets[0] = tstClipFindX11FormatByAtomText("text/plain");
+ targets[1] = tstClipFindX11FormatByAtomText("image/bmp");
+ x11Format = clipGetTextFormatFromTargets(pCtx, targets, 2);
+ if (clipRealFormatForX11Format(x11Format) != SHCLX11FMT_TEXT)
+ fSuccess = false;
+ targets[0] = tstClipFindX11FormatByAtomText("UTF8_STRING");
+ targets[1] = tstClipFindX11FormatByAtomText("text/plain");
+ x11Format = clipGetTextFormatFromTargets(pCtx, targets, 2);
+ if (clipRealFormatForX11Format(x11Format) != SHCLX11FMT_UTF8)
+ fSuccess = false;
+ return fSuccess;
+}
+
+static void tstClipGetCompletedRequest(int *prc, char ** ppc, uint32_t *pcb, CLIPREADCBREQ **ppReq)
+{
+ *prc = g_tst_rcCompleted;
+ *ppc = g_tst_abCompletedBuf;
+ *pcb = g_tst_cbCompleted;
+ *ppReq = g_tst_pCompletedReq;
+}
+
+static void tstStringFromX11(RTTEST hTest, PSHCLX11CTX pCtx,
+ const char *pcszExp, int rcExp)
+{
+ bool retval = true;
+ tstClipSendTargetUpdate(pCtx);
+ if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT)
+ {
+ RTTestFailed(hTest, "Wrong targets reported: %02X\n", tstClipQueryFormats());
+ }
+ else
+ {
+ char *pc;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
+ ShClX11ReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
+ int rc = VINF_SUCCESS;
+ uint32_t cbActual = 0;
+ tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
+ if (rc != rcExp)
+ RTTestFailed(hTest, "Wrong return code, expected %Rrc, got %Rrc\n",
+ rcExp, rc);
+ else if (pReqRet != pReq)
+ RTTestFailed(hTest, "Wrong returned request data, expected %p, got %p\n",
+ pReq, pReqRet);
+ else if (RT_FAILURE(rcExp))
+ retval = true;
+ else
+ {
+ RTUTF16 wcExp[TESTCASE_MAX_BUF_SIZE / 2];
+ RTUTF16 *pwcExp = wcExp;
+ size_t cwc = 0;
+ rc = RTStrToUtf16Ex(pcszExp, RTSTR_MAX, &pwcExp,
+ RT_ELEMENTS(wcExp), &cwc);
+ size_t cbExp = cwc * 2 + 2;
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbActual != cbExp)
+ {
+ RTTestFailed(hTest, "Returned string is the wrong size, string \"%.*ls\", size %u, expected \"%s\", size %u\n",
+ RT_MIN(TESTCASE_MAX_BUF_SIZE, cbActual), pc, cbActual,
+ pcszExp, cbExp);
+ }
+ else
+ {
+ if (memcmp(pc, wcExp, cbExp) == 0)
+ retval = true;
+ else
+ RTTestFailed(hTest, "Returned string \"%.*ls\" does not match expected string \"%s\"\n",
+ TESTCASE_MAX_BUF_SIZE, pc, pcszExp);
+ }
+ }
+ }
+ }
+ if (!retval)
+ RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n",
+ pcszExp, rcExp);
+}
+
+static void tstLatin1FromX11(RTTEST hTest, PSHCLX11CTX pCtx,
+ const char *pcszExp, int rcExp)
+{
+ bool retval = false;
+ tstClipSendTargetUpdate(pCtx);
+ if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT)
+ RTTestFailed(hTest, "Wrong targets reported: %02X\n",
+ tstClipQueryFormats());
+ else
+ {
+ char *pc;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
+ ShClX11ReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
+ int rc = VINF_SUCCESS;
+ uint32_t cbActual = 0;
+ tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
+ if (rc != rcExp)
+ RTTestFailed(hTest, "Wrong return code, expected %Rrc, got %Rrc\n",
+ rcExp, rc);
+ else if (pReqRet != pReq)
+ RTTestFailed(hTest, "Wrong returned request data, expected %p, got %p\n",
+ pReq, pReqRet);
+ else if (RT_FAILURE(rcExp))
+ retval = true;
+ else
+ {
+ RTUTF16 wcExp[TESTCASE_MAX_BUF_SIZE / 2];
+ //RTUTF16 *pwcExp = wcExp; - unused
+ size_t cwc;
+ for (cwc = 0; cwc == 0 || pcszExp[cwc - 1] != '\0'; ++cwc)
+ wcExp[cwc] = pcszExp[cwc];
+ size_t cbExp = cwc * 2;
+ if (cbActual != cbExp)
+ {
+ RTTestFailed(hTest, "Returned string is the wrong size, string \"%.*ls\", size %u, expected \"%s\", size %u\n",
+ RT_MIN(TESTCASE_MAX_BUF_SIZE, cbActual), pc, cbActual,
+ pcszExp, cbExp);
+ }
+ else
+ {
+ if (memcmp(pc, wcExp, cbExp) == 0)
+ retval = true;
+ else
+ RTTestFailed(hTest, "Returned string \"%.*ls\" does not match expected string \"%s\"\n",
+ TESTCASE_MAX_BUF_SIZE, pc, pcszExp);
+ }
+ }
+ }
+ if (!retval)
+ RTTestFailureDetails(hTest, "Expected: string \"%s\", rc %Rrc\n",
+ pcszExp, rcExp);
+}
+
+static void tstStringFromVBox(RTTEST hTest, PSHCLX11CTX pCtx, const char *pcszTarget, Atom typeExp, const char *valueExp)
+{
+ RT_NOREF(pCtx);
+ bool retval = false;
+ Atom type;
+ XtPointer value = NULL;
+ unsigned long length;
+ int format;
+ size_t lenExp = strlen(valueExp);
+ if (tstClipConvertSelection(pcszTarget, &type, &value, &length, &format))
+ {
+ if ( type != typeExp
+ || length != lenExp
+ || format != 8
+ || memcmp((const void *) value, (const void *)valueExp,
+ lenExp))
+ {
+ RTTestFailed(hTest, "Bad data: type %d, (expected %d), length %u, (expected %u), format %d (expected %d), value \"%.*s\" (expected \"%.*s\")\n",
+ type, typeExp, length, lenExp, format, 8,
+ RT_MIN(length, 20), value, RT_MIN(lenExp, 20), valueExp);
+ }
+ else
+ retval = true;
+ }
+ else
+ RTTestFailed(hTest, "Conversion failed\n");
+ XtFree((char *)value);
+ if (!retval)
+ RTTestFailureDetails(hTest, "Conversion to %s, expected \"%s\"\n",
+ pcszTarget, valueExp);
+}
+
+static void tstNoX11(PSHCLX11CTX pCtx, const char *pcszTestCtx)
+{
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq;
+ int rc = ShClX11ReadDataFromX11(pCtx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
+ RTTESTI_CHECK_MSG(rc == VERR_NO_DATA, ("context: %s\n", pcszTestCtx));
+}
+
+static void tstStringFromVBoxFailed(RTTEST hTest, PSHCLX11CTX pCtx, const char *pcszTarget)
+{
+ RT_NOREF(pCtx);
+ Atom type;
+ XtPointer value = NULL;
+ unsigned long length;
+ int format;
+ RTTEST_CHECK_MSG(hTest, !tstClipConvertSelection(pcszTarget, &type, &value,
+ &length, &format),
+ (hTest, "Conversion to target %s, should have failed but didn't, returned type %d, length %u, format %d, value \"%.*s\"\n",
+ pcszTarget, type, length, format, RT_MIN(length, 20),
+ value));
+ XtFree((char *)value);
+}
+
+static void tstNoSelectionOwnership(PSHCLX11CTX pCtx, const char *pcszTestCtx)
+{
+ RT_NOREF(pCtx);
+ RTTESTI_CHECK_MSG(!g_tst_fOwnsSel, ("context: %s\n", pcszTestCtx));
+}
+
+static void tstBadFormatRequestFromHost(RTTEST hTest, PSHCLX11CTX pCtx)
+{
+ tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
+ sizeof("hello world"), 8);
+ tstClipSendTargetUpdate(pCtx);
+ if (tstClipQueryFormats() != VBOX_SHCL_FMT_UNICODETEXT)
+ RTTestFailed(hTest, "Wrong targets reported: %02X\n",
+ tstClipQueryFormats());
+ else
+ {
+ char *pc;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
+ ShClX11ReadDataFromX11(pCtx, 0xF000 /* vboxFormat */, pReq); /* Bad format. */
+ int rc = VINF_SUCCESS;
+ uint32_t cbActual = 0;
+ tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
+ if (rc != VERR_NOT_IMPLEMENTED)
+ RTTestFailed(hTest, "Wrong return code, expected VERR_NOT_IMPLEMENTED, got %Rrc\n",
+ rc);
+ tstClipSetSelectionValues("", XA_STRING, "", sizeof(""), 8);
+ tstClipSendTargetUpdate(pCtx);
+ if (tstClipQueryFormats() == VBOX_SHCL_FMT_UNICODETEXT)
+ RTTestFailed(hTest, "Failed to report targets after bad host request.\n");
+ }
+}
+
+int main()
+{
+ /*
+ * Init the runtime, test and say hello.
+ */
+ RTTEST hTest;
+ int rc = RTTestInitAndCreate("tstClipboardGH-X11", &hTest);
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+ RTTestBanner(hTest);
+
+ /*
+ * Run the tests.
+ */
+ SHCLCALLBACKS Callbacks;
+ RT_ZERO(Callbacks);
+ Callbacks.pfnReportFormats = tstShClReportFormatsCallback;
+ Callbacks.pfnOnRequestDataFromSource = tstShClOnRequestDataFromSourceCallback;
+ Callbacks.pfnOnSendDataToDest = tstShClOnSendDataToDestCallback;
+
+ SHCLX11CTX X11Ctx;
+ rc = ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, false /* fHeadless */);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+
+ char *pc;
+ uint32_t cbActual;
+ CLIPREADCBREQ *pReq = (CLIPREADCBREQ *)&pReq, *pReqRet = NULL;
+
+ /* UTF-8 from X11 */
+ RTTestSub(hTest, "reading UTF-8 from X11");
+ /* Simple test */
+ tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
+ sizeof("hello world"), 8);
+ tstStringFromX11(hTest, &X11Ctx, "hello world", VINF_SUCCESS);
+ /* With an embedded carriage return */
+ tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
+ "hello\nworld", sizeof("hello\nworld"), 8);
+ tstStringFromX11(hTest, &X11Ctx, "hello\r\nworld", VINF_SUCCESS);
+ /* With an embedded CRLF */
+ tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
+ "hello\r\nworld", sizeof("hello\r\nworld"), 8);
+ tstStringFromX11(hTest, &X11Ctx, "hello\r\r\nworld", VINF_SUCCESS);
+ /* With an embedded LFCR */
+ tstClipSetSelectionValues("text/plain;charset=UTF-8", XA_STRING,
+ "hello\n\rworld", sizeof("hello\n\rworld"), 8);
+ tstStringFromX11(hTest, &X11Ctx, "hello\r\n\rworld", VINF_SUCCESS);
+ /* An empty string */
+ tstClipSetSelectionValues("text/plain;charset=utf-8", XA_STRING, "",
+ sizeof(""), 8);
+ tstStringFromX11(hTest, &X11Ctx, "", VINF_SUCCESS);
+ /* With an embedded UTF-8 character. */
+ tstClipSetSelectionValues("STRING", XA_STRING,
+ "100\xE2\x82\xAC" /* 100 Euro */,
+ sizeof("100\xE2\x82\xAC"), 8);
+ tstStringFromX11(hTest, &X11Ctx, "100\xE2\x82\xAC", VINF_SUCCESS);
+ /* A non-zero-terminated string */
+ tstClipSetSelectionValues("TEXT", XA_STRING,
+ "hello world", sizeof("hello world") - 1, 8);
+ tstStringFromX11(hTest, &X11Ctx, "hello world", VINF_SUCCESS);
+
+ /* Latin1 from X11 */
+ RTTestSub(hTest, "reading Latin1 from X11");
+ /* Simple test */
+ tstClipSetSelectionValues("STRING", XA_STRING, "Georges Dupr\xEA",
+ sizeof("Georges Dupr\xEA"), 8);
+ tstLatin1FromX11(hTest, &X11Ctx, "Georges Dupr\xEA", VINF_SUCCESS);
+ /* With an embedded carriage return */
+ tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\nDupr\xEA",
+ sizeof("Georges\nDupr\xEA"), 8);
+ tstLatin1FromX11(hTest, &X11Ctx, "Georges\r\nDupr\xEA", VINF_SUCCESS);
+ /* With an embedded CRLF */
+ tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\r\nDupr\xEA",
+ sizeof("Georges\r\nDupr\xEA"), 8);
+ tstLatin1FromX11(hTest, &X11Ctx, "Georges\r\r\nDupr\xEA", VINF_SUCCESS);
+ /* With an embedded LFCR */
+ tstClipSetSelectionValues("TEXT", XA_STRING, "Georges\n\rDupr\xEA",
+ sizeof("Georges\n\rDupr\xEA"), 8);
+ tstLatin1FromX11(hTest, &X11Ctx, "Georges\r\n\rDupr\xEA", VINF_SUCCESS);
+ /* A non-zero-terminated string */
+ tstClipSetSelectionValues("text/plain", XA_STRING,
+ "Georges Dupr\xEA!",
+ sizeof("Georges Dupr\xEA!") - 1, 8);
+ tstLatin1FromX11(hTest, &X11Ctx, "Georges Dupr\xEA!", VINF_SUCCESS);
+
+ /*
+ * Unknown X11 format
+ */
+ RTTestSub(hTest, "handling of an unknown X11 format");
+ tstClipInvalidateFormats();
+ tstClipSetSelectionValues("CLIPBOARD", XA_STRING, "Test",
+ sizeof("Test"), 8);
+ tstClipSendTargetUpdate(&X11Ctx);
+ RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 0,
+ (hTest, "Failed to send a format update notification\n"));
+
+ /*
+ * Timeout from X11
+ */
+ RTTestSub(hTest, "X11 timeout");
+ tstClipSetSelectionValues("UTF8_STRING", XT_CONVERT_FAIL, NULL,0, 8);
+ tstStringFromX11(hTest, &X11Ctx, "", VERR_NO_DATA);
+
+ /*
+ * No data in X11 clipboard
+ */
+ RTTestSub(hTest, "a data request from an empty X11 clipboard");
+ tstClipSetSelectionValues("UTF8_STRING", XA_STRING, NULL,
+ 0, 8);
+ ShClX11ReadDataFromX11(&X11Ctx, VBOX_SHCL_FMT_UNICODETEXT, pReq);
+ tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
+ RTTEST_CHECK_MSG(hTest, rc == VERR_NO_DATA,
+ (hTest, "Returned %Rrc instead of VERR_NO_DATA\n",
+ rc));
+ RTTEST_CHECK_MSG(hTest, pReqRet == pReq,
+ (hTest, "Wrong returned request data, expected %p, got %p\n",
+ pReq, pReqRet));
+
+ /*
+ * Ensure that VBox is notified when we return the CB to X11
+ */
+ RTTestSub(hTest, "notification of switch to X11 clipboard");
+ tstClipInvalidateFormats();
+ clipReportEmpty(&X11Ctx);
+ RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 0,
+ (hTest, "Failed to send a format update (release) notification\n"));
+
+ /*
+ * Request for an invalid VBox format from X11
+ */
+ RTTestSub(hTest, "a request for an invalid VBox format from X11");
+ /* Testing for 0xffff will go into handling VBOX_SHCL_FMT_UNICODETEXT, where we don't have
+ * have any data at the moment so far, so this will return VERR_NO_DATA. */
+ ShClX11ReadDataFromX11(&X11Ctx, 0xffff /* vboxFormat */, pReq);
+ tstClipGetCompletedRequest(&rc, &pc, &cbActual, &pReqRet);
+ RTTEST_CHECK_MSG(hTest, rc == VERR_NO_DATA,
+ (hTest, "Returned %Rrc instead of VERR_NO_DATA\n",
+ rc));
+ RTTEST_CHECK_MSG(hTest, pReqRet == pReq,
+ (hTest, "Wrong returned request data, expected %p, got %p\n",
+ pReq, pReqRet));
+
+ /*
+ * Targets failure from X11
+ */
+ RTTestSub(hTest, "X11 targets conversion failure");
+ tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
+ sizeof("hello world"), 8);
+ tstClipSetTargetsFailure();
+ Atom atom = XA_STRING;
+ long unsigned int cLen = 0;
+ int format = 8;
+ clipQueryX11TargetsCallback(NULL, (XtPointer) &X11Ctx, NULL, &atom, NULL, &cLen,
+ &format);
+ RTTEST_CHECK_MSG(hTest, tstClipQueryFormats() == 0,
+ (hTest, "Wrong targets reported: %02X\n",
+ tstClipQueryFormats()));
+
+ /*
+ * X11 text format conversion
+ */
+ RTTestSub(hTest, "handling of X11 selection targets");
+ RTTEST_CHECK_MSG(hTest, tstClipTextFormatConversion(&X11Ctx),
+ (hTest, "failed to select the right X11 text formats\n"));
+ /*
+ * UTF-8 from VBox
+ */
+ RTTestSub(hTest, "reading UTF-8 from VBox");
+ /* Simple test */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello world",
+ sizeof("hello world") * 2);
+ tstStringFromVBox(hTest, &X11Ctx, "UTF8_STRING",
+ clipGetAtom(&X11Ctx, "UTF8_STRING"), "hello world");
+ /* With an embedded carriage return */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello\r\nworld",
+ sizeof("hello\r\nworld") * 2);
+ tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=UTF-8",
+ clipGetAtom(&X11Ctx, "text/plain;charset=UTF-8"),
+ "hello\nworld");
+ /* With an embedded CRCRLF */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello\r\r\nworld",
+ sizeof("hello\r\r\nworld") * 2);
+ tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=UTF-8",
+ clipGetAtom(&X11Ctx, "text/plain;charset=UTF-8"),
+ "hello\r\nworld");
+ /* With an embedded CRLFCR */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello\r\n\rworld",
+ sizeof("hello\r\n\rworld") * 2);
+ tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=UTF-8",
+ clipGetAtom(&X11Ctx, "text/plain;charset=UTF-8"),
+ "hello\n\rworld");
+ /* An empty string */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "", 2);
+ tstStringFromVBox(hTest, &X11Ctx, "text/plain;charset=utf-8",
+ clipGetAtom(&X11Ctx, "text/plain;charset=utf-8"), "");
+ /* With an embedded UTF-8 character. */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "100\xE2\x82\xAC" /* 100 Euro */,
+ 10);
+ tstStringFromVBox(hTest, &X11Ctx, "STRING",
+ clipGetAtom(&X11Ctx, "STRING"), "100\xE2\x82\xAC");
+ /* A non-zero-terminated string */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello world",
+ sizeof("hello world") * 2 - 2);
+ tstStringFromVBox(hTest, &X11Ctx, "TEXT", clipGetAtom(&X11Ctx, "TEXT"),
+ "hello world");
+
+ /*
+ * Timeout from VBox
+ */
+ RTTestSub(hTest, "reading from VBox with timeout");
+ tstClipEmptyVBox(&X11Ctx, VERR_TIMEOUT);
+ tstStringFromVBoxFailed(hTest, &X11Ctx, "UTF8_STRING");
+
+ /*
+ * No data in VBox clipboard
+ */
+ RTTestSub(hTest, "an empty VBox clipboard");
+ tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
+ tstClipEmptyVBox(&X11Ctx, VINF_SUCCESS);
+ RTTEST_CHECK_MSG(hTest, g_tst_fOwnsSel,
+ (hTest, "VBox grabbed the clipboard with no data and we ignored it\n"));
+ tstStringFromVBoxFailed(hTest, &X11Ctx, "UTF8_STRING");
+
+ /*
+ * An unknown VBox format
+ */
+ RTTestSub(hTest, "reading an unknown VBox format");
+ tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "", 2);
+ ShClX11ReportFormatsToX11(&X11Ctx, 0xa0000);
+ RTTEST_CHECK_MSG(hTest, g_tst_fOwnsSel,
+ (hTest, "VBox grabbed the clipboard with unknown data and we ignored it\n"));
+ tstStringFromVBoxFailed(hTest, &X11Ctx, "UTF8_STRING");
+
+ /*
+ * VBox requests a bad format
+ */
+ RTTestSub(hTest, "recovery from a bad format request");
+ tstBadFormatRequestFromHost(hTest, &X11Ctx);
+
+ ShClX11Destroy(&X11Ctx);
+
+ /*
+ * Headless clipboard tests
+ */
+ rc = ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, true /* fHeadless */);
+ AssertRCReturn(rc, RTEXITCODE_FAILURE);
+
+ /* Read from X11 */
+ RTTestSub(hTest, "reading from X11, headless clipboard");
+ /* Simple test */
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "",
+ sizeof("") * 2);
+ tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world",
+ sizeof("hello world"), 8);
+ tstNoX11(&X11Ctx, "reading from X11, headless clipboard");
+
+ /* Read from VBox */
+ RTTestSub(hTest, "reading from VBox, headless clipboard");
+ /* Simple test */
+ tstClipEmptyVBox(&X11Ctx, VERR_WRONG_ORDER);
+ tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8);
+ tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello world",
+ sizeof("hello world") * 2);
+ tstNoSelectionOwnership(&X11Ctx, "reading from VBox, headless clipboard");
+
+ ShClX11Destroy(&X11Ctx);
+
+ return RTTestSummaryAndDestroy(hTest);
+}
+
diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp
new file mode 100644
index 00000000..226c6fac
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp
@@ -0,0 +1,103 @@
+/* $Id: tstClipboardGH-X11Smoke.cpp $ */
+/** @file
+ * Shared Clipboard guest/host X11 code smoke tests.
+ */
+
+/*
+ * Copyright (C) 2011-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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/* This is a simple test case that just starts a copy of the X11 clipboard
+ * backend, checks the X11 clipboard and exits. If ever needed I will add an
+ * interactive mode in which the user can read and copy to the clipboard from
+ * the command line. */
+
+#include <iprt/assert.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/test.h>
+
+#include <VBox/GuestHost/SharedClipboard.h>
+#include <VBox/GuestHost/SharedClipboard-x11.h>
+#include <VBox/GuestHost/clipboard-helper.h>
+
+
+static DECLCALLBACK(int) tstShClReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser)
+{
+ RT_NOREF(pCtx, fFormats, pvUser);
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) tstShClOnRequestDataFromSourceCallback(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser)
+{
+ RT_NOREF(pCtx, uFmt, ppv, pcb, pvUser);
+ return VERR_NO_DATA;
+}
+
+static DECLCALLBACK(int) tstShClOnSendDataToDest(PSHCLCONTEXT pCtx, void *pv, uint32_t cb, void *pvUser)
+{
+ RT_NOREF(pCtx, pv, cb, pvUser);
+ return VINF_SUCCESS;
+}
+
+int main()
+{
+ /*
+ * Init the runtime, test and say hello.
+ */
+ RTTEST hTest;
+ int rc = RTTestInitAndCreate("tstClipboardGH-X11Smoke", &hTest);
+ if (rc)
+ return rc;
+ RTTestBanner(hTest);
+
+ /*
+ * Run the test.
+ */
+ rc = VINF_SUCCESS;
+ /* We can't test anything without an X session, so just return success
+ * in that case. */
+ if (!RTEnvExist("DISPLAY"))
+ {
+ RTTestPrintf(hTest, RTTESTLVL_INFO,
+ "X11 not available, not running test\n");
+ return RTTestSummaryAndDestroy(hTest);
+ }
+
+ SHCLCALLBACKS Callbacks;
+ RT_ZERO(Callbacks);
+ Callbacks.pfnReportFormats = tstShClReportFormatsCallback;
+ Callbacks.pfnOnRequestDataFromSource = tstShClOnRequestDataFromSourceCallback;
+ Callbacks.pfnOnSendDataToDest = tstShClOnSendDataToDest;
+
+ SHCLX11CTX X11Ctx;
+ rc = ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, false);
+ AssertRCReturn(rc, 1);
+ rc = ShClX11ThreadStart(&X11Ctx, false /* fGrab */);
+ AssertRCReturn(rc, 1);
+ /* Give the clipboard time to synchronise. */
+ RTThreadSleep(500);
+ rc = ShClX11ThreadStop(&X11Ctx);
+ AssertRCReturn(rc, 1);
+ ShClX11Destroy(&X11Ctx);
+ return RTTestSummaryAndDestroy(hTest);
+}
+
diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardQt/Makefile.kmk b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardQt/Makefile.kmk
new file mode 100644
index 00000000..bd9401a3
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardQt/Makefile.kmk
@@ -0,0 +1,52 @@
+# $Id: Makefile.kmk $
+## @file
+# Sub-Makefile for tstClipboardQt.
+#
+
+#
+# Copyright (C) 2011-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 <https://www.gnu.org/licenses>.
+#
+# SPDX-License-Identifier: GPL-3.0-only
+#
+
+SUB_DEPTH = ../../../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# Targets and units.
+#
+USES += qt5
+PROGRAMS += tstClipboardQt
+
+#
+# tstClipboardQt
+#
+tstClipboardQt_TEMPLATE = VBoxQtGuiExe
+tstClipboardQt_NAME = tstClipboardQt
+tstClipboardQt_CXXFLAGS = \
+ $(VBOX_GCC_Wno-implicit-fallthrough) \
+ $(VBOX_GCC_Wno-deprecated-declarations)
+
+tstClipboardQt_SOURCES = \
+ tstClipboardQt.cpp
+
+# The Qt modules we're using.
+tstClipboardQt_QT_MODULES = Core Gui Widgets PrintSupport
+
+include $(FILE_KBUILD_SUB_FOOTER)
diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardQt/tstClipboardQt.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardQt/tstClipboardQt.cpp
new file mode 100644
index 00000000..ebf3e8fb
--- /dev/null
+++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardQt/tstClipboardQt.cpp
@@ -0,0 +1,47 @@
+/* $Id: tstClipboardQt.cpp $ */
+/** @file
+ * Shared Clipboard guest/host Qt code test cases.
+ */
+
+/*
+ * Copyright (C) 2011-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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <QApplication>
+#include <QMainWindow>
+#include <QTextEdit>
+#include <QHBoxLayout>
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+ QMainWindow window;
+ QWidget *pWidget = new QWidget;
+ window.setCentralWidget(pWidget);
+ QHBoxLayout *pLayout = new QHBoxLayout(pWidget);
+
+ QTextEdit *pTextEdit = new QTextEdit;
+ pLayout->addWidget(pTextEdit);
+
+
+ window.show();
+
+ app.exec();
+}