@@ -104,6 +104,69 @@ class TextDropSource : public IDropSource {
104104 STDMETHODIMP GiveFeedback (__unused DWORD ) override { return DRAGDROP_S_USEDEFAULTCURSORS ; }
105105};
106106
107+ class SimpleEnumFormatEtc : public IEnumFORMATETC {
108+ LONG refCount = 1 ;
109+ const FORMATETC * formats = nullptr ;
110+ ULONG count = 0 ;
111+ ULONG index = 0 ;
112+
113+ public:
114+ SimpleEnumFormatEtc (const FORMATETC * fmts, ULONG n) : formats(fmts), count(n) {}
115+
116+ STDMETHODIMP QueryInterface (REFIID riid, void ** ppv) override {
117+ if (riid == IID_IUnknown || riid == IID_IEnumFORMATETC) {
118+ *ppv = this ;
119+ AddRef ();
120+ return S_OK ;
121+ }
122+ *ppv = nullptr ;
123+ return E_NOINTERFACE ;
124+ }
125+ ULONG STDMETHODCALLTYPE AddRef () override { return InterlockedIncrement (&refCount); }
126+ ULONG STDMETHODCALLTYPE Release () override {
127+ LONG r = InterlockedDecrement (&refCount);
128+ if (r == 0 ) {
129+ delete this ;
130+ }
131+ return r;
132+ }
133+
134+ STDMETHODIMP Next (ULONG celt, FORMATETC * rgelt, ULONG * pceltFetched) override {
135+ if (!rgelt) {
136+ return E_POINTER ;
137+ }
138+ ULONG fetched = 0 ;
139+ while (fetched < celt && index < count) {
140+ rgelt[fetched++] = formats[index++];
141+ }
142+ if (pceltFetched) {
143+ *pceltFetched = fetched;
144+ }
145+ return fetched == celt ? S_OK : S_FALSE ;
146+ }
147+ STDMETHODIMP Skip (ULONG celt) override {
148+ if (index + celt < count) {
149+ index += celt;
150+ return S_OK ;
151+ }
152+ index = count;
153+ return S_FALSE ;
154+ }
155+ STDMETHODIMP Reset () override {
156+ index = 0 ;
157+ return S_OK ;
158+ }
159+ STDMETHODIMP Clone (IEnumFORMATETC** ppEnum) override {
160+ if (!ppEnum) {
161+ return E_POINTER ;
162+ }
163+ auto * e = new SimpleEnumFormatEtc (formats, count);
164+ e->index = index;
165+ *ppEnum = e;
166+ return S_OK ;
167+ }
168+ };
169+
107170class TextDataObject : public IDataObject {
108171 LONG refCount = 1 ;
109172 HGLOBAL hText = nullptr ;
@@ -243,21 +306,45 @@ static HGLOBAL EncodeBitmapToPngGlobal(HBITMAP hbmp) {
243306}
244307
245308// IDataObject that provides an image as a virtual file (CFSTR_FILEDESCRIPTOR + CFSTR_FILECONTENTS)
246- // and CF_DIB, without creating any temporary files on disk.
247- // Browsers and web apps accept virtual file drops just like real file drops.
309+ // without creating any temporary files on disk.
248310class ImageDataObject : public IDataObject {
249311 LONG refCount = 1 ;
250312 HGLOBAL hPngData = nullptr ; // PNG-encoded image data
251313 size_t pngSize = 0 ;
252314 UINT cfFileDescriptor = 0 ;
253315 UINT cfFileContents = 0 ;
316+ UINT cfPreferredDropEffect = 0 ;
317+ FORMATETC fmts[3 ]{};
318+ ULONG fmtCount = 0 ;
319+
320+ bool QueryFormatSupported (FORMATETC * pFE) const {
321+ for (ULONG i = 0 ; i < fmtCount; i++) {
322+ const FORMATETC & fmt = fmts[i];
323+ if (pFE->cfFormat != fmt.cfFormat ) {
324+ continue ;
325+ }
326+ if (fmt.lindex >= 0 && pFE->lindex != fmt.lindex ) {
327+ continue ;
328+ }
329+ if (pFE->tymed & fmt.tymed ) {
330+ return true ;
331+ }
332+ }
333+ return false ;
334+ }
254335
255336 public:
256337 explicit ImageDataObject (HGLOBAL hPng) {
257338 hPngData = hPng;
258339 pngSize = GlobalSize (hPng);
259340 cfFileDescriptor = RegisterClipboardFormatW (CFSTR_FILEDESCRIPTORW );
260341 cfFileContents = RegisterClipboardFormatW (CFSTR_FILECONTENTS );
342+ cfPreferredDropEffect = RegisterClipboardFormatW (L" Preferred DropEffect" );
343+
344+ fmts[0 ] = {(CLIPFORMAT )cfFileDescriptor, nullptr , DVASPECT_CONTENT , -1 , TYMED_HGLOBAL };
345+ fmts[1 ] = {(CLIPFORMAT )cfFileContents, nullptr , DVASPECT_CONTENT , 0 , TYMED_ISTREAM | TYMED_HGLOBAL };
346+ fmts[2 ] = {(CLIPFORMAT )cfPreferredDropEffect, nullptr , DVASPECT_CONTENT , -1 , TYMED_HGLOBAL };
347+ fmtCount = 3 ;
261348 }
262349 ~ImageDataObject () {
263350 if (hPngData) {
@@ -290,14 +377,15 @@ class ImageDataObject : public IDataObject {
290377
291378 // CFSTR_FILEDESCRIPTORW: describe one virtual file "image.png"
292379 if (pFE->cfFormat == cfFileDescriptor && (pFE->tymed & TYMED_HGLOBAL )) {
293- size_t cb = sizeof (FILEGROUPDESCRIPTORW );
380+ size_t cb = offsetof (FILEGROUPDESCRIPTORW , fgd) + sizeof ( FILEDESCRIPTORW );
294381 HGLOBAL h = GlobalAlloc (GMEM_MOVEABLE | GMEM_ZEROINIT , cb);
295382 if (!h) {
296383 return E_OUTOFMEMORY ;
297384 }
298385 auto * fgd = (FILEGROUPDESCRIPTORW *)GlobalLock (h);
299386 fgd->cItems = 1 ;
300- fgd->fgd [0 ].dwFlags = FD_FILESIZE ;
387+ fgd->fgd [0 ].dwFlags = FD_FILESIZE | FD_ATTRIBUTES ;
388+ fgd->fgd [0 ].dwFileAttributes = FILE_ATTRIBUTE_NORMAL ;
301389 fgd->fgd [0 ].nFileSizeLow = (DWORD )pngSize;
302390 fgd->fgd [0 ].nFileSizeHigh = 0 ;
303391 wcscpy_s (fgd->fgd [0 ].cFileName , MAX_PATH , L" image.png" );
@@ -308,44 +396,75 @@ class ImageDataObject : public IDataObject {
308396 return S_OK ;
309397 }
310398
311- // CFSTR_FILECONTENTS: provide the PNG data as an IStream
312- if (pFE->cfFormat == cfFileContents && (pFE->tymed & TYMED_ISTREAM ) && pFE->lindex == 0 ) {
313- IStream* stream = nullptr ;
314- HRESULT hr = CreateStreamOnHGlobal (nullptr , TRUE , &stream);
315- if (FAILED (hr) || !stream) {
399+ // CFSTR_FILECONTENTS: provide the PNG data as an IStream or HGLOBAL
400+ if (pFE->cfFormat == cfFileContents && pFE->lindex == 0 ) {
401+ if (pFE->tymed & TYMED_HGLOBAL ) {
402+ HGLOBAL hCopy = GlobalAlloc (GMEM_MOVEABLE , pngSize);
403+ if (!hCopy) {
404+ return E_OUTOFMEMORY ;
405+ }
406+ void * src = GlobalLock (hPngData);
407+ void * dst = GlobalLock (hCopy);
408+ memcpy (dst, src, pngSize);
409+ GlobalUnlock (hCopy);
410+ GlobalUnlock (hPngData);
411+ pMedium->tymed = TYMED_HGLOBAL ;
412+ pMedium->hGlobal = hCopy;
413+ pMedium->pUnkForRelease = nullptr ;
414+ return S_OK ;
415+ }
416+ if (pFE->tymed & TYMED_ISTREAM ) {
417+ IStream* stream = nullptr ;
418+ HRESULT hr = CreateStreamOnHGlobal (nullptr , TRUE , &stream);
419+ if (FAILED (hr) || !stream) {
420+ return E_OUTOFMEMORY ;
421+ }
422+ void * src = GlobalLock (hPngData);
423+ ULONG written = 0 ;
424+ stream->Write (src, (ULONG )pngSize, &written);
425+ GlobalUnlock (hPngData);
426+ LARGE_INTEGER zero{};
427+ stream->Seek (zero, STREAM_SEEK_SET , nullptr );
428+ pMedium->tymed = TYMED_ISTREAM ;
429+ pMedium->pstm = stream;
430+ pMedium->pUnkForRelease = nullptr ;
431+ return S_OK ;
432+ }
433+ }
434+
435+ if (pFE->cfFormat == cfPreferredDropEffect && (pFE->tymed & TYMED_HGLOBAL )) {
436+ HGLOBAL h = GlobalAlloc (GMEM_MOVEABLE , sizeof (DWORD ));
437+ if (!h) {
316438 return E_OUTOFMEMORY ;
317439 }
318- void * src = GlobalLock (hPngData);
319- ULONG written = 0 ;
320- stream->Write (src, (ULONG )pngSize, &written);
321- GlobalUnlock (hPngData);
322- // reset stream position to beginning
323- LARGE_INTEGER zero{};
324- stream->Seek (zero, STREAM_SEEK_SET , nullptr );
325- pMedium->tymed = TYMED_ISTREAM ;
326- pMedium->pstm = stream;
440+ auto * effect = (DWORD *)GlobalLock (h);
441+ *effect = DROPEFFECT_COPY ;
442+ GlobalUnlock (h);
443+ pMedium->tymed = TYMED_HGLOBAL ;
444+ pMedium->hGlobal = h;
327445 pMedium->pUnkForRelease = nullptr ;
328446 return S_OK ;
329447 }
330448
331449 return DV_E_FORMATETC ;
332450 }
333451 STDMETHODIMP GetDataHere (__unused FORMATETC *, __unused STGMEDIUM *) override { return E_NOTIMPL ; }
334- STDMETHODIMP QueryGetData (FORMATETC * pFE) override {
335- if (pFE->cfFormat == cfFileDescriptor && (pFE->tymed & TYMED_HGLOBAL )) {
336- return S_OK ;
337- }
338- if (pFE->cfFormat == cfFileContents && (pFE->tymed & TYMED_ISTREAM ) && pFE->lindex == 0 ) {
339- return S_OK ;
340- }
341- return DV_E_FORMATETC ;
342- }
452+ STDMETHODIMP QueryGetData (FORMATETC * pFE) override { return QueryFormatSupported (pFE) ? S_OK : DV_E_FORMATETC ; }
343453 STDMETHODIMP GetCanonicalFormatEtc (__unused FORMATETC *, FORMATETC * pOut) override {
344454 pOut->ptd = nullptr ;
345455 return E_NOTIMPL ;
346456 }
347457 STDMETHODIMP SetData (__unused FORMATETC *, __unused STGMEDIUM *, __unused BOOL ) override { return E_NOTIMPL ; }
348- STDMETHODIMP EnumFormatEtc (__unused DWORD , __unused IEnumFORMATETC**) override { return E_NOTIMPL ; }
458+ STDMETHODIMP EnumFormatEtc (DWORD dwDirection, IEnumFORMATETC** ppEnum) override {
459+ if (!ppEnum) {
460+ return E_POINTER ;
461+ }
462+ if (dwDirection != DATADIR_GET ) {
463+ return E_NOTIMPL ;
464+ }
465+ *ppEnum = new SimpleEnumFormatEtc (fmts, fmtCount);
466+ return S_OK ;
467+ }
349468 STDMETHODIMP DAdvise (__unused FORMATETC *, __unused DWORD , __unused IAdviseSink*, __unused DWORD *) override {
350469 return E_NOTIMPL ;
351470 }
0 commit comments