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 const void *val = GlobalLock(s.hGlobal);
259 data = QByteArray::fromRawData(reinterpret_cast<const char *>(val), int(GlobalSize(s.hGlobal)));
260 data.detach();
261 GlobalUnlock(s.hGlobal);
262 }
263 ReleaseStgMedium(&s);
264 }
265 if (data.isEmpty()) {
266 //Try reading IStream data
267 formatetc.tymed = TYMED_ISTREAM;
268 if (pDataObj->GetData(&formatetc, &s) == S_OK) {
269 char szBuffer[4096];
270 ULONG actualRead = 0;
271 LARGE_INTEGER pos = {{0, 0}};
272 //Move to front (can fail depending on the data model implemented)
273 HRESULT hr = s.pstm->Seek(pos, STREAM_SEEK_SET, nullptr);
274 while(SUCCEEDED(hr)){
275 hr = s.pstm->Read(szBuffer, sizeof(szBuffer), &actualRead);
276 if (SUCCEEDED(hr) && actualRead > 0) {
277 data += QByteArray::fromRawData(szBuffer, int(actualRead));
278 }
279 if (actualRead != sizeof(szBuffer))
280 break;
281 }
282 data.detach();
283 ReleaseStgMedium(&s);
284 }
285 }
286 return data;
287}
288
289static bool canGetData(int cf, IDataObject * pDataObj)
290{
291 FORMATETC formatetc = setCf(cf);
292 if (pDataObj->QueryGetData(&formatetc) != S_OK){
293 formatetc.tymed = TYMED_ISTREAM;
294 return pDataObj->QueryGetData(&formatetc) == S_OK;
295 }
296 return true;
297}
298
299#ifndef QT_NO_DEBUG_STREAM
300QDebug operator<<(QDebug d, const FORMATETC &tc)
301{
302 QDebugStateSaver saver(d);
303 d.nospace();
304 d << "FORMATETC(cfFormat=" << tc.cfFormat << ' ';
305 switch (tc.cfFormat) {
306 case CF_TEXT:
307 d << "CF_TEXT";
308 break;
309 case CF_BITMAP:
310 d << "CF_BITMAP";
311 break;
312 case CF_TIFF:
313 d << "CF_TIFF";
314 break;
315 case CF_OEMTEXT:
316 d << "CF_OEMTEXT";
317 break;
318 case CF_DIB:
319 d << "CF_DIB";
320 break;
321 case CF_DIBV5:
322 d << "CF_DIBV5";
323 break;
324 case CF_UNICODETEXT:
325 d << "CF_UNICODETEXT";
326 break;
327 case CF_ENHMETAFILE:
328 d << "CF_ENHMETAFILE";
329 break;
330 default:
331 d << QWindowsMimeRegistry::clipboardFormatName(tc.cfFormat);
332 break;
333 }
334 d << ", dwAspect=" << tc.dwAspect << ", lindex=" << tc.lindex
335 << ", tymed=" << tc.tymed << ", ptd=" << tc.ptd << ')';
336 return d;
337}
338
339QDebug operator<<(QDebug d, IDataObject *dataObj)
340{
341 QDebugStateSaver saver(d);
342 d.nospace();
343 d.noquote();
344 d << "IDataObject(";
345 if (dataObj) { // Output formats contained in IDataObject.
346 IEnumFORMATETC *enumFormatEtc;
347 if (SUCCEEDED(dataObj->EnumFormatEtc(DATADIR_GET, &enumFormatEtc)) && enumFormatEtc) {
348 FORMATETC formatEtc[1];
349 ULONG fetched;
350 if (SUCCEEDED(enumFormatEtc->Reset())) {
351 while (SUCCEEDED(enumFormatEtc->Next(1, formatEtc, &fetched)) && fetched)
352 d << formatEtc[0] << ',';
353 enumFormatEtc->Release();
354 }
355 }
356 } else {
357 d << "0x0";
358 }
359 d << ')';
360 return d;
361}
362#endif // !QT_NO_DEBUG_STREAM
363
365{
366public:
367 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
368 QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QMetaType preferredType) const override;
369 QString mimeForFormat(const FORMATETC &formatetc) const override;
370 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
371 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override;
372 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
373};
374
375bool QWindowsMimeText::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
376{
377 int cf = getCf(formatetc);
378 return (cf == CF_UNICODETEXT || (cf == CF_TEXT && GetACP() != CP_UTF8)) && mimeData->hasText();
379}
380
381/*
382text/plain is defined as using CRLF, but so many programs don't,
383and programmers just look for '\n' in strings.
384Windows really needs CRLF, so we ensure it here.
385*/
386bool QWindowsMimeText::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const
387{
388 if (canConvertFromMime(formatetc, mimeData)) {
389 QByteArray data;
390 int cf = getCf(formatetc);
391 if (cf == CF_TEXT) {
392 data = mimeData->text().toLocal8Bit();
393 // Anticipate required space for CRLFs at 1/40
394 int maxsize=data.size()+data.size()/40+3;
395 QByteArray r(maxsize, '\0');
396 char* o = r.data();
397 const char* d = data.data();
398 const int s = data.size();
399 bool cr=false;
400 int j=0;
401 for (int i=0; i<s; i++) {
402 char c = d[i];
403 if (c=='\r')
404 cr=true;
405 else {
406 if (c=='\n') {
407 if (!cr)
408 o[j++]='\r';
409 }
410 cr=false;
411 }
412 o[j++]=c;
413 if (j+3 >= maxsize) {
414 maxsize += maxsize/4;
415 r.resize(maxsize);
416 o = r.data();
417 }
418 }
419 o[j]=0;
420 return setData(r, pmedium);
421 }
422 if (cf == CF_UNICODETEXT) {
423 QString str = mimeData->text();
424 const QChar *u = str.unicode();
425 QString res;
426 const int s = str.length();
427 int maxsize = s + s/40 + 3;
428 res.resize(maxsize);
429 int ri = 0;
430 bool cr = false;
431 for (int i=0; i < s; ++i) {
432 if (*u == u'\r')
433 cr = true;
434 else {
435 if (*u == u'\n' && !cr)
436 res[ri++] = u'\r';
437 cr = false;
438 }
439 res[ri++] = *u;
440 if (ri+3 >= maxsize) {
441 maxsize += maxsize/4;
442 res.resize(maxsize);
443 }
444 ++u;
445 }
446 res.truncate(ri);
447 const int byteLength = res.length() * int(sizeof(ushort));
448 QByteArray r(byteLength + 2, '\0');
449 memcpy(r.data(), res.unicode(), size_t(byteLength));
450 r[byteLength] = 0;
451 r[byteLength+1] = 0;
452 return setData(r, pmedium);
453 }
454 }
455 return false;
456}
457
458bool QWindowsMimeText::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
459{
460 return mimeType.startsWith(u"text/plain")
461 && (canGetData(CF_UNICODETEXT, pDataObj)
462 || canGetData(CF_TEXT, pDataObj));
463}
464
465QString QWindowsMimeText::mimeForFormat(const FORMATETC &formatetc) const
466{
467 int cf = getCf(formatetc);
468 if (cf == CF_UNICODETEXT || cf == CF_TEXT)
469 return u"text/plain"_s;
470 return QString();
471}
472
473
474QList<FORMATETC> QWindowsMimeText::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
475{
476 QList<FORMATETC> formatics;
477 if (mimeType.startsWith(u"text/plain") && mimeData->hasText()) {
478 formatics += setCf(CF_UNICODETEXT);
479 if (GetACP() != CP_UTF8)
480 formatics += setCf(CF_TEXT);
481 }
482 return formatics;
483}
484
485QVariant QWindowsMimeText::convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QMetaType preferredType) const
486{
487 QVariant ret;
488
489 if (canConvertToMime(mime, pDataObj)) {
490 QString str;
491 QByteArray data = getData(CF_UNICODETEXT, pDataObj);
492 if (!data.isEmpty()) {
493 str = QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData()));
494 str.replace("\r\n"_L1, "\n"_L1);
495 } else {
496 data = getData(CF_TEXT, pDataObj);
497 if (!data.isEmpty()) {
498 const char* d = data.data();
499 const unsigned s = unsigned(qstrlen(d));
500 QByteArray r(data.size()+1, '\0');
501 char* o = r.data();
502 int j=0;
503 for (unsigned i = 0; i < s; ++i) {
504 char c = d[i];
505 if (c!='\r')
506 o[j++]=c;
507 }
508 o[j]=0;
509 str = QString::fromLocal8Bit(r);
510 }
511 }
512 if (preferredType.id() == QMetaType::QString)
513 ret = str;
514 else
515 ret = std::move(str).toUtf8();
516 }
517 qCDebug(lcQpaMime) << __FUNCTION__ << ret;
518 return ret;
519}
520
522{
523public:
525 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
526 QVariant convertToMime(const QString &mime, LPDATAOBJECT pDataObj, QMetaType preferredType) const override;
527 QString mimeForFormat(const FORMATETC &formatetc) const override;
528 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
529 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const override;
530 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
531private:
532 int CF_INETURL_W; // wide char version
533 int CF_INETURL;
534};
535
537{
538 CF_INETURL_W = registerMimeType(u"UniformResourceLocatorW"_s);
539 CF_INETURL = registerMimeType(u"UniformResourceLocator"_s);
540}
541
542bool QWindowsMimeURI::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
543{
544 if (mimeData->hasUrls() && getCf(formatetc) == CF_HDROP) {
545 const auto urls = mimeData->urls();
546 for (const QUrl &url : urls) {
547 if (url.isLocalFile())
548 return true;
549 }
550 }
551 return (getCf(formatetc) == CF_INETURL_W || getCf(formatetc) == CF_INETURL) && mimeData->hasUrls();
552}
553
554bool QWindowsMimeURI::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM *pmedium) const
555{
556 if (canConvertFromMime(formatetc, mimeData)) {
557 if (getCf(formatetc) == CF_HDROP) {
558 const auto &urls = mimeData->urls();
559 QStringList fileNames;
560 size_t size = sizeof(DROPFILES) + 2;
561 for (const QUrl &url : urls) {
562 const QString fn = QDir::toNativeSeparators(url.toLocalFile());
563 if (!fn.isEmpty()) {
564 size += sizeof(ushort) * size_t(fn.length() + 1);
565 fileNames.append(fn);
566 }
567 }
568
569 QByteArray result(int(size), '\0');
570 auto* d = reinterpret_cast<DROPFILES *>(result.data());
571 d->pFiles = sizeof(DROPFILES);
572 GetCursorPos(&d->pt); // try
573 d->fNC = true;
574 char *files = (reinterpret_cast<char*>(d)) + d->pFiles;
575
576 d->fWide = true;
577 auto *f = reinterpret_cast<wchar_t *>(files);
578 for (int i=0; i<fileNames.size(); i++) {
579 const auto l = size_t(fileNames.at(i).length());
580 memcpy(f, fileNames.at(i).data(), l * sizeof(ushort));
581 f += l;
582 *f++ = 0;
583 }
584 *f = 0;
585
586 return setData(result, pmedium);
587 }
588 if (getCf(formatetc) == CF_INETURL_W) {
589 const auto urls = mimeData->urls();
590 QByteArray result;
591 if (!urls.isEmpty()) {
592 const QString url = urls.at(0).toString();
593 result = QByteArray(reinterpret_cast<const char *>(url.data()),
594 url.length() * int(sizeof(ushort)));
595 }
596 result.append('\0');
597 result.append('\0');
598 return setData(result, pmedium);
599 }
600 if (getCf(formatetc) == CF_INETURL) {
601 const auto urls = mimeData->urls();
602 QByteArray result;
603 if (!urls.isEmpty())
604 result = urls.at(0).toString().toLocal8Bit();
605 return setData(result, pmedium);
606 }
607 }
608
609 return false;
610}
611
612bool QWindowsMimeURI::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
613{
614 return mimeType == u"text/uri-list"
615 && (canGetData(CF_HDROP, pDataObj) || canGetData(CF_INETURL_W, pDataObj) || canGetData(CF_INETURL, pDataObj));
616}
617
618QString QWindowsMimeURI::mimeForFormat(const FORMATETC &formatetc) const
619{
620 QString format;
621 if (getCf(formatetc) == CF_HDROP || getCf(formatetc) == CF_INETURL_W || getCf(formatetc) == CF_INETURL)
622 format = u"text/uri-list"_s;
623 return format;
624}
625
626QList<FORMATETC> QWindowsMimeURI::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
627{
628 QList<FORMATETC> formatics;
629 if (mimeType == u"text/uri-list") {
630 if (canConvertFromMime(setCf(CF_HDROP), mimeData))
631 formatics += setCf(CF_HDROP);
632 if (canConvertFromMime(setCf(CF_INETURL_W), mimeData))
633 formatics += setCf(CF_INETURL_W);
634 if (canConvertFromMime(setCf(CF_INETURL), mimeData))
635 formatics += setCf(CF_INETURL);
636 }
637 return formatics;
638}
639
640QVariant QWindowsMimeURI::convertToMime(const QString &mimeType, LPDATAOBJECT pDataObj, QMetaType preferredType) const
641{
642 if (mimeType == u"text/uri-list") {
643 if (canGetData(CF_HDROP, pDataObj)) {
644 QList<QVariant> urls;
645
646 QByteArray data = getData(CF_HDROP, pDataObj);
647 if (data.isEmpty())
648 return QVariant();
649
650 const auto *hdrop = reinterpret_cast<const DROPFILES *>(data.constData());
651 if (hdrop->fWide) {
652 const auto *filesw = reinterpret_cast<const wchar_t *>(data.constData() + hdrop->pFiles);
653 int i = 0;
654 while (filesw[i]) {
655 QString fileurl = QString::fromWCharArray(filesw + i);
656 urls += QUrl::fromLocalFile(fileurl);
657 i += fileurl.length()+1;
658 }
659 } else {
660 const char* files = reinterpret_cast<const char *>(data.constData() + hdrop->pFiles);
661 int i=0;
662 while (files[i]) {
663 urls += QUrl::fromLocalFile(QString::fromLocal8Bit(files+i));
664 i += int(strlen(files+i))+1;
665 }
666 }
667
668 if (preferredType.id() == QMetaType::QUrl && urls.size() == 1)
669 return urls.at(0);
670 if (!urls.isEmpty())
671 return urls;
672 } else if (canGetData(CF_INETURL_W, pDataObj)) {
673 QByteArray data = getData(CF_INETURL_W, pDataObj);
674 if (data.isEmpty())
675 return QVariant();
676 return QUrl(QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData())));
677 } else if (canGetData(CF_INETURL, pDataObj)) {
678 QByteArray data = getData(CF_INETURL, pDataObj);
679 if (data.isEmpty())
680 return QVariant();
681 return QUrl(QString::fromLocal8Bit(data.constData()));
682 }
683 }
684 return QVariant();
685}
686
688{
689public:
691
692 // for converting from Qt
693 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
694 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
695 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
696
697 // for converting to Qt
698 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
699 QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
700 QString mimeForFormat(const FORMATETC &formatetc) const override;
701
702private:
703 int CF_HTML;
704};
705
707{
708 CF_HTML = registerMimeType(u"HTML Format"_s);
709}
710
711QList<FORMATETC> QWindowsMimeHtml::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
712{
713 QList<FORMATETC> formatetcs;
714 if (mimeType == u"text/html" && (!mimeData->html().isEmpty()))
715 formatetcs += setCf(CF_HTML);
716 return formatetcs;
717}
718
719QString QWindowsMimeHtml::mimeForFormat(const FORMATETC &formatetc) const
720{
721 if (getCf(formatetc) == CF_HTML)
722 return u"text/html"_s;
723 return QString();
724}
725
726bool QWindowsMimeHtml::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
727{
728 return mimeType == u"text/html" && canGetData(CF_HTML, pDataObj);
729}
730
731
732bool QWindowsMimeHtml::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
733{
734 return getCf(formatetc) == CF_HTML && (!mimeData->html().isEmpty());
735}
736
737/*
738The windows HTML clipboard format is as follows (xxxxxxxxxx is a 10 integer number giving the positions
739in bytes). Charset used is mostly utf8, but can be different, ie. we have to look for the <meta> charset tag
740
741 Version: 1.0
742 StartHTML:xxxxxxxxxx
743 EndHTML:xxxxxxxxxx
744 StartFragment:xxxxxxxxxx
745 EndFragment:xxxxxxxxxx
746 ...html...
747
748*/
749QVariant QWindowsMimeHtml::convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const
750{
751 Q_UNUSED(preferredType);
752 QVariant result;
753 if (canConvertToMime(mime, pDataObj)) {
754 QByteArray html = getData(CF_HTML, pDataObj);
755 static constexpr auto startMatcher = qMakeStaticByteArrayMatcher("StartHTML:");
756 static constexpr auto endMatcher = qMakeStaticByteArrayMatcher("EndHTML:");
757 qCDebug(lcQpaMime) << __FUNCTION__ << "raw:" << html;
758 int start = startMatcher.indexIn(html);
759 int end = endMatcher.indexIn(html);
760
761 if (start != -1) {
762 int startOffset = start + 10;
763 int i = startOffset;
764 while (html.at(i) != '\r' && html.at(i) != '\n')
765 ++i;
766 QByteArray bytecount = html.mid(startOffset, i - startOffset);
767 start = bytecount.toInt();
768 }
769
770 if (end != -1) {
771 int endOffset = end + 8;
772 int i = endOffset ;
773 while (html.at(i) != '\r' && html.at(i) != '\n')
774 ++i;
775 QByteArray bytecount = html.mid(endOffset , i - endOffset);
776 end = bytecount.toInt();
777 }
778
779 if (end > start && start > 0) {
780 html = html.mid(start, end - start);
781 html.replace('\r', "");
782 result = QString::fromUtf8(html);
783 }
784 }
785 return result;
786}
787
788bool QWindowsMimeHtml::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
789{
790 if (canConvertFromMime(formatetc, mimeData)) {
791 QByteArray data = mimeData->html().toUtf8();
792 QByteArray result =
793 "Version:1.0\r\n" // 0-12
794 "StartHTML:0000000107\r\n" // 13-35
795 "EndHTML:0000000000\r\n" // 36-55
796 "StartFragment:0000000000\r\n" // 56-81
797 "EndFragment:0000000000\r\n\r\n"; // 82-107
798
799 static constexpr auto startFragmentMatcher = qMakeStaticByteArrayMatcher("<!--StartFragment-->");
800 static constexpr auto endFragmentMatcher = qMakeStaticByteArrayMatcher("<!--EndFragment-->");
801
802 if (startFragmentMatcher.indexIn(data) == -1)
803 result += "<!--StartFragment-->";
804 result += data;
805 if (endFragmentMatcher.indexIn(data) == -1)
806 result += "<!--EndFragment-->";
807
808 // set the correct number for EndHTML
809 QByteArray pos = QByteArray::number(result.size());
810 memcpy(reinterpret_cast<char *>(result.data() + 53 - pos.length()), pos.constData(), size_t(pos.length()));
811
812 // set correct numbers for StartFragment and EndFragment
813 pos = QByteArray::number(startFragmentMatcher.indexIn(result) + 20);
814 memcpy(reinterpret_cast<char *>(result.data() + 79 - pos.length()), pos.constData(), size_t(pos.length()));
815 pos = QByteArray::number(endFragmentMatcher.indexIn(result));
816 memcpy(reinterpret_cast<char *>(result.data() + 103 - pos.length()), pos.constData(), size_t(pos.length()));
817
818 return setData(result, pmedium);
819 }
820 return false;
821}
822
823
824#if QT_CONFIG(imageformat_bmp)
825class QWindowsMimeImage : public QWindowsMimeConverter
826{
827public:
828 QWindowsMimeImage();
829 // for converting from Qt
830 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
831 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
832 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
833
834 // for converting to Qt
835 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
836 QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
837 QString mimeForFormat(const FORMATETC &formatetc) const override;
838private:
839 bool hasOriginalDIBV5(IDataObject *pDataObj) const;
840 UINT CF_PNG;
841};
842
843QWindowsMimeImage::QWindowsMimeImage()
844{
845 CF_PNG = RegisterClipboardFormat(L"PNG");
846}
847
848QList<FORMATETC> QWindowsMimeImage::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
849{
850 QList<FORMATETC> formatetcs;
851 if (mimeData->hasImage() && mimeType == u"application/x-qt-image") {
852 auto image = qvariant_cast<QImage>(mimeData->imageData());
853 if (!image.isNull() && image.hasAlphaChannel()) {
854 // CF_PNG is the only format modern MS Office (and other apps that
855 // deliver image data via TYMED_ISTREAM) reads with alpha
856 // preserved (QTBUG-126191).
857 formatetcs += setCf(CF_PNG);
858 formatetcs += setCf(CF_DIBV5);
859 }
860 formatetcs += setCf(CF_DIB);
861 }
862 if (!formatetcs.isEmpty())
863 qCDebug(lcQpaMime) << __FUNCTION__ << mimeType << formatetcs;
864 return formatetcs;
865}
866
867QString QWindowsMimeImage::mimeForFormat(const FORMATETC &formatetc) const
868{
869 int cf = getCf(formatetc);
870 if (cf == CF_DIB || cf == CF_DIBV5 || cf == int(CF_PNG))
871 return u"application/x-qt-image"_s;
872 return QString();
873}
874
875bool QWindowsMimeImage::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
876{
877 return mimeType == u"application/x-qt-image"
878 && (canGetData(CF_DIB, pDataObj) || canGetData(CF_PNG, pDataObj));
879}
880
881bool QWindowsMimeImage::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
882{
883 int cf = getCf(formatetc);
884 if (!mimeData->hasImage())
885 return false;
886 const QImage image = qvariant_cast<QImage>(mimeData->imageData());
887 if (image.isNull())
888 return false;
889 // QTBUG-64322: Use PNG only for transparent images as otherwise MS PowerPoint
890 // cannot handle it.
891 return cf == CF_DIBV5 || cf == CF_DIB
892 || (cf == int(CF_PNG) && image.hasAlphaChannel());
893}
894
895bool QWindowsMimeImage::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
896{
897 int cf = getCf(formatetc);
898 if ((cf == CF_DIB || cf == CF_DIBV5 || cf == int(CF_PNG)) && mimeData->hasImage()) {
899 auto img = qvariant_cast<QImage>(mimeData->imageData());
900 if (img.isNull())
901 return false;
902 QByteArray ba;
903 if (cf == CF_DIB) {
904 if (img.format() > QImage::Format_ARGB32)
905 img = std::move(img).convertToFormat(QImage::Format_RGB32);
906 const QByteArray ba = writeDib(img);
907 if (!ba.isEmpty())
908 return setData(ba, pmedium);
909 } else if (cf == int(CF_PNG)) {
910 QBuffer buffer(&ba);
911 const bool written = buffer.open(QIODevice::WriteOnly) && img.save(&buffer, "PNG");
912 buffer.close();
913 if (written)
914 return setData(ba, pmedium, formatetc.tymed);
915 } else {
916 QDataStream s(&ba, QIODevice::WriteOnly);
917 s.setByteOrder(QDataStream::LittleEndian);// Intel byte order ####
918 if (qt_write_dibv5(s, std::move(img)))
919 return setData(ba, pmedium);
920 }
921 }
922 return false;
923}
924
925bool QWindowsMimeImage::hasOriginalDIBV5(IDataObject *pDataObj) const
926{
927 bool isSynthesized = true;
928 IEnumFORMATETC *pEnum = nullptr;
929 HRESULT res = pDataObj->EnumFormatEtc(1, &pEnum);
930 if (res == S_OK && pEnum) {
931 FORMATETC fc;
932 while ((res = pEnum->Next(1, &fc, nullptr)) == S_OK) {
933 if (fc.ptd)
934 CoTaskMemFree(fc.ptd);
935 if (fc.cfFormat == CF_DIB)
936 break;
937 if (fc.cfFormat == CF_DIBV5) {
938 isSynthesized = false;
939 break;
940 }
941 }
942 pEnum->Release();
943 }
944 return !isSynthesized;
945}
946
947QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject *pDataObj, QMetaType preferredType) const
948{
949 Q_UNUSED(preferredType);
950 QVariant result;
951 if (mimeType != u"application/x-qt-image")
952 return result;
953 // Try to convert from DIBV5 as it is the most widespread format that supports transparency,
954 // but avoid synthesizing it, as that typically loses transparency, e.g. from Office
955 const bool canGetDibV5 = canGetData(CF_DIBV5, pDataObj);
956 const bool hasOrigDibV5 = canGetDibV5 ? hasOriginalDIBV5(pDataObj) : false;
957 qCDebug(lcQpaMime) << "canGetDibV5:" << canGetDibV5 << "hasOrigDibV5:" << hasOrigDibV5;
958 // PNG, MS Office place this (undocumented)
959 if (!hasOrigDibV5 && canGetData(CF_PNG, pDataObj)) {
960 qCDebug(lcQpaMime) << "Decoding PNG";
961 QImage img;
962 QByteArray data = getData(CF_PNG, pDataObj);
963 if (img.loadFromData(data, "PNG")) {
964 return img;
965 }
966 }
967
968 if (canGetDibV5 || canGetData(CF_DIB, pDataObj)) {
969 qCDebug(lcQpaMime) << "Decoding DIB";
970 QImage img;
971 QByteArray data = getData(canGetDibV5 ? CF_DIBV5 : CF_DIB, pDataObj);
972 QBuffer buffer(&data);
973 if (readDib(buffer, img))
974 return img;
975 }
976 // Failed
977 return result;
978}
979#endif //QT_CONFIG(imageformat_bmp)
980
982{
983public:
985
986 // for converting from Qt
987 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
988 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
989 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
990
991 // for converting to Qt
992 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
993 QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
994 QString mimeForFormat(const FORMATETC &formatetc) const override;
995
996private:
997 QMap<int, QString> outFormats;
998 QMap<int, QString> inFormats;
999};
1000
1003{
1004 outFormats.insert(registerMimeType(u"application/x-color"_s), u"application/x-color"_s);
1005 inFormats.insert(registerMimeType(u"application/x-color"_s), u"application/x-color"_s);
1006}
1007
1008bool QBuiltInMimes::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
1009{
1010 // really check
1011 return formatetc.tymed & TYMED_HGLOBAL
1012 && outFormats.contains(formatetc.cfFormat)
1013 && mimeData->formats().contains(outFormats.value(formatetc.cfFormat));
1014}
1015
1016bool QBuiltInMimes::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
1017{
1018 if (canConvertFromMime(formatetc, mimeData)) {
1019 QByteArray data;
1020 if (outFormats.value(getCf(formatetc)) == u"text/html") {
1021 // text/html is in wide chars on windows (compatible with mozillia)
1022 QString html = mimeData->html();
1023 // same code as in the text converter up above
1024 const QChar *u = html.unicode();
1025 QString res;
1026 const int s = html.length();
1027 int maxsize = s + s/40 + 3;
1028 res.resize(maxsize);
1029 int ri = 0;
1030 bool cr = false;
1031 for (int i=0; i < s; ++i) {
1032 if (*u == u'\r')
1033 cr = true;
1034 else {
1035 if (*u == u'\n' && !cr)
1036 res[ri++] = u'\r';
1037 cr = false;
1038 }
1039 res[ri++] = *u;
1040 if (ri+3 >= maxsize) {
1041 maxsize += maxsize/4;
1042 res.resize(maxsize);
1043 }
1044 ++u;
1045 }
1046 res.truncate(ri);
1047 const int byteLength = res.length() * int(sizeof(ushort));
1048 QByteArray r(byteLength + 2, '\0');
1049 memcpy(r.data(), res.unicode(), size_t(byteLength));
1050 r[byteLength] = 0;
1051 r[byteLength+1] = 0;
1052 data = r;
1053 } else {
1054#if QT_CONFIG(draganddrop)
1055 data = QInternalMimeData::renderDataHelper(outFormats.value(getCf(formatetc)), mimeData);
1056#endif // QT_CONFIG(draganddrop)
1057 }
1058 return setData(data, pmedium);
1059 }
1060 return false;
1061}
1062
1063QList<FORMATETC> QBuiltInMimes::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
1064{
1065 QList<FORMATETC> formatetcs;
1066 const auto mit = std::find(outFormats.cbegin(), outFormats.cend(), mimeType);
1067 if (mit != outFormats.cend() && mimeData->formats().contains(mimeType))
1068 formatetcs += setCf(mit.key());
1069 return formatetcs;
1070}
1071
1072bool QBuiltInMimes::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
1073{
1074 const auto mit = std::find(inFormats.cbegin(), inFormats.cend(), mimeType);
1075 return mit != inFormats.cend() && canGetData(mit.key(), pDataObj);
1076}
1077
1078QVariant QBuiltInMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QMetaType preferredType) const
1079{
1080 QVariant val;
1081 if (canConvertToMime(mimeType, pDataObj)) {
1082 QByteArray data = getData(inFormats.key(mimeType), pDataObj);
1083 if (!data.isEmpty()) {
1084 qCDebug(lcQpaMime) << __FUNCTION__;
1085 if (mimeType == u"text/html" && preferredType == QMetaType(QMetaType::QString)) {
1086 // text/html is in wide chars on windows (compatible with Mozilla)
1087 val = QString::fromWCharArray(reinterpret_cast<const wchar_t *>(data.constData()));
1088 } else {
1089 val = data; // it should be enough to return the data and let QMimeData do the rest.
1090 }
1091 }
1092 }
1093 return val;
1094}
1095
1096QString QBuiltInMimes::mimeForFormat(const FORMATETC &formatetc) const
1097{
1098 return inFormats.value(getCf(formatetc));
1099}
1100
1101
1103{
1104public:
1105
1107 // for converting from Qt
1108 bool canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const override;
1109 bool convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const override;
1110 QList<FORMATETC> formatsForMime(const QString &mimeType, const QMimeData *mimeData) const override;
1111
1112 // for converting to Qt
1113 bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
1114 QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
1115 QString mimeForFormat(const FORMATETC &formatetc) const override;
1116
1117private:
1118 mutable QMap<int, QString> formats;
1119 static QStringList ianaTypes;
1120 static QStringList excludeList;
1121};
1122
1123QStringList QLastResortMimes::ianaTypes;
1124QStringList QLastResortMimes::excludeList;
1125
1127{
1128 //MIME Media-Types
1129 if (ianaTypes.isEmpty()) {
1130 ianaTypes.append(u"application/"_s);
1131 ianaTypes.append(u"audio/"_s);
1132 ianaTypes.append(u"example/"_s);
1133 ianaTypes.append(u"image/"_s);
1134 ianaTypes.append(u"message/"_s);
1135 ianaTypes.append(u"model/"_s);
1136 ianaTypes.append(u"multipart/"_s);
1137 ianaTypes.append(u"text/"_s);
1138 ianaTypes.append(u"video/"_s);
1139 }
1140 //Types handled by other classes
1141 if (excludeList.isEmpty()) {
1142 excludeList.append(u"HTML Format"_s);
1143 excludeList.append(u"UniformResourceLocator"_s);
1144 excludeList.append(u"text/html"_s);
1145 excludeList.append(u"text/plain"_s);
1146 excludeList.append(u"text/uri-list"_s);
1147 excludeList.append(u"application/x-qt-image"_s);
1148 excludeList.append(u"application/x-color"_s);
1149 }
1150}
1151
1152bool QLastResortMimes::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
1153{
1154 // really check
1155#if QT_CONFIG(draganddrop)
1156 return formatetc.tymed & TYMED_HGLOBAL
1157 && (formats.contains(formatetc.cfFormat)
1158 && QInternalMimeData::hasFormatHelper(formats.value(formatetc.cfFormat), mimeData));
1159#else
1160 Q_UNUSED(mimeData);
1161 Q_UNUSED(formatetc);
1162 return formatetc.tymed & TYMED_HGLOBAL
1163 && formats.contains(formatetc.cfFormat);
1164#endif // QT_CONFIG(draganddrop)
1165}
1166
1167bool QLastResortMimes::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
1168{
1169#if QT_CONFIG(draganddrop)
1170 return canConvertFromMime(formatetc, mimeData)
1171 && setData(QInternalMimeData::renderDataHelper(formats.value(getCf(formatetc)), mimeData), pmedium);
1172#else
1173 Q_UNUSED(mimeData);
1174 Q_UNUSED(formatetc);
1175 Q_UNUSED(pmedium);
1176 return false;
1177#endif // QT_CONFIG(draganddrop)
1178}
1179
1180QList<FORMATETC> QLastResortMimes::formatsForMime(const QString &mimeType, const QMimeData * /*mimeData*/) const
1181{
1182 QList<FORMATETC> formatetcs;
1183 auto mit = std::find(formats.begin(), formats.end(), mimeType);
1184 // register any other available formats
1185 if (mit == formats.end() && !excludeList.contains(mimeType, Qt::CaseInsensitive))
1186 mit = formats.insert(registerMimeType(mimeType), mimeType);
1187 if (mit != formats.end())
1188 formatetcs += setCf(mit.key());
1189
1190 if (!formatetcs.isEmpty())
1191 qCDebug(lcQpaMime) << __FUNCTION__ << mimeType << formatetcs;
1192 return formatetcs;
1193}
1194static const char x_qt_windows_mime[] = "application/x-qt-windows-mime;value=\"";
1195
1196static bool isCustomMimeType(const QString &mimeType)
1197{
1198 return mimeType.startsWith(QLatin1StringView(x_qt_windows_mime), Qt::CaseInsensitive);
1199}
1200
1201static QString customMimeType(const QString &mimeType, int *lindex = nullptr)
1202{
1203 int len = sizeof(x_qt_windows_mime) - 1;
1204 int n = mimeType.lastIndexOf(u'\"') - len;
1205 QString ret = mimeType.mid(len, n);
1206
1207 const int beginPos = mimeType.indexOf(u";index=");
1208 if (beginPos > -1) {
1209 const int endPos = mimeType.indexOf(u';', beginPos + 1);
1210 const int indexStartPos = beginPos + 7;
1211 if (lindex)
1212 *lindex = QStringView{mimeType}.mid(indexStartPos, endPos == -1 ? endPos : endPos - indexStartPos).toInt();
1213 } else {
1214 if (lindex)
1215 *lindex = -1;
1216 }
1217 return ret;
1218}
1219
1220bool QLastResortMimes::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
1221{
1222 if (isCustomMimeType(mimeType)) {
1223 // MSDN documentation for QueryGetData says only -1 is supported, so ignore lindex here.
1224 QString clipFormat = customMimeType(mimeType);
1225 const UINT cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16()));
1226 return canGetData(int(cf), pDataObj);
1227 }
1228 // if it is not in there then register it and see if we can get it
1229 const auto mit = std::find(formats.cbegin(), formats.cend(), mimeType);
1230 const int cf = mit != formats.cend() ? mit.key() : registerMimeType(mimeType);
1231 return canGetData(cf, pDataObj);
1232}
1233
1234QVariant QLastResortMimes::convertToMime(const QString &mimeType, IDataObject *pDataObj, QMetaType preferredType) const
1235{
1236 Q_UNUSED(preferredType);
1237 QVariant val;
1238 if (canConvertToMime(mimeType, pDataObj)) {
1239 QByteArray data;
1240 if (isCustomMimeType(mimeType)) {
1241 int lindex;
1242 QString clipFormat = customMimeType(mimeType, &lindex);
1243 const UINT cf = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (clipFormat.utf16()));
1244 data = getData(int(cf), pDataObj, lindex);
1245 } else {
1246 const auto mit = std::find(formats.cbegin(), formats.cend(), mimeType);
1247 const int cf = mit != formats.cend() ? mit.key() : registerMimeType(mimeType);
1248 data = getData(cf, pDataObj);
1249 }
1250 if (!data.isEmpty())
1251 val = data; // it should be enough to return the data and let QMimeData do the rest.
1252 }
1253 return val;
1254}
1255
1256QString QLastResortMimes::mimeForFormat(const FORMATETC &formatetc) const
1257{
1258 QString format = formats.value(getCf(formatetc));
1259 if (!format.isEmpty())
1260 return format;
1261
1262 const QString clipFormat = QWindowsMimeRegistry::clipboardFormatName(getCf(formatetc));
1263 if (!clipFormat.isEmpty()) {
1264#if QT_CONFIG(draganddrop)
1265 if (QInternalMimeData::canReadData(clipFormat))
1266 format = clipFormat;
1267 else if ((formatetc.cfFormat >= 0xC000)){
1268 //create the mime as custom. not registered.
1269 if (!excludeList.contains(clipFormat, Qt::CaseInsensitive)) {
1270 //check if this is a mime type
1271 bool ianaType = false;
1272 int sz = ianaTypes.size();
1273 for (int i = 0; i < sz; i++) {
1274 if (clipFormat.startsWith(ianaTypes[i], Qt::CaseInsensitive)) {
1275 ianaType = true;
1276 break;
1277 }
1278 }
1279 if (!ianaType)
1280 format = QLatin1StringView(x_qt_windows_mime) + clipFormat + u'"';
1281 else
1282 format = clipFormat;
1283 }
1284 }
1285#endif // QT_CONFIG(draganddrop)
1286 }
1287
1288 return format;
1289}
1290
1291/*!
1292 \class QWindowsMimeRegistry
1293 \brief Manages the list of QWindowsMimeConverter instances.
1294 \internal
1295 \sa QWindowsMimeConverter
1296*/
1297
1299
1301{
1302 qDeleteAll(m_mimes.begin(), m_mimes.begin() + m_internalMimeCount);
1303}
1304
1305QWindowsMimeRegistry::QWindowsMimeConverter *QWindowsMimeRegistry::converterToMime(const QString &mimeType, IDataObject *pDataObj) const
1306{
1307 ensureInitialized();
1308 for (int i = m_mimes.size()-1; i >= 0; --i) {
1309 if (m_mimes.at(i)->canConvertToMime(mimeType, pDataObj))
1310 return m_mimes.at(i);
1311 }
1312 return nullptr;
1313}
1314
1316{
1317 qCDebug(lcQpaMime) << "QWindowsMimeConverter::allMimesForFormats()";
1318 ensureInitialized();
1319 QStringList formats;
1320 LPENUMFORMATETC FAR fmtenum;
1321 HRESULT hr = pDataObj->EnumFormatEtc(DATADIR_GET, &fmtenum);
1322
1323 if (hr == NOERROR) {
1324 FORMATETC fmtetc;
1325 while (S_OK == fmtenum->Next(1, &fmtetc, nullptr)) {
1326 for (int i= m_mimes.size() - 1; i >= 0; --i) {
1327 QString format = m_mimes.at(i)->mimeForFormat(fmtetc);
1328 if (!format.isEmpty() && !formats.contains(format)) {
1329 formats += format;
1330 if (QWindowsContext::verbose > 1 && lcQpaMime().isDebugEnabled())
1331 qCDebug(lcQpaMime) << __FUNCTION__ << fmtetc << format;
1332 }
1333 }
1334 // as documented in MSDN to avoid possible memleak
1335 if (fmtetc.ptd)
1336 CoTaskMemFree(fmtetc.ptd);
1337 }
1338 fmtenum->Release();
1339 }
1340 qCDebug(lcQpaMime) << pDataObj << formats;
1341 return formats;
1342}
1343
1344QWindowsMimeRegistry::QWindowsMimeConverter *QWindowsMimeRegistry::converterFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
1345{
1346 ensureInitialized();
1347 qCDebug(lcQpaMime) << __FUNCTION__ << formatetc;
1348 for (int i = m_mimes.size()-1; i >= 0; --i) {
1349 if (m_mimes.at(i)->canConvertFromMime(formatetc, mimeData))
1350 return m_mimes.at(i);
1351 }
1352 return nullptr;
1353}
1354
1355QList<FORMATETC> QWindowsMimeRegistry::allFormatsForMime(const QMimeData *mimeData) const
1356{
1357 ensureInitialized();
1358 QList<FORMATETC> formatics;
1359#if !QT_CONFIG(draganddrop)
1360 Q_UNUSED(mimeData);
1361#else
1362 formatics.reserve(20);
1363 const QStringList formats = QInternalMimeData::formatsHelper(mimeData);
1364 for (int f = 0; f < formats.size(); ++f) {
1365 for (int i = m_mimes.size() - 1; i >= 0; --i)
1366 formatics += m_mimes.at(i)->formatsForMime(formats.at(f), mimeData);
1367 }
1368#endif // QT_CONFIG(draganddrop)
1369 return formatics;
1370}
1371
1372void QWindowsMimeRegistry::ensureInitialized() const
1373{
1374 if (m_internalMimeCount == 0) {
1375 m_internalMimeCount = -1; // prevent reentrancy when types register themselves
1376#if QT_CONFIG(imageformat_bmp)
1377 (void)new QWindowsMimeImage;
1378#endif //QT_CONFIG(imageformat_bmp)
1379 (void)new QLastResortMimes;
1380 (void)new QWindowsMimeText;
1381 (void)new QWindowsMimeURI;
1382 (void)new QWindowsMimeHtml;
1383 (void)new QBuiltInMimes;
1384 m_internalMimeCount = m_mimes.size();
1385 Q_ASSERT(m_internalMimeCount > 0);
1386 }
1387}
1388
1389QString QWindowsMimeRegistry::clipboardFormatName(int cf)
1390{
1391 wchar_t buf[256] = {0};
1392 return GetClipboardFormatName(UINT(cf), buf, 255)
1393 ? QString::fromWCharArray(buf) : QString();
1394}
1395
1396QVariant QWindowsMimeRegistry::convertToMime(const QStringList &mimeTypes,
1397 IDataObject *pDataObj,
1398 QMetaType preferredType,
1399 QString *formatIn /* = 0 */) const
1400{
1401 for (const QString &format : mimeTypes) {
1402 if (const QWindowsMimeConverter *converter = converterToMime(format, pDataObj)) {
1403 if (converter->canConvertToMime(format, pDataObj)) {
1404 const QVariant dataV = converter->convertToMime(format, pDataObj, preferredType);
1405 if (dataV.isValid()) {
1406 qCDebug(lcQpaMime) << __FUNCTION__ << mimeTypes << "\nFormat: "
1407 << format << pDataObj << " returns " << dataV;
1408 if (formatIn)
1409 *formatIn = format;
1410 return dataV;
1411 }
1412 }
1413 }
1414 }
1415 qCDebug(lcQpaMime) << __FUNCTION__ << "fails" << mimeTypes << pDataObj << preferredType.id();
1416 return QVariant();
1417}
1418
1419void QWindowsMimeRegistry::registerMime(QWindowsMimeConverter *mime)
1420{
1421 ensureInitialized();
1422 m_mimes.append(mime);
1423}
1424
1425/*!
1426 Registers the MIME type \a mime, and returns an ID number
1427 identifying the format on Windows.
1428
1429 A mime type \c {application/x-qt-windows-mime;value="WindowsType"} will be
1430 registered as the clipboard format for \c WindowsType.
1431*/
1432int QWindowsMimeRegistry::registerMimeType(const QString &mime)
1433{
1434 const QString mimeType = isCustomMimeType(mime) ? customMimeType(mime) : mime;
1435 const UINT f = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (mimeType.utf16()));
1436 if (!f) {
1437 qErrnoWarning("QWindowsMimeRegistry::registerMimeType: Failed to register clipboard format "
1438 "for %s", qPrintable(mime));
1439 }
1440
1441 return int(f);
1442}
1443
1444QT_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[]