From 7ca94e89593bd115ebb56925d0d51ccfa91dd2e9 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Fri, 21 Aug 2026 13:40:34 +1000 Subject: [PATCH] [idd] helper: expose PNG clipboard images as DIB Advertise PNG offers as delayed CF_DIBV5 data while retaining the registered PNG representation. Decode requests with the native WIC PNG decoder so applications that only accept DIB formats can paste the image. --- idd/LGIddHelper/CClipboardManager.cpp | 161 +++++++++++++++++++++++--- idd/LGIddHelper/CClipboardManager.h | 2 +- idd/LGIddHelper/LGIddHelper.vcxproj | 2 +- 3 files changed, 150 insertions(+), 15 deletions(-) diff --git a/idd/LGIddHelper/CClipboardManager.cpp b/idd/LGIddHelper/CClipboardManager.cpp index abd2d8d6..2b75ecd8 100644 --- a/idd/LGIddHelper/CClipboardManager.cpp +++ b/idd/LGIddHelper/CClipboardManager.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -807,6 +808,124 @@ namespace GlobalUnlock(memory); return memory; } + + HGLOBAL PNGToDIBV5(CClipboardSpool& spool) + { + if (!spool.Size()) + { + SetLastError(ERROR_INVALID_DATA); + return nullptr; + } + + HGLOBAL source = CopySpoolToGlobal(spool, 0); + if (!source) + return nullptr; + + ClipboardComScope stream; + HRESULT result = CreateStreamOnHGlobal(source, TRUE, &stream.object); + if (FAILED(result)) + { + GlobalFree(source); + SetLastError(result == E_OUTOFMEMORY ? ERROR_OUTOFMEMORY : + ERROR_INVALID_DATA); + return nullptr; + } + + ClipboardComScope factory; + ClipboardComScope decoder; + ClipboardComScope frame; + ClipboardComScope converter; + result = CoCreateInstance(CLSID_WICImagingFactory, nullptr, + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory.object)); + if (SUCCEEDED(result)) + result = CoCreateInstance(CLSID_WICPngDecoder, nullptr, + CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&decoder.object)); + if (SUCCEEDED(result)) + result = decoder.object->Initialize(stream.object, + WICDecodeMetadataCacheOnLoad); + if (SUCCEEDED(result)) + result = decoder.object->GetFrame(0, &frame.object); + if (SUCCEEDED(result)) + result = factory.object->CreateFormatConverter(&converter.object); + if (SUCCEEDED(result)) + result = converter.object->Initialize(frame.object, + GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, nullptr, + 0.0, WICBitmapPaletteTypeCustom); + if (FAILED(result)) + { + SetLastError(result == E_OUTOFMEMORY ? ERROR_OUTOFMEMORY : + ERROR_INVALID_DATA); + return nullptr; + } + + UINT width = 0; + UINT height = 0; + result = converter.object->GetSize(&width, &height); + const UINT maxDimension = static_cast( + (std::numeric_limits::max)()); + if (FAILED(result) || !width || !height || width > maxDimension || + height > maxDimension || width > UINT_MAX / 4U) + { + SetLastError(result == E_OUTOFMEMORY ? ERROR_OUTOFMEMORY : + ERROR_INVALID_DATA); + return nullptr; + } + + const UINT stride = width * 4U; + if (height > UINT64_MAX / stride) + { + SetLastError(ERROR_FILE_TOO_LARGE); + return nullptr; + } + const uint64_t pixelBytes = static_cast(stride) * height; + if (pixelBytes > UINT_MAX || + pixelBytes > MAX_SPOOL_BYTES - sizeof(BITMAPV5HEADER) || + pixelBytes > (std::numeric_limits::max)() - + sizeof(BITMAPV5HEADER)) + { + SetLastError(ERROR_FILE_TOO_LARGE); + return nullptr; + } + + const SIZE_T outputBytes = sizeof(BITMAPV5HEADER) + + static_cast(pixelBytes); + HGLOBAL output = GlobalAlloc(GMEM_MOVEABLE, outputBytes); + if (!output) + return nullptr; + uint8_t * data = static_cast(GlobalLock(output)); + if (!data) + { + GlobalFree(output); + return nullptr; + } + + BITMAPV5HEADER header = {}; + header.bV5Size = static_cast(sizeof(header)); + header.bV5Width = static_cast(width); + header.bV5Height = -static_cast(height); + header.bV5Planes = 1; + header.bV5BitCount = 32; + header.bV5Compression = BI_BITFIELDS; + header.bV5SizeImage = static_cast(pixelBytes); + header.bV5RedMask = 0x00ff0000U; + header.bV5GreenMask = 0x0000ff00U; + header.bV5BlueMask = 0x000000ffU; + header.bV5AlphaMask = 0xff000000U; + header.bV5CSType = LCS_sRGB; + header.bV5Intent = LCS_GM_IMAGES; + memcpy(data, &header, sizeof(header)); + result = converter.object->CopyPixels(nullptr, stride, + static_cast(pixelBytes), data + sizeof(header)); + GlobalUnlock(output); + if (FAILED(result)) + { + GlobalFree(output); + SetLastError(result == E_OUTOFMEMORY ? ERROR_OUTOFMEMORY : + ERROR_INVALID_DATA); + return nullptr; + } + return output; + } } CClipboardManager::IncomingTransfer::~IncomingTransfer() @@ -4244,10 +4363,16 @@ bool CClipboardManager::ApplyRemoteOffer(uint32_t formats, bool complete = true; if (formats & KVMFR_CLIPBOARD_FORMAT_MASK_TEXT) complete = setDelayed(CF_UNICODETEXT) && complete; - if (formats & KVMFR_CLIPBOARD_FORMAT_MASK_PNG) + const bool havePNG = + (formats & KVMFR_CLIPBOARD_FORMAT_MASK_PNG) != 0; + const bool haveBMP = + (formats & KVMFR_CLIPBOARD_FORMAT_MASK_BMP) != 0; + if (havePNG) complete = setDelayed(m_formatPNG) && complete; - if (formats & KVMFR_CLIPBOARD_FORMAT_MASK_BMP) + if (haveBMP) complete = setDelayed(CF_DIB) && complete; + else if (havePNG) + complete = setDelayed(CF_DIBV5) && complete; if (formats & KVMFR_CLIPBOARD_FORMAT_MASK_TIFF) complete = setDelayed(CF_TIFF) && complete; if (formats & KVMFR_CLIPBOARD_FORMAT_MASK_JPEG) @@ -4526,9 +4651,8 @@ std::shared_ptr CClipboardManager::CaptureFormat( } bool CClipboardManager::MaterializeFormat(KVMFRClipboardFormat format, - CClipboardSpool& spool) + UINT windowsFormat, CClipboardSpool& spool) { - UINT windowsFormat = ToWindowsFormat(format); if (format == KVMFR_CLIPBOARD_FORMAT_BMP) windowsFormat = CF_DIB; if (!windowsFormat) @@ -4540,6 +4664,9 @@ bool CClipboardManager::MaterializeFormat(KVMFRClipboardFormat format, HGLOBAL memory = nullptr; if (format == KVMFR_CLIPBOARD_FORMAT_TEXT) memory = UnicodeFromUTF8(spool); + else if (format == KVMFR_CLIPBOARD_FORMAT_PNG && + (windowsFormat == CF_DIB || windowsFormat == CF_DIBV5)) + memory = PNGToDIBV5(spool); else if (format == KVMFR_CLIPBOARD_FORMAT_BMP) { BITMAPFILEHEADER header = {}; @@ -4575,7 +4702,11 @@ bool CClipboardManager::MaterializeFormat(KVMFRClipboardFormat format, void CClipboardManager::RenderFormat(UINT windowsFormat, uint64_t deadline) { - const KVMFRClipboardFormat format = ToWireFormat(windowsFormat); + KVMFRClipboardFormat format = ToWireFormat(windowsFormat); + if ((windowsFormat == CF_DIB || windowsFormat == CF_DIBV5) && + !(m_remoteFormats & KVMFR_CLIPBOARD_FORMAT_MASK_BMP) && + (m_remoteFormats & KVMFR_CLIPBOARD_FORMAT_MASK_PNG)) + format = KVMFR_CLIPBOARD_FORMAT_PNG; if (!m_available || !m_remoteGeneration || !kvmfrClipboardRepresentationFormatValid(format) || !(m_remoteFormats & kvmfrClipboardFormatFlag(format))) @@ -4658,9 +4789,11 @@ void CClipboardManager::RenderFormat(UINT windowsFormat, uint64_t deadline) m_incoming.reset(); } - if (render && !MaterializeFormat(format, *transfer->spool)) - DEBUG_WARN_HR(GetLastError(), "Failed to render clipboard format %u", - format); + if (render && + !MaterializeFormat(format, windowsFormat, *transfer->spool)) + DEBUG_WARN_HR(GetLastError(), + "Failed to render clipboard format %u as %u", format, + windowsFormat); } void CClipboardManager::RenderAllFormats() @@ -4675,14 +4808,16 @@ void CClipboardManager::RenderAllFormats() const uint32_t formats = m_remoteFormats; const uint64_t deadline = GetTickCount64() + RENDER_TIMEOUT_MS; + const bool havePNG = + (formats & KVMFR_CLIPBOARD_FORMAT_MASK_PNG) != 0; + const bool haveBMP = + (formats & KVMFR_CLIPBOARD_FORMAT_MASK_BMP) != 0; if (formats & KVMFR_CLIPBOARD_FORMAT_MASK_TEXT) RenderFormat(CF_UNICODETEXT, deadline); - if (GetTickCount64() < deadline && - (formats & KVMFR_CLIPBOARD_FORMAT_MASK_PNG)) + if (GetTickCount64() < deadline && (havePNG || haveBMP)) + RenderFormat(haveBMP ? CF_DIB : CF_DIBV5, deadline); + if (GetTickCount64() < deadline && havePNG) RenderFormat(m_formatPNG, deadline); - if (GetTickCount64() < deadline && - (formats & KVMFR_CLIPBOARD_FORMAT_MASK_BMP)) - RenderFormat(CF_DIB, deadline); if (GetTickCount64() < deadline && (formats & KVMFR_CLIPBOARD_FORMAT_MASK_TIFF)) RenderFormat(CF_TIFF, deadline); diff --git a/idd/LGIddHelper/CClipboardManager.h b/idd/LGIddHelper/CClipboardManager.h index 7b37ec55..9dd1ac5e 100644 --- a/idd/LGIddHelper/CClipboardManager.h +++ b/idd/LGIddHelper/CClipboardManager.h @@ -323,7 +323,7 @@ private: std::shared_ptr CaptureFormat( KVMFRClipboardFormat format, DWORD sequence); bool MaterializeFormat(KVMFRClipboardFormat format, - CClipboardSpool& spool); + UINT windowsFormat, CClipboardSpool& spool); void ClipboardState(bool available, uint64_t epoch) override; ClipboardChannelResult ClipboardRecord( diff --git a/idd/LGIddHelper/LGIddHelper.vcxproj b/idd/LGIddHelper/LGIddHelper.vcxproj index 8ae4b0d5..d97f3a5e 100644 --- a/idd/LGIddHelper/LGIddHelper.vcxproj +++ b/idd/LGIddHelper/LGIddHelper.vcxproj @@ -188,7 +188,7 @@ copy /Y "$(ProjectDir)VERSION" "$(SolutionDir)$(Platform)\$(Configuration)\LGIdd StdCall - %(AdditionalDependencies);Bcrypt.lib;Ole32.lib;Shell32.lib;Wtsapi32.lib + %(AdditionalDependencies);Bcrypt.lib;Ole32.lib;Shell32.lib;Windowscodecs.lib;Wtsapi32.lib