diff options
Diffstat (limited to '')
-rw-r--r-- | vcl/win/dtrans/DOTransferable.cxx | 577 |
1 files changed, 577 insertions, 0 deletions
diff --git a/vcl/win/dtrans/DOTransferable.cxx b/vcl/win/dtrans/DOTransferable.cxx new file mode 100644 index 000000000..7c5b55c02 --- /dev/null +++ b/vcl/win/dtrans/DOTransferable.cxx @@ -0,0 +1,577 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/types.h> +#include <rtl/process.h> +#include <osl/diagnose.h> +#include <sal/log.hxx> + +#include "DOTransferable.hxx" +#include "ImplHelper.hxx" +#include "WinClip.hxx" +#include "WinClipboard.hxx" +#include "DTransHelper.hxx" +#include "TxtCnvtHlp.hxx" +#include "MimeAttrib.hxx" +#include "FmtFilter.hxx" +#include "Fetc.hxx" +#include <com/sun/star/container/NoSuchElementException.hpp> +#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp> +#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp> +#include <com/sun/star/io/IOException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +using namespace osl; +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::io; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; + +namespace +{ + const Type CPPUTYPE_SEQINT8 = cppu::UnoType<Sequence< sal_Int8 >>::get(); + const Type CPPUTYPE_OUSTRING = cppu::UnoType<OUString>::get(); + + bool isValidFlavor( const DataFlavor& aFlavor ) + { + return ( aFlavor.MimeType.getLength( ) && + ( ( aFlavor.DataType == CPPUTYPE_SEQINT8 ) || + ( aFlavor.DataType == CPPUTYPE_OUSTRING ) ) ); + } + +void clipDataToByteStream( CLIPFORMAT cf, STGMEDIUM stgmedium, CDOTransferable::ByteSequence_t& aByteSequence ) +{ + CStgTransferHelper memTransferHelper; + LPSTREAM pStream = nullptr; + + switch( stgmedium.tymed ) + { + case TYMED_HGLOBAL: + memTransferHelper.init( stgmedium.hGlobal ); + break; + + case TYMED_MFPICT: + memTransferHelper.init( stgmedium.hMetaFilePict ); + break; + + case TYMED_ENHMF: + memTransferHelper.init( stgmedium.hEnhMetaFile ); + break; + + case TYMED_ISTREAM: + pStream = stgmedium.pstm; + break; + + default: + throw UnsupportedFlavorException( ); + break; + } + + if (pStream) + { + // We have a stream, read from it. + STATSTG aStat; + HRESULT hr = pStream->Stat(&aStat, STATFLAG_NONAME); + if (FAILED(hr)) + { + SAL_WARN("vcl.win.dtrans", "clipDataToByteStream: Stat() failed"); + return; + } + + size_t nMemSize = aStat.cbSize.QuadPart; + aByteSequence.realloc(nMemSize); + LARGE_INTEGER li; + li.QuadPart = 0; + hr = pStream->Seek(li, STREAM_SEEK_SET, nullptr); + if (FAILED(hr)) + { + SAL_WARN("vcl.win.dtrans", "clipDataToByteStream: Seek() failed"); + } + + ULONG nRead = 0; + hr = pStream->Read(aByteSequence.getArray(), nMemSize, &nRead); + if (FAILED(hr)) + { + SAL_WARN("vcl.win.dtrans", "clipDataToByteStream: Read() failed"); + } + if (nRead < nMemSize) + { + SAL_WARN("vcl.win.dtrans", "clipDataToByteStream: Read() was partial"); + } + + return; + } + + int nMemSize = memTransferHelper.memSize( cf ); + aByteSequence.realloc( nMemSize ); + memTransferHelper.read( aByteSequence.getArray( ), nMemSize ); +} + +OUString byteStreamToOUString( CDOTransferable::ByteSequence_t& aByteStream ) +{ + sal_Int32 nWChars; + sal_Int32 nMemSize = aByteStream.getLength( ); + + // if there is a trailing L"\0" subtract 1 from length + // for unknown reason, the sequence may sometimes arrive empty + if ( aByteStream.getLength( ) > 1 && + 0 == aByteStream[ aByteStream.getLength( ) - 2 ] && + 0 == aByteStream[ aByteStream.getLength( ) - 1 ] ) + nWChars = static_cast< sal_Int32 >( nMemSize / sizeof( sal_Unicode ) ) - 1; + else + nWChars = static_cast< sal_Int32 >( nMemSize / sizeof( sal_Unicode ) ); + + return OUString( reinterpret_cast< sal_Unicode* >( aByteStream.getArray( ) ), nWChars ); +} + +Any byteStreamToAny( CDOTransferable::ByteSequence_t& aByteStream, const Type& aRequestedDataType ) +{ + Any aAny; + + if ( aRequestedDataType == CPPUTYPE_OUSTRING ) + { + OUString str = byteStreamToOUString( aByteStream ); + if (str.isEmpty()) + throw RuntimeException(); + aAny <<= str; + } + else + aAny <<= aByteStream; + + return aAny; +} + +bool cmpFullMediaType( + const Reference< XMimeContentType >& xLhs, const Reference< XMimeContentType >& xRhs ) +{ + return xLhs->getFullMediaType().equalsIgnoreAsciiCase( xRhs->getFullMediaType( ) ); +} + +bool cmpAllContentTypeParameter( + const Reference< XMimeContentType >& xLhs, const Reference< XMimeContentType >& xRhs ) +{ + Sequence< OUString > xLhsFlavors = xLhs->getParameters( ); + Sequence< OUString > xRhsFlavors = xRhs->getParameters( ); + bool bRet = true; + + try + { + if ( xLhsFlavors.getLength( ) == xRhsFlavors.getLength( ) ) + { + OUString pLhs; + OUString pRhs; + + for ( sal_Int32 i = 0; i < xLhsFlavors.getLength( ); i++ ) + { + pLhs = xLhs->getParameterValue( xLhsFlavors[i] ); + pRhs = xRhs->getParameterValue( xLhsFlavors[i] ); + + if ( !pLhs.equalsIgnoreAsciiCase( pRhs ) ) + { + bRet = false; + break; + } + } + } + else + bRet = false; + } + catch( NoSuchElementException& ) + { + bRet = false; + } + catch( IllegalArgumentException& ) + { + bRet = false; + } + + return bRet; +} + +} // end namespace + +CDOTransferable::CDOTransferable( + const Reference< XComponentContext >& rxContext, IDataObjectPtr rDataObject ) : + m_rDataObject( rDataObject ), + m_xContext( rxContext ), + m_DataFormatTranslator( rxContext ), + m_bUnicodeRegistered( false ), + m_TxtFormatOnClipboard( CF_INVALID ) +{ + initFlavorList(); +} + +CDOTransferable::CDOTransferable( + const Reference<XComponentContext>& rxContext, + const css::uno::Reference<css::datatransfer::clipboard::XClipboard>& xClipboard, + const std::vector<sal_uInt32>& rFormats) + : m_xClipboard(xClipboard) + , m_xContext(rxContext) + , m_DataFormatTranslator(rxContext) + , m_bUnicodeRegistered(false) + , m_TxtFormatOnClipboard(CF_INVALID) +{ + initFlavorListFromFormatList(rFormats); +} + +Any SAL_CALL CDOTransferable::getTransferData( const DataFlavor& aFlavor ) +{ + OSL_ASSERT( isValidFlavor( aFlavor ) ); + + MutexGuard aGuard( m_aMutex ); + + // convert dataflavor to formatetc + + CFormatEtc fetc = m_DataFormatTranslator.getFormatEtcFromDataFlavor( aFlavor ); + OSL_ASSERT( CF_INVALID != fetc.getClipformat() ); + + // get the data from clipboard in a byte stream + + ByteSequence_t clipDataStream; + + try + { + clipDataStream = getClipboardData( fetc ); + } + catch( UnsupportedFlavorException& ) + { + if ( CDataFormatTranslator::isUnicodeTextFormat( fetc.getClipformat( ) ) && + m_bUnicodeRegistered ) + { + OUString aUnicodeText = synthesizeUnicodeText( ); + Any aAny( aUnicodeText ); + return aAny; + } + // #i124085# CF_DIBV5 should not be possible, but keep for reading from the + // clipboard for being on the safe side + else if(CF_DIBV5 == fetc.getClipformat()) + { + // #i123407# CF_DIBV5 has priority; if the try to fetch this failed, + // check CF_DIB availability as an alternative + fetc.setClipformat(CF_DIB); + + clipDataStream = getClipboardData( fetc ); + // pass UnsupportedFlavorException out, tried all possibilities + } + else + throw; // pass through exception + } + + // return the data as any + + return byteStreamToAny( clipDataStream, aFlavor.DataType ); +} + +// getTransferDataFlavors + +Sequence< DataFlavor > SAL_CALL CDOTransferable::getTransferDataFlavors( ) +{ + return m_FlavorList; +} + +// isDataFlavorSupported +// returns true if we find a DataFlavor with the same MimeType and +// DataType + +sal_Bool SAL_CALL CDOTransferable::isDataFlavorSupported( const DataFlavor& aFlavor ) +{ + OSL_ASSERT( isValidFlavor( aFlavor ) ); + + for ( DataFlavor const & df : std::as_const(m_FlavorList) ) + if ( compareDataFlavors( aFlavor, df ) ) + return true; + + return false; +} + +// the list of dataflavors currently on the clipboard will be initialized +// only once; if the client of this Transferable will hold a reference +// to it and the underlying clipboard content changes, the client does +// possible operate on an invalid list +// if there is only text on the clipboard we will also offer unicode text +// an synthesize this format on the fly if requested, to accomplish this +// we save the first offered text format which we will later use for the +// conversion + +void CDOTransferable::initFlavorList( ) +{ + std::vector<sal_uInt32> aFormats; + sal::systools::COMReference<IEnumFORMATETC> pEnumFormatEtc; + HRESULT hr = m_rDataObject->EnumFormatEtc( DATADIR_GET, &pEnumFormatEtc ); + if ( SUCCEEDED( hr ) ) + { + pEnumFormatEtc->Reset( ); + + FORMATETC fetc; + while ( S_OK == pEnumFormatEtc->Next( 1, &fetc, nullptr ) ) + { + aFormats.push_back(fetc.cfFormat); + // see MSDN IEnumFORMATETC + CoTaskMemFree( fetc.ptd ); + } + initFlavorListFromFormatList(aFormats); + } +} + +void CDOTransferable::initFlavorListFromFormatList(const std::vector<sal_uInt32>& rFormats) +{ + for (sal_uInt32 cfFormat : rFormats) + { + // we use locales only to determine the + // charset if there is text on the clipboard + // we don't offer this format + if (CF_LOCALE == cfFormat) + continue; + + // if text or oemtext is offered we pretend to have unicode text + if (CDataFormatTranslator::isTextFormat(cfFormat)) + { + if (!m_bUnicodeRegistered) + { + m_TxtFormatOnClipboard = cfFormat; + m_bUnicodeRegistered = true; + + // register unicode text as format + addSupportedFlavor(formatEtcToDataFlavor(CF_UNICODETEXT)); + } + } + else + addSupportedFlavor(formatEtcToDataFlavor(cfFormat)); + } +} + +inline +void CDOTransferable::addSupportedFlavor( const DataFlavor& aFlavor ) +{ + // we ignore all formats that couldn't be translated + if ( aFlavor.MimeType.getLength( ) ) + { + OSL_ASSERT( isValidFlavor( aFlavor ) ); + + m_FlavorList.realloc( m_FlavorList.getLength( ) + 1 ); + m_FlavorList.getArray()[m_FlavorList.getLength( ) - 1] = aFlavor; + } +} + +DataFlavor CDOTransferable::formatEtcToDataFlavor(sal_uInt32 cfFormat) +{ + return m_DataFormatTranslator.getDataFlavorFromFormatEtc(cfFormat); +} + +// returns the current locale on clipboard; if there is no locale on +// clipboard the function returns the current thread locale + +LCID CDOTransferable::getLocaleFromClipboard( ) +{ + LCID lcid = GetThreadLocale( ); + + try + { + CFormatEtc fetc = CDataFormatTranslator::getFormatEtcForClipformat( CF_LOCALE ); + ByteSequence_t aLCIDSeq = getClipboardData( fetc ); + lcid = *reinterpret_cast<LCID*>( aLCIDSeq.getArray( ) ); + + // because of a Win95/98 Bug; there the high word + // of a locale has the same value as the + // low word e.g. 0x07040704 that's not right + // correct is 0x00000704 + lcid &= 0x0000FFFF; + } + catch(...) + { + // we take the default locale + } + + return lcid; +} + +void CDOTransferable::tryToGetIDataObjectIfAbsent() +{ + if (!m_rDataObject.is()) + { + auto xClipboard = m_xClipboard.get(); // holding the reference while we get the object + if (CWinClipboard* pWinClipboard = dynamic_cast<CWinClipboard*>(xClipboard.get())) + { + m_rDataObject = pWinClipboard->getIDataObject(); + } + } +} + +// I think it's not necessary to call ReleaseStgMedium +// in case of failures because nothing should have been +// allocated etc. + +CDOTransferable::ByteSequence_t CDOTransferable::getClipboardData( CFormatEtc& aFormatEtc ) +{ + STGMEDIUM stgmedium; + tryToGetIDataObjectIfAbsent(); + if (!m_rDataObject.is()) // Maybe we are shutting down, and clipboard is already destroyed? + throw RuntimeException(); + HRESULT hr = m_rDataObject->GetData( aFormatEtc, &stgmedium ); + + // in case of failure to get a WMF metafile handle, try to get a memory block + if( FAILED( hr ) && + ( CF_METAFILEPICT == aFormatEtc.getClipformat() ) && + ( TYMED_MFPICT == aFormatEtc.getTymed() ) ) + { + CFormatEtc aTempFormat( aFormatEtc ); + aTempFormat.setTymed( TYMED_HGLOBAL ); + hr = m_rDataObject->GetData( aTempFormat, &stgmedium ); + } + + if (FAILED(hr) && aFormatEtc.getTymed() == TYMED_HGLOBAL) + { + // Handle type is not memory, try stream. + CFormatEtc aTempFormat(aFormatEtc); + aTempFormat.setTymed(TYMED_ISTREAM); + hr = m_rDataObject->GetData(aTempFormat, &stgmedium); + } + + if ( FAILED( hr ) ) + { + OSL_ASSERT( (hr != E_INVALIDARG) && + (hr != DV_E_DVASPECT) && + (hr != DV_E_LINDEX) && + (hr != DV_E_TYMED) ); + + if ( DV_E_FORMATETC == hr ) + throw UnsupportedFlavorException( ); + else if ( STG_E_MEDIUMFULL == hr ) + throw IOException( ); + else + throw RuntimeException( ); + } + + ByteSequence_t byteStream; + + try + { + if ( CF_ENHMETAFILE == aFormatEtc.getClipformat() ) + byteStream = WinENHMFPictToOOMFPict( stgmedium.hEnhMetaFile ); + else if (CF_HDROP == aFormatEtc.getClipformat()) + byteStream = CF_HDROPToFileList(stgmedium.hGlobal); + else if ( CF_BITMAP == aFormatEtc.getClipformat() ) + { + byteStream = WinBITMAPToOOBMP(stgmedium.hBitmap); + if( aFormatEtc.getTymed() == TYMED_GDI && + ! stgmedium.pUnkForRelease ) + { + DeleteObject(stgmedium.hBitmap); + } + } + else + { + clipDataToByteStream( aFormatEtc.getClipformat( ), stgmedium, byteStream ); + + // format conversion if necessary + // #i124085# DIBV5 should not happen currently, but keep as a hint here + if(CF_DIBV5 == aFormatEtc.getClipformat() || CF_DIB == aFormatEtc.getClipformat()) + { + byteStream = WinDIBToOOBMP(byteStream); + } + else if(CF_METAFILEPICT == aFormatEtc.getClipformat()) + { + byteStream = WinMFPictToOOMFPict(byteStream); + } + } + + ReleaseStgMedium( &stgmedium ); + } + catch( CStgTransferHelper::CStgTransferException& ) + { + ReleaseStgMedium( &stgmedium ); + throw IOException( ); + } + + return byteStream; +} + +OUString CDOTransferable::synthesizeUnicodeText( ) +{ + ByteSequence_t aTextSequence; + CFormatEtc fetc; + LCID lcid = getLocaleFromClipboard( ); + sal_uInt32 cpForTxtCnvt = 0; + + if ( CF_TEXT == m_TxtFormatOnClipboard ) + { + fetc = CDataFormatTranslator::getFormatEtcForClipformat( CF_TEXT ); + aTextSequence = getClipboardData( fetc ); + + // determine the codepage used for text conversion + cpForTxtCnvt = getWinCPFromLocaleId( lcid, LOCALE_IDEFAULTANSICODEPAGE ).toInt32( ); + } + else if ( CF_OEMTEXT == m_TxtFormatOnClipboard ) + { + fetc = CDataFormatTranslator::getFormatEtcForClipformat( CF_OEMTEXT ); + aTextSequence = getClipboardData( fetc ); + + // determine the codepage used for text conversion + cpForTxtCnvt = getWinCPFromLocaleId( lcid, LOCALE_IDEFAULTCODEPAGE ).toInt32( ); + } + else + OSL_ASSERT( false ); + + CStgTransferHelper stgTransferHelper; + + // convert the text + MultiByteToWideCharEx( cpForTxtCnvt, + reinterpret_cast<char*>( aTextSequence.getArray( ) ), + -1, + stgTransferHelper, + false); + + CRawHGlobalPtr ptrHGlob(stgTransferHelper); + sal_Unicode* pWChar = static_cast<sal_Unicode*>(ptrHGlob.GetMemPtr()); + + return OUString(pWChar); +} + +bool CDOTransferable::compareDataFlavors( + const DataFlavor& lhs, const DataFlavor& rhs ) +{ + if ( !m_rXMimeCntFactory.is( ) ) + { + m_rXMimeCntFactory = MimeContentTypeFactory::create( m_xContext ); + } + + bool bRet = false; + + try + { + Reference< XMimeContentType > xLhs( m_rXMimeCntFactory->createMimeContentType( lhs.MimeType ) ); + Reference< XMimeContentType > xRhs( m_rXMimeCntFactory->createMimeContentType( rhs.MimeType ) ); + + if ( cmpFullMediaType( xLhs, xRhs ) ) + { + bRet = cmpAllContentTypeParameter( xLhs, xRhs ); + } + } + catch( IllegalArgumentException& ) + { + OSL_FAIL( "Invalid content type detected" ); + bRet = false; + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |