diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 65845ffc6c0..072f28c8437 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -548,7 +548,9 @@ namespace winrt::TerminalApp::implementation { if (const auto& realArgs = args.ActionArgs().try_as()) { - const auto handled = _CopyText(realArgs.DismissSelection(), realArgs.SingleLine(), realArgs.WithControlSequences(), realArgs.CopyFormatting()); + const auto copyFormatting = realArgs.CopyFormatting(); + const auto format = copyFormatting ? copyFormatting.Value() : _settings.GlobalSettings().CopyFormatting(); + const auto handled = _CopyText(realArgs.DismissSelection(), realArgs.SingleLine(), realArgs.WithControlSequences(), format); args.Handled(handled); } } diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 6f510addc74..507d29392bd 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -56,6 +56,121 @@ namespace winrt using VirtualKeyModifiers = Windows::System::VirtualKeyModifiers; } +namespace clipboard +{ + wil::unique_close_clipboard_call open(HWND hwnd) + { + bool success = false; + + // OpenClipboard may fail to acquire the internal lock --> retry. + for (DWORD sleep = 10;; sleep *= 2) + { + if (OpenClipboard(hwnd)) + { + success = true; + break; + } + // 10 iterations + if (sleep > 10000) + { + break; + } + Sleep(sleep); + } + + return wil::unique_close_clipboard_call{ success }; + } + + void write(wil::zwstring_view text, std::string_view html, std::string_view rtf) + { + static const auto regular = [](const UINT format, const void* src, const size_t bytes) { + wil::unique_hglobal handle{ THROW_LAST_ERROR_IF_NULL(GlobalAlloc(GMEM_MOVEABLE, bytes)) }; + + const auto locked = GlobalLock(handle.get()); + memcpy(locked, src, bytes); + GlobalUnlock(handle.get()); + + THROW_LAST_ERROR_IF_NULL(SetClipboardData(format, handle.get())); + handle.release(); + }; + static const auto registered = [](const wchar_t* format, const void* src, size_t bytes) { + const auto id = RegisterClipboardFormatW(format); + if (!id) + { + LOG_LAST_ERROR(); + return; + } + regular(id, src, bytes); + }; + + EmptyClipboard(); + + if (!text.empty()) + { + // 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. + regular(CF_UNICODETEXT, text.c_str(), (text.size() + 1) * sizeof(wchar_t)); + } + + if (!html.empty()) + { + registered(L"HTML Format", html.data(), html.size()); + } + + if (!rtf.empty()) + { + registered(L"Rich Text Format", rtf.data(), rtf.size()); + } + } + + winrt::hstring read() + { + // This handles most cases of pasting text as the OS converts most formats to CF_UNICODETEXT automatically. + if (const auto handle = GetClipboardData(CF_UNICODETEXT)) + { + const wil::unique_hglobal_locked lock{ handle }; + const auto str = static_cast(lock.get()); + if (!str) + { + return {}; + } + + const auto maxLen = GlobalSize(handle) / sizeof(wchar_t); + const auto len = wcsnlen(str, maxLen); + return winrt::hstring{ str, gsl::narrow_cast(len) }; + } + + // We get CF_HDROP when a user copied a file with Ctrl+C in Explorer and pastes that into the terminal (among others). + if (const auto handle = GetClipboardData(CF_HDROP)) + { + const wil::unique_hglobal_locked lock{ handle }; + const auto drop = static_cast(lock.get()); + if (!drop) + { + return {}; + } + + const auto cap = DragQueryFileW(drop, 0, nullptr, 0); + if (cap == 0) + { + return {}; + } + + auto buffer = winrt::impl::hstring_builder{ cap }; + const auto len = DragQueryFileW(drop, 0, buffer.data(), cap + 1); + if (len == 0) + { + return {}; + } + + return buffer.to_hstring(); + } + + return {}; + } +} // namespace clipboard + namespace winrt::TerminalApp::implementation { TerminalPage::TerminalPage(TerminalApp::WindowProperties properties, const TerminalApp::ContentManager& manager) : @@ -1793,7 +1908,7 @@ namespace winrt::TerminalApp::implementation { term.RaiseNotice({ this, &TerminalPage::_ControlNoticeRaisedHandler }); - // Add an event handler when the terminal wants to paste data from the Clipboard. + term.WriteToClipboard({ get_weak(), &TerminalPage::_copyToClipboard }); term.PasteFromClipboard({ this, &TerminalPage::_PasteFromClipboardHandler }); term.OpenHyperlink({ this, &TerminalPage::_OpenHyperlinkHandler }); @@ -1815,9 +1930,7 @@ namespace winrt::TerminalApp::implementation }); term.ShowWindowChanged({ get_weak(), &TerminalPage::_ShowWindowChangedHandler }); - term.SearchMissingCommand({ get_weak(), &TerminalPage::_SearchMissingCommandHandler }); - term.WindowSizeChanged({ get_weak(), &TerminalPage::_WindowSizeChanged }); // Don't even register for the event if the feature is compiled off. @@ -2739,75 +2852,6 @@ namespace winrt::TerminalApp::implementation return dimension; } - static wil::unique_close_clipboard_call _openClipboard(HWND hwnd) - { - bool success = false; - - // OpenClipboard may fail to acquire the internal lock --> retry. - for (DWORD sleep = 10;; sleep *= 2) - { - if (OpenClipboard(hwnd)) - { - success = true; - break; - } - // 10 iterations - if (sleep > 10000) - { - break; - } - Sleep(sleep); - } - - return wil::unique_close_clipboard_call{ success }; - } - - static winrt::hstring _extractClipboard() - { - // This handles most cases of pasting text as the OS converts most formats to CF_UNICODETEXT automatically. - if (const auto handle = GetClipboardData(CF_UNICODETEXT)) - { - const wil::unique_hglobal_locked lock{ handle }; - const auto str = static_cast(lock.get()); - if (!str) - { - return {}; - } - - const auto maxLen = GlobalSize(handle) / sizeof(wchar_t); - const auto len = wcsnlen(str, maxLen); - return winrt::hstring{ str, gsl::narrow_cast(len) }; - } - - // We get CF_HDROP when a user copied a file with Ctrl+C in Explorer and pastes that into the terminal (among others). - if (const auto handle = GetClipboardData(CF_HDROP)) - { - const wil::unique_hglobal_locked lock{ handle }; - const auto drop = static_cast(lock.get()); - if (!drop) - { - return {}; - } - - const auto cap = DragQueryFileW(drop, 0, nullptr, 0); - if (cap == 0) - { - return {}; - } - - auto buffer = winrt::impl::hstring_builder{ cap }; - const auto len = DragQueryFileW(drop, 0, buffer.data(), cap + 1); - if (len == 0) - { - return {}; - } - - return buffer.to_hstring(); - } - - return {}; - } - // Function Description: // - This function is called when the `TermControl` requests that we send // it the clipboard's content. @@ -2827,36 +2871,51 @@ namespace winrt::TerminalApp::implementation const auto weakThis = get_weak(); const auto dispatcher = Dispatcher(); const auto globalSettings = _settings.GlobalSettings(); + const auto bracketedPaste = eventArgs.BracketedPasteEnabled(); // GetClipboardData might block for up to 30s for delay-rendered contents. co_await winrt::resume_background(); winrt::hstring text; - if (const auto clipboard = _openClipboard(nullptr)) + if (const auto clipboard = clipboard::open(nullptr)) { - text = _extractClipboard(); + text = clipboard::read(); } - if (globalSettings.TrimPaste()) + if (!bracketedPaste && globalSettings.TrimPaste()) { - text = { Utils::TrimPaste(text) }; - if (text.empty()) - { - // Text is all white space, nothing to paste - co_return; - } + text = winrt::hstring{ Utils::TrimPaste(text) }; + } + + if (text.empty()) + { + co_return; + } + + bool warnMultiLine = false; + switch (globalSettings.WarnAboutMultiLinePaste()) + { + case WarnAboutMultiLinePaste::Automatic: + // NOTE that this is unsafe, because a shell that doesn't support bracketed paste + // will allow an attacker to enable the mode, not realize that, and then accept + // the paste as if it was a series of legitimate commands. See GH#13014. + warnMultiLine = !bracketedPaste; + break; + case WarnAboutMultiLinePaste::Always: + warnMultiLine = true; + break; + default: + warnMultiLine = false; + break; } - // If the requesting terminal is in bracketed paste mode, then we don't need to warn about a multi-line paste. - auto warnMultiLine = globalSettings.WarnAboutMultiLinePaste() && !eventArgs.BracketedPasteEnabled(); if (warnMultiLine) { - const auto isNewLineLambda = [](auto c) { return c == L'\n' || c == L'\r'; }; - const auto hasNewLine = std::find_if(text.cbegin(), text.cend(), isNewLineLambda) != text.cend(); - warnMultiLine = hasNewLine; + const std::wstring_view view{ text }; + warnMultiLine = view.find_first_of(L"\r\n") != std::wstring_view::npos; } - constexpr const std::size_t minimumSizeForWarning = 1024 * 5; // 5 KiB + constexpr std::size_t minimumSizeForWarning = 1024 * 5; // 5 KiB const auto warnLargeText = text.size() > minimumSizeForWarning && globalSettings.WarnAboutLargePaste(); if (warnMultiLine || warnLargeText) @@ -2866,7 +2925,7 @@ namespace winrt::TerminalApp::implementation if (const auto strongThis = weakThis.get()) { // We have to initialize the dialog here to be able to change the text of the text block within it - FindName(L"MultiLinePasteDialog").try_as(); + std::ignore = FindName(L"MultiLinePasteDialog"); ClipboardText().Text(text); // The vertical offset on the scrollbar does not reset automatically, so reset it manually @@ -3047,7 +3106,7 @@ namespace winrt::TerminalApp::implementation // - formats: dictate which formats need to be copied // Return Value: // - true iff we we able to copy text (if a selection was active) - bool TerminalPage::_CopyText(const bool dismissSelection, const bool singleLine, const bool withControlSequences, const Windows::Foundation::IReference& formats) + bool TerminalPage::_CopyText(const bool dismissSelection, const bool singleLine, const bool withControlSequences, const CopyFormat formats) { if (const auto& control{ _GetActiveControl() }) { @@ -3190,6 +3249,21 @@ namespace winrt::TerminalApp::implementation } } + void TerminalPage::_copyToClipboard(const IInspectable, const WriteToClipboardEventArgs args) const + { + if (const auto clipboard = clipboard::open(_hostingHwnd.value_or(nullptr))) + { + const auto plain = args.Plain(); + const auto html = args.Html(); + const auto rtf = args.Rtf(); + + clipboard::write( + { plain.data(), plain.size() }, + { reinterpret_cast(html.data()), html.size() }, + { reinterpret_cast(rtf.data()), rtf.size() }); + } + } + // Method Description: // - Paste text from the Windows Clipboard to the focused terminal void TerminalPage::_PasteText() diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 7a973eaff20..36ecec4fd26 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -413,10 +413,11 @@ namespace winrt::TerminalApp::implementation bool _IsUriSupported(const winrt::Windows::Foundation::Uri& parsedUri); void _ShowCouldNotOpenDialog(winrt::hstring reason, winrt::hstring uri); - bool _CopyText(const bool dismissSelection, const bool singleLine, const bool withControlSequences, const Windows::Foundation::IReference& formats); + bool _CopyText(bool dismissSelection, bool singleLine, bool withControlSequences, Microsoft::Terminal::Control::CopyFormat formats); safe_void_coroutine _SetTaskbarProgressHandler(const IInspectable sender, const IInspectable eventArgs); + void _copyToClipboard(IInspectable, Microsoft::Terminal::Control::WriteToClipboardEventArgs args) const; void _PasteText(); safe_void_coroutine _ControlNoticeRaisedHandler(const IInspectable sender, const Microsoft::Terminal::Control::NoticeEventArgs eventArgs); diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 72c6acd877d..8a5b6b8d9a0 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -104,8 +104,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation // GH#8969: pre-seed working directory to prevent potential races _terminal->SetWorkingDirectory(_settings->StartingDirectory()); - auto pfnCopyToClipboard = [this](auto&& PH1) { _terminalCopyToClipboard(std::forward(PH1)); }; - _terminal->SetCopyToClipboardCallback(pfnCopyToClipboard); + _terminal->SetCopyToClipboardCallback([this](wil::zwstring_view wstr) { + WriteToClipboard.raise(*this, winrt::make(winrt::hstring{ std::wstring_view{ wstr } }, std::string{}, std::string{})); + }); auto pfnWarningBell = [this] { _terminalWarningBell(); }; _terminal->SetWarningBellCallback(pfnWarningBell); @@ -599,7 +600,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation else if (vkey == VK_RETURN && !mods.IsCtrlPressed() && !mods.IsAltPressed()) { // [Shift +] Enter --> copy text - CopySelectionToClipboard(mods.IsShiftPressed(), false, nullptr); + CopySelectionToClipboard(mods.IsShiftPressed(), false, _settings->CopyFormatting()); _terminal->ClearSelection(); _updateSelectionUI(); return true; @@ -1263,89 +1264,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation _updateSelectionUI(); } - static wil::unique_close_clipboard_call _openClipboard(HWND hwnd) - { - bool success = false; - - // OpenClipboard may fail to acquire the internal lock --> retry. - for (DWORD sleep = 10;; sleep *= 2) - { - if (OpenClipboard(hwnd)) - { - success = true; - break; - } - // 10 iterations - if (sleep > 10000) - { - break; - } - Sleep(sleep); - } - - return wil::unique_close_clipboard_call{ success }; - } - - static void _copyToClipboard(const UINT format, const void* src, const size_t bytes) - { - wil::unique_hglobal handle{ THROW_LAST_ERROR_IF_NULL(GlobalAlloc(GMEM_MOVEABLE, bytes)) }; - - const auto locked = GlobalLock(handle.get()); - memcpy(locked, src, bytes); - GlobalUnlock(handle.get()); - - THROW_LAST_ERROR_IF_NULL(SetClipboardData(format, handle.get())); - handle.release(); - } - - static void _copyToClipboardRegisteredFormat(const wchar_t* format, const void* src, size_t bytes) - { - const auto id = RegisterClipboardFormatW(format); - if (!id) - { - LOG_LAST_ERROR(); - return; - } - _copyToClipboard(id, src, bytes); - } - - static void copyToClipboard(wil::zwstring_view text, std::string_view html, std::string_view rtf) - { - const auto clipboard = _openClipboard(nullptr); - if (!clipboard) - { - LOG_LAST_ERROR(); - return; - } - - EmptyClipboard(); - - if (!text.empty()) - { - // 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)); - } - - if (!html.empty()) - { - _copyToClipboardRegisteredFormat(L"HTML Format", html.data(), html.size()); - } - - if (!rtf.empty()) - { - _copyToClipboardRegisteredFormat(L"Rich Text Format", rtf.data(), rtf.size()); - } - } - - // Called when the Terminal wants to set something to the clipboard, i.e. - // when an OSC 52 is emitted. - void ControlCore::_terminalCopyToClipboard(wil::zwstring_view wstr) - { - copyToClipboard(wstr, {}, {}); - } - // Method Description: // - Given a copy-able selection, get the selected text from the buffer and send it to the // Windows Clipboard (CascadiaWin32:main.cpp). @@ -1356,7 +1274,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // if we should defer which formats are copied to the global setting bool ControlCore::CopySelectionToClipboard(bool singleLine, bool withControlSequences, - const Windows::Foundation::IReference& formats) + const CopyFormat formats) { ::Microsoft::Terminal::Core::Terminal::TextCopyData payload; { @@ -1368,19 +1286,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation return false; } - // use action's copyFormatting if it's present, else fall back to globally - // set copyFormatting. - const auto copyFormats = formats != nullptr ? formats.Value() : _settings->CopyFormatting(); - - const auto copyHtml = WI_IsFlagSet(copyFormats, CopyFormat::HTML); - const auto copyRtf = WI_IsFlagSet(copyFormats, CopyFormat::RTF); + const auto copyHtml = WI_IsFlagSet(formats, CopyFormat::HTML); + const auto copyRtf = WI_IsFlagSet(formats, CopyFormat::RTF); // extract text from buffer // RetrieveSelectedTextFromBuffer will lock while it's reading payload = _terminal->RetrieveSelectedTextFromBuffer(singleLine, withControlSequences, copyHtml, copyRtf); } - copyToClipboard(payload.plainText, payload.html, payload.rtf); + WriteToClipboard.raise( + *this, + winrt::make( + winrt::hstring{ payload.plainText }, + std::move(payload.html), + std::move(payload.rtf))); return true; } diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 492916040ca..37810558694 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -124,7 +124,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void SendInput(std::wstring_view wstr); void PasteText(const winrt::hstring& hstr); - bool CopySelectionToClipboard(bool singleLine, bool withControlSequences, const Windows::Foundation::IReference& formats); + bool CopySelectionToClipboard(bool singleLine, bool withControlSequences, const CopyFormat formats); void SelectAll(); void ClearSelection(); bool ToggleBlockSelection(); @@ -277,6 +277,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation til::typed_event FontSizeChanged; til::typed_event TitleChanged; + til::typed_event WriteToClipboard; til::typed_event<> WarningBell; til::typed_event<> TabColorChanged; til::typed_event<> BackgroundColorChanged; @@ -325,7 +326,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _sendInputToConnection(std::wstring_view wstr); #pragma region TerminalCoreCallbacks - void _terminalCopyToClipboard(wil::zwstring_view wstr); void _terminalWarningBell(); void _terminalTitleChanged(std::wstring_view wstr); void _terminalScrollPositionChanged(const int viewTop, diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index bc704a13426..a70573d956a 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -185,6 +185,7 @@ namespace Microsoft.Terminal.Control // These events are called from some background thread event Windows.Foundation.TypedEventHandler TitleChanged; + event Windows.Foundation.TypedEventHandler WriteToClipboard; event Windows.Foundation.TypedEventHandler WarningBell; event Windows.Foundation.TypedEventHandler TabColorChanged; event Windows.Foundation.TypedEventHandler BackgroundColorChanged; diff --git a/src/cascadia/TerminalControl/ControlInteractivity.cpp b/src/cascadia/TerminalControl/ControlInteractivity.cpp index d9bc37571be..0190ac00d2e 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.cpp +++ b/src/cascadia/TerminalControl/ControlInteractivity.cpp @@ -199,7 +199,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // if we should defer which formats are copied to the global setting bool ControlInteractivity::CopySelectionToClipboard(bool singleLine, bool withControlSequences, - const Windows::Foundation::IReference& formats) + const CopyFormat formats) { if (_core) { @@ -316,7 +316,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation else { // Try to copy the text and clear the selection - const auto successfulCopy = CopySelectionToClipboard(shiftEnabled, false, nullptr); + const auto successfulCopy = CopySelectionToClipboard(shiftEnabled, false, _core->Settings().CopyFormatting()); _core->ClearSelection(); if (_core->CopyOnSelect() || !successfulCopy) { @@ -461,7 +461,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // IMPORTANT! // DO NOT clear the selection here! // Otherwise, the selection will be cleared immediately after you make it. - CopySelectionToClipboard(false, false, nullptr); + CopySelectionToClipboard(false, false, _core->Settings().CopyFormatting()); } _singleClickTouchdownPos = std::nullopt; diff --git a/src/cascadia/TerminalControl/ControlInteractivity.h b/src/cascadia/TerminalControl/ControlInteractivity.h index 02b198a77a4..fc6a1bcb0ca 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.h +++ b/src/cascadia/TerminalControl/ControlInteractivity.h @@ -84,7 +84,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation bool CopySelectionToClipboard(bool singleLine, bool withControlSequences, - const Windows::Foundation::IReference& formats); + const CopyFormat formats); void RequestPasteTextFromClipboard(); void SetEndSelectionPoint(const Core::Point pixelPosition); diff --git a/src/cascadia/TerminalControl/ControlInteractivity.idl b/src/cascadia/TerminalControl/ControlInteractivity.idl index a63726f2d38..bfd94703848 100644 --- a/src/cascadia/TerminalControl/ControlInteractivity.idl +++ b/src/cascadia/TerminalControl/ControlInteractivity.idl @@ -32,7 +32,7 @@ namespace Microsoft.Terminal.Control InteractivityAutomationPeer OnCreateAutomationPeer(); - Boolean CopySelectionToClipboard(Boolean singleLine, Boolean withControlSequences, Windows.Foundation.IReference formats); + Boolean CopySelectionToClipboard(Boolean singleLine, Boolean withControlSequences, CopyFormat formats); void RequestPasteTextFromClipboard(); void SetEndSelectionPoint(Microsoft.Terminal.Core.Point point); diff --git a/src/cascadia/TerminalControl/EventArgs.cpp b/src/cascadia/TerminalControl/EventArgs.cpp index b7e90a7511c..e5a9fe58765 100644 --- a/src/cascadia/TerminalControl/EventArgs.cpp +++ b/src/cascadia/TerminalControl/EventArgs.cpp @@ -6,6 +6,7 @@ #include "FontSizeChangedArgs.g.cpp" #include "TitleChangedEventArgs.g.cpp" #include "ContextMenuRequestedEventArgs.g.cpp" +#include "WriteToClipboardEventArgs.g.cpp" #include "PasteFromClipboardEventArgs.g.cpp" #include "OpenHyperlinkEventArgs.g.cpp" #include "NoticeEventArgs.g.cpp" diff --git a/src/cascadia/TerminalControl/EventArgs.h b/src/cascadia/TerminalControl/EventArgs.h index 526aa2b033f..53f1245ef06 100644 --- a/src/cascadia/TerminalControl/EventArgs.h +++ b/src/cascadia/TerminalControl/EventArgs.h @@ -6,6 +6,7 @@ #include "FontSizeChangedArgs.g.h" #include "TitleChangedEventArgs.g.h" #include "ContextMenuRequestedEventArgs.g.h" +#include "WriteToClipboardEventArgs.g.h" #include "PasteFromClipboardEventArgs.g.h" #include "OpenHyperlinkEventArgs.g.h" #include "NoticeEventArgs.g.h" @@ -56,6 +57,32 @@ namespace winrt::Microsoft::Terminal::Control::implementation WINRT_PROPERTY(winrt::Windows::Foundation::Point, Position); }; + struct WriteToClipboardEventArgs : WriteToClipboardEventArgsT + { + WriteToClipboardEventArgs(winrt::hstring&& plain, std::string&& html, std::string&& rtf) : + _plain(std::move(plain)), + _html(std::move(html)), + _rtf(std::move(rtf)) + { + } + + winrt::hstring Plain() const noexcept { return _plain; } + winrt::com_array Html() noexcept { return _cast(_html); } + winrt::com_array Rtf() noexcept { return _cast(_rtf); } + + private: + static winrt::com_array _cast(const std::string& str) + { + const auto beg = reinterpret_cast(str.data()); + const auto len = str.size(); + return { beg, beg + len }; + } + + winrt::hstring _plain; + std::string _html; + std::string _rtf; + }; + struct PasteFromClipboardEventArgs : public PasteFromClipboardEventArgsT { public: diff --git a/src/cascadia/TerminalControl/EventArgs.idl b/src/cascadia/TerminalControl/EventArgs.idl index 0cdaee97659..d6086fd922d 100644 --- a/src/cascadia/TerminalControl/EventArgs.idl +++ b/src/cascadia/TerminalControl/EventArgs.idl @@ -6,6 +6,7 @@ namespace Microsoft.Terminal.Control [flags] enum CopyFormat { + None = 0x0, HTML = 0x1, RTF = 0x2, All = 0xffffffff @@ -31,6 +32,13 @@ namespace Microsoft.Terminal.Control AlphanumericHalfWidth, }; + enum WarnAboutMultiLinePaste + { + Automatic, + Always, + Never, + }; + runtimeclass FontSizeChangedArgs { Int32 Width { get; }; @@ -47,6 +55,13 @@ namespace Microsoft.Terminal.Control String Title; } + runtimeclass WriteToClipboardEventArgs + { + String Plain { get; }; // UTF-16, as required by CF_UNICODETEXT + byte[] Html { get; }; // UTF-8, as required by "HTML Format" + byte[] Rtf { get; }; // UTF-8, as required by "Rich Text Format" + } + runtimeclass PasteFromClipboardEventArgs { void HandleClipboardData(String data); diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 0e6f6e8e483..a2580db435c 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -330,6 +330,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _revokers.RestartTerminalRequested = _core.RestartTerminalRequested(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleRestartTerminalRequested }); _revokers.SearchMissingCommand = _core.SearchMissingCommand(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleSearchMissingCommand }); _revokers.WindowSizeChanged = _core.WindowSizeChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleWindowSizeChanged }); + _revokers.WriteToClipboard = _core.WriteToClipboard(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleWriteToClipboard }); _revokers.PasteFromClipboard = _interactivity.PasteFromClipboard(winrt::auto_revoke, { get_weak(), &TermControl::_bubblePasteFromClipboard }); @@ -2634,7 +2635,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - withControlSequences: if enabled, the copied plain text contains color/style ANSI escape codes from the selection // - formats: which formats to copy (defined by action's CopyFormatting arg). nullptr // if we should defer which formats are copied to the global setting - bool TermControl::CopySelectionToClipboard(bool dismissSelection, bool singleLine, bool withControlSequences, const Windows::Foundation::IReference& formats) + bool TermControl::CopySelectionToClipboard(bool dismissSelection, bool singleLine, bool withControlSequences, const CopyFormat formats) { if (_IsClosing()) { @@ -4153,7 +4154,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation const IInspectable& /*args*/) { // formats = nullptr -> copy all formats - _interactivity.CopySelectionToClipboard(false, false, nullptr); + _interactivity.CopySelectionToClipboard(false, false, _core.Settings().CopyFormatting()); ContextMenu().Hide(); SelectionContextMenu().Hide(); } diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index fea4ec2f4a6..0aaae9c70f3 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -60,7 +60,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation hstring GetProfileName() const; - bool CopySelectionToClipboard(bool dismissSelection, bool singleLine, bool withControlSequences, const Windows::Foundation::IReference& formats); + bool CopySelectionToClipboard(bool dismissSelection, bool singleLine, bool withControlSequences, const CopyFormat formats); void PasteTextFromClipboard(); void SelectAll(); bool ToggleBlockSelection(); @@ -227,8 +227,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation BUBBLED_FORWARDED_TYPED_EVENT(CloseTerminalRequested, IInspectable, IInspectable); BUBBLED_FORWARDED_TYPED_EVENT(CompletionsChanged, IInspectable, Control::CompletionsChangedEventArgs); BUBBLED_FORWARDED_TYPED_EVENT(RestartTerminalRequested, IInspectable, IInspectable); - - BUBBLED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs); + BUBBLED_FORWARDED_TYPED_EVENT(WriteToClipboard, IInspectable, Control::WriteToClipboardEventArgs); + BUBBLED_FORWARDED_TYPED_EVENT(PasteFromClipboard, IInspectable, Control::PasteFromClipboardEventArgs); // clang-format on @@ -463,6 +463,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation Control::ControlCore::UpdateSelectionMarkers_revoker UpdateSelectionMarkers; Control::ControlCore::OpenHyperlink_revoker coreOpenHyperlink; Control::ControlCore::TitleChanged_revoker TitleChanged; + Control::ControlCore::WriteToClipboard_revoker WriteToClipboard; Control::ControlCore::TabColorChanged_revoker TabColorChanged; Control::ControlCore::TaskbarProgressChanged_revoker TaskbarProgressChanged; Control::ControlCore::ConnectionStateChanged_revoker ConnectionStateChanged; diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index 9d72977455d..2efe9a41066 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -53,6 +53,7 @@ namespace Microsoft.Terminal.Control Microsoft.Terminal.Control.IControlSettings Settings { get; }; event Windows.Foundation.TypedEventHandler TitleChanged; + event Windows.Foundation.TypedEventHandler WriteToClipboard; event Windows.Foundation.TypedEventHandler PasteFromClipboard; event Windows.Foundation.TypedEventHandler OpenHyperlink; event Windows.Foundation.TypedEventHandler SetTaskbarProgress; @@ -87,7 +88,7 @@ namespace Microsoft.Terminal.Control event Windows.Foundation.TypedEventHandler CloseTerminalRequested; event Windows.Foundation.TypedEventHandler RestartTerminalRequested; - Boolean CopySelectionToClipboard(Boolean dismissSelection, Boolean singleLine, Boolean withControlSequences, Windows.Foundation.IReference formats); + Boolean CopySelectionToClipboard(Boolean dismissSelection, Boolean singleLine, Boolean withControlSequences, CopyFormat formats); void PasteTextFromClipboard(); void SelectAll(); Boolean ToggleBlockSelection(); diff --git a/src/cascadia/TerminalSettingsEditor/Interaction.cpp b/src/cascadia/TerminalSettingsEditor/Interaction.cpp index ed3c55fbda6..24199f0c1b1 100644 --- a/src/cascadia/TerminalSettingsEditor/Interaction.cpp +++ b/src/cascadia/TerminalSettingsEditor/Interaction.cpp @@ -5,6 +5,8 @@ #include "Interaction.h" #include "Interaction.g.cpp" +#include "EnumEntry.h" + using namespace winrt::Windows::UI::Xaml::Navigation; using namespace winrt::Microsoft::Terminal::Settings::Model; @@ -13,6 +15,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Interaction::Interaction() { InitializeComponent(); + + INITIALIZE_BINDABLE_ENUM_SETTING(WarnAboutMultiLinePaste, WarnAboutMultiLinePaste, winrt::Microsoft::Terminal::Control::WarnAboutMultiLinePaste, L"Globals_WarnAboutMultiLinePaste", L"Content"); } void Interaction::OnNavigatedTo(const NavigationEventArgs& e) diff --git a/src/cascadia/TerminalSettingsEditor/Interaction.h b/src/cascadia/TerminalSettingsEditor/Interaction.h index cbb7846c38a..568139fdeed 100644 --- a/src/cascadia/TerminalSettingsEditor/Interaction.h +++ b/src/cascadia/TerminalSettingsEditor/Interaction.h @@ -16,6 +16,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation til::property_changed_event PropertyChanged; WINRT_OBSERVABLE_PROPERTY(Editor::InteractionViewModel, ViewModel, PropertyChanged.raise, nullptr); + GETSET_BINDABLE_ENUM_SETTING(WarnAboutMultiLinePaste, winrt::Microsoft::Terminal::Control::WarnAboutMultiLinePaste, ViewModel().WarnAboutMultiLinePaste); }; } diff --git a/src/cascadia/TerminalSettingsEditor/Interaction.idl b/src/cascadia/TerminalSettingsEditor/Interaction.idl index 840fca4a750..3a41823b87f 100644 --- a/src/cascadia/TerminalSettingsEditor/Interaction.idl +++ b/src/cascadia/TerminalSettingsEditor/Interaction.idl @@ -9,5 +9,8 @@ namespace Microsoft.Terminal.Settings.Editor { Interaction(); InteractionViewModel ViewModel { get; }; + + IInspectable CurrentWarnAboutMultiLinePaste; + Windows.Foundation.Collections.IObservableVector WarnAboutMultiLinePasteList { get; }; } } diff --git a/src/cascadia/TerminalSettingsEditor/Interaction.xaml b/src/cascadia/TerminalSettingsEditor/Interaction.xaml index 84a5019ea6d..fe3f67c6eb3 100644 --- a/src/cascadia/TerminalSettingsEditor/Interaction.xaml +++ b/src/cascadia/TerminalSettingsEditor/Interaction.xaml @@ -142,8 +142,11 @@ - + diff --git a/src/cascadia/TerminalSettingsEditor/InteractionViewModel.idl b/src/cascadia/TerminalSettingsEditor/InteractionViewModel.idl index c178c9f744b..ba77aec55f5 100644 --- a/src/cascadia/TerminalSettingsEditor/InteractionViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/InteractionViewModel.idl @@ -30,7 +30,7 @@ namespace Microsoft.Terminal.Settings.Editor PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, ConfirmCloseAllTabs); PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, InputServiceWarning); PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, WarnAboutLargePaste); - PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, WarnAboutMultiLinePaste); + PERMANENT_OBSERVABLE_PROJECTED_SETTING(Microsoft.Terminal.Control.WarnAboutMultiLinePaste, WarnAboutMultiLinePaste); PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, EnableColorSelection); } } diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index 816c799eb11..7b3ebe85a64 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -1924,10 +1924,26 @@ Warn when "Touch Keyboard and Handwriting Panel Service" is disabled - Warn when trying to paste more than 5 KiB of characters + Warn when pasting more than 5 KiB - Warn when trying to paste a "new line" character + Warn when pasting newlines + + + If your shell does not support "bracketed paste" mode, we recommend setting this to "Always" for security reasons. + "bracketed paste" is a technical term referring to escape sequences. "Always" is a setting, as in "Always VS Never". + + + Only if "bracketed paste" is disabled + "bracketed paste" is a technical term referring to terminal escape sequences. + + + Always + As in: Always VS Never + + + Never + As in: Always VS Never Windows Terminal is running in portable mode. diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.cpp b/src/cascadia/TerminalSettingsModel/EnumMappings.cpp index 7895bec7d95..6f867db0b65 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.cpp +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.cpp @@ -42,6 +42,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation DEFINE_ENUM_MAP(Microsoft::Terminal::Core::MatchMode, MatchMode); DEFINE_ENUM_MAP(Microsoft::Terminal::Control::GraphicsAPI, GraphicsAPI); DEFINE_ENUM_MAP(Microsoft::Terminal::Control::TextMeasurement, TextMeasurement); + DEFINE_ENUM_MAP(Microsoft::Terminal::Control::WarnAboutMultiLinePaste, WarnAboutMultiLinePaste); // Profile Settings DEFINE_ENUM_MAP(Model::CloseOnExitMode, CloseOnExitMode); diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.h b/src/cascadia/TerminalSettingsModel/EnumMappings.h index 15fbf50a403..a3412185952 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.h +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.h @@ -38,6 +38,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static winrt::Windows::Foundation::Collections::IMap MatchMode(); static winrt::Windows::Foundation::Collections::IMap GraphicsAPI(); static winrt::Windows::Foundation::Collections::IMap TextMeasurement(); + static winrt::Windows::Foundation::Collections::IMap WarnAboutMultiLinePaste(); // Profile Settings static winrt::Windows::Foundation::Collections::IMap CloseOnExitMode(); diff --git a/src/cascadia/TerminalSettingsModel/EnumMappings.idl b/src/cascadia/TerminalSettingsModel/EnumMappings.idl index 57735eccd5b..befb2cea5db 100644 --- a/src/cascadia/TerminalSettingsModel/EnumMappings.idl +++ b/src/cascadia/TerminalSettingsModel/EnumMappings.idl @@ -20,6 +20,7 @@ namespace Microsoft.Terminal.Settings.Model static Windows.Foundation.Collections.IMap MatchMode { get; }; static Windows.Foundation.Collections.IMap GraphicsAPI { get; }; static Windows.Foundation.Collections.IMap TextMeasurement { get; }; + static Windows.Foundation.Collections.IMap WarnAboutMultiLinePaste { get; }; // Profile Settings static Windows.Foundation.Collections.IMap CloseOnExitMode { get; }; diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index 7e6c1180e4a..e003660f329 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -70,7 +70,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_SETTING(Boolean, InputServiceWarning); INHERITABLE_SETTING(Microsoft.Terminal.Control.CopyFormat, CopyFormatting); INHERITABLE_SETTING(Boolean, WarnAboutLargePaste); - INHERITABLE_SETTING(Boolean, WarnAboutMultiLinePaste); + INHERITABLE_SETTING(Microsoft.Terminal.Control.WarnAboutMultiLinePaste, WarnAboutMultiLinePaste); INHERITABLE_SETTING(Boolean, TrimPaste); INHERITABLE_SETTING(LaunchPosition, InitialPosition); INHERITABLE_SETTING(Boolean, CenterOnLaunch); diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index ee2d95c23e4..ff66ab92782 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -46,7 +46,7 @@ Author(s): X(bool, InputServiceWarning, "warning.inputService", true) \ X(winrt::Microsoft::Terminal::Control::CopyFormat, CopyFormatting, "copyFormatting", 0) \ X(bool, WarnAboutLargePaste, "warning.largePaste", true) \ - X(bool, WarnAboutMultiLinePaste, "warning.multiLinePaste", true) \ + X(winrt::Microsoft::Terminal::Control::WarnAboutMultiLinePaste, WarnAboutMultiLinePaste, "warning.multiLinePaste", winrt::Microsoft::Terminal::Control::WarnAboutMultiLinePaste::Automatic) \ X(Model::LaunchPosition, InitialPosition, "initialPosition", nullptr, nullptr) \ X(bool, CenterOnLaunch, "centerOnLaunch", false) \ X(Model::FirstWindowPreference, FirstWindowPreference, "firstWindowPreference", FirstWindowPreference::DefaultProfile) \ diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index 84e2e6f9289..b2355ce4c73 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -841,3 +841,29 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::PathTranslationStyle) pair_type{ "mingw", ValueType::MinGW }, }; }; + +JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::WarnAboutMultiLinePaste) +{ + JSON_MAPPINGS(3) = { + pair_type{ "automatic", ValueType::Automatic }, + pair_type{ "always", ValueType::Always }, + pair_type{ "never", ValueType::Never }, + }; + + // Override mapping parser to add boolean parsing + ::winrt::Microsoft::Terminal::Control::WarnAboutMultiLinePaste FromJson(const Json::Value& json) + { + if (json.isBool()) + { + return json.asBool() ? ValueType::Automatic : ValueType::Never; + } + return EnumMapper::FromJson(json); + } + + bool CanConvert(const Json::Value& json) + { + return EnumMapper::CanConvert(json) || json.isBool(); + } + + using EnumMapper::TypeDescription; +}; diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index 2202b491835..3112f83939e 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -128,7 +128,7 @@ namespace SettingsModelUnitTests "warning.confirmCloseAllTabs" : true, "warning.inputService" : true, "warning.largePaste" : true, - "warning.multiLinePaste" : true, + "warning.multiLinePaste" : "automatic", "actions": [], "keybindings": []