Rabbit Remote Control 0.1.0-bate7
Loading...
Searching...
No Matches
ClipboardMimeData.cpp
1// Author: Kang Lin <kl222@126.com>
2
3// see: https://learn.microsoft.com/en-us/windows/win32/shell/clipboard
4
5#include "ClipboardMimeData.h"
6#include "ClipboardFreeRDP.h"
7#include <QLoggingCategory>
8#include <QEventLoop>
9#include <QRegularExpression>
10#include <QImage>
11#include <QUrl>
12#include <QDir>
13#include <QFileInfo>
14#include <QDateTime>
15
16#if !(defined (Q_OS_WINDOWS) || defined(Q_OS_WIN) || defined(Q_OS_WIN32) || defined(Q_OS_WINRT))
17 //
18 // format of CF_FILEGROUPDESCRIPTOR
19 //
20 typedef struct _FILEGROUPDESCRIPTORW { // fgd
21 UINT cItems;
22 FILEDESCRIPTORW fgd[1];
24
25#endif
26
27QAtomicInteger<qint32> CClipboardMimeData::m_nId(1);
28static int g_UINT32 = qRegisterMetaType<UINT32>("UINT32");
29
30static Q_LOGGING_CATEGORY(log, "FreeRDP.Clipboard.MimeData")
31
32CClipboardMimeData::CClipboardMimeData(CliprdrClientContext *pContext)
33 : QMimeData(),
34 m_pContext(pContext),
35 m_pClipboard(nullptr),
36 m_bExit(false)
37{
38 m_Id = m_nId++;
39 while(0 == m_Id)
40 m_Id = m_nId++;
41 qDebug(log) << "CClipboardMimeData::CClipboardMimeData:" << GetId();
42
43 CClipboardFreeRDP* pThis = (CClipboardFreeRDP*)pContext->custom;
44 m_pClipboard = pThis->m_pClipboard;
45 bool check = false;
46 check = connect(this, SIGNAL(sigRequestFileFromServer(const QString&,
47 const QString&,
48 const void*,
49 const UINT32)),
50 this, SLOT(slotRequestFileFromServer(const QString&,
51 const QString&,
52 const void*,
53 const UINT32)),
54 Qt::DirectConnection);
55 Q_ASSERT(check);
56}
57
58CClipboardMimeData::~CClipboardMimeData()
59{
60 qDebug(log) << "CClipboardMimeData::~CClipboardMimeData():" << GetId();
61 m_bExit = true;
62 emit sigContinue();
63 qDebug(log) << "CClipboardMimeData::~CClipboardMimeData() end:" << GetId();
64}
65
66const qint32 CClipboardMimeData::GetId() const
67{
68 return m_Id;
69}
70
71int CClipboardMimeData::SetFormat(const CLIPRDR_FORMAT_LIST *pList)
72{
73 if(!m_pClipboard)
74 {
75 Q_ASSERT(FALSE);
76 return -1;
77 }
78 m_Formats.clear();
79 for (UINT32 i = 0; i < pList->numFormats; i++)
80 {
81 CLIPRDR_FORMAT* pFormat = &pList->formats[i];
82 /*
83 qDebug(log) << "Format Id:" << pFormat->formatId
84 << "name:" << pFormat->formatName;//*/
85 AddFormat(pFormat->formatId, pFormat->formatName);
86 }
87
88 if(m_Formats.isEmpty())
89 return 0;
90
91 QString szFormats;
92 for(auto it = m_Formats.begin(); it != m_Formats.end(); it++)
93 {
94 //*
95 szFormats += QString::number(it->id) + "[";
96 szFormats += it->name;
97 szFormats += "]; "; //*/
98
99 m_indexId.insert(it->id, *it);
100 if(it->name.isEmpty())
101 {
102 switch (it->id) {
103 case CF_TEXT:
104 case CF_OEMTEXT:
105 case CF_UNICODETEXT:
106 case CF_LOCALE:
107 {
108 m_indexString.insert("text/plain", *it);
109 break;
110 }
111 case CF_DIB:
112 //case CF_BITMAP:
113 case CF_DIBV5:
114 {
115 m_indexString.insert("image/bmp", *it);
116 break;
117 }
118 case CF_HDROP:
119 {
120 m_indexString.insert("text/uri-list", *it);
121 break;
122 }
123 default:
124 {
125 const char* name = ClipboardGetFormatName(m_pClipboard, it->id);
126 if(name)
127 m_indexString.insert(name, *it);
128 }
129 }
130 } else {
131 m_indexString.insert(it->name, *it);
132 if("FileGroupDescriptorW" == it->name) {
133#ifdef Q_OS_WINDOWS
134 m_indexString.insert("text/uri-list", *it);
135#else
136 m_indexString.insert("x-special/gnome-copied-files", *it);
137#endif
138 } else if("FileGroupDescriptor" == it->name) {
139#ifdef Q_OS_WINDOWS
140 m_indexString.insert("text/uri-list", *it);
141#else
142 m_indexString.insert("x-special/gnome-copied-files", *it);
143#endif
144 } else if("UniformResourceLocatorW" == it->name) {
145 m_indexString.insert("text/uri-list", *it);
146 } else if("UniformResourceLocator" == it->name) {
147 m_indexString.insert("text/uri-list", *it);
148 } else if("x-special/gnome-copied-files" == it->name) {
149 m_indexString.insert("text/uri-list", *it);
150 } else if("text/html" != it->name && isHtml(it->name, false)) {
151 m_indexString.insert("text/html", *it);
152 } else if("text/plain" != it->name && isText(it->name, false)) {
153 m_indexString.insert("text/plain", *it);
154 } else if("image/bmp" != it->name && isImage(it->name)) {
155 m_indexString.insert("image/bmp", *it);
156 }
157 }
158 }
159
160 m_lstFormats.clear();
161 for(auto it = m_indexString.begin(); m_indexString.end() != it; it++)
162 {
163 if(!m_lstFormats.contains(it.key()))
164 m_lstFormats << (it.key());
165 }
166 if(m_lstFormats.contains("image/bmp")
167 && !m_lstFormats.contains("application/x-qt-image"))
168 {
169 m_lstFormats << ("application/x-qt-image");
170 }
171
172 // Only used by linux or unix
173 if(m_lstFormats.contains("text/uri-list")
174 && !m_lstFormats.contains("x-special/gnome-copied-files"))
175 {
176 m_lstFormats.push_front("x-special/gnome-copied-files");
177 m_lstFormats.removeOne("text/uri-list");
178 m_lstFormats.push_front("text/uri-list");
179 }
180
181 // Only used by windows
182 if(m_lstFormats.contains("text/uri-list")
183 && !m_lstFormats.contains("FileGroupDescriptorW"))
184 {
185 m_lstFormats.push_front("FileGroupDescriptorW");
186 m_lstFormats.removeOne("text/uri-list");
187 m_lstFormats.push_front("text/uri-list");
188 }
189
190 // Used to identify oneself
191 m_lstFormats << MIME_TYPE_RABBITREMOTECONTROL_PLUGINS_FREERDP;
192
193 qDebug(log) << "CClipboardMimeData::SetFormat: input formats:" << szFormats
194 << "Formats:" << m_lstFormats;
195
196 return 0;
197}
198
199int CClipboardMimeData::AddFormat(UINT32 id, const char *name)
200{
201 int nRet = 0;
202
203 foreach(auto it, m_Formats)
204 {
205 if(it.id == id)
206 {
207 qWarning(log) << "Repeat format id:" << id;
208 return -1;
209 }
210
211 if(name)
212 {
213 if(name == it.name)
214 {
215 qWarning(log) << "Repeat format name:" << name;
216 return -2;
217 }
218 }
219 }
220
221 _FORMAT f = {id, name, id};
222 if(name)
223 {
224 f.localId = ClipboardRegisterFormat(m_pClipboard, name);
225 }
226
227 m_Formats.push_back(f);
228
229 return nRet;
230}
231
232bool CClipboardMimeData::hasFormat(const QString &mimetype) const
233{
234 //*
235 qDebug(log) << "CClipboardMimeData::hasFormat:"
236 << mimetype.toStdString().c_str();//*/
237
238 if(isImage(mimetype) && m_lstFormats.contains("image/bmp"))
239 return true;
240 if(isUrls(mimetype) && m_lstFormats.contains("text/uri-list"))
241 return true;
242 return m_lstFormats.contains(mimetype);
243}
244
245QStringList CClipboardMimeData::formats() const
246{
247 /*
248 qDebug(log) << "CClipboardMimeData::formats:" << m_lstFormats; //*/
249 return m_lstFormats;
250}
251
252#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
253QVariant CClipboardMimeData::retrieveData(const QString &mimeType,
254 QMetaType preferredType) const
255
256#else
257QVariant CClipboardMimeData::retrieveData(const QString &mimeType,
258 QVariant::Type preferredType) const
259#endif
260{
261 //*
262 qDebug(log) << "CClipboardMimeData::retrieveData:" << GetId() << mimeType
263 << "Variant:" << m_Variant; //*/
264 if(MIME_TYPE_RABBITREMOTECONTROL_PLUGINS_FREERDP == mimeType)
265 return GetId();
266
267 QString mt = mimeType;
268 if(isImage(mt)) mt = "image/bmp";
269 if(m_indexString.find(mt) == m_indexString.end())
270 return QVariant();
271
272 _FORMAT value;
273 auto lstValue = m_indexString.values(mt);
274 if(lstValue.isEmpty())
275 return QVariant();
276 value = *lstValue.crbegin();
277 //*
278 qDebug(log) << "CClipboardMimeData::retrieveData: format id:" << value.id
279 << "name:" << value.name << "mimeData:" << mimeType; //*/
280
281 if(m_Variant.isValid() && !m_Variant.isNull()) {
282 if(isUrls(mimeType))
283 {
284 QByteArray data = m_Variant.toByteArray();
285 emit sigRequestFileFromServer(mimeType, value.name, data.data(), data.size());
286 }
287 return m_Variant;
288 }
289 if(!m_pContext) return QVariant();
290
291 emit sigSendDataRequest(m_pContext, value.id);
292
293 // add wait response event
294 QEventLoop loop;
295 connect(this, SIGNAL(sigContinue()), &loop, SLOT(quit()), Qt::DirectConnection);
296 loop.exec();
297 qDebug(log) << "CClipboardMimeData::retrieveData end";
298 // Objecte destruct
299 if(m_bExit)
300 return QVariant();
301
302 if(isUrls(mimeType) && !m_Variant.isNull())
303 {
304 QByteArray data = m_Variant.toByteArray();
305 emit sigRequestFileFromServer(mimeType, value.name, data.data(), data.size());
306 }
307 return m_Variant;
308}
309
311void CClipboardMimeData::slotServerFormatData(const BYTE* pData, UINT32 nLen,
312 UINT32 id)
313{
314 //*
315 qDebug(log) << "CClipboardMimeData::slotServerFormatData: id:" << id;//*/
316
317 UINT32 srcId = 0;
318 UINT32 dstId = 0;
319 do{
320 if(!pData && 0 == nLen)
321 {
322 m_pContext = nullptr;
323 break;
324 }
325
326 if(m_indexId.find(id) == m_indexId.end())
327 break;
328
329 auto it = m_indexId[id];
330 switch (id) {
331 case CF_DIB:
332 case CF_DIBV5:
333 {
334 srcId = it.localId;
335 dstId = ClipboardGetFormatId(m_pClipboard, "image/bmp");
336 break;
337 }
338 default:
339 {
340 srcId = it.localId;
341 if(it.name.isEmpty())
342 dstId = it.localId;
343 else {
344 if(isHtml(it.name, false))
345 dstId = ClipboardGetFormatId(m_pClipboard, "text/html");
346 else
347 dstId = it.localId;
348 }
349 }
350 }
351 bool bSuccess = ClipboardSetData(m_pClipboard, srcId, pData, nLen);
352 if(!bSuccess) break;
353
354 UINT32 size = 0;
355 void* data = ClipboardGetData(m_pClipboard, dstId, &size);
356 if(!data)
357 {
358 qDebug(log) << "ClipboardGetData fail: dstId:" << dstId
359 << "srcId:" << srcId;
360 break;
361 }
362
363 QByteArray d((char*)data, size);
364 if(d.isEmpty()) break;
365
366 switch (id) {
367 case CF_TEXT:
368 {
369 m_Variant = QString::fromLatin1(d);
370 break;
371 }
372 case CF_OEMTEXT:
373 {
374#ifdef Q_OS_WINDOWS
375 m_Variant = QString::fromLocal8Bit(d);
376#else
377 m_Variant = d;
378#endif
379 break;
380 }
381 case CF_UNICODETEXT:
382 {
383 m_Variant = QString::fromUtf16((const char16_t*)data, size / 2);
384 break;
385 }
386 default:
387 if("UTF8_STRING" == it.name) {
388 m_Variant = QString::fromUtf8(d);
389 } else if(isHtml(it.name)) {
390 m_Variant = QString(d);
391 } else if(ClipboardGetFormatId(m_pClipboard, "image/bmp") == dstId) {
392 QImage img;
393 if(img.loadFromData(d, "BMP"))
394 m_Variant = img;
395 } else
396 m_Variant = QVariant(d);
397 }
398 }while(0);
399 emit sigContinue();
400 return;
401}
402
403bool CClipboardMimeData::isText(QString mimeType, bool bRegular) const
404{
405 //qDebug(log) << "CClipboardMimeData::isText:" << mimeType;
406 if("UTF8_STRING" == mimeType) return true;
407 if("TEXT" == mimeType) return true;
408 if("STRING" == mimeType) return true;
409 if("text/plain" == mimeType) return true;
410 if(bRegular)
411 {
412 QRegularExpression re("text/plain[;]*.*",
413 QRegularExpression::CaseInsensitiveOption);
414 QRegularExpressionMatch match = re.match(mimeType);
415 if(match.hasMatch())
416 return true;
417 }
418 return false;
419}
420
421bool CClipboardMimeData::isHtml(QString mimeType, bool bRegular) const
422{
423 //qDebug(log) << "CClipboardMimeData::isHtml:" << mimeType;
424
425 if("text/html" == mimeType || "HTML Format" == mimeType)
426 return true;
427
428 return false;
429}
430
431bool CClipboardMimeData::isUrls(QString mimeType, bool bRegular) const
432{
433 //qDebug(log) << "CClipboardMimeData::isUrls:" << mimeType;
434
435 if("FileGroupDescriptorW" == mimeType
436 || "FileGroupDescriptor" == mimeType
437 || "UniformResourceLocatorW" == mimeType
438 || "UniformResourceLocator" == mimeType
439 || "text/uri-list" == mimeType
440 || "x-special/gnome-copied-files" == mimeType)
441 return true;
442
443 return false;
444}
445
446bool CClipboardMimeData::isImage(QString mimeType, bool bRegular) const
447{
448 //qDebug(log) << "CClipboardMimeData::isImage:" << mimeType;
449
450 if("image/bmp" == mimeType) return true;
451 // QClipboard return QImage mimeType is "application/x-qt-image"
452 if("application/x-qt-image" == mimeType) return true;
453 if(bRegular)
454 {
455 QRegularExpression re("image/.*",
456 QRegularExpression::CaseInsensitiveOption);
457 QRegularExpressionMatch match = re.match(mimeType);
458 if(match.hasMatch())
459 return true;
460 }
461 return false;
462}
463
464void CClipboardMimeData::slotRequestFileFromServer(const QString &mimeType,
465 const QString &valueName,
466 const void *pData,
467 const UINT32 nLen)
468{
469 return; //TODO: delete it!!!
470 //*
471 qDebug(log) << "CClipboardMimeData::slotRequestFileFromServer:"
472 << valueName << mimeType << pData;//*/
473 if(!("FileGroupDescriptorW" == valueName
474 || "FileGroupDescriptor" == valueName))
475 return;
476
477 int srcId = ClipboardGetFormatId(m_pClipboard, valueName.toStdString().c_str());
478 int dstId = ClipboardGetFormatId(m_pClipboard, mimeType.toStdString().c_str());
479 bool bSuccess = ClipboardSetData(m_pClipboard, srcId, pData, nLen);
480 if(!bSuccess) {
481 qCritical(log) << "ClipboardSetData fail: dstId:" << dstId
482 << "srcId:" << srcId;
483 return;
484 }
485
486 UINT32 size = 0;
487 void* data = ClipboardGetData(m_pClipboard, dstId, &size);
488 if(!data) {
489 qCritical(log) << "ClipboardGetData fail: dstId:" << dstId
490 << "srcId:" << srcId;
491 return;
492 }
493 QString szFiles = QString::fromLatin1((char*)data, size);
494 QStringList lstFile = szFiles.split("\n");
495 free(data);
496
498 for(int i = 0; i < pDes->cItems; i++)
499 {
500 QString szFile = lstFile[i].trimmed();
501 szFile = QUrl(szFile).toLocalFile();
502 QFileInfo fileInfo(szFile);
503 QDir d(fileInfo.absolutePath());
504 if(!d.exists())
505 d.mkpath(fileInfo.absolutePath());
506
507 QSharedPointer<_CliprdrFileStream> stream
508 = QSharedPointer<_CliprdrFileStream>(new _CliprdrFileStream());
509 stream->m_Success = false;
510 stream->m_File.setFileName(szFile) ;
511 m_Stream.insert(i, stream);
512
513 if(pDes->fgd[i].dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
514 continue;
515 // Get file size
516 UINT rc = sendRequestFilecontents(i, FILECONTENTS_SIZE, 0, 0, 8);
517 if(CHANNEL_RC_OK != rc)
518 {
519 continue;
520 }
521 if(stream->m_Data.isNull() || stream->m_Data.size() == 0)
522 continue;
523 ULARGE_INTEGER size, offset;
524 offset.QuadPart = 0;
525 size.QuadPart = *((LONGLONG*)(stream->m_Data.data()));
526 if(size.QuadPart <= 0)
527 continue;
528
529 qDebug(log) << "File" << szFile
530 << ";Length:" << size.u.HighPart << size.u.LowPart;
531 // Open local file
532 if(!stream->m_File.open(QFile::WriteOnly))
533 {
534 qCritical(log) << "Open file fail:" << szFile
535 << stream->m_File.errorString();
536 return;
537 }
538 bool bSuccess = true;
539 do {
540 UINT32 nBlock = 2 << 15;
541 UINT32 nLen = size.QuadPart - offset.QuadPart;
542 if(nLen > nBlock)
543 nLen = nBlock;
544 // Request file from server
545 rc = sendRequestFilecontents(i, FILECONTENTS_RANGE,
546 offset.u.HighPart,
547 offset.u.LowPart,
548 nLen);
549 if(CHANNEL_RC_OK != rc)
550 {
551 bSuccess = false;
552 break;
553 }
554 if(stream->m_Data.isNull() || stream->m_Data.size() == 0)
555 {
556 bSuccess = false;
557 break;
558 }
559 // Save to local file
560 stream->m_File.write(stream->m_Data);
561 offset.QuadPart += stream->m_Data.size();
562 } while(offset.QuadPart < size.QuadPart);
563 stream->m_File.close();
564 if(!bSuccess)
565 stream->m_File.remove();
566 stream->m_Success = bSuccess;
567 }
568
569 // Convert file list
570 // "x-special/gnome-copied-files" format is copy\nLocalFile1\nLocalFile2\n...
571 if("x-special/gnome-copied-files" == mimeType)
572 {
573 QByteArray gnomeFormat;
574 gnomeFormat.append("copy\n");
575 int b = 0;
576 foreach(auto s, m_Stream)
577 {
578 if(!s->m_Success)
579 continue;
580 QString fileName;
581 if(b)
582 fileName += "\n";
583 else
584 b=1;
585 fileName += QUrl::fromLocalFile(s->m_File.fileName()).toEncoded();
586 gnomeFormat.append(fileName.toStdString().c_str());
587 }
588 m_gnomeFiles = gnomeFormat;
589 m_Variant = gnomeFormat;
590 }
591 //*
592 // "text/uri-list" format is LocalFile1\r\nLocalFile2\r\n...
593 // See:
594 // URI is specified by RFC 8089: https://datatracker.ietf.org/doc/html/rfc8089
595 // uri syntax: https://www.rfc-editor.org/rfc/rfc3986#section-3
596 // uri-lists format: https://www.rfc-editor.org/rfc/rfc2483#section-5
597 if("text/uri-list" == mimeType || "FileGroupDescriptorW" == mimeType)
598 {
599 QByteArray uriFormat;
600 foreach(auto s, m_Stream)
601 {
602 if(!s->m_Success)
603 continue;
604 QString fileName;
605 fileName += QUrl::fromLocalFile(s->m_File.fileName()).toEncoded();
606 fileName += "\r\n";
607 uriFormat.append(fileName.toStdString().c_str());
608 }
609 m_uriFiles = uriFormat;
610 m_Variant = uriFormat;
611 } //*/
612
613 qDebug(log) << "CClipboardMimeData::slotRequestFileFromServer::QVariant:" << m_Variant;
614
615 return;
616}
617
618UINT CClipboardMimeData::sendRequestFilecontents(UINT32 listIndex,
619 UINT32 dwFlags,
620 DWORD nPositionHigh,
621 DWORD nPositionLow,
622 UINT32 cbRequested)
623{
624 //*
625 qDebug(log) << "CClipboardMimeData::sendRequestFilecontents";//*/
626 UINT rc = ERROR_INTERNAL_ERROR;
627 if(!m_pContext) return rc;
628
629 CLIPRDR_FILE_CONTENTS_REQUEST fileContentsRequest = {0};
630 fileContentsRequest.streamId = listIndex;
631 fileContentsRequest.listIndex = listIndex;
632 fileContentsRequest.dwFlags = dwFlags;
633 switch (dwFlags)
634 {
635 /*
636 * [MS-RDPECLIP] 2.2.5.3 File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST).
637 *
638 * A request for the size of the file identified by the lindex field. The size MUST be
639 * returned as a 64-bit, unsigned integer. The cbRequested field MUST be set to
640 * 0x00000008 and both the nPositionLow and nPositionHigh fields MUST be
641 * set to 0x00000000.
642 */
643 case FILECONTENTS_SIZE:
644 fileContentsRequest.cbRequested = sizeof(UINT64);
645 fileContentsRequest.nPositionHigh = 0;
646 fileContentsRequest.nPositionLow = 0;
647 break;
648 case FILECONTENTS_RANGE:
649 fileContentsRequest.cbRequested = cbRequested;
650 fileContentsRequest.nPositionHigh = nPositionHigh;
651 fileContentsRequest.nPositionLow = nPositionLow;
652 break;
653 }
654 rc = m_pContext->ClientFileContentsRequest(m_pContext, &fileContentsRequest);
655
656 // add wait response event
657 QEventLoop loop;
658 connect(this, SIGNAL(sigContinue()), &loop, SLOT(quit()), Qt::DirectConnection);
659 loop.exec();
660
661 // Objecte destruct
662 if(m_bExit)
663 return CHANNEL_RC_NULL_DATA;
664
665 return rc;
666}
667
668void CClipboardMimeData::slotServerFileContentsRespose(UINT32 streamId,
669 QByteArray &data)
670{
671 //*
672 qDebug(log) << "CClipboardMimeData::slotServerFileContentsRespose: index:"
673 << streamId << ";Data length:" << data.size();//*/
674 auto stream = m_Stream.find(streamId);
675 do{
676 if(m_Stream.end() == stream || data.isNull())
677 break;
678 QSharedPointer<_CliprdrFileStream> s = *stream;
679 s->m_Data = data;
680 }while(0);
681 emit sigContinue();
682}
void slotServerFormatData(const BYTE *pData, UINT32 nLen, UINT32 id)
if(pData == nullptr && nLen == 0) is Notify clipboard program has exited