Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwindowsmimeregistry.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
7
8#include <QtGui/private/qinternalmimedata_p.h>
9#include <QtCore/qbytearraymatcher.h>
10#include <QtCore/qmap.h>
11#include <QtCore/qurl.h>
12#include <QtCore/qdir.h>
13#include <QtCore/qdebug.h>
14#include <QtCore/qbuffer.h>
15#include <QtGui/qimagereader.h>
16#include <QtGui/qimagewriter.h>
17
18#include <shlobj.h>
19#include <algorithm>
20
21QT_BEGIN_NAMESPACE
22
23using namespace Qt::StringLiterals;
24
25/* The MSVC compilers allows multi-byte characters, that has the behavior of
26 * that each character gets shifted into position. 0x73524742 below is for MSVC
27 * equivalent to doing 'sRGB', but this does of course not work
28 * on conformant C++ compilers. */
29#define BMP_LCS_sRGB 0x73524742
30#define BMP_LCS_GM_IMAGES 0x00000004L
31
32struct _CIEXYZ {
34};
35
39
66
67static const char dibFormatC[] = "dib";
68
69static inline QByteArray msgConversionError(const char *func, const char *format)
70{
71 QByteArray msg = func;
72 msg += ": Unable to convert DIB image. The image converter plugin for '";
73 msg += format;
74 msg += "' is not available. Available formats: ";
75 const auto &formats = QImageReader::supportedImageFormats();
76 for (const QByteArray &af : formats) {
77 msg += af;
78 msg += ' ';
79 }
80 return msg;
81}
82
83static inline bool readDib(QBuffer &buffer, QImage &img)
84{
85 QImageReader reader(&buffer, dibFormatC);
86 if (!reader.canRead()) {
87 qWarning("%s", msgConversionError(__FUNCTION__, dibFormatC).constData());
88 return false;
89 }
90 img = reader.read();
91 return true;
92}
93
94#if QT_CONFIG(imageformat_bmp)
95static QByteArray writeDib(const QImage &img)
96{
97 QByteArray ba;
98 QBuffer buffer(&ba);
99 buffer.open(QIODevice::ReadWrite);
100 QImageWriter writer(&buffer, dibFormatC);
101 if (!writer.canWrite()) {
102 qWarning("%s", msgConversionError(__FUNCTION__, dibFormatC).constData());
103 return ba;
104 }
105 if (!writer.write(img))
106 ba.clear();
107 return ba;
108}
109
110static bool qt_write_dibv5(QDataStream &s, QImage image)
111{
112 QIODevice* d = s.device();
113 if (!d->isWritable())
114 return false;
115
116 //depth will be always 32
117 qsizetype bpl_bmp = qsizetype(image.width()) * 4;
118 qsizetype size = bpl_bmp * image.height();
119 if (qsizetype(DWORD(size)) != size)
120 return false;
121
122 BMP_BITMAPV5HEADER bi;
123 ZeroMemory(&bi, sizeof(bi));
124 bi.bV5Size = sizeof(BMP_BITMAPV5HEADER);
125 bi.bV5Width = image.width();
126 bi.bV5Height = image.height();
127 bi.bV5Planes = 1;
128 bi.bV5BitCount = 32;
129 bi.bV5Compression = BI_BITFIELDS;
130 bi.bV5SizeImage = DWORD(bpl_bmp * image.height());
131 bi.bV5XPelsPerMeter = 0;
132 bi.bV5YPelsPerMeter = 0;
133 bi.bV5ClrUsed = 0;
134 bi.bV5ClrImportant = 0;
135 bi.bV5BlueMask = 0x000000ff;
136 bi.bV5GreenMask = 0x0000ff00;
137 bi.bV5RedMask = 0x00ff0000;
138 bi.bV5AlphaMask = 0xff000000;
139 bi.bV5CSType = BMP_LCS_sRGB; //LCS_sRGB
140 bi.bV5Intent = BMP_LCS_GM_IMAGES; //LCS_GM_IMAGES
141
142 d->write(reinterpret_cast<const char*>(&bi), bi.bV5Size);
143 if (s.status() != QDataStream::Ok)
144 return false;
145
146 d->write(reinterpret_cast<const char *>(&bi.bV5RedMask), sizeof(bi.bV5RedMask));
147 if (s.status() != QDataStream::Ok)
148 return false;
149
150 d->write(reinterpret_cast<const char *>(&bi.bV5GreenMask), sizeof(bi.bV5GreenMask));
151 if (s.status() != QDataStream::Ok)
152 return false;
153
154 d->write(reinterpret_cast<const char *>(&bi.bV5BlueMask), sizeof(bi.bV5BlueMask));
155 if (s.status() != QDataStream::Ok)
156 return false;
157
158 if (image.format() != QImage::Format_ARGB32)
159 image = std::move(image).convertToFormat(QImage::Format_ARGB32);
160
161 auto *buf = new uchar[bpl_bmp];
162
163 memset(buf, 0, size_t(bpl_bmp));
164 for (int y=image.height()-1; y>=0; y--) {
165 // write the image bits
166 const QRgb *p = reinterpret_cast<const QRgb *>(image.constScanLine(y));
167 const QRgb *end = p + image.width();
168 uchar *b = buf;
169 while (p < end) {
170 int alpha = qAlpha(*p);
171 if (alpha) {
172 *b++ = uchar(qBlue(*p));
173 *b++ = uchar(qGreen(*p));
174 *b++ = uchar(qRed(*p));
175 } else {
176 //white for fully transparent pixels.
177 *b++ = 0xff;
178 *b++ = 0xff;
179 *b++ = 0xff;
180 }
181 *b++ = uchar(alpha);
182 p++;
183 }
184 d->write(reinterpret_cast<const char *>(buf), bpl_bmp);
185 if (s.status() != QDataStream::Ok) {
186 delete[] buf;
187 return false;
188 }
189 }
190 delete[] buf;
191 return true;
192}
193#endif //QT_CONFIG(imageformat_bmp)
194
195// helpers for using global memory
196
197static int getCf(const FORMATETC &formatetc)
198{
199 return formatetc.cfFormat;
200}
201
202static FORMATETC setCf(int cf)
203{
204 FORMATETC formatetc;
205 formatetc.cfFormat = CLIPFORMAT(cf);
206 formatetc.dwAspect = DVASPECT_CONTENT;
207 formatetc.lindex = -1;
208 formatetc.ptd = nullptr;
209 formatetc.tymed = TYMED_HGLOBAL;
210 return formatetc;
211}
212
213static bool setData(const QByteArray &data, STGMEDIUM *pmedium, DWORD tymed = TYMED_HGLOBAL)
214{
215 HGLOBAL hData = GlobalAlloc(0, SIZE_T(data.size()));
216 if (!hData)
217 return false;
218
219 void *out = GlobalLock(hData);
220 memcpy(out, data.data(), size_t(data.size()));
221 GlobalUnlock(hData);
222
223 // MS Office (and other consumers that prefer streamed delivery for image
224 // payloads) request CF_PNG via TYMED_ISTREAM rather than TYMED_HGLOBAL,
225 // and reject the result if the returned tymed doesn't match. Wrap the
226 // buffer in an IStream only when TYMED_HGLOBAL was not requested, so that
227 // legacy consumers asking for HGLOBAL (or HGLOBAL|ISTREAM) keep getting
228 // it. (QTBUG-126191)
229 if (!(tymed & TYMED_HGLOBAL) && (tymed & TYMED_ISTREAM)) {
230 IStream *stream = nullptr;
231 if (FAILED(::CreateStreamOnHGlobal(hData, /*fDeleteOnRelease*/ TRUE, &stream))) {
232 GlobalFree(hData);
233 return false;
234 }
235 pmedium->tymed = TYMED_ISTREAM;
236 pmedium->pstm = stream;
237 pmedium->pUnkForRelease = nullptr;
238 return true;
239 }
240
241 pmedium->tymed = TYMED_HGLOBAL;
242 pmedium->hGlobal = hData;
243 pmedium->pUnkForRelease = nullptr;
244 return true;
245}
246
247static QByteArray getData(int cf, IDataObject *pDataObj, int lindex = -1)
248{
249 QByteArray data;
250 FORMATETC formatetc = setCf(cf);
251 formatetc.lindex = lindex;
252 STGMEDIUM s;
253 if (pDataObj->GetData(&formatetc, &s) == S_OK) {
254 // Explorer's zipped-folder shell extension returns S_OK with a null
255 // or zero-sized hGlobal for CFSTR_FILECONTENTS (QTBUG-126980); fall
256 // through to the IStream path in that case.
257 if (s.hGlobal && GlobalSize(s.hGlobal) > 0) {
258 if (const void *val = GlobalLock(s.hGlobal)) {
259 data = QByteArray::fromRawData(reinterpret_cast<const char *>(val),
260 qsizetype(GlobalSize(s.hGlobal)));
261 data.detach();
262 GlobalUnlock(s.hGlobal);
263 }
264 }
265 ReleaseStgMedium(&s);
266 }
267 if (data.isEmpty()) {
268 //Try reading IStream data
269 formatetc.tymed = TYMED_ISTREAM;
270 if (pDataObj->GetData(&formatetc, &s) == S_OK) {
271 char szBuffer[4096];
272 ULONG actualRead = 0;
273 LARGE_INTEGER pos = {{0, 0}};
274 //Move to front (can fail depending on the data model implemented)
275 HRESULT hr = s.pstm->Seek(pos, STREAM_SEEK_SET, nullptr);
276 while(SUCCEEDED(hr)){
277 hr = s.pstm->Read(szBuffer, sizeof(szBuffer), &actualRead);
278 if (SUCCEEDED(hr) && actualRead > 0) {
279 data += QByteArray::fromRawData(szBuffer, int(actualRead));
280 }
281 if (actualRead != sizeof(szBuffer))
282 break;
283 }
284 data.detach();
285 ReleaseStgMedium(&s);
286 }
287 }
288 return data;
289}
290
291static bool canGetData(int cf, IDataObject * pDataObj)
292{
293 FORMATETC formatetc = setCf(cf);
294 if (pDataObj->QueryGetData(&formatetc) != S_OK){
295 formatetc.tymed = TYMED_ISTREAM;
296 return pDataObj->QueryGetData(&formatetc) == S_OK;
297 }
298 return true;
299}
300
301#ifndef QT_NO_DEBUG_STREAM
302QDebug operator<<(QDebug d, const FORMATETC &tc)
303{
304 QDebugStateSaver saver(d);
305 d.nospace();
306 d << "FORMATETC(cfFormat=" << tc.cfFormat << ' ';
307 switch (tc.cfFormat) {
308 case CF_TEXT:
309 d << "CF_TEXT";
310 break;
311 case CF_BITMAP:
312 d << "CF_BITMAP";
313 break;
314 case CF_TIFF:
315 d << "CF_TIFF";
316 break;
317 case CF_OEMTEXT:
318 d << "CF_OEMTEXT";
319 break;
320 case CF_DIB:
321 d << "CF_DIB";
322 break;
323 case CF_DIBV5:
324 d << "CF_DIBV5";
325 break;
326 case CF_UNICODETEXT:
327 d << "CF_UNICODETEXT";
328 break;
329 case CF_ENHMETAFILE:
330 d << "CF_ENHMETAFILE";
331 break;
332 default:
333 d << QWindowsMimeRegistry::clipboardFormatName(tc.cfFormat);
334 break;
335 }
336 d << ", dwAspect=" << tc.dwAspect << ", lindex=" << tc.lindex
337 << ", tymed=" << tc.tymed << ", ptd=" << tc.ptd << ')';
338 return d;
339}
340
341QDebug operator<<(QDebug d, IDataObject *dataObj)
342{
343 QDebugStateSaver saver(d);
344 d.nospace();
345 d.noquote();
346 d << "IDataObject(";
347 if (dataObj) { // Output formats contained in IDataObject.
348 IEnumFORMATETC *enumFormatEtc;
349 if (SUCCEEDED(dataObj->EnumFormatEtc(DATADIR_GET, &enumFormatEtc)) && enumFormatEtc) {
350 FORMATETC formatEtc[1];
351 ULONG fetched;
352 if (SUCCEEDED(enumFormatEtc->Reset())) {
353 while (SUCCEEDED(enumFormatEtc->Next(1, formatEtc, &fetched)) && fetched)
354 d << formatEtc[0] << ',';
355 enumFormatEtc->Release();
356 }
357 }
358 } else {
359 d << "0x0";
360 }
361 d << ')';
362 return d;
363}
364#endif // !QT_NO_DEBUG_STREAM
365
367{
368public:
369 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
370 QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QMetaType preferredType) const override;
371 QString mimeForFormat(const FORMATETC &formatetc) const override;
372 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
373 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override;
374 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
375};
376
377bool QWindowsMimeText::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
378{
379 int cf = getCf(formatetc);
380 return (cf == CF_UNICODETEXT || (cf == CF_TEXT && GetACP() != CP_UTF8)) && mimeData->hasText();
381}
382
383/*
384text/plain is defined as using CRLF, but so many programs don't,
385and programmers just look for '\n' in strings.
386Windows really needs CRLF, so we ensure it here.
387*/
388bool QWindowsMimeText::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const
389{
390 if (canConvertFromMime(formatetc, mimeData)) {
391 QByteArray data;
392 int cf = getCf(formatetc);
393 if (cf == CF_TEXT) {
394 data = mimeData->text().toLocal8Bit();
395 // Anticipate required space for CRLFs at 1/40
396 int maxsize=data.size()+data.size()/40+3;
397 QByteArray r(maxsize, '\0');
398 char* o = r.data();
399 const char* d = data.data();
400 const int s = data.size();
401 bool cr=false;
402 int j=0;
403 for (int i=0; i<s; i++) {
404 char c = d[i];
405 if (c=='\r')
406 cr=true;
407 else {
408 if (c=='\n') {
409 if (!cr)
410 o[j++]='\r';
411 }
412 cr=false;
413 }
414 o[j++]=c;
415 if (j+3 >= maxsize) {
416 maxsize += maxsize/4;
417 r.resize(maxsize);
418 o = r.data();
419 }
420 }
421 o[j]=0;
422 return setData(r, pmedium);
423 }
424 if (cf == CF_UNICODETEXT) {
425 QString str = mimeData->text();
426 const QChar *u = str.unicode();
427 QString res;
428 const int s = str.length();
429 int maxsize = s + s/40 + 3;
430 res.resize(maxsize);
431 int ri = 0;
432 bool cr = false;
433 for (int i=0; i < s; ++i) {
434 if (*u == u'\r')
435 cr = true;
436 else {
437 if (*u == u'\n' && !cr)
438 res[ri++] = u'\r';
439 cr = false;
440 }
441 res[ri++] = *u;
442 if (ri+3 >= maxsize) {
443 maxsize += maxsize/4;
444 res.resize(maxsize);
445 }
446 ++u;
447 }
448 res.truncate(ri);
449 const int byteLength = res.length() * int(sizeof(ushort));
450 QByteArray r(byteLength + 2, '\0');
451 memcpy(r.data(), res.unicode(), size_t(byteLength));
452 r[byteLength] = 0;
453 r[byteLength+1] = 0;
454 return setData(r, pmedium);
455 }
456 }
457 return false;
458}
459
460bool QWindowsMimeText::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
461{
462 return mimeType.startsWith(u"text/plain")
463 && (canGetData(CF_UNICODETEXT, pDataObj)
464 || canGetData(CF_TEXT, pDataObj));
465}
466
467QString QWindowsMimeText::mimeForFormat(const FORMATETC &formatetc) const
468{
469 int cf = getCf(formatetc);
470 if (cf == CF_UNICODETEXT || cf == CF_TEXT)
471 return u"text/plain"_s;
472 return QString();
473}
474
475
476QList<FORMATETC> QWindowsMimeText::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
477{
478 QList<FORMATETC> formatics;
479 if (mimeType.startsWith(u"text/plain") && mimeData->hasText()) {
480 formatics += setCf(CF_UNICODETEXT);
481 if (GetACP() != CP_UTF8)
482 formatics += setCf(CF_TEXT);
483 }
484 return formatics;
485}
486
487QVariant QWindowsMimeText::convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QMetaType preferredType) const
488{
489 QVariant ret;
490
491 if (canConvertToMime(mime, pDataObj)) {
492 QString str;
493 QByteArray data = getData(CF_UNICODETEXT, pDataObj);
494 if (!data.isEmpty()) {
495 str = QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData()));
496 str.replace("\r\n"_L1, "\n"_L1);
497 } else {
498 data = getData(CF_TEXT, pDataObj);
499 if (!data.isEmpty()) {
500 const char* d = data.data();
501 const unsigned s = unsigned(qstrlen(d));
502 QByteArray r(data.size()+1, '\0');
503 char* o = r.data();
504 int j=0;
505 for (unsigned i = 0; i < s; ++i) {
506 char c = d[i];
507 if (c!='\r')
508 o[j++]=c;
509 }
510 o[j]=0;
511 str = QString::fromLocal8Bit(r);
512 }
513 }
514 if (preferredType.id() == QMetaType::QString)
515 ret = str;
516 else
517 ret = std::move(str).toUtf8();
518 }
519 qCDebug(lcQpaMime) << __FUNCTION__ << ret;
520 return ret;
521}
522
524{
525public:
527 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
528 QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QMetaType preferredType) const override;
529 QString mimeForFormat(const FORMATETC &formatetc) const override;
530 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
531 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override;
532 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
533private:
534 int CF_INETURL_W; // wide char version
535 int CF_INETURL;
536};
537
539{
540 CF_INETURL_W = registerMimeType(u"UniformResourceLocatorW"_s);
541 CF_INETURL = registerMimeType(u"UniformResourceLocator"_s);
542}
543
544bool QWindowsMimeURI::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
545{
546 if (mimeData->hasUrls() && getCf(formatetc) == CF_HDROP) {
547 const auto urls = mimeData->urls();
548 for (const QUrl &url : urls) {
549 if (url.isLocalFile())
550 return true;
551 }
552 }
553 return (getCf(formatetc) == CF_INETURL_W || getCf(formatetc) == CF_INETURL) && mimeData->hasUrls();
554}
555
556bool QWindowsMimeURI::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const
557{
558 if (canConvertFromMime(formatetc, mimeData)) {
559 if (getCf(formatetc) == CF_HDROP) {
560 const auto &urls = mimeData->urls();
561 QStringList fileNames;
562 size_t size = sizeof(DROPFILES) + 2;
563 for (const QUrl &url : urls) {
564 const QString fn = QDir::toNativeSeparators(url.toLocalFile());
565 if (!fn.isEmpty()) {
566 size += sizeof(ushort) * size_t(fn.length() + 1);
567 fileNames.append(fn);
568 }
569 }
570
571 QByteArray result(int(size), '\0');
572 auto* d = reinterpret_cast<DROPFILES *>(result.data());
573 d->pFiles = sizeof(DROPFILES);
574 GetCursorPos(&d->pt); // try
575 d->fNC = true;
576 char *files = (reinterpret_cast<char*>(d)) + d->pFiles;
577
578 d->fWide = true;
579 auto *f = reinterpret_cast<wchar_t *>(files);
580 for (int i=0; i<fileNames.size(); i++) {
581 const auto l = size_t(fileNames.at(i).length());
582 memcpy(f, fileNames.at(i).data(), l * sizeof(ushort));
583 f += l;
584 *f++ = 0;
585 }
586 *f = 0;
587
588 return setData(result, pmedium);
589 }
590 if (getCf(formatetc) == CF_INETURL_W) {
591 const auto urls = mimeData->urls();
592 QByteArray result;
593 if (!urls.isEmpty()) {
594 const QString url = urls.at(0).toString();
595 result = QByteArray(reinterpret_cast<const char *>(url.data()),
596 url.length() * int(sizeof(ushort)));
597 }
598 result.append('\0');
599 result.append('\0');
600 return setData(result, pmedium);
601 }
602 if (getCf(formatetc) == CF_INETURL) {
603 const auto urls = mimeData->urls();
604 QByteArray result;
605 if (!urls.isEmpty())
606 result = urls.at(0).toString().toLocal8Bit();
607 return setData(result, pmedium);
608 }
609 }
610
611 return false;
612}
613
614bool QWindowsMimeURI::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
615{
616 return mimeType == u"text/uri-list"
617 && (canGetData(CF_HDROP, pDataObj) || canGetData(CF_INETURL_W, pDataObj) || canGetData(CF_INETURL, pDataObj));
618}
619
620QString QWindowsMimeURI::mimeForFormat(const FORMATETC &formatetc) const
621{
622 QString format;
623 if (getCf(formatetc) == CF_HDROP || getCf(formatetc) == CF_INETURL_W || getCf(formatetc) == CF_INETURL)
624 format = u"text/uri-list"_s;
625 return format;
626}
627
628QList<FORMATETC> QWindowsMimeURI::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
629{
630 QList<FORMATETC> formatics;
631 if (mimeType == u"text/uri-list") {
632 if (canConvertFromMime(setCf(CF_HDROP), mimeData))
633 formatics += setCf(CF_HDROP);
634 if (canConvertFromMime(setCf(CF_INETURL_W), mimeData))
635 formatics += setCf(CF_INETURL_W);
636 if (canConvertFromMime(setCf(CF_INETURL), mimeData))
637 formatics += setCf(CF_INETURL);
638 }
639 return formatics;
640}
641
642QVariant QWindowsMimeURI::convertToMime(const QString &mimeType, LPDATAOBJECT pDataObj, QMetaType preferredType) const
643{
644 if (mimeType == u"text/uri-list") {
645 if (canGetData(CF_HDROP, pDataObj)) {
646 QList<QVariant> urls;
647
648 QByteArray data = getData(CF_HDROP, pDataObj);
649 if (data.size() < qsizetype(sizeof(DROPFILES)))
650 return QVariant();
651
652 // Don't use constData. It falls back to the one-byte QByteArray::_empty for a null
653 // array, which makes MinGW report the DROPFILES access as out of bounds.
654 const auto *hdrop = reinterpret_cast<const DROPFILES *>(data.cbegin());
655 if (hdrop->fWide) {
656 const auto *filesw = reinterpret_cast<const wchar_t *>(data.cbegin() + hdrop->pFiles);
657 int i = 0;
658 while (filesw[i]) {
659 QString fileurl = QString::fromWCharArray(filesw + i);
660 urls += QUrl::fromLocalFile(fileurl);
661 i += fileurl.length()+1;
662 }
663 } else {
664 const char* files = reinterpret_cast<const char *>(data.cbegin() + hdrop->pFiles);
665 int i=0;
666 while (files[i]) {
667 urls += QUrl::fromLocalFile(QString::fromLocal8Bit(files+i));
668 i += int(strlen(files+i))+1;
669 }
670 }
671
672 if (preferredType.id() == QMetaType::QUrl && urls.size() == 1)
673 return urls.at(0);
674 if (!urls.isEmpty())
675 return urls;
676 } else if (canGetData(CF_INETURL_W, pDataObj)) {
677 QByteArray data = getData(CF_INETURL_W, pDataObj);
678 if (data.isEmpty())
679 return QVariant();
680 return QUrl(QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData())));
681 } else if (canGetData(CF_INETURL, pDataObj)) {
682 QByteArray data = getData(CF_INETURL, pDataObj);
683 if (data.isEmpty())
684 return QVariant();
685 return QUrl(QString::fromLocal8Bit(data.constData()));
686 }
687 }
688 return QVariant();
689}
690
692{
693public:
695
696 // for converting from Qt
697 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
698 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
699 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
700
701 // for converting to Qt
702 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
703 QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
704 QString mimeForFormat(const FORMATETC &formatetc) const override;
705
706private:
707 int CF_HTML;
708};
709
711{
712 CF_HTML = registerMimeType(u"HTML Format"_s);
713}
714
715QList<FORMATETC> QWindowsMimeHtml::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
716{
717 QList<FORMATETC> formatetcs;
718 if (mimeType == u"text/html" && (!mimeData->html().isEmpty()))
719 formatetcs += setCf(CF_HTML);
720 return formatetcs;
721}
722
723QString QWindowsMimeHtml::mimeForFormat(const FORMATETC &formatetc) const
724{
725 if (getCf(formatetc) == CF_HTML)
726 return u"text/html"_s;
727 return QString();
728}
729
730bool QWindowsMimeHtml::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
731{
732 return mimeType == u"text/html" && canGetData(CF_HTML, pDataObj);
733}
734
735
736bool QWindowsMimeHtml::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
737{
738 return getCf(formatetc) == CF_HTML && (!mimeData->html().isEmpty());
739}
740
741/*
742The windows HTML clipboard format is as follows (xxxxxxxxxx is a 10 integer number giving the positions
743in bytes). Charset used is mostly utf8, but can be different, ie. we have to look for the <meta> charset tag
744
745 Version: 1.0
746 StartHTML:xxxxxxxxxx
747 EndHTML:xxxxxxxxxx
748 StartFragment:xxxxxxxxxx
749 EndFragment:xxxxxxxxxx
750 ...html...
751
752*/
753QVariant QWindowsMimeHtml::convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const
754{
755 Q_UNUSED(preferredType);
756 QVariant result;
757 if (canConvertToMime(mime, pDataObj)) {
758 QByteArray html = getData(CF_HTML, pDataObj);
759 static constexpr auto startMatcher = qMakeStaticByteArrayMatcher("StartHTML:");
760 static constexpr auto endMatcher = qMakeStaticByteArrayMatcher("EndHTML:");
761 qCDebug(lcQpaMime) << __FUNCTION__ << "raw:" << html;
762 int start = startMatcher.indexIn(html);
763 int end = endMatcher.indexIn(html);
764
765 if (start != -1) {
766 int startOffset = start + 10;
767 int i = startOffset;
768 while (html.at(i) != '\r' && html.at(i) != '\n')
769 ++i;
770 QByteArray bytecount = html.mid(startOffset, i - startOffset);
771 start = bytecount.toInt();
772 }
773
774 if (end != -1) {
775 int endOffset = end + 8;
776 int i = endOffset ;
777 while (html.at(i) != '\r' && html.at(i) != '\n')
778 ++i;
779 QByteArray bytecount = html.mid(endOffset , i - endOffset);
780 end = bytecount.toInt();
781 }
782
783 if (end > start && start > 0) {
784 html = html.mid(start, end - start);
785 html.replace('\r', "");
786 result = QString::fromUtf8(html);
787 }
788 }
789 return result;
790}
791
792bool QWindowsMimeHtml::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
793{
794 if (canConvertFromMime(formatetc, mimeData)) {
795 QByteArray data = mimeData->html().toUtf8();
796 QByteArray result =
797 "Version:1.0\r\n" // 0-12
798 "StartHTML:0000000107\r\n" // 13-35
799 "EndHTML:0000000000\r\n" // 36-55
800 "StartFragment:0000000000\r\n" // 56-81
801 "EndFragment:0000000000\r\n\r\n"; // 82-107
802
803 static constexpr auto startFragmentMatcher = qMakeStaticByteArrayMatcher("<!--StartFragment-->");
804 static constexpr auto endFragmentMatcher = qMakeStaticByteArrayMatcher("<!--EndFragment-->");
805
806 if (startFragmentMatcher.indexIn(data) == -1)
807 result += "<!--StartFragment-->";
808 result += data;
809 if (endFragmentMatcher.indexIn(data) == -1)
810 result += "<!--EndFragment-->";
811
812 // set the correct number for EndHTML
813 QByteArray pos = QByteArray::number(result.size());
814 memcpy(reinterpret_cast<char *>(result.data() + 53 - pos.length()), pos.constData(), size_t(pos.length()));
815
816 // set correct numbers for StartFragment and EndFragment
817 pos = QByteArray::number(startFragmentMatcher.indexIn(result) + 20);
818 memcpy(reinterpret_cast<char *>(result.data() + 79 - pos.length()), pos.constData(), size_t(pos.length()));
819 pos = QByteArray::number(endFragmentMatcher.indexIn(result));
820 memcpy(reinterpret_cast<char *>(result.data() + 103 - pos.length()), pos.constData(), size_t(pos.length()));
821
822 return setData(result, pmedium);
823 }
824 return false;
825}
826
827
828#if QT_CONFIG(imageformat_bmp)
829class QWindowsMimeImage : public QWindowsMimeConverter
830{
831public:
832 QWindowsMimeImage();
833 // for converting from Qt
834 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
835 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
836 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
837
838 // for converting to Qt
839 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
840 QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
841 QString mimeForFormat(const FORMATETC &formatetc) const override;
842private:
843 bool hasOriginalDIBV5(IDataObject *pDataObj) const;
844 UINT CF_PNG;
845};
846
847QWindowsMimeImage::QWindowsMimeImage()
848{
849 CF_PNG = RegisterClipboardFormat(L"PNG");
850}
851
852QList<FORMATETC> QWindowsMimeImage::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
853{
854 QList<FORMATETC> formatetcs;
855 if (mimeData->hasImage() && mimeType == u"application/x-qt-image") {
856 auto image = qvariant_cast<QImage>(mimeData->imageData());
857 if (!image.isNull() && image.hasAlphaChannel()) {
858 // CF_PNG is the only format modern MS Office (and other apps that
859 // deliver image data via TYMED_ISTREAM) reads with alpha
860 // preserved (QTBUG-126191).
861 formatetcs += setCf(CF_PNG);
862 formatetcs += setCf(CF_DIBV5);
863 }
864 formatetcs += setCf(CF_DIB);
865 }
866 if (!formatetcs.isEmpty())
867 qCDebug(lcQpaMime) << __FUNCTION__ << mimeType << formatetcs;
868 return formatetcs;
869}
870
871QString QWindowsMimeImage::mimeForFormat(const FORMATETC &formatetc) const
872{
873 int cf = getCf(formatetc);
874 if (cf == CF_DIB || cf == CF_DIBV5 || cf == int(CF_PNG))
875 return u"application/x-qt-image"_s;
876 return QString();
877}
878
879bool QWindowsMimeImage::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
880{
881 return mimeType == u"application/x-qt-image"
882 && (canGetData(CF_DIB, pDataObj) || canGetData(CF_PNG, pDataObj));
883}
884
885bool QWindowsMimeImage::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
886{
887 int cf = getCf(formatetc);
888 if (!mimeData->hasImage())
889 return false;
890 const QImage image = qvariant_cast<QImage>(mimeData->imageData());
891 if (image.isNull())
892 return false;
893 // QTBUG-64322: Use PNG only for transparent images as otherwise MS PowerPoint
894 // cannot handle it.
895 return cf == CF_DIBV5 || cf == CF_DIB
896 || (cf == int(CF_PNG) && image.hasAlphaChannel());
897}
898
899bool QWindowsMimeImage::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
900{
901 int cf = getCf(formatetc);
902 if ((cf == CF_DIB || cf == CF_DIBV5 || cf == int(CF_PNG)) && mimeData->hasImage()) {
903 auto img = qvariant_cast<QImage>(mimeData->imageData());
904 if (img.isNull())
905 return false;
906 QByteArray ba;
907 if (cf == CF_DIB) {
908 if (img.format() > QImage::Format_ARGB32)
909 img = std::move(img).convertToFormat(QImage::Format_RGB32);
910 const QByteArray ba = writeDib(img);
911 if (!ba.isEmpty())
912 return setData(ba, pmedium);
913 } else if (cf == int(CF_PNG)) {
914 QBuffer buffer(&ba);
915 const bool written = buffer.open(QIODevice::WriteOnly) && img.save(&buffer, "PNG");
916 buffer.close();
917 if (written)
918 return setData(ba, pmedium, formatetc.tymed);
919 } else {
920 QDataStream s(&ba, QIODevice::WriteOnly);
921 s.setByteOrder(QDataStream::LittleEndian);// Intel byte order ####
922 if (qt_write_dibv5(s, std::move(img)))
923 return setData(ba, pmedium);
924 }
925 }
926 return false;
927}
928
929bool QWindowsMimeImage::hasOriginalDIBV5(IDataObject *pDataObj) const
930{
931 bool isSynthesized = true;
932 IEnumFORMATETC *pEnum = nullptr;
933 HRESULT res = pDataObj->EnumFormatEtc(1, &pEnum);
934 if (res == S_OK && pEnum) {
935 FORMATETC fc;
936 while ((res = pEnum->Next(1, &fc, nullptr)) == S_OK) {
937 if (fc.ptd)
938 CoTaskMemFree(fc.ptd);
939 if (fc.cfFormat == CF_DIB)
940 break;
941 if (fc.cfFormat == CF_DIBV5) {
942 isSynthesized = false;
943 break;
944 }
945 }
946 pEnum->Release();
947 }
948 return !isSynthesized;
949}
950
951QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject *pDataObj, QMetaType preferredType) const
952{
953 Q_UNUSED(preferredType);
954 QVariant result;
955 if (mimeType != u"application/x-qt-image")
956 return result;
957 // Try to convert from DIBV5 as it is the most widespread format that supports transparency,
958 // but avoid synthesizing it, as that typically loses transparency, e.g. from Office
959 const bool canGetDibV5 = canGetData(CF_DIBV5, pDataObj);
960 const bool hasOrigDibV5 = canGetDibV5 ? hasOriginalDIBV5(pDataObj) : false;
961 qCDebug(lcQpaMime) << "canGetDibV5:" << canGetDibV5 << "hasOrigDibV5:" << hasOrigDibV5;
962 // PNG, MS Office place this (undocumented)
963 if (!hasOrigDibV5 && canGetData(CF_PNG, pDataObj)) {
964 qCDebug(lcQpaMime) << "Decoding PNG";
965 QImage img;
966 QByteArray data = getData(CF_PNG, pDataObj);
967 if (img.loadFromData(data, "PNG")) {
968 return img;
969 }
970 }
971
972 if (canGetDibV5 || canGetData(CF_DIB, pDataObj)) {
973 qCDebug(lcQpaMime) << "Decoding DIB";
974 QImage img;
975 QByteArray data = getData(canGetDibV5 ? CF_DIBV5 : CF_DIB, pDataObj);
976 QBuffer buffer(&data);
977 if (readDib(buffer, img))
978 return img;
979 }
980 // Failed
981 return result;
982}
983#endif //QT_CONFIG(imageformat_bmp)
984
986{
987public:
989
990 // for converting from Qt
991 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
992 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
993 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
994
995 // for converting to Qt
996 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
997 QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
998 QString mimeForFormat(const FORMATETC &formatetc) const override;
999
1000private:
1001 QMap<int, QString> outFormats;
1002 QMap<int, QString> inFormats;
1003};
1004
1007{
1008 outFormats.insert(registerMimeType(u"application/x-color"_s), u"application/x-color"_s);
1009 inFormats.insert(registerMimeType(u"application/x-color"_s), u"application/x-color"_s);
1010}
1011
1012bool QBuiltInMimes::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
1013{
1014 // really check
1015 return formatetc.tymed & TYMED_HGLOBAL
1016 && outFormats.contains(formatetc.cfFormat)
1017 && mimeData->formats().contains(outFormats.value(formatetc.cfFormat));
1018}
1019
1020bool QBuiltInMimes::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
1021{
1022 if (canConvertFromMime(formatetc, mimeData)) {
1023 QByteArray data;
1024 if (outFormats.value(getCf(formatetc)) == u"text/html") {
1025 // text/html is in wide chars on windows (compatible with mozillia)
1026 QString html = mimeData->html();
1027 // same code as in the text converter up above
1028 const QChar *u = html.unicode();
1029 QString res;
1030 const int s = html.length();
1031 int maxsize = s + s/40 + 3;
1032 res.resize(maxsize);
1033 int ri = 0;
1034 bool cr = false;
1035 for (int i=0; i < s; ++i) {
1036 if (*u == u'\r')
1037 cr = true;
1038 else {
1039 if (*u == u'\n' && !cr)
1040 res[ri++] = u'\r';
1041 cr = false;
1042 }
1043 res[ri++] = *u;
1044 if (ri+3 >= maxsize) {
1045 maxsize += maxsize/4;
1046 res.resize(maxsize);
1047 }
1048 ++u;
1049 }
1050 res.truncate(ri);
1051 const int byteLength = res.length() * int(sizeof(ushort));
1052 QByteArray r(byteLength + 2, '\0');
1053 memcpy(r.data(), res.unicode(), size_t(byteLength));
1054 r[byteLength] = 0;
1055 r[byteLength+1] = 0;
1056 data = r;
1057 } else {
1058#if QT_CONFIG(draganddrop)
1059 data = QInternalMimeData::renderDataHelper(outFormats.value(getCf(formatetc)), mimeData);
1060#endif // QT_CONFIG(draganddrop)
1061 }
1062 return setData(data, pmedium);
1063 }
1064 return false;
1065}
1066
1067QList<FORMATETC> QBuiltInMimes::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
1068{
1069 QList<FORMATETC> formatetcs;
1070 const auto mit = std::find(outFormats.cbegin(), outFormats.cend(), mimeType);
1071 if (mit != outFormats.cend() && mimeData->formats().contains(mimeType))
1072 formatetcs += setCf(mit.key());
1073 return formatetcs;
1074}
1075
1076bool QBuiltInMimes::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
1077{
1078 const auto mit = std::find(inFormats.cbegin(), inFormats.cend(), mimeType);
1079 return mit != inFormats.cend() && canGetData(mit.key(), pDataObj);
1080}
1081
1082QVariant QBuiltInMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QMetaType preferredType) const
1083{
1084 QVariant val;
1085 if (canConvertToMime(mimeType, pDataObj)) {
1086 QByteArray data = getData(inFormats.key(mimeType), pDataObj);
1087 if (!data.isEmpty()) {
1088 qCDebug(lcQpaMime) << __FUNCTION__;
1089 if (mimeType == u"text/html" && preferredType == QMetaType(QMetaType::QString)) {
1090 // text/html is in wide chars on windows (compatible with Mozilla)
1091 val = QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData()));
1092 } else {
1093 val = data; // it should be enough to return the data and let QMimeData do the rest.
1094 }
1095 }
1096 }
1097 return val;
1098}
1099
1100QString QBuiltInMimes::mimeForFormat(const FORMATETC &formatetc) const
1101{
1102 return inFormats.value(getCf(formatetc));
1103}
1104
1105
1107{
1108public:
1109
1111 // for converting from Qt
1112 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
1113 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
1114 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
1115
1116 // for converting to Qt
1117 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
1118 QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
1119 QString mimeForFormat(const FORMATETC &formatetc) const override;
1120
1121private:
1122 mutable QMap<int, QString> formats;
1123 static QStringList ianaTypes;
1124 static QStringList excludeList;
1125};
1126
1127QStringList QLastResortMimes::ianaTypes;
1128QStringList QLastResortMimes::excludeList;
1129
1131{
1132 //MIME Media-Types
1133 if (ianaTypes.isEmpty()) {
1134 ianaTypes.append(u"application/"_s);
1135 ianaTypes.append(u"audio/"_s);
1136 ianaTypes.append(u"example/"_s);
1137 ianaTypes.append(u"image/"_s);
1138 ianaTypes.append(u"message/"_s);
1139 ianaTypes.append(u"model/"_s);
1140 ianaTypes.append(u"multipart/"_s);
1141 ianaTypes.append(u"text/"_s);
1142 ianaTypes.append(u"video/"_s);
1143 }
1144 //Types handled by other classes
1145 if (excludeList.isEmpty()) {
1146 excludeList.append(u"HTML Format"_s);
1147 excludeList.append(u"UniformResourceLocator"_s);
1148 excludeList.append(u"text/html"_s);
1149 excludeList.append(u"text/plain"_s);
1150 excludeList.append(u"text/uri-list"_s);
1151 excludeList.append(u"application/x-qt-image"_s);
1152 excludeList.append(u"application/x-color"_s);
1153 }
1154}
1155
1156bool QLastResortMimes::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
1157{
1158 // really check
1159#if QT_CONFIG(draganddrop)
1160 return formatetc.tymed & TYMED_HGLOBAL
1161 && (formats.contains(formatetc.cfFormat)
1162 && QInternalMimeData::hasFormatHelper(formats.value(formatetc.cfFormat), mimeData));
1163#else
1164 Q_UNUSED(mimeData);
1165 Q_UNUSED(formatetc);
1166 return formatetc.tymed & TYMED_HGLOBAL
1167 && formats.contains(formatetc.cfFormat);
1168#endif // QT_CONFIG(draganddrop)
1169}
1170
1171bool QLastResortMimes::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
1172{
1173#if QT_CONFIG(draganddrop)
1174 return canConvertFromMime(formatetc, mimeData)
1175 && setData(QInternalMimeData::renderDataHelper(formats.value(getCf(formatetc)), mimeData), pmedium);
1176#else
1177 Q_UNUSED(mimeData);
1178 Q_UNUSED(formatetc);
1179 Q_UNUSED(pmedium);
1180 return false;
1181#endif // QT_CONFIG(draganddrop)
1182}
1183
1184QList<FORMATETC> QLastResortMimes::formatsForMime(const QString &mimeType, const QMimeData * /*mimeData*/) const
1185{
1186 QList<FORMATETC> formatetcs;
1187 auto mit = std::find(formats.begin(), formats.end(), mimeType);
1188 // register any other available formats
1189 if (mit == formats.end() && !excludeList.contains(mimeType, Qt::CaseInsensitive))
1190 mit = formats.insert(registerMimeType(mimeType), mimeType);
1191 if (mit != formats.end())
1192 formatetcs += setCf(mit.key());
1193
1194 if (!formatetcs.isEmpty())
1195 qCDebug(lcQpaMime) << __FUNCTION__ << mimeType << formatetcs;
1196 return formatetcs;
1197}
1198static const char x_qt_windows_mime[] = "application/x-qt-windows-mime;value=\"";
1199
1200static bool isCustomMimeType(const QString &mimeType)
1201{
1202 return mimeType.startsWith(QLatin1StringView(x_qt_windows_mime), Qt::CaseInsensitive);
1203}
1204
1205static QString customMimeType(const QString &mimeType, int *lindex = nullptr)
1206{
1207 int len = sizeof(x_qt_windows_mime) - 1;
1208 int n = mimeType.lastIndexOf(u'\"') - len;
1209 QString ret = mimeType.mid(len, n);
1210
1211 const int beginPos = mimeType.indexOf(u";index=");
1212 if (beginPos > -1) {
1213 const int endPos = mimeType.indexOf(u';', beginPos + 1);
1214 const int indexStartPos = beginPos + 7;
1215 if (lindex)
1216 *lindex = QStringView{mimeType}.mid(indexStartPos, endPos == -1 ? endPos : endPos - indexStartPos).toInt();
1217 } else {
1218 if (lindex)
1219 *lindex = -1;
1220 }
1221 return ret;
1222}
1223
1224bool QLastResortMimes::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
1225{
1226 if (isCustomMimeType(mimeType)) {
1227 // MSDN documentation for QueryGetData says only -1 is supported, so ignore lindex here.
1228 QString clipFormat = customMimeType(mimeType);
1229 const UINT cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16()));
1230 return canGetData(int(cf), pDataObj);
1231 }
1232 // if it is not in there then register it and see if we can get it
1233 const auto mit = std::find(formats.cbegin(), formats.cend(), mimeType);
1234 const int cf = mit != formats.cend() ? mit.key() : registerMimeType(mimeType);
1235 return canGetData(cf, pDataObj);
1236}
1237
1238QVariant QLastResortMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QMetaType preferredType) const
1239{
1240 Q_UNUSED(preferredType);
1241 QVariant val;
1242 if (canConvertToMime(mimeType, pDataObj)) {
1243 QByteArray data;
1244 if (isCustomMimeType(mimeType)) {
1245 int lindex;
1246 QString clipFormat = customMimeType(mimeType, &lindex);
1247 const UINT cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16()));
1248 data = getData(int(cf), pDataObj, lindex);
1249 } else {
1250 const auto mit = std::find(formats.cbegin(), formats.cend(), mimeType);
1251 const int cf = mit != formats.cend() ? mit.key() : registerMimeType(mimeType);
1252 data = getData(cf, pDataObj);
1253 }
1254 if (!data.isEmpty())
1255 val = data; // it should be enough to return the data and let QMimeData do the rest.
1256 }
1257 return val;
1258}
1259
1260QString QLastResortMimes::mimeForFormat(const FORMATETC &formatetc) const
1261{
1262 QString format = formats.value(getCf(formatetc));
1263 if (!format.isEmpty())
1264 return format;
1265
1266 const QString clipFormat = QWindowsMimeRegistry::clipboardFormatName(getCf(formatetc));
1267 if (!clipFormat.isEmpty()) {
1268#if QT_CONFIG(draganddrop)
1269 if (QInternalMimeData::canReadData(clipFormat))
1270 format = clipFormat;
1271 else if ((formatetc.cfFormat >= 0xC000)){
1272 //create the mime as custom. not registered.
1273 if (!excludeList.contains(clipFormat, Qt::CaseInsensitive)) {
1274 //check if this is a mime type
1275 bool ianaType = false;
1276 int sz = ianaTypes.size();
1277 for (int i = 0; i < sz; i++) {
1278 if (clipFormat.startsWith(ianaTypes[i], Qt::CaseInsensitive)) {
1279 ianaType = true;
1280 break;
1281 }
1282 }
1283 if (!ianaType)
1284 format = QLatin1StringView(x_qt_windows_mime) + clipFormat + u'"';
1285 else
1286 format = clipFormat;
1287 }
1288 }
1289#endif // QT_CONFIG(draganddrop)
1290 }
1291
1292 return format;
1293}
1294
1295/*!
1296 \class QWindowsMimeRegistry
1297 \brief Manages the list of QWindowsMimeConverter instances.
1298 \internal
1299 \sa QWindowsMimeConverter
1300*/
1301
1303
1305{
1306 qDeleteAll(m_mimes.begin(), m_mimes.begin() + m_internalMimeCount);
1307}
1308
1309QWindowsMimeRegistry::QWindowsMimeConverter *QWindowsMimeRegistry::converterToMime(const QString &mimeType, IDataObject *pDataObj) const
1310{
1311 ensureInitialized();
1312 for (int i = m_mimes.size()-1; i >= 0; --i) {
1313 if (m_mimes.at(i)->canConvertToMime(mimeType, pDataObj))
1314 return m_mimes.at(i);
1315 }
1316 return nullptr;
1317}
1318
1320{
1321 qCDebug(lcQpaMime) << "QWindowsMimeConverter::allMimesForFormats()";
1322 ensureInitialized();
1323 QStringList formats;
1324 LPENUMFORMATETC FAR fmtenum;
1325 HRESULT hr = pDataObj->EnumFormatEtc(DATADIR_GET, &fmtenum);
1326
1327 if (hr == NOERROR) {
1328 FORMATETC fmtetc;
1329 while (S_OK == fmtenum->Next(1, &fmtetc, nullptr)) {
1330 for (int i= m_mimes.size() - 1; i >= 0; --i) {
1331 QString format = m_mimes.at(i)->mimeForFormat(fmtetc);
1332 if (!format.isEmpty() && !formats.contains(format)) {
1333 formats += format;
1334 if (QWindowsContext::verbose > 1 && lcQpaMime().isDebugEnabled())
1335 qCDebug(lcQpaMime) << __FUNCTION__ << fmtetc << format;
1336 }
1337 }
1338 // as documented in MSDN to avoid possible memleak
1339 if (fmtetc.ptd)
1340 CoTaskMemFree(fmtetc.ptd);
1341 }
1342 fmtenum->Release();
1343 }
1344 qCDebug(lcQpaMime) << pDataObj << formats;
1345 return formats;
1346}
1347
1348QWindowsMimeRegistry::QWindowsMimeConverter *QWindowsMimeRegistry::converterFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
1349{
1350 ensureInitialized();
1351 qCDebug(lcQpaMime) << __FUNCTION__ << formatetc;
1352 for (int i = m_mimes.size()-1; i >= 0; --i) {
1353 if (m_mimes.at(i)->canConvertFromMime(formatetc, mimeData))
1354 return m_mimes.at(i);
1355 }
1356 return nullptr;
1357}
1358
1359QList<FORMATETC> QWindowsMimeRegistry::allFormatsForMime(const QMimeData *mimeData) const
1360{
1361 ensureInitialized();
1362 QList<FORMATETC> formatics;
1363#if !QT_CONFIG(draganddrop)
1364 Q_UNUSED(mimeData);
1365#else
1366 formatics.reserve(20);
1367 const QStringList formats = QInternalMimeData::formatsHelper(mimeData);
1368 for (int f = 0; f < formats.size(); ++f) {
1369 for (int i = m_mimes.size() - 1; i >= 0; --i)
1370 formatics += m_mimes.at(i)->formatsForMime(formats.at(f), mimeData);
1371 }
1372#endif // QT_CONFIG(draganddrop)
1373 return formatics;
1374}
1375
1376void QWindowsMimeRegistry::ensureInitialized() const
1377{
1378 if (m_internalMimeCount == 0) {
1379 m_internalMimeCount = -1; // prevent reentrancy when types register themselves
1380#if QT_CONFIG(imageformat_bmp)
1381 (void)new QWindowsMimeImage;
1382#endif //QT_CONFIG(imageformat_bmp)
1383 (void)new QLastResortMimes;
1384 (void)new QWindowsMimeText;
1385 (void)new QWindowsMimeURI;
1386 (void)new QWindowsMimeHtml;
1387 (void)new QBuiltInMimes;
1388 m_internalMimeCount = m_mimes.size();
1389 Q_ASSERT(m_internalMimeCount > 0);
1390 }
1391}
1392
1393QString QWindowsMimeRegistry::clipboardFormatName(int cf)
1394{
1395 wchar_t buf[256] = {0};
1396 return GetClipboardFormatName(UINT(cf), buf, 255)
1397 ? QString::fromWCharArray(buf) : QString();
1398}
1399
1400QVariant QWindowsMimeRegistry::convertToMime(const QStringList &mimeTypes,
1401 IDataObject *pDataObj,
1402 QMetaType preferredType,
1403 QString *formatIn /* = 0 */) const
1404{
1405 for (const QString &format : mimeTypes) {
1406 if (const QWindowsMimeConverter *converter = converterToMime(format, pDataObj)) {
1407 if (converter->canConvertToMime(format, pDataObj)) {
1408 const QVariant dataV = converter->convertToMime(format, pDataObj, preferredType);
1409 if (dataV.isValid()) {
1410 qCDebug(lcQpaMime) << __FUNCTION__ << mimeTypes << "\nFormat: "
1411 << format << pDataObj << " returns " << dataV;
1412 if (formatIn)
1413 *formatIn = format;
1414 return dataV;
1415 }
1416 }
1417 }
1418 }
1419 qCDebug(lcQpaMime) << __FUNCTION__ << "fails" << mimeTypes << pDataObj << preferredType.id();
1420 return QVariant();
1421}
1422
1423void QWindowsMimeRegistry::registerMime(QWindowsMimeConverter *mime)
1424{
1425 ensureInitialized();
1426 m_mimes.append(mime);
1427}
1428
1429/*!
1430 Registers the MIME type \a mime, and returns an ID number
1431 identifying the format on Windows.
1432
1433 A mime type \c {application/x-qt-windows-mime;value="WindowsType"} will be
1434 registered as the clipboard format for \c WindowsType.
1435*/
1436int QWindowsMimeRegistry::registerMimeType(const QString &mime)
1437{
1438 const QString mimeType = isCustomMimeType(mime) ? customMimeType(mime) : mime;
1439 const UINT f = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (mimeType.utf16()));
1440 if (!f) {
1441 qErrnoWarning("QWindowsMimeRegistry::registerMimeType: Failed to register clipboard format "
1442 "for %s", qPrintable(mime));
1443 }
1444
1445 return int(f);
1446}
1447
1448QT_END_NAMESPACE
QList< FORMATETC > formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override
Returns a QList of FORMATETC structures representing the different windows clipboard formats that can...
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override
Returns true if the converter can convert to the mimeType from the available formats in pDataObj.
QString mimeForFormat(const FORMATETC &formatetc) const override
Returns the mime type that will be created form the format specified in formatetc,...
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override
Convert the mimeData to the format specified in formatetc.
QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override
Returns a QVariant containing the converted data for mimeType from pDataObj.
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override
Returns true if the converter can convert from the mimeData to the format specified in formatetc.
QString mimeForFormat(const FORMATETC &formatetc) const override
Returns the mime type that will be created form the format specified in formatetc,...
QList< FORMATETC > formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override
Returns a QList of FORMATETC structures representing the different windows clipboard formats that can...
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override
Returns true if the converter can convert to the mimeType from the available formats in pDataObj.
QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override
Returns a QVariant containing the converted data for mimeType from pDataObj.
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override
Convert the mimeData to the format specified in formatetc.
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override
Returns true if the converter can convert from the mimeData to the format specified in formatetc.
Singleton container for all relevant information.
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override
Returns true if the converter can convert from the mimeData to the format specified in formatetc.
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override
Convert the mimeData to the format specified in formatetc.
QString mimeForFormat(const FORMATETC &formatetc) const override
Returns the mime type that will be created form the format specified in formatetc,...
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override
Returns true if the converter can convert to the mimeType from the available formats in pDataObj.
QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override
Returns a QVariant containing the converted data for mimeType from pDataObj.
QList< FORMATETC > formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override
Returns a QList of FORMATETC structures representing the different windows clipboard formats that can...
Manages the list of QWindowsMimeConverter instances.
QVariant convertToMime(const QStringList &mimeTypes, IDataObject *pDataObj, QMetaType preferredType, QString *format=nullptr) const
QWindowsMimeConverter * converterFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
void registerMime(QWindowsMimeConverter *mime)
QStringList allMimesForFormats(IDataObject *pDataObj) const
QList< FORMATETC > allFormatsForMime(const QMimeData *mimeData) const
QWindowsMimeConverter * converterToMime(const QString &mimeType, IDataObject *pDataObj) const
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override
Returns true if the converter can convert to the mimeType from the available formats in pDataObj.
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override
Returns true if the converter can convert from the mimeData to the format specified in formatetc.
QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QMetaType preferredType) const override
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override
Convert the mimeData to the format specified in formatetc.
QList< FORMATETC > formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override
Returns a QList of FORMATETC structures representing the different windows clipboard formats that can...
QString mimeForFormat(const FORMATETC &formatetc) const override
Returns the mime type that will be created form the format specified in formatetc,...
QList< FORMATETC > formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override
Returns a QList of FORMATETC structures representing the different windows clipboard formats that can...
QString mimeForFormat(const FORMATETC &formatetc) const override
Returns the mime type that will be created form the format specified in formatetc,...
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override
Returns true if the converter can convert to the mimeType from the available formats in pDataObj.
QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QMetaType preferredType) const override
bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override
Returns true if the converter can convert from the mimeData to the format specified in formatetc.
bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override
Convert the mimeData to the format specified in formatetc.
static QByteArray msgConversionError(const char *func, const char *format)
static const char x_qt_windows_mime[]
static bool canGetData(int cf, IDataObject *pDataObj)
static bool setData(const QByteArray &data, STGMEDIUM *pmedium, DWORD tymed=TYMED_HGLOBAL)
static FORMATETC setCf(int cf)
static bool isCustomMimeType(const QString &mimeType)
static QByteArray getData(int cf, IDataObject *pDataObj, int lindex=-1)
static QString customMimeType(const QString &mimeType, int *lindex=nullptr)
static bool readDib(QBuffer &buffer, QImage &img)
static int getCf(const FORMATETC &formatetc)
static const char dibFormatC[]