vcl/win/dtrans/DOTransferable.cxx | 82 +++++++++++++++++++++++++++----------- vcl/win/dtrans/FmtFilter.cxx | 38 ++++++----------- 2 files changed, 73 insertions(+), 47 deletions(-)
New commits: commit 262d39f6cf40dd2f41e13662d861769b6eaa3c70 Author: Mike Kaganski <[email protected]> AuthorDate: Sat Nov 8 17:49:01 2025 +0500 Commit: Adolfo Jayme Barrientos <[email protected]> CommitDate: Tue Nov 11 10:00:55 2025 +0100 tdf#155855: fix pasting from CF_BITMAP It has been implemented in commit e9391ae8eb3d0a4edf977f0159ee7e07a0a95859 (vcl113: #i111834# support pasting HBITMAP). But I fail to see how it could ever work, despite the OOo bug was verified. The problems were: 1. MIME type added for CF_BITMAP was exactly the same as for CF_DIB. Given that the wanted flavor is passed to CDOTransferable::getTransferData in DataFlavor struct, which uses the MIME type string, which is only turned into CFormatEtc (encapsulating FORMATETC) in the function, it was never possible to request CF_BITMAP: always CF_DIB got requested. 2. WinBITMAPToOOBMP used a call to GetBitmapDimensionEx to obtain the dimensions out of HBITMAP. But that function is only used together with SetBitmapDimensionEx; and the value that these two functions control is documented to be only usable by applications, not by the system, and it has nothing to do with the actual bitmap data stored in HBITMAP, it's a kind of custom metadata. Generally, GetBitmapDimensionEx returns (0, 0) for any bitmap in the clipboard. And then, it passed nullptr as HDC to GetDIBits, which is 100% guaranteed to fail. My theory is, that the CF_BITMAP-related code added in that patch always was a dead code, and it happened to work because the system has a feature of Synthesized Clipboard Formats, where CF_DIB will be created by system on demand - so the change allowed to find and map CF_BITMAP in clipboard to the MIME type shared with CF_DIB; then the MIME type was converted to CF_DIB; and its functional code path was used. However, this automatic generation of CF_DIB obviously doesn't happen in case of Windows Clipboard History. Request for CF_DIB fails, so we have to make the code for CF_BITMAP (the only data in the clipboard that the history provides for image data) work. This change adds a fallback to CDOTransferable::getClipboardData, that tries CF_BITMAP when CF_DIB was requested and has failed. It also fixes WinBITMAPToOOBMP. Change-Id: I7d5eb64cc378510d303686d2bab9c53129607980 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193630 Reviewed-by: Mike Kaganski <[email protected]> Tested-by: Jenkins (cherry picked from commit 426a9af71d99c8610b28c3bf73fb8e3fd88e0a64) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193722 Reviewed-by: Adolfo Jayme Barrientos <[email protected]> diff --git a/vcl/win/dtrans/DOTransferable.cxx b/vcl/win/dtrans/DOTransferable.cxx index 38329a209c1c..3004f9fe16d7 100644 --- a/vcl/win/dtrans/DOTransferable.cxx +++ b/vcl/win/dtrans/DOTransferable.cxx @@ -419,30 +419,65 @@ void CDOTransferable::tryToGetIDataObjectIfAbsent() // in case of failures because nothing should have been // allocated etc. -CDOTransferable::ByteSequence_t CDOTransferable::getClipboardData( CFormatEtc& aFormatEtc ) +namespace { - 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 ); +// Uses a fallback to try TYMED_ISTREAM instead of TYMED_HGLOBAL +HRESULT getClipboardData_impl(const IDataObjectPtr& pDataObject, CFormatEtc& rFormatEtc, + STGMEDIUM& rStgmedium) +{ + const HRESULT hr = pDataObject->GetData( rFormatEtc, &rStgmedium ); + if (SUCCEEDED(hr)) + return hr; + + const DWORD nOrigTymed = rFormatEtc.getTymed(); // 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() ) ) + if( ( CF_METAFILEPICT == rFormatEtc.getClipformat() ) && + ( TYMED_MFPICT == nOrigTymed) ) { - CFormatEtc aTempFormat( aFormatEtc ); - aTempFormat.setTymed( TYMED_HGLOBAL ); - hr = m_rDataObject->GetData( aTempFormat, &stgmedium ); + rFormatEtc.setTymed(TYMED_HGLOBAL); + // Do not overwrite original error + HRESULT hr2 = pDataObject->GetData(rFormatEtc, &rStgmedium); + if (SUCCEEDED(hr2)) + return hr2; } - if (FAILED(hr) && aFormatEtc.getTymed() == TYMED_HGLOBAL) + if (nOrigTymed == TYMED_HGLOBAL) { // Handle type is not memory, try stream. - CFormatEtc aTempFormat(aFormatEtc); - aTempFormat.setTymed(TYMED_ISTREAM); - hr = m_rDataObject->GetData(aTempFormat, &stgmedium); + rFormatEtc.setTymed(TYMED_ISTREAM); + HRESULT hr2 = pDataObject->GetData(rFormatEtc, &rStgmedium); + if (SUCCEEDED(hr2)) + return hr2; + } + + if (rFormatEtc.getClipformat() == CF_BITMAP && nOrigTymed != TYMED_GDI) + { + // Try GDI + rFormatEtc.setTymed(TYMED_GDI); + HRESULT hr2 = pDataObject->GetData(rFormatEtc, &rStgmedium); + if (SUCCEEDED(hr2)) + return hr2; + } + + return hr; // original error +} +} + +CDOTransferable::ByteSequence_t CDOTransferable::getClipboardData( CFormatEtc& aFormatEtc ) +{ + CFormatEtc aLocalFormatEtc(aFormatEtc); + STGMEDIUM stgmedium; + tryToGetIDataObjectIfAbsent(); + if (!m_rDataObject.is()) // Maybe we are shutting down, and clipboard is already destroyed? + throw RuntimeException(); + HRESULT hr = getClipboardData_impl(m_rDataObject, aLocalFormatEtc, stgmedium); + + if (FAILED(hr) && aFormatEtc.getClipformat() == CF_DIB) + { + aLocalFormatEtc = aFormatEtc; + aLocalFormatEtc.setClipformat(CF_BITMAP); + hr = getClipboardData_impl(m_rDataObject, aLocalFormatEtc, stgmedium); } if ( FAILED( hr ) ) @@ -464,14 +499,14 @@ CDOTransferable::ByteSequence_t CDOTransferable::getClipboardData( CFormatEtc& a try { - if ( CF_ENHMETAFILE == aFormatEtc.getClipformat() ) + if (CF_ENHMETAFILE == aLocalFormatEtc.getClipformat()) byteStream = WinENHMFPictToOOMFPict( stgmedium.hEnhMetaFile ); - else if (CF_HDROP == aFormatEtc.getClipformat()) + else if (CF_HDROP == aLocalFormatEtc.getClipformat()) byteStream = CF_HDROPToFileList(stgmedium.hGlobal); - else if ( CF_BITMAP == aFormatEtc.getClipformat() ) + else if (CF_BITMAP == aLocalFormatEtc.getClipformat()) { byteStream = WinBITMAPToOOBMP(stgmedium.hBitmap); - if( aFormatEtc.getTymed() == TYMED_GDI && + if (aLocalFormatEtc.getTymed() == TYMED_GDI && ! stgmedium.pUnkForRelease ) { DeleteObject(stgmedium.hBitmap); @@ -479,15 +514,16 @@ CDOTransferable::ByteSequence_t CDOTransferable::getClipboardData( CFormatEtc& a } else { - clipDataToByteStream( aFormatEtc.getClipformat( ), stgmedium, byteStream ); + clipDataToByteStream(aLocalFormatEtc.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()) + if (CF_DIBV5 == aLocalFormatEtc.getClipformat() + || CF_DIB == aLocalFormatEtc.getClipformat()) { byteStream = WinDIBToOOBMP(byteStream); } - else if(CF_METAFILEPICT == aFormatEtc.getClipformat()) + else if (CF_METAFILEPICT == aLocalFormatEtc.getClipformat()) { byteStream = WinMFPictToOOMFPict(byteStream); } diff --git a/vcl/win/dtrans/FmtFilter.cxx b/vcl/win/dtrans/FmtFilter.cxx index 614c8d27f375..47aff85c6cf4 100644 --- a/vcl/win/dtrans/FmtFilter.cxx +++ b/vcl/win/dtrans/FmtFilter.cxx @@ -183,9 +183,8 @@ HENHMETAFILE OOMFPictToWinENHMFPict( Sequence< sal_Int8 > const & aOOMetaFilePic Sequence< sal_Int8 > WinDIBToOOBMP( const Sequence< sal_Int8 >& aWinDIB ) { OSL_ENSURE(o3tl::make_unsigned(aWinDIB.getLength()) > sizeof(BITMAPINFOHEADER), "CF_DIBV5/CF_DIB too small (!)"); - Sequence< sal_Int8 > ooBmpStream; + Sequence< sal_Int8 > ooBmpStream(aWinDIB.getLength( ) + sizeof(BITMAPFILEHEADER)); - ooBmpStream.realloc(aWinDIB.getLength( ) + sizeof(BITMAPFILEHEADER)); const BITMAPINFOHEADER* pBmpInfoHdr = reinterpret_cast< const BITMAPINFOHEADER* >(aWinDIB.getConstArray()); BITMAPFILEHEADER* pBmpFileHdr = reinterpret_cast< BITMAPFILEHEADER* >(ooBmpStream.getArray()); const DWORD nSizeInfoOrV5(pBmpInfoHdr->biSize > sizeof(BITMAPINFOHEADER) ? sizeof(BITMAPV5HEADER) : sizeof(BITMAPINFOHEADER)); @@ -400,36 +399,27 @@ Sequence< sal_Int8 > WinBITMAPToOOBMP( HBITMAP aHBMP ) { Sequence< sal_Int8 > ooBmpStream; - SIZE aBmpSize; - if( GetBitmapDimensionEx( aHBMP, &aBmpSize ) ) + if (BITMAP bm{}; GetObjectW(aHBMP, sizeof(bm), &bm)) { - // fill bitmap info header - size_t nDataBytes = 4 * aBmpSize.cy * aBmpSize.cy; + size_t nDataBytes = ((bm.bmWidth * bm.bmBitsPixel + 31) / 32) * 4 * bm.bmHeight; Sequence< sal_Int8 > aBitmapStream( sizeof(BITMAPINFO) + nDataBytes ); - PBITMAPINFOHEADER pBmp = reinterpret_cast<PBITMAPINFOHEADER>(aBitmapStream.getArray()); - pBmp->biSize = sizeof( BITMAPINFOHEADER ); - pBmp->biWidth = aBmpSize.cx; - pBmp->biHeight = aBmpSize.cy; - pBmp->biPlanes = 1; - pBmp->biBitCount = 32; - pBmp->biCompression = BI_RGB; - pBmp->biSizeImage = static_cast<DWORD>(nDataBytes); - pBmp->biXPelsPerMeter = 1000; - pBmp->biYPelsPerMeter = 1000; - pBmp->biClrUsed = 0; - pBmp->biClrImportant = 0; - if( GetDIBits( nullptr, // DC, 0 is a default GC, basically that of the desktop - aHBMP, - 0, aBmpSize.cy, - aBitmapStream.getArray() + sizeof(BITMAPINFO), - reinterpret_cast<LPBITMAPINFO>(pBmp), - DIB_RGB_COLORS ) ) + PBITMAPINFO pBmp = reinterpret_cast<PBITMAPINFO>(aBitmapStream.getArray()); + pBmp->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + pBmp->bmiHeader.biWidth = bm.bmWidth; + pBmp->bmiHeader.biHeight = bm.bmHeight; + pBmp->bmiHeader.biPlanes = 1; + pBmp->bmiHeader.biBitCount = bm.bmBitsPixel; + pBmp->bmiHeader.biCompression = BI_RGB; + + HDC hdc = GetDC(nullptr); + if (GetDIBits(hdc, aHBMP, 0, bm.bmHeight, pBmp + 1, pBmp, DIB_RGB_COLORS)) { ooBmpStream = WinDIBToOOBMP( aBitmapStream ); } + ReleaseDC(nullptr, hdc); } return ooBmpStream;
