Add support for OSC 52 clipboard copy in conhost (#18949)

This adds support for copying to the clipboard in conhost using the OSC
52 escape sequence, extending the original implementation which was for
Windows Terminal only.

The Windows Terminal implementation was added in PR #5823.

Because the clipboard can't be accessed from a background thread, this
works by saving the content in a global variable, and then posting a
custom message to the main GUI thread, which takes care of the actual
copy operation.

Validation:
I've manually confirmed that tmux copy mode is now able to copy to the
system clipboard.

Closes #18943
This commit is contained in:
James Holderness 2025-06-10 21:09:51 +01:00 committed by GitHub
parent 155d8a9ab2
commit 4abc041eb7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 60 additions and 2 deletions

View File

@ -13,6 +13,7 @@
#include "srvinit.h"
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../interactivity/win32/CustomWindowMessages.h"
#include "../types/inc/convert.hpp"
using Microsoft::Console::Interactivity::ServiceLocator;
@ -179,6 +180,32 @@ void CONSOLE_INFORMATION::SetBracketedPasteMode(const bool enabled) noexcept
_bracketedPasteMode = enabled;
}
void CONSOLE_INFORMATION::CopyTextToClipboard(const std::wstring_view text)
{
const auto window = ServiceLocator::LocateConsoleWindow();
if (window)
{
// The clipboard can only be updated from the main GUI thread, so we
// need to post a message to trigger the actual copy operation. But if
// the pending clipboard content is already set, a message would have
// already been posted, so there's no need to post another one.
const auto clipboardMessageSent = _pendingClipboardText.has_value();
_pendingClipboardText = text;
if (!clipboardMessageSent)
{
PostMessageW(window->GetWindowHandle(), CM_UPDATE_CLIPBOARD, 0, 0);
}
}
}
std::optional<std::wstring> CONSOLE_INFORMATION::UsePendingClipboardText()
{
// Once the pending text has been used, we clear the variable to let the
// CopyTextToClipboard method know that the last CM_UPDATE_CLIPBOARD message
// has been processed, and future updates will require another message.
return std::exchange(_pendingClipboardText, {});
}
// Method Description:
// - Return the active screen buffer of the console.
// Arguments:

View File

@ -280,9 +280,9 @@ unsigned int ConhostInternalGetSet::GetInputCodePage() const
// - content - the text to be copied.
// Return Value:
// - <none>
void ConhostInternalGetSet::CopyToClipboard(const wil::zwstring_view /*content*/)
void ConhostInternalGetSet::CopyToClipboard(const wil::zwstring_view content)
{
// TODO
ServiceLocator::LocateGlobals().getConsoleInformation().CopyTextToClipboard(content);
}
// Routine Description:

View File

@ -126,6 +126,8 @@ public:
bool GetBracketedPasteMode() const noexcept;
void SetBracketedPasteMode(const bool enabled) noexcept;
void CopyTextToClipboard(const std::wstring_view text);
std::optional<std::wstring> UsePendingClipboardText();
void SetTitle(const std::wstring_view newTitle);
void SetTitlePrefix(const std::wstring_view newTitlePrefix);
@ -160,6 +162,7 @@ private:
SCREEN_INFORMATION* pCurrentScreenBuffer = nullptr;
COOKED_READ_DATA* _cookedReadData = nullptr; // non-ownership pointer
bool _bracketedPasteMode = false;
std::optional<std::wstring> _pendingClipboardText;
Microsoft::Console::VirtualTerminal::VtIo _vtIo;
Microsoft::Console::CursorBlinker _blinker;

View File

@ -24,6 +24,22 @@ using namespace Microsoft::Console::Types;
#pragma region Public Methods
void Clipboard::CopyText(const std::wstring& text)
{
const auto clipboard = _openClipboard(ServiceLocator::LocateConsoleWindow()->GetWindowHandle());
if (!clipboard)
{
LOG_LAST_ERROR();
return;
}
EmptyClipboard();
// As per: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats
// CF_UNICODETEXT: [...] A null character signals the end of the data.
// --> We add +1 to the length. This works because .c_str() is null-terminated.
_copyToClipboard(CF_UNICODETEXT, text.c_str(), (text.size() + 1) * sizeof(wchar_t));
}
// Arguments:
// - fAlsoCopyFormatting - Place colored HTML & RTF text onto the clipboard as well as the usual plain text.
// Return Value:

View File

@ -29,4 +29,6 @@
#define CM_SET_KEYBOARD_LAYOUT (WM_USER+19)
#endif
#define CM_UPDATE_CLIPBOARD (WM_USER+20)
// clang-format on

View File

@ -29,6 +29,7 @@ namespace Microsoft::Console::Interactivity::Win32
public:
static Clipboard& Instance();
void CopyText(const std::wstring& text);
void Copy(_In_ const bool fAlsoCopyFormatting = false);
void Paste();
void PasteDrop(HDROP drop);

View File

@ -773,6 +773,15 @@ static constexpr TsfDataProvider s_tsfDataProvider;
}
#endif // DBG
case CM_UPDATE_CLIPBOARD:
{
if (const auto clipboardText = gci.UsePendingClipboardText())
{
Clipboard::Instance().CopyText(clipboardText.value());
}
break;
}
case EVENT_CONSOLE_CARET:
case EVENT_CONSOLE_UPDATE_REGION:
case EVENT_CONSOLE_UPDATE_SIMPLE: