Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qmacclipboard.mm
Go to the documentation of this file.
1// Copyright (C) 2016 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
4#include <AppKit/AppKit.h>
5
6#include "qmacclipboard.h"
7#include <QtGui/private/qmacmimeregistry_p.h>
8#include <QtGui/qutimimeconverter.h>
9#include <QtGui/qclipboard.h>
10#include <QtGui/qguiapplication.h>
11#include <QtGui/qbitmap.h>
12#include <QtCore/qdatetime.h>
13#include <QtCore/qmetatype.h>
14#include <QtCore/qdebug.h>
15#include <QtCore/private/qcore_mac_p.h>
16#include <QtGui/qguiapplication.h>
17#include <QtGui/qevent.h>
18#include <QtCore/qurl.h>
19#include <stdlib.h>
20#include <string.h>
21#include "qcocoahelpers.h"
22#include <type_traits>
23
25
26using namespace Qt::StringLiterals;
27
28/*****************************************************************************
29 QMacPasteboard code
30*****************************************************************************/
31
32namespace
33{
34OSStatus PasteboardGetItemCountSafe(PasteboardRef paste, ItemCount *cnt)
35{
37 Q_ASSERT(cnt);
38 const OSStatus result = PasteboardGetItemCount(paste, cnt);
39 // Despite being declared unsigned, this API can return -1
40 if (std::make_signed<ItemCount>::type(*cnt) < 0)
41 *cnt = 0;
42 return result;
43}
44} // namespace
45
46// Ensure we don't call the broken one later on
47#define PasteboardGetItemCount
48
49class QMacMimeData : public QMimeData
50{
51public:
53private:
55};
56
57QMacPasteboard::Promise::Promise(int itemId, const QUtiMimeConverter *c, const QString &m, QMimeData *md, int o, DataRequestType drt)
58 : itemId(itemId), offset(o), converter(c), mime(m), dataRequestType(drt)
59{
60 // Request the data from the application immediately for eager requests.
61 if (dataRequestType == QMacPasteboard::EagerRequest) {
62 variantData = static_cast<QMacMimeData *>(md)->variantData(m);
63 isPixmap = variantData.metaType().id() == QMetaType::QPixmap;
64 mimeData = nullptr;
65 } else {
66 mimeData = md;
67 if (md->hasImage())
68 isPixmap = md->imageData().metaType().id() == QMetaType::QPixmap;
69 }
70}
71
72QMacPasteboard::QMacPasteboard(PasteboardRef p, QUtiMimeConverter::HandlerScope scope)
73 : scope(scope)
74{
75 mac_mime_source = false;
76 paste = p;
77 CFRetain(paste);
78 resolvingBeforeDestruction = false;
79}
80
81QMacPasteboard::QMacPasteboard(QUtiMimeConverter::HandlerScope scope)
82 : scope(scope)
83{
84 mac_mime_source = false;
85 paste = nullptr;
86 OSStatus err = PasteboardCreate(nullptr, &paste);
87 if (err == noErr)
88 PasteboardSetPromiseKeeper(paste, promiseKeeper, this);
89 else
90 qDebug("PasteBoard: Error creating pasteboard: [%d]", (int)err);
91 resolvingBeforeDestruction = false;
92}
93
94QMacPasteboard::QMacPasteboard(CFStringRef name, QUtiMimeConverter::HandlerScope scope)
95 : scope(scope)
96{
97 mac_mime_source = false;
98 paste = nullptr;
99 OSStatus err = PasteboardCreate(name, &paste);
100 if (err == noErr) {
101 PasteboardSetPromiseKeeper(paste, promiseKeeper, this);
102 } else {
103 qDebug("PasteBoard: Error creating pasteboard: %s [%d]", QString::fromCFString(name).toLatin1().constData(), (int)err);
104 }
105 resolvingBeforeDestruction = false;
106}
107
109{
110 /*
111 Commit all promises for paste when shutting down,
112 unless we are the stack-allocated clipboard used by QCocoaDrag.
113 */
115 resolvingBeforeDestruction = true;
116 PasteboardResolvePromises(paste);
117 if (paste)
118 CFRelease(paste);
119}
120
121PasteboardRef QMacPasteboard::pasteBoard() const
122{
123 return paste;
124}
125
126OSStatus QMacPasteboard::promiseKeeper(PasteboardRef paste, PasteboardItemID id,
127 CFStringRef uti, void *_qpaste)
128{
129 QMacPasteboard *qpaste = (QMacPasteboard*)_qpaste;
130 const long promise_id = (long)id;
131
132 // Find the kept promise
133 const QList<QUtiMimeConverter*> availableConverters = QMacMimeRegistry::all(QUtiMimeConverter::HandlerScopeFlag::All);
134 const QString utiAsQString = QString::fromCFString(uti);
135 QMacPasteboard::Promise promise;
136 for (int i = 0; i < qpaste->promises.size(); i++){
137 const QMacPasteboard::Promise tmp = qpaste->promises[i];
138 if (!availableConverters.contains(tmp.converter)) {
139 // promise.converter is a pointer initialized by the value found
140 // in QUtiMimeConverter's global list of QMacMimes.
141 // We add pointers to this list in QUtiMimeConverter's ctor;
142 // we remove these pointers in QUtiMimeConverter's dtor.
143 // If tmp.converter was not found in this list, we probably have a
144 // dangling pointer so let's skip it.
145 continue;
146 }
147
148 if (tmp.itemId == promise_id && tmp.converter->canConvert(tmp.mime, utiAsQString)) {
149 promise = tmp;
150 break;
151 }
152 }
153
154 if (!promise.itemId && utiAsQString == "com.trolltech.qt.MimeTypeName"_L1) {
155 // we have promised this data, but won't be able to convert, so return null data.
156 // This helps in making the application/x-qt-mime-type-name hidden from normal use.
158 const QCFType<CFDataRef> data = CFDataCreate(nullptr, (UInt8*)ba.constData(), ba.size());
159 PasteboardPutItemFlavor(paste, id, uti, data, kPasteboardFlavorNoFlags);
160 return noErr;
161 }
162
163 if (!promise.itemId) {
164 // There was no promise that could deliver data for the
165 // given id and uti. This should not happen.
166 qDebug("Pasteboard: %d: Request for %ld, %s, but no promise found!", __LINE__, promise_id, qPrintable(utiAsQString));
167 return cantGetFlavorErr;
168 }
169
170 qCDebug(lcQpaClipboard, "PasteBoard: Calling in promise for %s[%ld] [%s] [%d]", qPrintable(promise.mime), promise_id,
171 qPrintable(utiAsQString), promise.offset);
172
173 // Get the promise data. If this is a "lazy" promise call variantData()
174 // to request the data from the application.
175 QVariant promiseData;
176 if (promise.dataRequestType == LazyRequest) {
177 if (!qpaste->resolvingBeforeDestruction && !promise.mimeData.isNull()) {
178 if (promise.isPixmap && !QGuiApplication::instance()) {
179 qCWarning(lcQpaClipboard,
180 "Cannot keep promise, data contains QPixmap and requires livining QGuiApplication");
181 return cantGetFlavorErr;
182 }
183 promiseData = static_cast<QMacMimeData *>(promise.mimeData.data())->variantData(promise.mime);
184 }
185 } else {
186 promiseData = promise.variantData;
187 }
188
189 const QList<QByteArray> md = promise.converter->convertFromMime(promise.mime, promiseData, utiAsQString);
190 if (md.size() <= promise.offset)
191 return cantGetFlavorErr;
192 const QByteArray &ba = md[promise.offset];
193 const QCFType<CFDataRef> data = CFDataCreate(nullptr, (UInt8*)ba.constData(), ba.size());
194 PasteboardPutItemFlavor(paste, id, uti, data, kPasteboardFlavorNoFlags);
195 return noErr;
196}
197
198bool QMacPasteboard::hasUti(const QString &uti) const
199{
200 if (!paste)
201 return false;
202
203 sync();
204
205 ItemCount cnt = 0;
206 if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt)
207 return false;
208
209 qCDebug(lcQpaClipboard, "PasteBoard: hasUti [%s]", qPrintable(uti));
210 const QCFString c_uti(uti);
211 for (uint index = 1; index <= cnt; ++index) {
212
213 PasteboardItemID id;
214 if (PasteboardGetItemIdentifier(paste, index, &id) != noErr)
215 return false;
216
217 PasteboardFlavorFlags flags;
218 if (PasteboardGetItemFlavorFlags(paste, id, c_uti, &flags) == noErr) {
219 qCDebug(lcQpaClipboard, " - Found!");
220 return true;
221 }
222 }
223 qCDebug(lcQpaClipboard, " - NotFound!");
224 return false;
225}
226
228{
229 const QMacPasteboard *paste;
230public:
233 QStringList formats() const override { return paste->formats(); }
235 {
236 return paste->retrieveData(format);
237 }
238};
239
241{
242 if (!mime) {
243 mac_mime_source = true;
244 mime = new QMacPasteboardMimeSource(this);
245
246 }
247 return mime;
248}
249
251{
252 if (!paste)
253 return;
254
255 if (mime == mime_src || (!mime_src && mime && mac_mime_source))
256 return;
257 mac_mime_source = false;
258 delete mime;
259 mime = mime_src;
260
261 const QList<QUtiMimeConverter*> availableConverters = QMacMimeRegistry::all(scope);
262 if (mime != nullptr) {
263 clear_helper();
264 QStringList formats = mime_src->formats();
265
266 // QMimeData sub classes reimplementing the formats() might not expose the
267 // temporary "application/x-qt-mime-type-name" mimetype. So check the existence
268 // of this mime type while doing drag and drop.
269 QString dummyMimeType("application/x-qt-mime-type-name"_L1);
270 if (!formats.contains(dummyMimeType)) {
271 QByteArray dummyType = mime_src->data(dummyMimeType);
272 if (!dummyType.isEmpty())
273 formats.append(dummyMimeType);
274 }
275 for (const auto &mimeType : formats) {
276 for (const auto *c : availableConverters) {
277 // Hack: The Rtf handler converts incoming Rtf to Html. We do
278 // not want to convert outgoing Html to Rtf but instead keep
279 // posting it as Html. Skip the Rtf handler here.
280 if (c->utiForMime("text/html"_L1) == "public.rtf"_L1)
281 continue;
282 const QString uti(c->utiForMime(mimeType));
283 if (!uti.isEmpty()) {
284
285 const int numItems = c->count(mime_src);
286 for (int item = 0; item < numItems; ++item) {
287 const NSInteger itemID = item + 1; //id starts at 1
288 //QMacPasteboard::Promise promise = (dataRequestType == QMacPasteboard::EagerRequest) ?
289 // QMacPasteboard::Promise::eagerPromise(itemID, c, mimeType, mimeData, item) :
290 // QMacPasteboard::Promise::lazyPromise(itemID, c, mimeType, mimeData, item);
291
292 const QMacPasteboard::Promise promise(itemID, c, mimeType, mime_src, item, dataRequestType);
293 promises.append(promise);
294 PasteboardPutItemFlavor(paste, reinterpret_cast<PasteboardItemID>(itemID), QCFString(uti), 0, kPasteboardFlavorNoFlags);
295 qCDebug(lcQpaClipboard, " - adding %ld %s [%s] [%d]",
296 itemID, qPrintable(mimeType), qPrintable(uti), item);
297 }
298 }
299 }
300 }
301 }
302}
303
305{
306 if (!paste)
307 return QStringList();
308
309 sync();
310
312 ItemCount cnt = 0;
313 if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt)
314 return ret;
315
316 qCDebug(lcQpaClipboard, "PasteBoard: Formats [%d]", (int)cnt);
317 for (uint index = 1; index <= cnt; ++index) {
318
319 PasteboardItemID id;
320 if (PasteboardGetItemIdentifier(paste, index, &id) != noErr)
321 continue;
322
323 QCFType<CFArrayRef> types;
324 if (PasteboardCopyItemFlavors(paste, id, &types ) != noErr)
325 continue;
326
327 const int type_count = CFArrayGetCount(types);
328 for (int i = 0; i < type_count; ++i) {
329 const QString uti = QString::fromCFString((CFStringRef)CFArrayGetValueAtIndex(types, i));
330 qCDebug(lcQpaClipboard, " -%s", qPrintable(QString(uti)));
332 if (!mimeType.isEmpty() && !ret.contains(mimeType)) {
333 qCDebug(lcQpaClipboard, " -<%lld> %s [%s]", ret.size(), qPrintable(mimeType), qPrintable(QString(uti)));
334 ret << mimeType;
335 }
336 }
337 }
338 return ret;
339}
340
342{
343 if (!paste)
344 return false;
345
346 sync();
347
348 ItemCount cnt = 0;
349 if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt)
350 return false;
351
352 qCDebug(lcQpaClipboard, "PasteBoard: hasFormat [%s]", qPrintable(format));
353 for (uint index = 1; index <= cnt; ++index) {
354
355 PasteboardItemID id;
356 if (PasteboardGetItemIdentifier(paste, index, &id) != noErr)
357 continue;
358
359 QCFType<CFArrayRef> types;
360 if (PasteboardCopyItemFlavors(paste, id, &types ) != noErr)
361 continue;
362
363 const int type_count = CFArrayGetCount(types);
364 for (int i = 0; i < type_count; ++i) {
365 const QString uti = QString::fromCFString((CFStringRef)CFArrayGetValueAtIndex(types, i));
366 qCDebug(lcQpaClipboard, " -%s [0x%x]", qPrintable(uti), uchar(scope));
368 if (!mimeType.isEmpty())
369 qCDebug(lcQpaClipboard, " - %s", qPrintable(mimeType));
370 if (mimeType == format)
371 return true;
372 }
373 }
374 return false;
375}
376
378{
379 if (!paste)
380 return QVariant();
381
382 sync();
383
384 ItemCount cnt = 0;
385 if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt)
386 return QByteArray();
387
388 qCDebug(lcQpaClipboard, "Pasteboard: retrieveData [%s]", qPrintable(format));
389 const QList<QUtiMimeConverter *> availableConverters = QMacMimeRegistry::all(scope);
390 for (const auto *c : availableConverters) {
391 const QString c_uti = c->utiForMime(format);
392 if (!c_uti.isEmpty()) {
393 // Converting via PasteboardCopyItemFlavorData below will for some UITs result
394 // in newlines mapping to '\r' instead of '\n'. To work around this we shortcut
395 // the conversion via NSPasteboard's NSStringPboardType if possible.
396 if (c_uti == "com.apple.traditional-mac-plain-text"_L1
397 || c_uti == "public.utf8-plain-text"_L1
398 || c_uti == "public.utf16-plain-text"_L1) {
400 if (!str.isEmpty())
401 return str;
402 }
403
405 QList<QByteArray> retList;
406 for (uint index = 1; index <= cnt; ++index) {
407 PasteboardItemID id;
408 if (PasteboardGetItemIdentifier(paste, index, &id) != noErr)
409 continue;
410
411 QCFType<CFArrayRef> types;
412 if (PasteboardCopyItemFlavors(paste, id, &types ) != noErr)
413 continue;
414
415 const int type_count = CFArrayGetCount(types);
416 for (int i = 0; i < type_count; ++i) {
417 const CFStringRef uti = static_cast<CFStringRef>(CFArrayGetValueAtIndex(types, i));
418 if (c_uti == QString::fromCFString(uti)) {
419 QCFType<CFDataRef> macBuffer;
420 if (PasteboardCopyItemFlavorData(paste, id, uti, &macBuffer) == noErr) {
421 QByteArray buffer((const char *)CFDataGetBytePtr(macBuffer),
422 CFDataGetLength(macBuffer));
423 if (!buffer.isEmpty()) {
424 qCDebug(lcQpaClipboard, " - %s [%s]", qPrintable(format),
425 qPrintable(c_uti));
426 buffer.detach(); //detach since we release the macBuffer
427 retList.append(buffer);
428 break; //skip to next element
429 }
430 }
431 } else {
432 qCDebug(lcQpaClipboard, " - NoMatch %s [%s]", qPrintable(c_uti),
433 qPrintable(QString::fromCFString(uti)));
434 }
435 }
436 }
437
438 if (!retList.isEmpty()) {
439 ret = c->convertToMime(format, retList, c_uti);
440 return ret;
441 }
442 }
443 }
444 return QVariant();
445}
446
447void QMacPasteboard::clear_helper()
448{
449 if (paste)
450 PasteboardClear(paste);
451 promises.clear();
452}
453
455{
456 qCDebug(lcQpaClipboard, "PasteBoard: clear!");
457 clear_helper();
458}
459
461{
462 if (!paste)
463 return false;
464 const bool fromGlobal = PasteboardSynchronize(paste) & kPasteboardModified;
465
466 if (fromGlobal)
467 const_cast<QMacPasteboard *>(this)->setMimeData(nullptr);
468
469 if (fromGlobal)
470 qCDebug(lcQpaClipboard, "Pasteboard: Synchronize!");
471 return fromGlobal;
472}
473
474
476{
478 NSPasteboard *pb = nil;
479 CFStringRef pbname;
480 if (PasteboardCopyName(paste, &pbname) == noErr) {
481 pb = [NSPasteboard pasteboardWithName:const_cast<NSString *>(reinterpret_cast<const NSString *>(pbname))];
482 CFRelease(pbname);
483 } else {
484 pb = [NSPasteboard generalPasteboard];
485 }
486 if (pb) {
487 NSString *text = [pb stringForType:NSPasteboardTypeString];
488 if (text)
489 return QString::fromNSString(text);
490 }
491 return QString();
492}
493
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:611
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
static QCoreApplication * instance() noexcept
Returns a pointer to the application's QCoreApplication (or QGuiApplication/QApplication) instance.
void append(parameter_type t)
Definition qlist.h:458
void clear()
Definition qlist.h:434
QVariant variantData(const QString &mime)
QVariant retrieveData(const QString &format, QMetaType) const override
Returns a variant with the given type containing data for the MIME type specified by mimeType.
QMacPasteboardMimeSource(const QMacPasteboard *p)
QStringList formats() const override
Returns a list of formats supported by the object.
QStringList formats() const
PasteboardRef pasteBoard() const
void setMimeData(QMimeData *mime, DataRequestType dataRequestType=EagerRequest)
QMimeData * mimeData() const
QMacPasteboard(PasteboardRef p, QUtiMimeConverter::HandlerScope scope=QUtiMimeConverter::HandlerScopeFlag::All)
bool sync() const
bool hasUti(const QString &uti) const
QVariant retrieveData(const QString &format) const
bool hasFormat(const QString &format) const
\inmodule QtCore
Definition qmetatype.h:341
int id(int=0) const
Definition qmetatype.h:475
\inmodule QtCore
Definition qmimedata.h:16
QVariant imageData() const
Returns a QVariant storing a QImage if the object can return an image; otherwise returns a null varia...
virtual QVariant retrieveData(const QString &mimetype, QMetaType preferredType) const
Returns a variant with the given type containing data for the MIME type specified by mimeType.
T * data() const noexcept
Definition qpointer.h:73
bool isNull() const noexcept
Definition qpointer.h:84
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
The QUtiMimeConverter class converts between a MIME type and a \l{https://developer....
virtual QList< QByteArray > convertFromMime(const QString &mime, const QVariant &data, const QString &uti) const =0
Returns data converted from MIME type mime to Mac UTI uti.
bool canConvert(const QString &mime, const QString &uti) const
Returns true if the converter can convert (both ways) between mime and uti; otherwise returns false.
\inmodule QtCore
Definition qvariant.h:65
QMetaType metaType() const
QString str
[2]
QString text
EGLint EGLint * formats
QList< QUtiMimeConverter * > all(QUtiMimeConverter::HandlerScope scope)
QString flavorToMime(QUtiMimeConverter::HandlerScope scope, const QString &uti)
Combined button and popup list for selecting options.
static jboolean paste(JNIEnv *, jobject)
long NSInteger
QList< QString > QStringList
Constructs a string list that contains the given string, str.
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
const char * mimeType
#define qDebug
[1]
Definition qlogging.h:164
#define qCWarning(category,...)
#define qCDebug(category,...)
QString qt_mac_get_pasteboardString(PasteboardRef paste)
QString qt_mac_get_pasteboardString(PasteboardRef paste)
#define PasteboardGetItemCount
return ret
const GLfloat * m
GLuint index
[2]
GLsizei GLenum GLenum * types
GLenum GLuint id
[7]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLbitfield flags
GLenum GLuint GLintptr offset
GLuint name
GLint GLsizei GLsizei GLenum format
const GLubyte * c
GLuint64EXT * result
[6]
GLfloat GLfloat p
[1]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define qPrintable(string)
Definition qstring.h:1531
unsigned char uchar
Definition qtypes.h:32
unsigned int uint
Definition qtypes.h:34
QByteArray ba
[0]
application x qt windows mime
[2]
QMimeData * mimeData
QGraphicsItem * item