1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <osl/diagnose.h>
21 #include <o3tl/char16_t2wchar_t.hxx>
22 #include <o3tl/safeint.hxx>
24 #include "XTDataObject.hxx"
25 #include <com/sun/star/datatransfer/DataFlavor.hpp>
26 #include "ImplHelper.hxx"
27 #include "DTransHelper.hxx"
28 #include "TxtCnvtHlp.hxx"
29 #include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
30 #include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
31 #include <com/sun/star/awt/AsyncCallback.hpp>
32 #include <com/sun/star/awt/XCallback.hpp>
33 #include "FmtFilter.hxx"
34 #include <cppuhelper/implbase.hxx>
36 #if !defined WIN32_LEAN_AND_MEAN
37 # define WIN32_LEAN_AND_MEAN
42 using namespace com::sun::star::datatransfer
;
43 using namespace com::sun::star::datatransfer::clipboard
;
44 using namespace com::sun::star::uno
;
45 using namespace com::sun::star::lang
;
49 void setupStgMedium( const FORMATETC
& fetc
,
50 CStgTransferHelper
& stgTransHlp
,
51 STGMEDIUM
& stgmedium
)
53 stgmedium
.pUnkForRelease
= nullptr;
55 if ( fetc
.cfFormat
== CF_METAFILEPICT
)
57 stgmedium
.tymed
= TYMED_MFPICT
;
58 stgmedium
.hMetaFilePict
= static_cast< HMETAFILEPICT
>( stgTransHlp
.getHGlobal( ) );
60 else if ( fetc
.cfFormat
== CF_ENHMETAFILE
)
62 stgmedium
.tymed
= TYMED_ENHMF
;
63 stgmedium
.hEnhMetaFile
= static_cast< HENHMETAFILE
>( stgTransHlp
.getHGlobal( ) );
65 else if ( fetc
.tymed
& TYMED_HGLOBAL
)
67 stgmedium
.tymed
= TYMED_HGLOBAL
;
68 stgmedium
.hGlobal
= stgTransHlp
.getHGlobal( );
70 else if ( fetc
.tymed
& TYMED_ISTREAM
)
72 stgmedium
.tymed
= TYMED_ISTREAM
;
73 stgTransHlp
.getIStream( &stgmedium
.pstm
);
80 We need to destroy XTransferable in the main thread to avoid dead lock
81 when locking in the clipboard thread. So we transfer the ownership of the
82 XTransferable reference to this object and release it when the callback
83 is executed in main thread.
85 class AsyncDereference
: public cppu::WeakImplHelper
<css::awt::XCallback
>
87 Reference
<XTransferable
> maTransferable
;
90 AsyncDereference(css::uno::Reference
<css::datatransfer::XTransferable
> const & rTransferable
)
91 : maTransferable(rTransferable
)
94 virtual void SAL_CALL
notify(css::uno::Any
const &) override
96 maTransferable
.clear();
100 // a helper class that will be thrown by the function validateFormatEtc
102 class CInvalidFormatEtcException
106 explicit CInvalidFormatEtcException( HRESULT hr
) : m_hr( hr
) {};
109 void validateFormatEtc( LPFORMATETC lpFormatEtc
)
113 if ( lpFormatEtc
->lindex
!= -1 )
114 throw CInvalidFormatEtcException( DV_E_LINDEX
);
116 if ( !(lpFormatEtc
->dwAspect
& DVASPECT_CONTENT
) &&
117 !(lpFormatEtc
->dwAspect
& DVASPECT_SHORTNAME
) )
118 throw CInvalidFormatEtcException( DV_E_DVASPECT
);
120 if ( !(lpFormatEtc
->tymed
& TYMED_HGLOBAL
) &&
121 !(lpFormatEtc
->tymed
& TYMED_ISTREAM
) &&
122 !(lpFormatEtc
->tymed
& TYMED_MFPICT
) &&
123 !(lpFormatEtc
->tymed
& TYMED_ENHMF
) )
124 throw CInvalidFormatEtcException( DV_E_TYMED
);
126 if ( lpFormatEtc
->cfFormat
== CF_METAFILEPICT
&&
127 !(lpFormatEtc
->tymed
& TYMED_MFPICT
) )
128 throw CInvalidFormatEtcException( DV_E_TYMED
);
130 if ( lpFormatEtc
->cfFormat
== CF_ENHMETAFILE
&&
131 !(lpFormatEtc
->tymed
& TYMED_ENHMF
) )
132 throw CInvalidFormatEtcException( DV_E_TYMED
);
135 void invalidateStgMedium( STGMEDIUM
& stgmedium
)
137 stgmedium
.tymed
= TYMED_NULL
;
140 HRESULT
translateStgExceptionCode( HRESULT hr
)
146 case STG_E_MEDIUMFULL
:
151 hrTransl
= E_UNEXPECTED
;
159 void renderDataAndSetupStgMedium(
160 const sal_Int8
* lpStorage
, const FORMATETC
& fetc
, sal_uInt32 nInitStgSize
,
161 sal_uInt32 nBytesToTransfer
, STGMEDIUM
& stgmedium
)
163 OSL_PRECOND( !nInitStgSize
|| (nInitStgSize
>= nBytesToTransfer
),
164 "Memory size less than number of bytes to transfer" );
166 CStgTransferHelper
stgTransfHelper( AUTO_INIT
);
168 // setup storage size
169 if ( nInitStgSize
> 0 )
170 stgTransfHelper
.init( nInitStgSize
);
172 #if OSL_DEBUG_LEVEL > 0
173 sal_uInt32 nBytesWritten
= 0;
174 stgTransfHelper
.write( lpStorage
, nBytesToTransfer
, &nBytesWritten
);
175 OSL_ASSERT( nBytesWritten
== nBytesToTransfer
);
177 stgTransfHelper
.write( lpStorage
, nBytesToTransfer
);
180 setupStgMedium( fetc
, stgTransfHelper
, stgmedium
);
185 CXTDataObject::CXTDataObject( const Reference
< XComponentContext
>& rxContext
,
186 const Reference
< XTransferable
>& aXTransferable
)
188 , m_XTransferable( aXTransferable
)
189 , m_XComponentContext( rxContext
)
190 , m_bFormatEtcContainerInitialized( false )
191 , m_DataFormatTranslator( rxContext
)
192 , m_FormatRegistrar( rxContext
, m_DataFormatTranslator
)
196 CXTDataObject::~CXTDataObject()
198 css::awt::AsyncCallback::create(m_XComponentContext
)->addCallback(
199 new AsyncDereference(m_XTransferable
),
203 // IUnknown->QueryInterface
205 STDMETHODIMP
CXTDataObject::QueryInterface( REFIID iid
, void** ppvObject
)
207 if ( nullptr == ppvObject
)
210 HRESULT hr
= E_NOINTERFACE
;
212 *ppvObject
= nullptr;
213 if ( ( __uuidof( IUnknown
) == iid
) ||
214 ( __uuidof( IDataObject
) == iid
) )
216 *ppvObject
= static_cast< IUnknown
* >( this );
217 static_cast<LPUNKNOWN
>(*ppvObject
)->AddRef( );
226 STDMETHODIMP_(ULONG
) CXTDataObject::AddRef( )
228 return static_cast< ULONG
>( InterlockedIncrement( &m_nRefCnt
) );
233 STDMETHODIMP_(ULONG
) CXTDataObject::Release( )
236 static_cast< ULONG
>( InterlockedDecrement( &m_nRefCnt
) );
244 STDMETHODIMP
CXTDataObject::GetData( FORMATETC
* pFormatetc
, STGMEDIUM
* pmedium
)
246 if ( !(pFormatetc
&& pmedium
) )
251 // prepare data transfer
252 invalidateStgMedium( *pmedium
);
253 validateFormatEtc( pFormatetc
);
255 // handle locale request, because locale is an artificial format for us
256 if ( CF_LOCALE
== pFormatetc
->cfFormat
)
257 renderLocaleAndSetupStgMedium( *pFormatetc
, *pmedium
);
258 else if ( CF_UNICODETEXT
== pFormatetc
->cfFormat
)
259 renderUnicodeAndSetupStgMedium( *pFormatetc
, *pmedium
);
261 renderAnyDataAndSetupStgMedium( *pFormatetc
, *pmedium
);
263 catch(UnsupportedFlavorException
&)
265 HRESULT hr
= DV_E_FORMATETC
;
267 CFormatEtc
aFormatetc(*pFormatetc
);
268 if (CFormatRegistrar::isSynthesizeableFormat(aFormatetc
))
269 hr
= renderSynthesizedFormatAndSetupStgMedium( *pFormatetc
, *pmedium
);
273 catch( CInvalidFormatEtcException
& ex
)
277 catch( CStgTransferHelper::CStgTransferException
& ex
)
279 return translateStgExceptionCode( ex
.m_hr
);
290 void CXTDataObject::renderLocaleAndSetupStgMedium(
291 FORMATETC
const & fetc
, STGMEDIUM
& stgmedium
)
293 if ( !m_FormatRegistrar
.hasSynthesizedLocale( ) )
294 throw CInvalidFormatEtcException( DV_E_FORMATETC
);
295 LCID lcid
= CFormatRegistrar::getSynthesizedLocale( );
296 renderDataAndSetupStgMedium(
297 reinterpret_cast< sal_Int8
* >( &lcid
),
304 void CXTDataObject::renderUnicodeAndSetupStgMedium(
305 FORMATETC
const & fetc
, STGMEDIUM
& stgmedium
)
307 DataFlavor aFlavor
= formatEtcToDataFlavor( fetc
);
309 Any aAny
= m_XTransferable
->getTransferData( aFlavor
);
311 // unfortunately not all transferables fulfill the
312 // spec. and do throw an UnsupportedFlavorException
313 // so we must check the any
314 if ( !aAny
.hasValue( ) )
316 OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" );
317 throw UnsupportedFlavorException( );
323 sal_uInt32 nBytesToTransfer
= aText
.getLength( ) * sizeof( sal_Unicode
);
325 // to be sure there is an ending 0
326 sal_uInt32 nRequiredMemSize
= nBytesToTransfer
+ sizeof( sal_Unicode
);
328 renderDataAndSetupStgMedium(
329 reinterpret_cast< const sal_Int8
* >( aText
.getStr( ) ),
336 void CXTDataObject::renderAnyDataAndSetupStgMedium(
337 FORMATETC
& fetc
, STGMEDIUM
& stgmedium
)
339 DataFlavor aFlavor
= formatEtcToDataFlavor( fetc
);
341 Any aAny
= m_XTransferable
->getTransferData( aFlavor
);
343 // unfortunately not all transferables fulfill the
344 // spec. and do throw an UnsupportedFlavorException
345 // so we must check the any
346 if ( !aAny
.hasValue( ) )
348 OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" );
349 throw UnsupportedFlavorException( );
352 Sequence
< sal_Int8
> clipDataStream
;
353 aAny
>>= clipDataStream
;
355 sal_uInt32 nRequiredMemSize
= 0;
356 if ( CDataFormatTranslator::isOemOrAnsiTextFormat( fetc
.cfFormat
) )
357 nRequiredMemSize
= sizeof( sal_Int8
) * clipDataStream
.getLength( ) + 1;
359 // prepare data for transmission
360 // #i124085# DIBV5 should not happen for now, but keep as hint here
361 if ( CF_DIBV5
== fetc
.cfFormat
|| CF_DIB
== fetc
.cfFormat
)
364 if(CF_DIBV5
== fetc
.cfFormat
)
366 OSL_ENSURE(o3tl::make_unsigned(clipDataStream
.getLength()) > (sizeof(BITMAPFILEHEADER
) + sizeof(BITMAPV5HEADER
)), "Wrong size on CF_DIBV5 data (!)");
368 else // CF_DIB == fetc.cfFormat
370 OSL_ENSURE(o3tl::make_unsigned(clipDataStream
.getLength()) > (sizeof(BITMAPFILEHEADER
) + sizeof(BITMAPINFOHEADER
)), "Wrong size on CF_DIB data (!)");
374 // remove BITMAPFILEHEADER
375 clipDataStream
= OOBmpToWinDIB( clipDataStream
);
378 if ( CF_METAFILEPICT
== fetc
.cfFormat
)
380 stgmedium
.tymed
= TYMED_MFPICT
;
381 stgmedium
.hMetaFilePict
= OOMFPictToWinMFPict( clipDataStream
);
382 stgmedium
.pUnkForRelease
= nullptr;
384 else if( CF_ENHMETAFILE
== fetc
.cfFormat
)
386 stgmedium
.tymed
= TYMED_ENHMF
;
387 stgmedium
.hMetaFilePict
= OOMFPictToWinENHMFPict( clipDataStream
);
388 stgmedium
.pUnkForRelease
= nullptr;
391 renderDataAndSetupStgMedium(
392 clipDataStream
.getArray( ),
395 clipDataStream
.getLength( ),
399 HRESULT
CXTDataObject::renderSynthesizedFormatAndSetupStgMedium( FORMATETC
& fetc
, STGMEDIUM
& stgmedium
)
405 if ( CF_UNICODETEXT
== fetc
.cfFormat
)
406 // the transferable seems to have only text
407 renderSynthesizedUnicodeAndSetupStgMedium( fetc
, stgmedium
);
408 else if ( CDataFormatTranslator::isOemOrAnsiTextFormat( fetc
.cfFormat
) )
409 // the transferable seems to have only unicode text
410 renderSynthesizedTextAndSetupStgMedium( fetc
, stgmedium
);
412 // the transferable seems to have only text/html
413 renderSynthesizedHtmlAndSetupStgMedium( fetc
, stgmedium
);
415 catch(UnsupportedFlavorException
&)
419 catch( CInvalidFormatEtcException
& )
421 OSL_FAIL( "Unexpected exception" );
423 catch( CStgTransferHelper::CStgTransferException
& ex
)
425 return translateStgExceptionCode( ex
.m_hr
);
435 // the transferable must have only text, so we will synthesize unicode text
437 void CXTDataObject::renderSynthesizedUnicodeAndSetupStgMedium( FORMATETC
const & fetc
, STGMEDIUM
& stgmedium
)
439 OSL_ASSERT( CF_UNICODETEXT
== fetc
.cfFormat
);
441 Any aAny
= m_XTransferable
->getTransferData( m_FormatRegistrar
.getRegisteredTextFlavor( ) );
443 // unfortunately not all transferables fulfill the
444 // spec. and do throw an UnsupportedFlavorException
445 // so we must check the any
446 if ( !aAny
.hasValue( ) )
448 OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" );
449 throw UnsupportedFlavorException( );
452 Sequence
< sal_Int8
> aText
;
455 CStgTransferHelper stgTransfHelper
;
457 MultiByteToWideCharEx(
458 CFormatRegistrar::getRegisteredTextCodePage( ),
459 reinterpret_cast< char* >( aText
.getArray( ) ),
463 setupStgMedium( fetc
, stgTransfHelper
, stgmedium
);
466 // the transferable must have only unicode text so we will synthesize text
468 void CXTDataObject::renderSynthesizedTextAndSetupStgMedium( FORMATETC
& fetc
, STGMEDIUM
& stgmedium
)
470 OSL_ASSERT( CDataFormatTranslator::isOemOrAnsiTextFormat( fetc
.cfFormat
) );
472 DataFlavor aFlavor
= formatEtcToDataFlavor(
473 CDataFormatTranslator::getFormatEtcForClipformat( CF_UNICODETEXT
) );
475 Any aAny
= m_XTransferable
->getTransferData( aFlavor
);
477 // unfortunately not all transferables fulfill the
478 // spec. and do throw an UnsupportedFlavorException
479 // so we must check the any
480 if ( !aAny
.hasValue( ) )
482 OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" );
483 throw UnsupportedFlavorException( );
486 OUString aUnicodeText
;
487 aAny
>>= aUnicodeText
;
489 CStgTransferHelper stgTransfHelper
;
491 WideCharToMultiByteEx(
493 o3tl::toW( aUnicodeText
.getStr( ) ),
494 aUnicodeText
.getLength( ),
497 setupStgMedium( fetc
, stgTransfHelper
, stgmedium
);
500 void CXTDataObject::renderSynthesizedHtmlAndSetupStgMedium( FORMATETC
& fetc
, STGMEDIUM
& stgmedium
)
502 OSL_ASSERT( CDataFormatTranslator::isHTMLFormat( fetc
.cfFormat
) );
506 // creating a DataFlavor on the fly
507 aFlavor
.MimeType
= "text/html";
508 aFlavor
.DataType
= cppu::UnoType
<Sequence
< sal_Int8
>>::get();
510 Any aAny
= m_XTransferable
->getTransferData( aFlavor
);
512 // unfortunately not all transferables fulfill the
513 // spec. and do throw an UnsupportedFlavorException
514 // so we must check the any
515 if ( !aAny
.hasValue( ) )
517 OSL_FAIL( "XTransferable should throw an exception if ask for an unsupported flavor" );
518 throw UnsupportedFlavorException( );
521 Sequence
< sal_Int8
> aTextHtmlSequence
;
522 aAny
>>= aTextHtmlSequence
;
524 Sequence
< sal_Int8
> aHTMLFormatSequence
= TextHtmlToHTMLFormat( aTextHtmlSequence
);
526 sal_uInt32 nBytesToTransfer
= aHTMLFormatSequence
.getLength( );
528 renderDataAndSetupStgMedium(
529 reinterpret_cast< const sal_Int8
* >( aHTMLFormatSequence
.getArray( ) ),
536 // IDataObject->EnumFormatEtc
538 STDMETHODIMP
CXTDataObject::EnumFormatEtc(
539 DWORD dwDirection
, IEnumFORMATETC
** ppenumFormatetc
)
541 if ( nullptr == ppenumFormatetc
)
544 if ( DATADIR_SET
== dwDirection
)
547 *ppenumFormatetc
= nullptr;
549 InitializeFormatEtcContainer( );
552 if ( DATADIR_GET
== dwDirection
)
554 *ppenumFormatetc
= new CEnumFormatEtc( this, m_FormatEtcContainer
);
555 static_cast< LPUNKNOWN
>( *ppenumFormatetc
)->AddRef( );
565 // IDataObject->QueryGetData
567 STDMETHODIMP
CXTDataObject::QueryGetData( FORMATETC
* pFormatetc
)
569 if ( (nullptr == pFormatetc
) || IsBadReadPtr( pFormatetc
, sizeof( FORMATETC
) ) )
572 InitializeFormatEtcContainer( );
574 CFormatEtc
aFormatetc(*pFormatetc
);
575 return m_FormatEtcContainer
.hasFormatEtc(aFormatetc
) ? S_OK
: S_FALSE
;
578 // IDataObject->GetDataHere
580 STDMETHODIMP
CXTDataObject::GetDataHere( FORMATETC
*, STGMEDIUM
* )
585 // IDataObject->GetCanonicalFormatEtc
587 STDMETHODIMP
CXTDataObject::GetCanonicalFormatEtc( FORMATETC
*, FORMATETC
* )
592 // IDataObject->SetData
594 STDMETHODIMP
CXTDataObject::SetData( FORMATETC
*, STGMEDIUM
*, BOOL
)
599 // IDataObject->DAdvise
601 STDMETHODIMP
CXTDataObject::DAdvise( FORMATETC
*, DWORD
, IAdviseSink
*, DWORD
* )
606 // IDataObject->DUnadvise
608 STDMETHODIMP
CXTDataObject::DUnadvise( DWORD
)
613 // IDataObject->EnumDAdvise
615 STDMETHODIMP
CXTDataObject::EnumDAdvise( IEnumSTATDATA
** )
620 // for our convenience
622 CXTDataObject::operator IDataObject
*( )
624 return static_cast< IDataObject
* >( this );
628 DataFlavor
CXTDataObject::formatEtcToDataFlavor( const FORMATETC
& aFormatEtc
) const
632 if ( m_FormatRegistrar
.hasSynthesizedLocale( ) )
633 aFlavor
= m_DataFormatTranslator
.getDataFlavorFromFormatEtc(
634 aFormatEtc
.cfFormat
, CFormatRegistrar::getSynthesizedLocale());
636 aFlavor
= m_DataFormatTranslator
.getDataFlavorFromFormatEtc(aFormatEtc
.cfFormat
);
638 if ( !aFlavor
.MimeType
.getLength( ) )
639 throw UnsupportedFlavorException( );
644 inline void CXTDataObject::InitializeFormatEtcContainer( )
646 if ( !m_bFormatEtcContainerInitialized
)
648 m_FormatRegistrar
.RegisterFormats( m_XTransferable
, m_FormatEtcContainer
);
649 m_bFormatEtcContainerInitialized
= true;
653 CEnumFormatEtc::CEnumFormatEtc( LPUNKNOWN lpUnkOuter
, const CFormatEtcContainer
& aFormatEtcContainer
) :
655 m_lpUnkOuter( lpUnkOuter
),
656 m_FormatEtcContainer( aFormatEtcContainer
)
661 // IUnknown->QueryInterface
663 STDMETHODIMP
CEnumFormatEtc::QueryInterface( REFIID iid
, void** ppvObject
)
665 if ( nullptr == ppvObject
)
668 HRESULT hr
= E_NOINTERFACE
;
670 *ppvObject
= nullptr;
672 if ( ( __uuidof( IUnknown
) == iid
) ||
673 ( __uuidof( IEnumFORMATETC
) == iid
) )
675 *ppvObject
= static_cast< IUnknown
* >( this );
676 static_cast< LPUNKNOWN
>( *ppvObject
)->AddRef( );
685 STDMETHODIMP_(ULONG
) CEnumFormatEtc::AddRef( )
687 // keep the dataobject alive
688 m_lpUnkOuter
->AddRef( );
689 return InterlockedIncrement( &m_nRefCnt
);
694 STDMETHODIMP_(ULONG
) CEnumFormatEtc::Release( )
696 // release the outer dataobject
697 m_lpUnkOuter
->Release( );
699 ULONG nRefCnt
= InterlockedDecrement( &m_nRefCnt
);
706 // IEnumFORMATETC->Next
708 STDMETHODIMP
CEnumFormatEtc::Next( ULONG nRequested
, FORMATETC
* lpDest
, ULONG
* lpFetched
)
710 if ( ( nRequested
< 1 ) ||
711 (( nRequested
> 1 ) && ( nullptr == lpFetched
)) ||
712 IsBadWritePtr( lpDest
, sizeof( FORMATETC
) * nRequested
) )
715 sal_uInt32 nFetched
= m_FormatEtcContainer
.nextFormatEtc( lpDest
, nRequested
);
717 if ( nullptr != lpFetched
)
718 *lpFetched
= nFetched
;
720 return (nFetched
== nRequested
) ? S_OK
: S_FALSE
;
723 // IEnumFORMATETC->Skip
725 STDMETHODIMP
CEnumFormatEtc::Skip( ULONG celt
)
727 return m_FormatEtcContainer
.skipFormatEtc( celt
) ? S_OK
: S_FALSE
;
730 // IEnumFORMATETC->Reset
732 STDMETHODIMP
CEnumFormatEtc::Reset( )
734 m_FormatEtcContainer
.beginEnumFormatEtc( );
738 // IEnumFORMATETC->Clone
740 STDMETHODIMP
CEnumFormatEtc::Clone( IEnumFORMATETC
** ppenum
)
742 if ( nullptr == ppenum
)
745 *ppenum
= new CEnumFormatEtc( m_lpUnkOuter
, m_FormatEtcContainer
);
746 static_cast< LPUNKNOWN
>( *ppenum
)->AddRef( );
751 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */