/* $Id: VBoxDnDDataObject.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */ /** @file * VBoxDnDDataObject.cpp - IDataObject 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 . * * SPDX-License-Identifier: GPL-3.0-only */ #define LOG_GROUP LOG_GROUP_GUEST_DND #include #include #include /* For bad_alloc. */ #include #include #include #include #include #include "VBoxTray.h" #include "VBoxHelpers.h" #include "VBoxDnD.h" /** @todo Implement IDataObjectAsyncCapability interface? */ VBoxDnDDataObject::VBoxDnDDataObject(LPFORMATETC pFormatEtc, LPSTGMEDIUM pStgMed, ULONG cFormats) : m_enmStatus(Status_Uninitialized) , m_cRefs(1) , m_cFormats(0) , m_paFormatEtc(0) , m_paStgMedium(0) , m_EvtDropped(NIL_RTSEMEVENT) , m_pvData(NULL) , m_cbData(0) { int rc2 = Init(pFormatEtc, pStgMed, cFormats); AssertRC(rc2); } VBoxDnDDataObject::~VBoxDnDDataObject(void) { int rc2 = Destroy(); AssertRC(rc2); } /* * IUnknown methods. */ STDMETHODIMP_(ULONG) VBoxDnDDataObject::AddRef(void) { AssertReturn(m_cRefs < 32, m_cRefs); /* Paranoia. */ return InterlockedIncrement(&m_cRefs); } STDMETHODIMP_(ULONG) VBoxDnDDataObject::Release(void) { AssertReturn(m_cRefs > 0, 0); LONG lCount = InterlockedDecrement(&m_cRefs); if (lCount == 0) { delete this; return 0; } return lCount; } STDMETHODIMP VBoxDnDDataObject::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; } STDMETHODIMP VBoxDnDDataObject::GetData(LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium) { AssertPtrReturn(pFormatEtc, DV_E_FORMATETC); AssertPtrReturn(pMedium, DV_E_FORMATETC); ULONG lIndex; if (!LookupFormatEtc(pFormatEtc, &lIndex)) /* Format supported? */ return DV_E_FORMATETC; if (lIndex >= m_cFormats) /* Paranoia. */ return DV_E_FORMATETC; LPFORMATETC pThisFormat = &m_paFormatEtc[lIndex]; AssertPtr(pThisFormat); LPSTGMEDIUM pThisMedium = &m_paStgMedium[lIndex]; AssertPtr(pThisMedium); LogFlowFunc(("Using pThisFormat=%p, pThisMedium=%p\n", pThisFormat, pThisMedium)); HRESULT hr = DV_E_FORMATETC; /* Play safe. */ LogFlowFunc(("mStatus=%ld\n", m_enmStatus)); if (m_enmStatus == Status_Dropping) { LogRel2(("DnD: Waiting for drop event ...\n")); int rc2 = RTSemEventWait(m_EvtDropped, RT_INDEFINITE_WAIT); LogFlowFunc(("rc2=%Rrc, mStatus=%ld\n", rc2, m_enmStatus)); RT_NOREF(rc2); } if (m_enmStatus == Status_Dropped) { LogRel2(("DnD: Drop event received\n")); LogRel3(("DnD: cfFormat=%RI16, sFormat=%s, tyMed=%RU32, dwAspect=%RU32\n", pThisFormat->cfFormat, VBoxDnDDataObject::ClipboardFormatToString(pFormatEtc->cfFormat), pThisFormat->tymed, pThisFormat->dwAspect)); LogRel3(("DnD: Got strFormat=%s, pvData=%p, cbData=%RU32\n", m_strFormat.c_str(), m_pvData, m_cbData)); /* * Initialize default values. */ pMedium->tymed = pThisFormat->tymed; pMedium->pUnkForRelease = NULL; /* * URI list handling. */ if (DnDMIMEHasFileURLs(m_strFormat.c_str(), RTSTR_MAX)) { char **papszFiles; size_t cFiles; int rc = RTStrSplit((const char *)m_pvData, m_cbData, DND_PATH_SEPARATOR_STR, &papszFiles, &cFiles); if ( RT_SUCCESS(rc) && cFiles) { LogRel2(("DnD: Files (%zu)\n", cFiles)); for (size_t i = 0; i < cFiles; i++) LogRel2(("\tDnD: File '%s'\n", papszFiles[i])); #if 0 if ( (pFormatEtc->tymed & TYMED_ISTREAM) && (pFormatEtc->dwAspect == DVASPECT_CONTENT) && (pFormatEtc->cfFormat == CF_FILECONTENTS)) { } else if ( (pFormatEtc->tymed & TYMED_HGLOBAL) && (pFormatEtc->dwAspect == DVASPECT_CONTENT) && (pFormatEtc->cfFormat == CF_FILEDESCRIPTOR)) { } else if ( (pFormatEtc->tymed & TYMED_HGLOBAL) && (pFormatEtc->cfFormat == CF_PREFERREDDROPEFFECT)) { HGLOBAL hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE | GMEM_ZEROINIT, sizeof(DWORD)); DWORD *pdwEffect = (DWORD *)GlobalLock(hData); AssertPtr(pdwEffect); *pdwEffect = DROPEFFECT_COPY; GlobalUnlock(hData); pMedium->hGlobal = hData; pMedium->tymed = TYMED_HGLOBAL; } else #endif if ( (pFormatEtc->tymed & TYMED_HGLOBAL) && (pFormatEtc->dwAspect == DVASPECT_CONTENT) && (pFormatEtc->cfFormat == CF_TEXT)) { pMedium->hGlobal = GlobalAlloc(GHND, m_cbData + 1); if (pMedium->hGlobal) { char *pcDst = (char *)GlobalLock(pMedium->hGlobal); memcpy(pcDst, m_pvData, m_cbData); pcDst[m_cbData] = '\0'; GlobalUnlock(pMedium->hGlobal); hr = S_OK; } } else if ( (pFormatEtc->tymed & TYMED_HGLOBAL) && (pFormatEtc->dwAspect == DVASPECT_CONTENT) && (pFormatEtc->cfFormat == CF_HDROP)) { size_t cchFiles = 0; /* Number of ASCII characters. */ for (size_t i = 0; i < cFiles; i++) { cchFiles += strlen(papszFiles[i]); cchFiles += 1; /* Terminating '\0'. */ } size_t cbBuf = sizeof(DROPFILES) + ((cchFiles + 1) * sizeof(RTUTF16)); DROPFILES *pBuf = (DROPFILES *)RTMemAllocZ(cbBuf); if (pBuf) { pBuf->pFiles = sizeof(DROPFILES); pBuf->fWide = 1; /* We use unicode. Always. */ uint8_t *pCurFile = (uint8_t *)pBuf + pBuf->pFiles; AssertPtr(pCurFile); for (size_t i = 0; i < cFiles && RT_SUCCESS(rc); i++) { size_t cchCurFile; PRTUTF16 pwszFile; rc = RTStrToUtf16(papszFiles[i], &pwszFile); if (RT_SUCCESS(rc)) { cchCurFile = RTUtf16Len(pwszFile); Assert(cchCurFile); memcpy(pCurFile, pwszFile, cchCurFile * sizeof(RTUTF16)); RTUtf16Free(pwszFile); } else break; pCurFile += cchCurFile * sizeof(RTUTF16); /* Terminate current file name. */ *pCurFile = L'\0'; pCurFile += sizeof(RTUTF16); } if (RT_SUCCESS(rc)) { *pCurFile = L'\0'; /* Final list terminator. */ pMedium->tymed = TYMED_HGLOBAL; pMedium->pUnkForRelease = NULL; pMedium->hGlobal = GlobalAlloc( GMEM_ZEROINIT | GMEM_MOVEABLE | GMEM_DDESHARE, cbBuf); if (pMedium->hGlobal) { LPVOID pMem = GlobalLock(pMedium->hGlobal); if (pMem) { memcpy(pMem, pBuf, cbBuf); GlobalUnlock(pMedium->hGlobal); hr = S_OK; } } } RTMemFree(pBuf); } else rc = VERR_NO_MEMORY; } for (size_t i = 0; i < cFiles; ++i) RTStrFree(papszFiles[i]); RTMemFree(papszFiles); } if (RT_FAILURE(rc)) hr = DV_E_FORMATETC; } /* * Plain text handling. */ else if ( m_strFormat.equalsIgnoreCase("text/plain") || m_strFormat.equalsIgnoreCase("text/html") || m_strFormat.equalsIgnoreCase("text/plain;charset=utf-8") || m_strFormat.equalsIgnoreCase("text/plain;charset=utf-16") || m_strFormat.equalsIgnoreCase("text/richtext") || m_strFormat.equalsIgnoreCase("UTF8_STRING") || m_strFormat.equalsIgnoreCase("TEXT") || m_strFormat.equalsIgnoreCase("STRING")) { pMedium->hGlobal = GlobalAlloc(GHND, m_cbData + 1); if (pMedium->hGlobal) { char *pcDst = (char *)GlobalLock(pMedium->hGlobal); memcpy(pcDst, m_pvData, m_cbData); pcDst[m_cbData] = '\0'; GlobalUnlock(pMedium->hGlobal); hr = S_OK; } } else LogRel(("DnD: Error: Format '%s' not implemented\n", m_strFormat.c_str())); } /* Error handling; at least return some basic data. */ if (FAILED(hr)) { LogFlowFunc(("Copying medium ...\n")); switch (pThisMedium->tymed) { case TYMED_HGLOBAL: pMedium->hGlobal = (HGLOBAL)OleDuplicateData(pThisMedium->hGlobal, pThisFormat->cfFormat, NULL); break; default: break; } pMedium->tymed = pThisFormat->tymed; pMedium->pUnkForRelease = NULL; } if (hr == DV_E_FORMATETC) LogRel(("DnD: Error handling format '%s' (%RU32 bytes)\n", m_strFormat.c_str(), m_cbData)); LogFlowFunc(("hr=%Rhrc\n", hr)); return hr; } STDMETHODIMP VBoxDnDDataObject::GetDataHere(LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium) { RT_NOREF(pFormatEtc, pMedium); LogFlowFunc(("\n")); return DATA_E_FORMATETC; } STDMETHODIMP VBoxDnDDataObject::QueryGetData(LPFORMATETC pFormatEtc) { LogFlowFunc(("\n")); return (LookupFormatEtc(pFormatEtc, NULL /* puIndex */)) ? S_OK : DV_E_FORMATETC; } STDMETHODIMP VBoxDnDDataObject::GetCanonicalFormatEtc(LPFORMATETC pFormatEtc, LPFORMATETC pFormatEtcOut) { RT_NOREF(pFormatEtc); LogFlowFunc(("\n")); /* Set this to NULL in any case. */ pFormatEtcOut->ptd = NULL; return E_NOTIMPL; } STDMETHODIMP VBoxDnDDataObject::SetData(LPFORMATETC pFormatEtc, LPSTGMEDIUM pMedium, BOOL fRelease) { RT_NOREF(pFormatEtc, pMedium, fRelease); return E_NOTIMPL; } STDMETHODIMP VBoxDnDDataObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppEnumFormatEtc) { LogFlowFunc(("dwDirection=%RI32, mcFormats=%RI32, mpFormatEtc=%p\n", dwDirection, m_cFormats, m_paFormatEtc)); HRESULT hr; if (dwDirection == DATADIR_GET) hr = VBoxDnDEnumFormatEtc::CreateEnumFormatEtc(m_cFormats, m_paFormatEtc, ppEnumFormatEtc); else hr = E_NOTIMPL; LogFlowFunc(("hr=%Rhrc\n", hr)); return hr; } STDMETHODIMP VBoxDnDDataObject::DAdvise(LPFORMATETC pFormatEtc, DWORD fAdvise, IAdviseSink *pAdvSink, DWORD *pdwConnection) { RT_NOREF(pFormatEtc, fAdvise, pAdvSink, pdwConnection); return OLE_E_ADVISENOTSUPPORTED; } STDMETHODIMP VBoxDnDDataObject::DUnadvise(DWORD dwConnection) { RT_NOREF(dwConnection); return OLE_E_ADVISENOTSUPPORTED; } STDMETHODIMP VBoxDnDDataObject::EnumDAdvise(IEnumSTATDATA **ppEnumAdvise) { RT_NOREF(ppEnumAdvise); return OLE_E_ADVISENOTSUPPORTED; } /* * Own stuff. */ int VBoxDnDDataObject::Init(LPFORMATETC pFormatEtc, LPSTGMEDIUM pStgMed, ULONG cFormats) { AssertReturn(m_enmStatus == Status_Uninitialized, VERR_WRONG_ORDER); int rc = RTSemEventCreate(&m_EvtDropped); AssertRCReturn(rc, rc); /* * Allocate arrays for format related stuff. */ ULONG cFixedFormats = 1; ULONG cAllFormats = cFormats + cFixedFormats; m_paFormatEtc = (LPFORMATETC)RTMemAllocZ(sizeof(m_paFormatEtc[0]) * cAllFormats); if (m_paFormatEtc) { m_paStgMedium = (LPSTGMEDIUM)RTMemAllocZ(sizeof(m_paStgMedium[0]) * cAllFormats); if (m_EvtDropped) { /* * 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_paFormatEtc[i] = pFormatEtc[i]; m_paStgMedium[i] = pStgMed[i]; } } /* * Register fixed formats. */ #if 0 /* CF_HDROP. */ RegisterFormat(&mpFormatEtc[cFormats], CF_HDROP); mpStgMedium[cFormats++].tymed = TYMED_HGLOBAL; /* IStream. */ RegisterFormat(&mpFormatEtc[cFormats++], RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR)); RegisterFormat(&mpFormatEtc[cFormats++], RegisterClipboardFormat(CFSTR_FILECONTENTS), TYMED_ISTREAM, 0 /* lIndex */); /* Required for e.g. Windows Media Player. */ RegisterFormat(&mpFormatEtc[cFormats++], RegisterClipboardFormat(CFSTR_FILENAME)); RegisterFormat(&mpFormatEtc[cFormats++], RegisterClipboardFormat(CFSTR_FILENAMEW)); RegisterFormat(&mpFormatEtc[cFormats++], RegisterClipboardFormat(CFSTR_SHELLIDLIST)); RegisterFormat(&mpFormatEtc[cFormats++], RegisterClipboardFormat(CFSTR_SHELLIDLISTOFFSET)); #endif m_cFormats = cFormats; m_enmStatus = Status_Initialized; } else rc = VERR_NO_MEMORY; } else rc = VERR_NO_MEMORY; LogFlowFunc(("cFormats=%RU32 - %Rrc\n", cFormats, rc)); return rc; } int VBoxDnDDataObject::Destroy(void) { if (m_enmStatus == Status_Uninitialized) return VINF_SUCCESS; AssertReturn(m_cRefs == 0, VERR_WRONG_ORDER); if (m_paFormatEtc) { RTMemFree(m_paFormatEtc); m_paFormatEtc = NULL; } if (m_paStgMedium) { RTMemFree(m_paStgMedium); m_paStgMedium = NULL; } if (m_pvData) { RTMemFree(m_pvData); m_pvData = NULL; } int rc = RTSemEventDestroy(m_EvtDropped); AssertRCReturn(rc, rc); m_EvtDropped = NIL_RTSEMEVENT; m_enmStatus = Status_Uninitialized; return VINF_SUCCESS; } /** * Aborts waiting for data being "dropped". * * @returns VBox status code. */ int VBoxDnDDataObject::Abort(void) { LogFlowFunc(("Aborting ...\n")); if (m_enmStatus == Status_Dropping) { m_enmStatus = Status_Aborted; return RTSemEventSignal(m_EvtDropped); } return VINF_SUCCESS; } /** * Static helper function to convert a CLIPFORMAT to a string and return it. * * @returns Pointer to converted stringified CLIPFORMAT, or "unknown" if not found / invalid. * @param fmt CLIPFORMAT to return string for. */ /* static */ const char* VBoxDnDDataObject::ClipboardFormatToString(CLIPFORMAT fmt) { #if 0 char szFormat[128]; if (GetClipboardFormatName(fmt, szFormat, sizeof(szFormat))) LogFlowFunc(("wFormat=%RI16, szName=%s\n", fmt, szFormat)); #endif switch (fmt) { case 1: return "CF_TEXT"; case 2: return "CF_BITMAP"; case 3: return "CF_METAFILEPICT"; case 4: return "CF_SYLK"; case 5: return "CF_DIF"; case 6: return "CF_TIFF"; case 7: return "CF_OEMTEXT"; case 8: return "CF_DIB"; case 9: return "CF_PALETTE"; case 10: return "CF_PENDATA"; case 11: return "CF_RIFF"; case 12: return "CF_WAVE"; case 13: return "CF_UNICODETEXT"; case 14: return "CF_ENHMETAFILE"; case 15: return "CF_HDROP"; case 16: return "CF_LOCALE"; case 17: return "CF_DIBV5"; case 18: return "CF_MAX"; case 49158: return "FileName"; case 49159: return "FileNameW"; case 49161: return "DATAOBJECT"; case 49171: return "Ole Private Data"; case 49314: return "Shell Object Offsets"; case 49316: return "File Contents"; case 49317: return "File Group Descriptor"; case 49323: return "Preferred Drop Effect"; case 49380: return "Shell Object Offsets"; case 49382: return "FileContents"; case 49383: return "FileGroupDescriptor"; case 49389: return "Preferred DropEffect"; case 49268: return "Shell IDList Array"; case 49619: return "RenPrivateFileAttachments"; default: break; } return "unknown"; } /** * Checks whether a given FORMATETC is supported by this data object and returns its index. * * @returns \c true if format is supported, \c false if not. * @param pFormatEtc Pointer to FORMATETC to check for. * @param puIndex Where to store the index if format is supported. */ bool VBoxDnDDataObject::LookupFormatEtc(LPFORMATETC pFormatEtc, ULONG *puIndex) { AssertReturn(pFormatEtc, false); /* puIndex is optional. */ for (ULONG i = 0; i < m_cFormats; i++) { if( (pFormatEtc->tymed & m_paFormatEtc[i].tymed) && pFormatEtc->cfFormat == m_paFormatEtc[i].cfFormat && pFormatEtc->dwAspect == m_paFormatEtc[i].dwAspect) { LogRel3(("DnD: Format found: tyMed=%RI32, cfFormat=%RI16, sFormats=%s, dwAspect=%RI32, ulIndex=%RU32\n", pFormatEtc->tymed, pFormatEtc->cfFormat, VBoxDnDDataObject::ClipboardFormatToString(m_paFormatEtc[i].cfFormat), pFormatEtc->dwAspect, i)); if (puIndex) *puIndex = i; return true; } } LogRel3(("DnD: Format NOT found: tyMed=%RI32, cfFormat=%RI16, sFormats=%s, dwAspect=%RI32\n", pFormatEtc->tymed, pFormatEtc->cfFormat, VBoxDnDDataObject::ClipboardFormatToString(pFormatEtc->cfFormat), pFormatEtc->dwAspect)); return false; } /** * Registers a new format with this data object. * * @param pFormatEtc Where to store the new format into. * @param clipFormat Clipboard format to register. * @param tyMed Format medium type to register. * @param lIndex Format index to register. * @param dwAspect Format aspect to register. * @param pTargetDevice Format target device to register. */ void VBoxDnDDataObject::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, sFormat=%s\n", pFormatEtc->cfFormat, VBoxDnDDataObject::ClipboardFormatToString(pFormatEtc->cfFormat))); } /** * Sets the current status of this data object. * * @param status New status to set. */ void VBoxDnDDataObject::SetStatus(Status status) { LogFlowFunc(("Setting status to %ld\n", status)); m_enmStatus = status; } /** * Signals that data has been "dropped". * * @returns VBox status code. * @param strFormat Format of data (MIME string). * @param pvData Pointer to data. * @param cbData Size (in bytes) of data. */ int VBoxDnDDataObject::Signal(const RTCString &strFormat, const void *pvData, size_t cbData) { int rc; if (cbData) { m_pvData = RTMemAlloc(cbData); if (m_pvData) { memcpy(m_pvData, pvData, cbData); m_cbData = cbData; rc = VINF_SUCCESS; } else rc = VERR_NO_MEMORY; } else rc = VINF_SUCCESS; if (RT_SUCCESS(rc)) { m_enmStatus = Status_Dropped; m_strFormat = strFormat; } else { m_enmStatus = Status_Aborted; } /* Signal in any case. */ LogRel2(("DnD: Signalling drop event\n")); int rc2 = RTSemEventSignal(m_EvtDropped); if (RT_SUCCESS(rc)) rc = rc2; LogFunc(("mStatus=%RU32, rc=%Rrc\n", m_enmStatus, rc)); return rc; }