From 9c3838daa55178f3d33a09d0d1c97ac2b4ebbca1 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Fri, 15 Nov 2019 17:41:08 +0530 Subject: [PATCH 1/3] Conhost: copy RTF to clipboard RTF data is now copied to the clipboard. The clipboard format name used for RTF data is `Rich Text Format`. Refactored some code in `Clipboard.cpp` so that code for setting data to the clipboard is re-used. Also, renamed parameter `fAlsoCopyHtml` to `fAlsoCopyFormatting` to make it more generic. Tested by copying text from console to WordPad. Also verified that HTML copy is not regressed by copying to Word. --- src/interactivity/win32/Clipboard.cpp | 80 +++++++++++++++------------ src/interactivity/win32/clipboard.hpp | 8 +-- src/interactivity/win32/windowio.cpp | 4 +- 3 files changed, 51 insertions(+), 41 deletions(-) diff --git a/src/interactivity/win32/Clipboard.cpp b/src/interactivity/win32/Clipboard.cpp index 4ac60083816..1906a1bc68a 100644 --- a/src/interactivity/win32/Clipboard.cpp +++ b/src/interactivity/win32/Clipboard.cpp @@ -24,20 +24,20 @@ using namespace Microsoft::Console::Types; #pragma region Public Methods // Arguments: -// - fAlsoCopyHtml - Place colored HTML text onto the clipboard as well as the usual plain text. +// - fAlsoCopyFormatting - Place colored HTML & RTF text onto the clipboard as well as the usual plain text. // Return Value: // -// NOTE: if the registry is set to always copy color data then we will even if fAlsoCopyHTML is false -void Clipboard::Copy(bool fAlsoCopyHtml) +// NOTE: if the registry is set to always copy color data then we will even if fAlsoCopyFormatting is false +void Clipboard::Copy(bool fAlsoCopyFormatting) { try { // registry settings may tell us to always copy the color/formating CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); - fAlsoCopyHtml = fAlsoCopyHtml || gci.GetCopyColor(); + fAlsoCopyFormatting = fAlsoCopyFormatting || gci.GetCopyColor(); // store selection in clipboard - StoreSelectionToClipboard(fAlsoCopyHtml); + StoreSelectionToClipboard(fAlsoCopyFormatting); Selection::Instance().ClearSelection(); // clear selection in console } CATCH_LOG(); @@ -188,10 +188,10 @@ std::deque> Clipboard::TextToKeyEvents(_In_reads_(c // - Copies the selected area onto the global system clipboard. // - NOTE: Throws on allocation and other clipboard failures. // Arguments: -// - fAlsoCopyHtml - This will also place colored HTML text onto the clipboard as well as the usual plain text. +// - fAlsoCopyFormatting - This will also place colored HTML & RTF text onto the clipboard as well as the usual plain text. // Return Value: // -void Clipboard::StoreSelectionToClipboard(bool const fAlsoCopyHtml) +void Clipboard::StoreSelectionToClipboard(bool const fAlsoCopyFormatting) { const auto& selection = Selection::Instance(); @@ -212,7 +212,7 @@ void Clipboard::StoreSelectionToClipboard(bool const fAlsoCopyHtml) lineSelection, selectionRects); - CopyTextToSystemClipboard(text, fAlsoCopyHtml); + CopyTextToSystemClipboard(text, fAlsoCopyFormatting); } // Routine Description: @@ -243,7 +243,8 @@ TextBuffer::TextAndColor Clipboard::RetrieveTextFromBuffer(const SCREEN_INFORMAT // - Copies the text given onto the global system clipboard. // Arguments: // - rows - Rows of text data to copy -void Clipboard::CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, bool const fAlsoCopyHtml) +// - fAlsoCopyFormatting - true if the color and formatting should also be copied, false otherwise +void Clipboard::CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, bool const fAlsoCopyFormatting) { std::wstring finalString; @@ -279,37 +280,17 @@ void Clipboard::CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, THROW_LAST_ERROR_IF(!EmptyClipboard()); THROW_LAST_ERROR_IF_NULL(SetClipboardData(CF_UNICODETEXT, globalHandle.get())); - if (fAlsoCopyHtml) + if (fAlsoCopyFormatting) { const auto& fontData = ServiceLocator::LocateGlobals().getConsoleInformation().GetActiveOutputBuffer().GetCurrentFont(); int const iFontHeightPoints = fontData.GetUnscaledSize().Y * 72 / ServiceLocator::LocateGlobals().dpi; const COLORREF bgColor = ServiceLocator::LocateGlobals().getConsoleInformation().GetDefaultBackground(); + std::string HTMLToPlaceOnClip = TextBuffer::GenHTML(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor, "Windows Console Host"); - const size_t cbNeededHTML = HTMLToPlaceOnClip.size() + 1; - if (cbNeededHTML) - { - wil::unique_hglobal globalHandleHTML(GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cbNeededHTML)); - THROW_LAST_ERROR_IF_NULL(globalHandleHTML.get()); - - PSTR pszClipboardHTML = (PSTR)GlobalLock(globalHandleHTML.get()); - THROW_LAST_ERROR_IF_NULL(pszClipboardHTML); - - // The pattern gets a bit strange here because there's no good wil built-in for global lock of this type. - // Try to copy then immediately unlock. Don't throw until after (so the hglobal won't be freed until we unlock). - const HRESULT hr2 = StringCchCopyA(pszClipboardHTML, cbNeededHTML, HTMLToPlaceOnClip.data()); - GlobalUnlock(globalHandleHTML.get()); - THROW_IF_FAILED(hr2); - - UINT const CF_HTML = RegisterClipboardFormatW(L"HTML Format"); - THROW_LAST_ERROR_IF(0 == CF_HTML); - - THROW_LAST_ERROR_IF_NULL(SetClipboardData(CF_HTML, globalHandleHTML.get())); - - // only free if we failed. - // the memory has to remain allocated if we successfully placed it on the clipboard. - // Releasing the smart pointer will leave it allocated as we exit scope. - globalHandleHTML.release(); - } + CopyToSystemClipboard(HTMLToPlaceOnClip, L"HTML Format"); + + std::string RTFToPlaceOnClip = TextBuffer::GenRTF(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor); + CopyToSystemClipboard(RTFToPlaceOnClip, L"Rich Text Format"); } } @@ -319,6 +300,35 @@ void Clipboard::CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, globalHandle.release(); } +void Clipboard::CopyToSystemClipboard(std::string s, LPCWSTR lpszFormat) +{ + const size_t rtfSize = s.size() + 1; // +1 for '\0' + if (rtfSize) + { + wil::unique_hglobal globalHandleHTML(GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, rtfSize)); + THROW_LAST_ERROR_IF_NULL(globalHandleHTML.get()); + + PSTR pszClipboardHTML = (PSTR)GlobalLock(globalHandleHTML.get()); + THROW_LAST_ERROR_IF_NULL(pszClipboardHTML); + + // The pattern gets a bit strange here because there's no good wil built-in for global lock of this type. + // Try to copy then immediately unlock. Don't throw until after (so the hglobal won't be freed until we unlock). + const HRESULT hr2 = StringCchCopyA(pszClipboardHTML, rtfSize, s.data()); + GlobalUnlock(globalHandleHTML.get()); + THROW_IF_FAILED(hr2); + + UINT const CF_FORMAT = RegisterClipboardFormatW(lpszFormat); + THROW_LAST_ERROR_IF(0 == CF_FORMAT); + + THROW_LAST_ERROR_IF_NULL(SetClipboardData(CF_FORMAT, globalHandleHTML.get())); + + // only free if we failed. + // the memory has to remain allocated if we successfully placed it on the clipboard. + // Releasing the smart pointer will leave it allocated as we exit scope. + globalHandleHTML.release(); + } +} + // Returns true if the character should be emitted to the paste stream // -- in some cases, we will change what character should be emitted, as in the case of "smart quotes" // Returns false if the character should not be emitted (e.g. ) diff --git a/src/interactivity/win32/clipboard.hpp b/src/interactivity/win32/clipboard.hpp index d187ba51b7c..17e65b1aab8 100644 --- a/src/interactivity/win32/clipboard.hpp +++ b/src/interactivity/win32/clipboard.hpp @@ -29,7 +29,7 @@ namespace Microsoft::Console::Interactivity::Win32 public: static Clipboard& Instance(); - void Copy(_In_ bool const fAlsoCopyHtml = false); + void Copy(_In_ bool const fAlsoCopyFormatting = false); void StringPaste(_In_reads_(cchData) PCWCHAR pwchData, const size_t cchData); void Paste(); @@ -38,14 +38,14 @@ namespace Microsoft::Console::Interactivity::Win32 std::deque> TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData, const size_t cchData); - void StoreSelectionToClipboard(_In_ bool const fAlsoCopyHtml); + void StoreSelectionToClipboard(_In_ bool const fAlsoCopyFormatting); TextBuffer::TextAndColor RetrieveTextFromBuffer(const SCREEN_INFORMATION& screenInfo, const bool lineSelection, const std::vector& selectionRects); - void CopyHTMLToClipboard(const TextBuffer::TextAndColor& rows); - void CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, _In_ bool const fAlsoCopyHtml); + void CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, _In_ bool const fAlsoCopyFormatting); + void CopyToSystemClipboard(std::string stringToPlaceOnClip, LPCWSTR lpszFormat); bool FilterCharacterOnPaste(_Inout_ WCHAR* const pwch); diff --git a/src/interactivity/win32/windowio.cpp b/src/interactivity/win32/windowio.cpp index 921f3f01179..08aca5db646 100644 --- a/src/interactivity/win32/windowio.cpp +++ b/src/interactivity/win32/windowio.cpp @@ -800,8 +800,8 @@ BOOL HandleMouseEvent(const SCREEN_INFORMATION& ScreenInfo, Telemetry::Instance().LogQuickEditCopyRawUsed(); } // If the ALT key is held, also select HTML as well as plain text. - bool const fAlsoSelectHtml = WI_IsFlagSet(GetKeyState(VK_MENU), KEY_PRESSED); - Clipboard::Instance().Copy(fAlsoSelectHtml); + bool const fAlsoCopyFormatting = WI_IsFlagSet(GetKeyState(VK_MENU), KEY_PRESSED); + Clipboard::Instance().Copy(fAlsoCopyFormatting); } else if (gci.Flags & CONSOLE_QUICK_EDIT_MODE) { From 805058430093cbf71698d61d4140b8213a81a5d7 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Sat, 23 Nov 2019 14:43:15 +0530 Subject: [PATCH 2/3] Added description for CopyToSystemClipboard function --- src/interactivity/win32/Clipboard.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/interactivity/win32/Clipboard.cpp b/src/interactivity/win32/Clipboard.cpp index 1906a1bc68a..601a4dc69c1 100644 --- a/src/interactivity/win32/Clipboard.cpp +++ b/src/interactivity/win32/Clipboard.cpp @@ -300,9 +300,14 @@ void Clipboard::CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, globalHandle.release(); } -void Clipboard::CopyToSystemClipboard(std::string s, LPCWSTR lpszFormat) +// Routine Description: +// - Copies the given string onto the global system clipboard in the sepcified format +// Arguments: +// - stringToCopy - The string to copy +// - lpszFormat - the name of the format +void Clipboard::CopyToSystemClipboard(std::string stringToCopy, LPCWSTR lpszFormat) { - const size_t rtfSize = s.size() + 1; // +1 for '\0' + const size_t rtfSize = stringToCopy.size() + 1; // +1 for '\0' if (rtfSize) { wil::unique_hglobal globalHandleHTML(GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, rtfSize)); @@ -313,7 +318,7 @@ void Clipboard::CopyToSystemClipboard(std::string s, LPCWSTR lpszFormat) // The pattern gets a bit strange here because there's no good wil built-in for global lock of this type. // Try to copy then immediately unlock. Don't throw until after (so the hglobal won't be freed until we unlock). - const HRESULT hr2 = StringCchCopyA(pszClipboardHTML, rtfSize, s.data()); + const HRESULT hr2 = StringCchCopyA(pszClipboardHTML, rtfSize, stringToCopy.data()); GlobalUnlock(globalHandleHTML.get()); THROW_IF_FAILED(hr2); From c5c21ffb0babffd5dfb7e3391d4596ed7a4cfbb2 Mon Sep 17 00:00:00 2001 From: Anirudh Rayabharam Date: Thu, 5 Dec 2019 16:05:57 +0530 Subject: [PATCH 3/3] Renamed vars as per code review suggestions --- src/interactivity/win32/Clipboard.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/interactivity/win32/Clipboard.cpp b/src/interactivity/win32/Clipboard.cpp index 601a4dc69c1..3b38271a952 100644 --- a/src/interactivity/win32/Clipboard.cpp +++ b/src/interactivity/win32/Clipboard.cpp @@ -307,30 +307,30 @@ void Clipboard::CopyTextToSystemClipboard(const TextBuffer::TextAndColor& rows, // - lpszFormat - the name of the format void Clipboard::CopyToSystemClipboard(std::string stringToCopy, LPCWSTR lpszFormat) { - const size_t rtfSize = stringToCopy.size() + 1; // +1 for '\0' - if (rtfSize) + const size_t cbData = stringToCopy.size() + 1; // +1 for '\0' + if (cbData) { - wil::unique_hglobal globalHandleHTML(GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, rtfSize)); - THROW_LAST_ERROR_IF_NULL(globalHandleHTML.get()); + wil::unique_hglobal globalHandleData(GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, cbData)); + THROW_LAST_ERROR_IF_NULL(globalHandleData.get()); - PSTR pszClipboardHTML = (PSTR)GlobalLock(globalHandleHTML.get()); + PSTR pszClipboardHTML = (PSTR)GlobalLock(globalHandleData.get()); THROW_LAST_ERROR_IF_NULL(pszClipboardHTML); // The pattern gets a bit strange here because there's no good wil built-in for global lock of this type. // Try to copy then immediately unlock. Don't throw until after (so the hglobal won't be freed until we unlock). - const HRESULT hr2 = StringCchCopyA(pszClipboardHTML, rtfSize, stringToCopy.data()); - GlobalUnlock(globalHandleHTML.get()); + const HRESULT hr2 = StringCchCopyA(pszClipboardHTML, cbData, stringToCopy.data()); + GlobalUnlock(globalHandleData.get()); THROW_IF_FAILED(hr2); UINT const CF_FORMAT = RegisterClipboardFormatW(lpszFormat); THROW_LAST_ERROR_IF(0 == CF_FORMAT); - THROW_LAST_ERROR_IF_NULL(SetClipboardData(CF_FORMAT, globalHandleHTML.get())); + THROW_LAST_ERROR_IF_NULL(SetClipboardData(CF_FORMAT, globalHandleData.get())); // only free if we failed. // the memory has to remain allocated if we successfully placed it on the clipboard. // Releasing the smart pointer will leave it allocated as we exit scope. - globalHandleHTML.release(); + globalHandleData.release(); } }