1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 */
10
11 #include <Qt5Transferable.hxx>
12
13 #include <comphelper/sequence.hxx>
14 #include <sal/log.hxx>
15
16 #include <QtWidgets/QApplication>
17
18 #include <Qt5Instance.hxx>
19 #include <Qt5Tools.hxx>
20
21 #include <cassert>
22
lcl_textMimeInfo(const OUString & rMimeString,bool & bHaveNoCharset,bool & bHaveUTF16,bool & bHaveUTF8)23 static bool lcl_textMimeInfo(const OUString& rMimeString, bool& bHaveNoCharset, bool& bHaveUTF16,
24 bool& bHaveUTF8)
25 {
26 sal_Int32 nIndex = 0;
27 if (rMimeString.getToken(0, ';', nIndex) == "text/plain")
28 {
29 OUString aToken(rMimeString.getToken(0, ';', nIndex));
30 if (aToken == "charset=utf-16")
31 bHaveUTF16 = true;
32 else if (aToken == "charset=utf-8")
33 bHaveUTF8 = true;
34 else if (aToken.isEmpty())
35 bHaveNoCharset = true;
36 else // we just handle UTF-16 and UTF-8, everything else is "bytes"
37 return false;
38 return true;
39 }
40 return false;
41 }
42
Qt5Transferable(const QMimeData * pMimeData)43 Qt5Transferable::Qt5Transferable(const QMimeData* pMimeData)
44 : m_pMimeData(pMimeData)
45 , m_bConvertFromLocale(false)
46 {
47 assert(pMimeData);
48 }
49
getTransferDataFlavors()50 css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL Qt5Transferable::getTransferDataFlavors()
51 {
52 // it's just filled once, ever, so just try to get it without locking first
53 if (m_aMimeTypeSeq.hasElements())
54 return m_aMimeTypeSeq;
55
56 // better safe then sorry; preventing broken usage
57 // DnD should not be shared and Clipboard access runs in the GUI thread
58 osl::MutexGuard aGuard(m_aMutex);
59 if (m_aMimeTypeSeq.hasElements())
60 return m_aMimeTypeSeq;
61
62 QStringList aFormatList(m_pMimeData->formats());
63 // we might add the UTF-16 mime text variant later
64 const int nMimeTypeSeqSize = aFormatList.size() + 1;
65 bool bHaveNoCharset = false, bHaveUTF16 = false;
66 css::uno::Sequence<css::datatransfer::DataFlavor> aMimeTypeSeq(nMimeTypeSeqSize);
67
68 css::datatransfer::DataFlavor aFlavor;
69 int nMimeTypeCount = 0;
70
71 for (const QString& rMimeType : aFormatList)
72 {
73 // filter out non-MIME types such as TARGETS, MULTIPLE, TIMESTAMP
74 if (rMimeType.indexOf('/') == -1)
75 continue;
76
77 // gtk3 thinks it is not well defined - skip too
78 if (rMimeType == QStringLiteral("text/plain;charset=unicode"))
79 continue;
80
81 // LO doesn't like 'text/plain', so we have to provide UTF-16
82 bool bIsNoCharset = false, bIsUTF16 = false, bIsUTF8 = false;
83 if (lcl_textMimeInfo(toOUString(rMimeType), bIsNoCharset, bIsUTF16, bIsUTF8))
84 {
85 bHaveNoCharset |= bIsNoCharset;
86 bHaveUTF16 |= bIsUTF16;
87 if (bIsUTF16)
88 aFlavor.DataType = cppu::UnoType<OUString>::get();
89 else
90 aFlavor.DataType = cppu::UnoType<css::uno::Sequence<sal_Int8>>::get();
91 }
92 else
93 aFlavor.DataType = cppu::UnoType<css::uno::Sequence<sal_Int8>>::get();
94
95 aFlavor.MimeType = toOUString(rMimeType);
96 assert(nMimeTypeCount < nMimeTypeSeqSize);
97 aMimeTypeSeq[nMimeTypeCount] = aFlavor;
98 nMimeTypeCount++;
99 }
100
101 m_bConvertFromLocale = bHaveNoCharset && !bHaveUTF16;
102 if (m_bConvertFromLocale)
103 {
104 aFlavor.MimeType = "text/plain;charset=utf-16";
105 aFlavor.DataType = cppu::UnoType<OUString>::get();
106 assert(nMimeTypeCount < nMimeTypeSeqSize);
107 aMimeTypeSeq[nMimeTypeCount] = aFlavor;
108 nMimeTypeCount++;
109 }
110
111 aMimeTypeSeq.realloc(nMimeTypeCount);
112
113 m_aMimeTypeSeq = aMimeTypeSeq;
114 return m_aMimeTypeSeq;
115 }
116
117 sal_Bool SAL_CALL
isDataFlavorSupported(const css::datatransfer::DataFlavor & rFlavor)118 Qt5Transferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
119 {
120 const auto aSeq = getTransferDataFlavors();
121 return std::any_of(aSeq.begin(), aSeq.end(), [&](const css::datatransfer::DataFlavor& aFlavor) {
122 return rFlavor.MimeType == aFlavor.MimeType;
123 });
124 }
125
126 css::uno::Any SAL_CALL
getTransferData(const css::datatransfer::DataFlavor & rFlavor)127 Qt5Transferable::getTransferData(const css::datatransfer::DataFlavor& rFlavor)
128 {
129 css::uno::Any aAny;
130 if (!isDataFlavorSupported(rFlavor))
131 return aAny;
132
133 if (rFlavor.MimeType == "text/plain;charset=utf-16")
134 {
135 OUString aString;
136 if (m_bConvertFromLocale)
137 {
138 QByteArray aByteData(m_pMimeData->data(QStringLiteral("text/plain")));
139 aString = OUString(reinterpret_cast<const sal_Char*>(aByteData.data()),
140 aByteData.size(), osl_getThreadTextEncoding());
141 }
142 else
143 {
144 QByteArray aByteData(m_pMimeData->data(toQString(rFlavor.MimeType)));
145 aString = OUString(reinterpret_cast<const sal_Unicode*>(aByteData.data()),
146 aByteData.size() / 2);
147 }
148 aAny <<= aString;
149 }
150 else
151 {
152 QByteArray aByteData(m_pMimeData->data(toQString(rFlavor.MimeType)));
153 css::uno::Sequence<sal_Int8> aSeq(reinterpret_cast<const sal_Int8*>(aByteData.data()),
154 aByteData.size());
155 aAny <<= aSeq;
156 }
157
158 return aAny;
159 }
160
Qt5ClipboardTransferable(const QClipboard::Mode aMode,const QMimeData * pMimeData)161 Qt5ClipboardTransferable::Qt5ClipboardTransferable(const QClipboard::Mode aMode,
162 const QMimeData* pMimeData)
163 : Qt5Transferable(pMimeData)
164 , m_aMode(aMode)
165 {
166 }
167
hasInFlightChanged() const168 bool Qt5ClipboardTransferable::hasInFlightChanged() const
169 {
170 const bool bChanged(mimeData() != QApplication::clipboard()->mimeData(m_aMode));
171 SAL_WARN_IF(bChanged, "vcl.qt5",
172 "In flight clipboard change detected - broken clipboard read!");
173 return bChanged;
174 }
175
176 css::uno::Any SAL_CALL
getTransferData(const css::datatransfer::DataFlavor & rFlavor)177 Qt5ClipboardTransferable::getTransferData(const css::datatransfer::DataFlavor& rFlavor)
178 {
179 css::uno::Any aAny;
180 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
181 SolarMutexGuard g;
182 pSalInst->RunInMainThread([&, this]() {
183 if (!hasInFlightChanged())
184 aAny = Qt5Transferable::getTransferData(rFlavor);
185 });
186 return aAny;
187 }
188
189 css::uno::Sequence<css::datatransfer::DataFlavor>
getTransferDataFlavors()190 SAL_CALL Qt5ClipboardTransferable::getTransferDataFlavors()
191 {
192 css::uno::Sequence<css::datatransfer::DataFlavor> aSeq;
193 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
194 SolarMutexGuard g;
195 pSalInst->RunInMainThread([&, this]() {
196 if (!hasInFlightChanged())
197 aSeq = Qt5Transferable::getTransferDataFlavors();
198 });
199 return aSeq;
200 }
201
202 sal_Bool SAL_CALL
isDataFlavorSupported(const css::datatransfer::DataFlavor & rFlavor)203 Qt5ClipboardTransferable::isDataFlavorSupported(const css::datatransfer::DataFlavor& rFlavor)
204 {
205 bool bIsSupported = false;
206 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
207 SolarMutexGuard g;
208 pSalInst->RunInMainThread([&, this]() {
209 if (!hasInFlightChanged())
210 bIsSupported = Qt5Transferable::isDataFlavorSupported(rFlavor);
211 });
212 return bIsSupported;
213 }
214
Qt5MimeData(const css::uno::Reference<css::datatransfer::XTransferable> & xTrans)215 Qt5MimeData::Qt5MimeData(const css::uno::Reference<css::datatransfer::XTransferable>& xTrans)
216 : m_aContents(xTrans)
217 , m_bHaveNoCharset(false)
218 , m_bHaveUTF8(false)
219 {
220 assert(xTrans.is());
221 }
222
deepCopy(QMimeData ** const pMimeCopy) const223 bool Qt5MimeData::deepCopy(QMimeData** const pMimeCopy) const
224 {
225 if (!pMimeCopy)
226 return false;
227
228 QMimeData* pMimeData = new QMimeData();
229 for (QString& format : formats())
230 {
231 QByteArray aData = data(format);
232 // Checking for custom MIME types
233 if (format.startsWith("application/x-qt"))
234 {
235 // Retrieving true format name
236 int indexBegin = format.indexOf('"') + 1;
237 int indexEnd = format.indexOf('"', indexBegin);
238 format = format.mid(indexBegin, indexEnd - indexBegin);
239 }
240 pMimeData->setData(format, aData);
241 }
242
243 *pMimeCopy = pMimeData;
244 return true;
245 }
246
formats() const247 QStringList Qt5MimeData::formats() const
248 {
249 if (!m_aMimeTypeList.isEmpty())
250 return m_aMimeTypeList;
251
252 const css::uno::Sequence<css::datatransfer::DataFlavor> aFormats
253 = m_aContents->getTransferDataFlavors();
254 QStringList aList;
255 bool bHaveUTF16 = false;
256
257 for (const auto& rFlavor : aFormats)
258 {
259 aList << toQString(rFlavor.MimeType);
260 lcl_textMimeInfo(rFlavor.MimeType, m_bHaveNoCharset, bHaveUTF16, m_bHaveUTF8);
261 }
262
263 // we provide a locale encoded and a UTF-8 variant, if missing
264 if (m_bHaveNoCharset || bHaveUTF16 || m_bHaveUTF8)
265 {
266 // if there is a text representation from LO point of view, it'll be UTF-16
267 assert(bHaveUTF16);
268 if (!m_bHaveUTF8)
269 aList << QStringLiteral("text/plain;charset=utf-8");
270 if (!m_bHaveNoCharset)
271 aList << QStringLiteral("text/plain");
272 }
273
274 m_aMimeTypeList = aList;
275 return m_aMimeTypeList;
276 }
277
retrieveData(const QString & mimeType,QVariant::Type) const278 QVariant Qt5MimeData::retrieveData(const QString& mimeType, QVariant::Type) const
279 {
280 if (!hasFormat(mimeType))
281 return QVariant();
282
283 css::datatransfer::DataFlavor aFlavor;
284 aFlavor.MimeType = toOUString(mimeType);
285 aFlavor.DataType = cppu::UnoType<css::uno::Sequence<sal_Int8>>::get();
286
287 bool bWantNoCharset = false, bWantUTF16 = false, bWantUTF8 = false;
288 if (lcl_textMimeInfo(aFlavor.MimeType, bWantNoCharset, bWantUTF16, bWantUTF8))
289 {
290 if ((bWantNoCharset && !m_bHaveNoCharset) || (bWantUTF8 && !m_bHaveUTF8))
291 {
292 aFlavor.MimeType = "text/plain;charset=utf-16";
293 aFlavor.DataType = cppu::UnoType<OUString>::get();
294 }
295 else if (bWantUTF16)
296 aFlavor.DataType = cppu::UnoType<OUString>::get();
297 }
298
299 css::uno::Any aValue;
300
301 try
302 {
303 // tdf#129809 take a reference in case m_aContents is replaced during this call
304 css::uno::Reference<com::sun::star::datatransfer::XTransferable> xCurrentContents(
305 m_aContents);
306 aValue = xCurrentContents->getTransferData(aFlavor);
307 }
308 catch (...)
309 {
310 }
311
312 QByteArray aByteArray;
313 if (aValue.getValueTypeClass() == css::uno::TypeClass_STRING)
314 {
315 OUString aString;
316 aValue >>= aString;
317
318 if (bWantUTF8)
319 {
320 OString aUTF8String(OUStringToOString(aString, RTL_TEXTENCODING_UTF8));
321 aByteArray = QByteArray(reinterpret_cast<const char*>(aUTF8String.getStr()),
322 aUTF8String.getLength());
323 }
324 else if (bWantNoCharset)
325 {
326 OString aLocaleString(OUStringToOString(aString, osl_getThreadTextEncoding()));
327 aByteArray = QByteArray(reinterpret_cast<const char*>(aLocaleString.getStr()),
328 aLocaleString.getLength());
329 }
330 else
331 return QVariant(toQString(aString));
332 }
333 else
334 {
335 css::uno::Sequence<sal_Int8> aData;
336 aValue >>= aData;
337 aByteArray
338 = QByteArray(reinterpret_cast<const char*>(aData.getConstArray()), aData.getLength());
339 }
340 return QVariant::fromValue(aByteArray);
341 }
342
hasFormat(const QString & mimeType) const343 bool Qt5MimeData::hasFormat(const QString& mimeType) const { return formats().contains(mimeType); }
344
345 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
346