Skip to content
Merged
2 changes: 1 addition & 1 deletion .github/actions/spell-check/patterns/patterns.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ Scro\&ll
# selectionInput.cpp
:\\windows\\syste\b
TestUtils::VerifyExpectedString\(tb, L"[^"]+"
hostSm\.ProcessString\(L"[^"]+"
(?:hostSm|mach)\.ProcessString\(L"[^"]+"
\b([A-Za-z])\1{3,}\b
11 changes: 11 additions & 0 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
auto pfnTerminalCursorPositionChanged = std::bind(&TermControl::_TerminalCursorPositionChanged, this);
_terminal->SetCursorPositionChangedCallback(pfnTerminalCursorPositionChanged);

auto pfnCopyToClipboard = std::bind(&TermControl::_CopyToClipboard, this, std::placeholders::_1);
_terminal->SetCopyToClipboardCallback(pfnCopyToClipboard);

// This event is explicitly revoked in the destructor: does not need weak_ref
auto onReceiveOutputFn = [this](const hstring str) {
_terminal->Write(str);
Expand Down Expand Up @@ -1906,6 +1909,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_titleChangedHandlers(winrt::hstring{ wstr });
}

void TermControl::_CopyToClipboard(const std::wstring_view& wstr)
{
auto copyArgs = winrt::make_self<CopyToClipboardEventArgs>(winrt::hstring(wstr),
winrt::hstring(L""),
winrt::hstring(L""));
_clipboardCopyHandlers(*this, *copyArgs);
}

// Method Description:
// - Update the position and size of the scrollbar to match the given
// viewport top, viewport height, and buffer size.
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _DoResizeUnderLock(const double newWidth, const double newHeight);
void _RefreshSizeUnderLock();
void _TerminalTitleChanged(const std::wstring_view& wstr);
void _CopyToClipboard(const std::wstring_view& wstr);
winrt::fire_and_forget _TerminalScrollPositionChanged(const int viewTop, const int viewHeight, const int bufferSize);
winrt::fire_and_forget _TerminalCursorPositionChanged();

Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalCore/ITerminalApi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ namespace Microsoft::Terminal::Core

virtual bool IsVtInputEnabled() const = 0;

virtual bool CopyToClipboard(std::wstring_view content) noexcept = 0;

protected:
ITerminalApi() = default;
};
Expand Down
5 changes: 5 additions & 0 deletions src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,11 @@ void Terminal::SetTitleChangedCallback(std::function<void(const std::wstring_vie
_pfnTitleChanged.swap(pfn);
}

void Terminal::SetCopyToClipboardCallback(std::function<void(const std::wstring_view&)> pfn) noexcept
{
_pfnCopyToClipboard.swap(pfn);
}

void Terminal::SetScrollPositionChangedCallback(std::function<void(const int, const int, const int)> pfn) noexcept
{
_pfnScrollPositionChanged.swap(pfn);
Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ class Microsoft::Terminal::Core::Terminal final :
bool EnableAlternateScrollMode(const bool enabled) noexcept override;

bool IsVtInputEnabled() const noexcept override;

bool CopyToClipboard(std::wstring_view content) noexcept override;
#pragma endregion

#pragma region ITerminalInput
Expand Down Expand Up @@ -169,6 +171,7 @@ class Microsoft::Terminal::Core::Terminal final :

void SetWriteInputCallback(std::function<void(std::wstring&)> pfn) noexcept;
void SetTitleChangedCallback(std::function<void(const std::wstring_view&)> pfn) noexcept;
void SetCopyToClipboardCallback(std::function<void(const std::wstring_view&)> pfn) noexcept;
void SetScrollPositionChangedCallback(std::function<void(const int, const int, const int)> pfn) noexcept;
void SetCursorPositionChangedCallback(std::function<void()> pfn) noexcept;
void SetBackgroundCallback(std::function<void(const uint32_t)> pfn) noexcept;
Expand All @@ -195,6 +198,7 @@ class Microsoft::Terminal::Core::Terminal final :
private:
std::function<void(std::wstring&)> _pfnWriteInput;
std::function<void(const std::wstring_view&)> _pfnTitleChanged;
std::function<void(const std::wstring_view&)> _pfnCopyToClipboard;
std::function<void(const int, const int, const int)> _pfnScrollPositionChanged;
std::function<void(const uint32_t)> _pfnBackgroundColorChanged;
std::function<void()> _pfnCursorPositionChanged;
Expand Down
9 changes: 9 additions & 0 deletions src/cascadia/TerminalCore/TerminalApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -578,3 +578,12 @@ bool Terminal::EnableCursorBlinking(const bool enable) noexcept
_buffer->GetCursor().SetIsOn(true);
return true;
}

bool Terminal::CopyToClipboard(std::wstring_view content) noexcept
try
{
_pfnCopyToClipboard(content);

return true;
}
CATCH_LOG_RETURN_FALSE()
7 changes: 7 additions & 0 deletions src/cascadia/TerminalCore/TerminalDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ try
}
CATCH_LOG_RETURN_FALSE()

bool TerminalDispatch::SetClipboard(std::wstring_view content) noexcept
try
{
return _terminalApi.CopyToClipboard(content);
}
CATCH_LOG_RETURN_FALSE()

// Method Description:
// - Sets the default foreground color to a new value
// Arguments:
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalCore/TerminalDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class TerminalDispatch : public Microsoft::Console::VirtualTerminal::TermDispatc
bool SetColorTableEntry(const size_t tableIndex, const DWORD color) noexcept override;
bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) noexcept override;

bool SetClipboard(std::wstring_view content) noexcept override;

bool SetDefaultForeground(const DWORD color) noexcept override;
bool SetDefaultBackground(const DWORD color) noexcept override;
bool EraseInLine(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) noexcept override; // ED
Expand Down
2 changes: 2 additions & 0 deletions src/terminal/adapter/ITermDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch
virtual bool SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) = 0; // DECSCUSR
virtual bool SetCursorColor(const COLORREF color) = 0; // OSCSetCursorColor, OSCResetCursorColor

virtual bool SetClipboard(std::wstring_view content) = 0; // OscSetClipboard
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
virtual bool SetClipboard(std::wstring_view content) = 0; // OscSetClipboard
virtual bool SetClipboard(std::wstring_view content) = 0; // OSCSetClipboard

for naming consistency (when we inevitably get a tool that parses these comments)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Which is in the works back on the first page of pull requests :P)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.


// DTTERM_WindowManipulation
virtual bool WindowManipulation(const DispatchTypes::WindowManipulationType function,
const std::basic_string_view<size_t> parameters) = 0;
Expand Down
11 changes: 11 additions & 0 deletions src/terminal/adapter/adaptDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2070,6 +2070,17 @@ bool AdaptDispatch::SetCursorColor(const COLORREF cursorColor)
return _pConApi->SetCursorColor(cursorColor);
}

// Routine Description:
// - OSC Copy to Clipboard
// Arguments:
// - content - The content to copy to clipboard. Must be null terminated.
// Return Value:
// - True if handled successfully. False otherwise.
bool AdaptDispatch::SetClipboard(const std::wstring_view /*content*/) noexcept
{
return false;
}

// Method Description:
// - Sets a single entry of the colortable to a new value
// Arguments:
Expand Down
2 changes: 2 additions & 0 deletions src/terminal/adapter/adaptDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ namespace Microsoft::Console::VirtualTerminal
bool SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) override; // DECSCUSR
bool SetCursorColor(const COLORREF cursorColor) override;

bool SetClipboard(const std::wstring_view content) noexcept override; // OscSetClipboard

bool SetColorTableEntry(const size_t tableIndex,
const DWORD color) override; // OscColorTable
bool SetDefaultForeground(const DWORD color) override; // OSCDefaultForeground
Expand Down
2 changes: 2 additions & 0 deletions src/terminal/adapter/termDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons
bool SetCursorStyle(const DispatchTypes::CursorStyle /*cursorStyle*/) noexcept override { return false; } // DECSCUSR
bool SetCursorColor(const COLORREF /*color*/) noexcept override { return false; } // OSCSetCursorColor, OSCResetCursorColor

bool SetClipboard(std::wstring_view /*content*/) noexcept override { return false; } // OscSetClipboard

// DTTERM_WindowManipulation
bool WindowManipulation(const DispatchTypes::WindowManipulationType /*function*/,
const std::basic_string_view<size_t> /*params*/) noexcept override { return false; }
Expand Down
55 changes: 55 additions & 0 deletions src/terminal/parser/OutputStateMachineEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include "ascii.hpp"
using namespace Microsoft::Console;
using namespace Microsoft::Console::VirtualTerminal;
using namespace winrt::Windows::Security::Cryptography;
using namespace winrt::Windows::Storage::Streams;

// takes ownership of pDispatch
OutputStateMachineEngine::OutputStateMachineEngine(std::unique_ptr<ITermDispatch> pDispatch) :
Expand Down Expand Up @@ -726,6 +728,8 @@ bool OutputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/,
{
bool success = false;
std::wstring title;
std::wstring setClipboardContent;
bool queryClipboard = false;
size_t tableIndex = 0;
DWORD color = 0;

Expand All @@ -744,6 +748,9 @@ bool OutputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/,
case OscActionCodes::SetCursorColor:
success = _GetOscSetColor(string, color);
break;
case OscActionCodes::SetClipboard:
success = _GetOscSetClipboard(string, setClipboardContent, queryClipboard);
break;
case OscActionCodes::ResetCursorColor:
// the console uses 0xffffffff as an "invalid color" value
color = 0xffffffff;
Expand Down Expand Up @@ -780,6 +787,13 @@ bool OutputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/,
success = _dispatch->SetCursorColor(color);
TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCSCC);
break;
case OscActionCodes::SetClipboard:
if (!queryClipboard)
{
success = _dispatch->SetClipboard(setClipboardContent);
}
TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCSCC);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be the wrong TermTelemetry code!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked http://inwap.com/pdp10/ansicode.txt to find a proper name for that code, but failed. So I named it as OSCSCB where SCB means set clipboard. I didn't use SC because it's too generic and may cause conflict.

break;
case OscActionCodes::ResetCursorColor:
success = _dispatch->SetCursorColor(color);
TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCRCC);
Expand Down Expand Up @@ -1731,6 +1745,47 @@ bool OutputStateMachineEngine::_GetRepeatCount(std::basic_string_view<size_t> pa
return success;
}

// Routine Description:
// - Parse OscSetClipboard parameters with the format `Pc;Pd`. Currently the first parameter `Pc` is
// ignored. The second parameter `Pd` should be a valid base64 string or character `?`.
// Arguments:
// - string - Osc String input.
// - content - Content to set to clipboard.
// - queryClipboard - Whether to get clipboard content and return it to terminal with base64 encoded.
// Return Value:
// - True if there was a valid base64 string or the passed parameter was `?`.
bool OutputStateMachineEngine::_GetOscSetClipboard(const std::wstring_view string,
std::wstring& content,
bool& queryClipboard) const noexcept
{
bool success = false;
const size_t pos = string.find(';');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I supposed @DHowett is right, and we could save ourselves a bit of work with a

if (_pfnFlushToTerminal != nullptr)
{
    return false;
}

here. That should cause conpty to pass this through without decoding it once first. That being said, I'm pretty sure setting the clipboard isn't something that's going to be done in a tight loop or end up as any sort of hot path, so I wouldn't block this PR on account of that.

if (pos != std::wstring_view::npos)
{
const std::wstring_view substr = string.substr(pos + 1);
if (substr == L"?")
{
queryClipboard = true;
success = true;
}
else
{
try
{
auto buffer = CryptographicBuffer::DecodeFromBase64String(winrt::hstring(substr));
auto reader = DataReader::FromBuffer(buffer);
content = reader.ReadString(buffer.Length());
success = true;
}
catch (...)
{
}
}
}

return success;
}

// Method Description:
// - Clears our last stored character. The last stored character is the last
// graphical character we printed, which is reset if any other action is
Expand Down
5 changes: 5 additions & 0 deletions src/terminal/parser/OutputStateMachineEngine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ namespace Microsoft::Console::VirtualTerminal
SetForegroundColor = 10,
SetBackgroundColor = 11,
SetCursorColor = 12,
SetClipboard = 52,
ResetForegroundColor = 110, // Not implemented
ResetBackgroundColor = 111, // Not implemented
ResetCursorColor = 112,
Expand Down Expand Up @@ -230,6 +231,10 @@ namespace Microsoft::Console::VirtualTerminal
bool _GetRepeatCount(const std::basic_string_view<size_t> parameters,
size_t& repeatCount) const noexcept;

bool OutputStateMachineEngine::_GetOscSetClipboard(const std::wstring_view string,
std::wstring& title,
bool& queryClipboard) const noexcept;

void _ClearLastChar() noexcept;
};
}
3 changes: 3 additions & 0 deletions src/terminal/parser/precomp.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ Module Name:
- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building).
*/

#include <winrt/Windows.Security.Cryptography.h>
#include <winrt/Windows.Storage.Streams.h>

// This includes support libraries from the CRT, STL, WIL, and GSL
#include "LibraryIncludes.h"

Expand Down
66 changes: 66 additions & 0 deletions src/terminal/parser/ut_parser/OutputEngineTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,12 @@ class StatefulDispatch final : public TermDispatch
return true;
}

bool SetClipboard(std::wstring_view content) noexcept override
{
_copyContent = { content.begin(), content.end() };
return true;
}

size_t _cursorDistance;
size_t _line;
size_t _column;
Expand Down Expand Up @@ -1030,6 +1036,7 @@ class StatefulDispatch final : public TermDispatch
size_t _numTabs;
bool _isDECCOLMAllowed;
size_t _windowWidth;
std::wstring _copyContent;

static const size_t s_cMaxOptions = 16;
static const size_t s_uiGraphicsCleared = UINT_MAX;
Expand Down Expand Up @@ -2011,4 +2018,63 @@ class StateMachineExternalTest final

pDispatch->ClearState();
}

TEST_METHOD(TestSetClipboard)
{
auto dispatch = std::make_unique<StatefulDispatch>();
auto pDispatch = dispatch.get();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));

mach.ProcessString(L"\x1b]52;;Zm9v\x07");
VERIFY_ARE_EQUAL(L"foo", pDispatch->_copyContent);

pDispatch->ClearState();

mach.ProcessString(L"\x1b]52;;Zm9vDQpiYXI=\x07");
VERIFY_ARE_EQUAL(L"foo\r\nbar", pDispatch->_copyContent);

pDispatch->ClearState();

mach.ProcessString(L"\x1b]52;s0;Zm9v\x07");
VERIFY_ARE_EQUAL(L"foo", pDispatch->_copyContent);

pDispatch->ClearState();

pDispatch->_copyContent = L"UNCHANGED";
mach.ProcessString(L"\x1b]52;Zm9v\x07");
VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent);

pDispatch->ClearState();

pDispatch->_copyContent = L"UNCHANGED";
mach.ProcessString(L"\x1b]52;;foo\x07");
VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent);

pDispatch->ClearState();

pDispatch->_copyContent = L"UNCHANGED";
mach.ProcessString(L"\x1b]52;;;Zm9v\x07");
VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent);

pDispatch->ClearState();

pDispatch->_copyContent = L"UNCHANGED";
mach.ProcessString(L"\x1b]52;;?\x07");
VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent);

pDispatch->ClearState();

pDispatch->_copyContent = L"UNCHANGED";
mach.ProcessString(L"\x1b]52;?\x07");
VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent);

pDispatch->ClearState();

pDispatch->_copyContent = L"UNCHANGED";
mach.ProcessString(L"\x1b]52;;;?\x07");
VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent);

pDispatch->ClearState();
}
};