diff --git a/.github/actions/spell-check/expect/expect.txt b/.github/actions/spell-check/expect/expect.txt index 32d4bc08b06..61a6c95d773 100644 --- a/.github/actions/spell-check/expect/expect.txt +++ b/.github/actions/spell-check/expect/expect.txt @@ -1611,6 +1611,7 @@ OSCCT OSCFG OSCRCC OSCSCC +OSCSCB OSCWT OSDEPENDSROOT osfhandle diff --git a/.github/actions/spell-check/patterns/patterns.txt b/.github/actions/spell-check/patterns/patterns.txt index 3833627a51d..413709e1202 100644 --- a/.github/actions/spell-check/patterns/patterns.txt +++ b/.github/actions/spell-check/patterns/patterns.txt @@ -14,5 +14,8 @@ 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 +Base64::s_(?:En|De)code\(L"[^"]+" +VERIFY_ARE_EQUAL\(L"[^"]+" +L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\+/" diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 341897b17af..cdd95474ebf 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -93,6 +93,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); @@ -2009,6 +2012,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _titleChangedHandlers(winrt::hstring{ wstr }); } + void TermControl::_CopyToClipboard(const std::wstring_view& wstr) + { + auto copyArgs = winrt::make_self(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. diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 9c4740a795d..a0a9fde4778 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -209,6 +209,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); void _TerminalScrollPositionChanged(const int viewTop, const int viewHeight, const int bufferSize); winrt::fire_and_forget _TerminalCursorPositionChanged(); diff --git a/src/cascadia/TerminalCore/ITerminalApi.hpp b/src/cascadia/TerminalCore/ITerminalApi.hpp index 66b4e9a14cf..29053bc0290 100644 --- a/src/cascadia/TerminalCore/ITerminalApi.hpp +++ b/src/cascadia/TerminalCore/ITerminalApi.hpp @@ -61,6 +61,8 @@ namespace Microsoft::Terminal::Core virtual bool IsVtInputEnabled() const = 0; + virtual bool CopyToClipboard(std::wstring_view content) noexcept = 0; + protected: ITerminalApi() = default; }; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index b12e07628df..f620d82779b 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -891,6 +891,11 @@ void Terminal::SetTitleChangedCallback(std::function pfn) noexcept +{ + _pfnCopyToClipboard.swap(pfn); +} + void Terminal::SetScrollPositionChangedCallback(std::function pfn) noexcept { _pfnScrollPositionChanged.swap(pfn); diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 7fa047cb90a..8f34b8b641f 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -114,6 +114,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 @@ -172,6 +174,7 @@ class Microsoft::Terminal::Core::Terminal final : void SetWriteInputCallback(std::function pfn) noexcept; void SetTitleChangedCallback(std::function pfn) noexcept; + void SetCopyToClipboardCallback(std::function pfn) noexcept; void SetScrollPositionChangedCallback(std::function pfn) noexcept; void SetCursorPositionChangedCallback(std::function pfn) noexcept; void SetBackgroundCallback(std::function pfn) noexcept; @@ -198,6 +201,7 @@ class Microsoft::Terminal::Core::Terminal final : private: std::function _pfnWriteInput; std::function _pfnTitleChanged; + std::function _pfnCopyToClipboard; std::function _pfnScrollPositionChanged; std::function _pfnBackgroundColorChanged; std::function _pfnCursorPositionChanged; diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 01ab410a4ca..22ab5706f41 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -601,3 +601,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() diff --git a/src/cascadia/TerminalCore/TerminalDispatch.cpp b/src/cascadia/TerminalCore/TerminalDispatch.cpp index eab8627f9f8..dc5310d4775 100644 --- a/src/cascadia/TerminalCore/TerminalDispatch.cpp +++ b/src/cascadia/TerminalCore/TerminalDispatch.cpp @@ -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: diff --git a/src/cascadia/TerminalCore/TerminalDispatch.hpp b/src/cascadia/TerminalCore/TerminalDispatch.hpp index 4defa9f4cfb..01798cce89c 100644 --- a/src/cascadia/TerminalCore/TerminalDispatch.hpp +++ b/src/cascadia/TerminalCore/TerminalDispatch.hpp @@ -36,6 +36,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 diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index e6dc5e9dffa..66cd88f0449 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -110,6 +110,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 + // DTTERM_WindowManipulation virtual bool WindowManipulation(const DispatchTypes::WindowManipulationType function, const std::basic_string_view parameters) = 0; diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 4efb3a5cfe1..e2253e3013d 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -2225,6 +2225,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: diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 5aecde064f8..0f28184e013 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -80,7 +80,7 @@ namespace Microsoft::Console::VirtualTerminal bool CarriageReturn() override; // CR bool LineFeed(const DispatchTypes::LineFeedType lineFeedType) override; // IND, NEL, LF, FF, VT bool ReverseLineFeed() override; // RI - bool SetWindowTitle(const std::wstring_view title) override; // OscWindowTitle + bool SetWindowTitle(const std::wstring_view title) override; // OSCWindowTitle bool UseAlternateScreenBuffer() override; // ASBSET bool UseMainScreenBuffer() override; // ASBRST bool HorizontalTabSet() override; // HTS @@ -106,8 +106,10 @@ 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 + const DWORD color) override; // OSCColorTable bool SetDefaultForeground(const DWORD color) override; // OSCDefaultForeground bool SetDefaultBackground(const DWORD color) override; // OSCDefaultBackground diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index ce0e5ca637f..7bc7174b626 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -104,6 +104,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 /*params*/) noexcept override { return false; } diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 060bbd119ce..511cf4cb2d5 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -5,6 +5,7 @@ #include "stateMachine.hpp" #include "OutputStateMachineEngine.hpp" +#include "base64.hpp" #include "ascii.hpp" @@ -881,6 +882,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; @@ -899,6 +902,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; @@ -935,6 +941,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::OSCSCB); + break; case OscActionCodes::ResetCursorColor: success = _dispatch->SetCursorColor(color); TermTelemetry::Instance().Log(TermTelemetry::Codes::OSCRCC); @@ -1859,6 +1872,37 @@ bool OutputStateMachineEngine::_GetRepeatCount(std::basic_string_view 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 +{ + const size_t pos = string.find(';'); + if (pos != std::wstring_view::npos) + { + const std::wstring_view substr = string.substr(pos + 1); + if (substr == L"?") + { + queryClipboard = true; + return true; + } + else + { + return Base64::s_Decode(substr, content); + } + } + + return false; +} + // 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 diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index bbe0fc5e42d..08c29aa07d6 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -170,6 +170,7 @@ namespace Microsoft::Console::VirtualTerminal SetForegroundColor = 10, SetBackgroundColor = 11, SetCursorColor = 12, + SetClipboard = 52, ResetForegroundColor = 110, // Not implemented ResetBackgroundColor = 111, // Not implemented ResetCursorColor = 112 @@ -252,6 +253,10 @@ namespace Microsoft::Console::VirtualTerminal bool _GetRepeatCount(const std::basic_string_view parameters, size_t& repeatCount) const noexcept; + bool _GetOscSetClipboard(const std::wstring_view string, + std::wstring& content, + bool& queryClipboard) const noexcept; + void _ClearLastChar() noexcept; }; } diff --git a/src/terminal/parser/base64.cpp b/src/terminal/parser/base64.cpp new file mode 100644 index 00000000000..f7cfc1e5c8c --- /dev/null +++ b/src/terminal/parser/base64.cpp @@ -0,0 +1,186 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" +#include "base64.hpp" + +using namespace Microsoft::Console::VirtualTerminal; + +static const wchar_t base64Chars[] = L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const wchar_t padChar = L'='; + +#pragma warning(disable : 26446 26447 26482 26485 26493 26494) + +// Routine Description: +// - Encode a string using base64. When there are not enough characters +// for one quantum, paddings are added. +// Arguments: +// - src - String to base64 encode. +// Return Value: +// - the encoded string. +std::wstring Base64::s_Encode(const std::wstring_view src) noexcept +{ + std::wstring dst; + wchar_t input[3]; + + const auto len = (src.size() + 2) / 3 * 4; + if (len == 0) + { + return dst; + } + dst.reserve(len); + + auto iter = src.cbegin(); + // Encode each three chars into one quantum (four chars). + while (iter < src.cend() - 2) + { + input[0] = *iter++; + input[1] = *iter++; + input[2] = *iter++; + dst.push_back(base64Chars[input[0] >> 2]); + dst.push_back(base64Chars[(input[0] & 0x03) << 4 | input[1] >> 4]); + dst.push_back(base64Chars[(input[1] & 0x0f) << 2 | input[2] >> 6]); + dst.push_back(base64Chars[(input[2] & 0x3f)]); + } + + // Here only zero, or one, or two chars are left. We may need to add paddings. + if (iter < src.cend()) + { + input[0] = *iter++; + dst.push_back(base64Chars[input[0] >> 2]); + if (iter < src.cend()) // Two chars left. + { + input[1] = *iter++; + dst.push_back(base64Chars[(input[0] & 0x03) << 4 | input[1] >> 4]); + dst.push_back(base64Chars[(input[1] & 0x0f) << 2]); + } + else // Only one char left. + { + dst.push_back(base64Chars[(input[0] & 0x03) << 4]); + dst.push_back(padChar); + } + dst.push_back(padChar); + } + + return dst; +} + +// Routine Description: +// - Decode a base64 string. This requires the base64 string is properly padded. +// Otherwise, false will be returned. +// Arguments: +// - src - String to decode. +// - dst - Destination to decode into. +// Return Value: +// - true if decoding successfully, otherwise false. +bool Base64::s_Decode(const std::wstring_view src, std::wstring& dst) noexcept +{ + int state = 0; + wchar_t tmp; + + const auto len = src.size() / 4 * 3; + if (len == 0) + { + return false; + } + dst.reserve(len); + + auto iter = src.cbegin(); + while (iter < src.cend()) + { + if (s_IsSpace(*iter)) // Skip whitespace anywhere. + { + iter++; + continue; + } + + if (*iter == padChar) + { + break; + } + + auto pos = wcschr(base64Chars, *iter); + if (!pos) // A non-base64 character found. + { + return false; + } + + switch (state) + { + case 0: + tmp = (wchar_t)(pos - base64Chars) << 2; + state = 1; + break; + case 1: + tmp |= (pos - base64Chars) >> 4; + dst.push_back(tmp); + tmp = (wchar_t)((pos - base64Chars) & 0x0f) << 4; + state = 2; + break; + case 2: + tmp |= (pos - base64Chars) >> 2; + dst.push_back(tmp); + tmp = (wchar_t)((pos - base64Chars) & 0x03) << 6; + state = 3; + break; + case 3: + tmp |= pos - base64Chars; + dst.push_back(tmp); + state = 0; + break; + } + + iter++; + } + + if (iter < src.cend()) // Padding char is met. + { + iter++; + switch (state) + { + // Invalid when state is 0 or 1. + case 0: + case 1: + return false; + case 2: + // Skip any number of spaces. + while (iter < src.cend() && s_IsSpace(*iter)) + { + iter++; + } + // Make sure there is another trailing padding character. + if (iter == src.cend() || *iter != padChar) + { + return false; + } + iter++; // Skip the padding character and fallthrough to "single trailing padding character" case. + case 3: + while (iter < src.cend()) + { + if (!s_IsSpace(*iter)) + { + return false; + } + iter++; + } + } + } + else if (state != 0) // When no padding, we must be in state 0. + { + return false; + } + + return true; +} + +// Routine Description: +// - Check if parameter is a base64 whitespace. Only carriage return or line feed +// is valid whitespace. +// Arguments: +// - ch - Character to check. +// Return Value: +// - true iff ch is a carriage return or line feed. +constexpr bool Base64::s_IsSpace(const wchar_t ch) noexcept +{ + return ch == L'\r' || ch == L'\n'; +} diff --git a/src/terminal/parser/base64.hpp b/src/terminal/parser/base64.hpp new file mode 100644 index 00000000000..976bcc5209a --- /dev/null +++ b/src/terminal/parser/base64.hpp @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +/* +Module Name: +- base64.hpp + +Abstract: +- This declares standard base64 encoding and decoding, with paddings when needed. +*/ + +#pragma once + +namespace Microsoft::Console::VirtualTerminal +{ + class Base64 + { + public: + static std::wstring s_Encode(const std::wstring_view src) noexcept; + static bool s_Decode(const std::wstring_view src, std::wstring& dst) noexcept; + + private: + static constexpr bool s_IsSpace(const wchar_t ch) noexcept; + }; +} diff --git a/src/terminal/parser/parser-common.vcxitems b/src/terminal/parser/parser-common.vcxitems index 35dc08e3368..75656dcb462 100644 --- a/src/terminal/parser/parser-common.vcxitems +++ b/src/terminal/parser/parser-common.vcxitems @@ -11,6 +11,7 @@ Create + @@ -20,5 +21,6 @@ + diff --git a/src/terminal/parser/sources.inc b/src/terminal/parser/sources.inc index d49cff67e2a..fbc49025354 100644 --- a/src/terminal/parser/sources.inc +++ b/src/terminal/parser/sources.inc @@ -35,6 +35,7 @@ SOURCES = \ ..\OutputStateMachineEngine.cpp \ ..\telemetry.cpp \ ..\tracing.cpp \ + ..\base64.cpp \ INCLUDES = \ $(INCLUDES); \ diff --git a/src/terminal/parser/telemetry.cpp b/src/terminal/parser/telemetry.cpp index 205efdbf298..6bed34f5434 100644 --- a/src/terminal/parser/telemetry.cpp +++ b/src/terminal/parser/telemetry.cpp @@ -268,6 +268,7 @@ void TermTelemetry::WriteFinalTraceLog() const TraceLoggingUInt32(_uiTimesUsed[OSCRCC], "OscResetCursorColor"), TraceLoggingUInt32(_uiTimesUsed[OSCFG], "OscForegroundColor"), TraceLoggingUInt32(_uiTimesUsed[OSCBG], "OscBackgroundColor"), + TraceLoggingUInt32(_uiTimesUsed[OSCSCB], "OscSetClipboard"), TraceLoggingUInt32(_uiTimesUsed[REP], "REP"), TraceLoggingUInt32(_uiTimesUsed[DECALN], "DECALN"), TraceLoggingUInt32Array(_uiTimesFailed, ARRAYSIZE(_uiTimesFailed), "Failed"), diff --git a/src/terminal/parser/telemetry.hpp b/src/terminal/parser/telemetry.hpp index 04396bdeb01..6f1b06ebb66 100644 --- a/src/terminal/parser/telemetry.hpp +++ b/src/terminal/parser/telemetry.hpp @@ -97,6 +97,7 @@ namespace Microsoft::Console::VirtualTerminal OSCFG, OSCBG, DECALN, + OSCSCB, // Only use this last enum as a count of the number of codes. NUMBER_OF_CODES }; diff --git a/src/terminal/parser/ut_parser/Base64Test.cpp b/src/terminal/parser/ut_parser/Base64Test.cpp new file mode 100644 index 00000000000..2b84d109d53 --- /dev/null +++ b/src/terminal/parser/ut_parser/Base64Test.cpp @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" +#include "WexTestClass.h" +#include "../../inc/consoletaeftemplates.hpp" + +#include "base64.hpp" + +using namespace WEX::Common; +using namespace WEX::Logging; +using namespace WEX::TestExecution; + +namespace Microsoft +{ + namespace Console + { + namespace VirtualTerminal + { + class Base64Test; + }; + }; +}; + +using namespace Microsoft::Console::VirtualTerminal; + +class Microsoft::Console::VirtualTerminal::Base64Test +{ + TEST_CLASS(Base64Test); + + TEST_METHOD(TestBase64Encode) + { + VERIFY_ARE_EQUAL(L"Zm9v", Base64::s_Encode(L"foo")); + VERIFY_ARE_EQUAL(L"Zm9vYg==", Base64::s_Encode(L"foob")); + VERIFY_ARE_EQUAL(L"Zm9vYmE=", Base64::s_Encode(L"fooba")); + VERIFY_ARE_EQUAL(L"Zm9vYmFy", Base64::s_Encode(L"foobar")); + VERIFY_ARE_EQUAL(L"Zm9vYmFyDQo=", Base64::s_Encode(L"foobar\r\n")); + } + + TEST_METHOD(TestBase64Decode) + { + std::wstring result; + bool success; + + success = Base64::s_Decode(L"Zm9v", result); + VERIFY_ARE_EQUAL(true, success); + VERIFY_ARE_EQUAL(L"foo", result); + + result = L""; + success = Base64::s_Decode(L"Zm9vYg==", result); + VERIFY_ARE_EQUAL(true, success); + VERIFY_ARE_EQUAL(L"foob", result); + + result = L""; + success = Base64::s_Decode(L"Zm9vYmE=", result); + VERIFY_ARE_EQUAL(true, success); + VERIFY_ARE_EQUAL(L"fooba", result); + + result = L""; + success = Base64::s_Decode(L"Zm9vYmFy", result); + VERIFY_ARE_EQUAL(true, success); + VERIFY_ARE_EQUAL(L"foobar", result); + + result = L""; + success = Base64::s_Decode(L"Zm9vYmFyDQo=", result); + VERIFY_ARE_EQUAL(true, success); + VERIFY_ARE_EQUAL(L"foobar\r\n", result); + + result = L""; + success = Base64::s_Decode(L"Zm9v\rYmFy", result); + VERIFY_ARE_EQUAL(true, success); + VERIFY_ARE_EQUAL(L"foobar", result); + + result = L""; + success = Base64::s_Decode(L"Zm9v\r\nYmFy\n", result); + VERIFY_ARE_EQUAL(true, success); + VERIFY_ARE_EQUAL(L"foobar", result); + + success = Base64::s_Decode(L"Z", result); + VERIFY_ARE_EQUAL(false, success); + + success = Base64::s_Decode(L"Zm9vYg", result); + VERIFY_ARE_EQUAL(false, success); + + success = Base64::s_Decode(L"Zm9vYg=", result); + VERIFY_ARE_EQUAL(false, success); + } +}; diff --git a/src/terminal/parser/ut_parser/OutputEngineTest.cpp b/src/terminal/parser/ut_parser/OutputEngineTest.cpp index c5e9a61106b..f16ebf1aad5 100644 --- a/src/terminal/parser/ut_parser/OutputEngineTest.cpp +++ b/src/terminal/parser/ut_parser/OutputEngineTest.cpp @@ -944,6 +944,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; @@ -988,6 +994,7 @@ class StatefulDispatch final : public TermDispatch bool _isDECCOLMAllowed; size_t _windowWidth; bool _win32InputMode; + std::wstring _copyContent; static const size_t s_cMaxOptions = 16; static const size_t s_uiGraphicsCleared = UINT_MAX; @@ -2080,4 +2087,72 @@ class StateMachineExternalTest final mach.ProcessCharacter(L'Z'); VERIFY_IS_TRUE(pDispatch->_vt52DeviceAttributes); } + + TEST_METHOD(TestSetClipboard) + { + auto dispatch = std::make_unique(); + auto pDispatch = dispatch.get(); + auto engine = std::make_unique(std::move(dispatch)); + StateMachine mach(std::move(engine)); + + // Passing an empty `Pc` param and a base64-encoded simple text `Pd` param works. + mach.ProcessString(L"\x1b]52;;Zm9v\x07"); + VERIFY_ARE_EQUAL(L"foo", pDispatch->_copyContent); + + pDispatch->ClearState(); + + // Passing an empty `Pc` param and a base64-encoded multi-lines text `Pd` works. + mach.ProcessString(L"\x1b]52;;Zm9vDQpiYXI=\x07"); + VERIFY_ARE_EQUAL(L"foo\r\nbar", pDispatch->_copyContent); + + pDispatch->ClearState(); + + // Passing a non-empty `Pc` param (`s0` is ignored) and a valid `Pd` param works. + mach.ProcessString(L"\x1b]52;s0;Zm9v\x07"); + VERIFY_ARE_EQUAL(L"foo", pDispatch->_copyContent); + + pDispatch->ClearState(); + + pDispatch->_copyContent = L"UNCHANGED"; + // Passing only base64 `Pd` param is illegal, won't change the content. + mach.ProcessString(L"\x1b]52;Zm9v\x07"); + VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent); + + pDispatch->ClearState(); + + pDispatch->_copyContent = L"UNCHANGED"; + // Passing a non-base64 `Pd` param is illegal, won't change the content. + mach.ProcessString(L"\x1b]52;;foo\x07"); + VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent); + + pDispatch->ClearState(); + + pDispatch->_copyContent = L"UNCHANGED"; + // Passing a valid `Pc;Pd` with one more extra param is illegal, won't change the content. + mach.ProcessString(L"\x1b]52;;;Zm9v\x07"); + VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent); + + pDispatch->ClearState(); + + pDispatch->_copyContent = L"UNCHANGED"; + // Passing a query character won't change the content. + mach.ProcessString(L"\x1b]52;;?\x07"); + VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent); + + pDispatch->ClearState(); + + pDispatch->_copyContent = L"UNCHANGED"; + // Passing a query character with missing `Pc` param is illegal, won't change the content. + mach.ProcessString(L"\x1b]52;?\x07"); + VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent); + + pDispatch->ClearState(); + + pDispatch->_copyContent = L"UNCHANGED"; + // Passing a query character with one more extra param is illegal, won't change the content. + mach.ProcessString(L"\x1b]52;;;?\x07"); + VERIFY_ARE_EQUAL(L"UNCHANGED", pDispatch->_copyContent); + + pDispatch->ClearState(); + } }; diff --git a/src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj b/src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj index 250a7f8b1b6..02e923f575e 100644 --- a/src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj +++ b/src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj @@ -21,6 +21,7 @@ + Create diff --git a/src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj.filters b/src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj.filters index ecc61100147..adc0b0fec1f 100644 --- a/src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj.filters +++ b/src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj.filters @@ -24,6 +24,9 @@ Source Files + + Source Files + Source Files @@ -33,4 +36,4 @@ Header Files - \ No newline at end of file + diff --git a/src/terminal/parser/ut_parser/sources b/src/terminal/parser/ut_parser/sources index a7f53883e69..decf6b69e56 100644 --- a/src/terminal/parser/ut_parser/sources +++ b/src/terminal/parser/ut_parser/sources @@ -24,6 +24,7 @@ SOURCES = \ OutputEngineTest.cpp \ InputEngineTest.cpp \ StateMachineTest.cpp \ + Base64Test.cpp \ # The InputEngineTest requires VTRedirMapVirtualKeyW, which means we need the # ServiceLocator, which means we need the entire host and all it's dependencies,