Don't trim bracketed pastes (#19067)

* Moves clipboard writing from `ControlCore` to `TerminalPage`.
  This requires adding a bunch of event types and logic.
  This is technically not needed anymore after changing the
  direction of this PR, but I kept it because it's better.
* Add a `WarnAboutMultiLinePaste` enum to differentiate between
  "paste without warning always/never/if-bracketed-paste-disabled".

Closes #13014
Closes https://github.com/microsoft/edit/issues/279

## Validation Steps Performed
* Launch Microsoft Edit and copy text with a trailing newline
* Paste it with Ctrl+Shift+V
* It's pasted as it was copied 
* Changing the setting to "always" always warns 

Co-authored-by: Dustin L. Howett <duhowett@microsoft.com>
This commit is contained in:
Leonard Hecker 2025-08-08 22:53:42 +02:00 committed by GitHub
parent 5b41f14660
commit e2f3e53064
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 305 additions and 206 deletions

View File

@ -548,7 +548,9 @@ namespace winrt::TerminalApp::implementation
{
if (const auto& realArgs = args.ActionArgs().try_as<CopyTextArgs>())
{
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);
}
}

View File

@ -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<const wchar_t*>(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<uint32_t>(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<HDROP>(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<const wchar_t*>(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<uint32_t>(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<HDROP>(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<WUX::Controls::ContentDialog>();
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<CopyFormat>& 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<const char*>(html.data()), html.size() },
{ reinterpret_cast<const char*>(rtf.data()), rtf.size() });
}
}
// Method Description:
// - Paste text from the Windows Clipboard to the focused terminal
void TerminalPage::_PasteText()

View File

@ -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<Microsoft::Terminal::Control::CopyFormat>& 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);

View File

@ -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<decltype(PH1)>(PH1)); };
_terminal->SetCopyToClipboardCallback(pfnCopyToClipboard);
_terminal->SetCopyToClipboardCallback([this](wil::zwstring_view wstr) {
WriteToClipboard.raise(*this, winrt::make<WriteToClipboardEventArgs>(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<CopyFormat>& 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<WriteToClipboardEventArgs>(
winrt::hstring{ payload.plainText },
std::move(payload.html),
std::move(payload.rtf)));
return true;
}

View File

@ -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<CopyFormat>& 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<IInspectable, Control::FontSizeChangedArgs> FontSizeChanged;
til::typed_event<IInspectable, Control::TitleChangedEventArgs> TitleChanged;
til::typed_event<IInspectable, Control::WriteToClipboardEventArgs> 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,

View File

@ -185,6 +185,7 @@ namespace Microsoft.Terminal.Control
// These events are called from some background thread
event Windows.Foundation.TypedEventHandler<Object, TitleChangedEventArgs> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, WriteToClipboardEventArgs> WriteToClipboard;
event Windows.Foundation.TypedEventHandler<Object, Object> WarningBell;
event Windows.Foundation.TypedEventHandler<Object, Object> TabColorChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> BackgroundColorChanged;

View File

@ -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<CopyFormat>& 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;

View File

@ -84,7 +84,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool CopySelectionToClipboard(bool singleLine,
bool withControlSequences,
const Windows::Foundation::IReference<CopyFormat>& formats);
const CopyFormat formats);
void RequestPasteTextFromClipboard();
void SetEndSelectionPoint(const Core::Point pixelPosition);

View File

@ -32,7 +32,7 @@ namespace Microsoft.Terminal.Control
InteractivityAutomationPeer OnCreateAutomationPeer();
Boolean CopySelectionToClipboard(Boolean singleLine, Boolean withControlSequences, Windows.Foundation.IReference<CopyFormat> formats);
Boolean CopySelectionToClipboard(Boolean singleLine, Boolean withControlSequences, CopyFormat formats);
void RequestPasteTextFromClipboard();
void SetEndSelectionPoint(Microsoft.Terminal.Core.Point point);

View File

@ -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"

View File

@ -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>
{
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<uint8_t> Html() noexcept { return _cast(_html); }
winrt::com_array<uint8_t> Rtf() noexcept { return _cast(_rtf); }
private:
static winrt::com_array<uint8_t> _cast(const std::string& str)
{
const auto beg = reinterpret_cast<const uint8_t*>(str.data());
const auto len = str.size();
return { beg, beg + len };
}
winrt::hstring _plain;
std::string _html;
std::string _rtf;
};
struct PasteFromClipboardEventArgs : public PasteFromClipboardEventArgsT<PasteFromClipboardEventArgs>
{
public:

View File

@ -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);

View File

@ -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<CopyFormat>& 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();
}

View File

@ -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<CopyFormat>& 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;

View File

@ -53,6 +53,7 @@ namespace Microsoft.Terminal.Control
Microsoft.Terminal.Control.IControlSettings Settings { get; };
event Windows.Foundation.TypedEventHandler<Object, TitleChangedEventArgs> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, WriteToClipboardEventArgs> WriteToClipboard;
event Windows.Foundation.TypedEventHandler<Object, PasteFromClipboardEventArgs> PasteFromClipboard;
event Windows.Foundation.TypedEventHandler<Object, OpenHyperlinkEventArgs> OpenHyperlink;
event Windows.Foundation.TypedEventHandler<Object, Object> SetTaskbarProgress;
@ -87,7 +88,7 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, Object> CloseTerminalRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> RestartTerminalRequested;
Boolean CopySelectionToClipboard(Boolean dismissSelection, Boolean singleLine, Boolean withControlSequences, Windows.Foundation.IReference<CopyFormat> formats);
Boolean CopySelectionToClipboard(Boolean dismissSelection, Boolean singleLine, Boolean withControlSequences, CopyFormat formats);
void PasteTextFromClipboard();
void SelectAll();
Boolean ToggleBlockSelection();

View File

@ -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)

View File

@ -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);
};
}

View File

@ -9,5 +9,8 @@ namespace Microsoft.Terminal.Settings.Editor
{
Interaction();
InteractionViewModel ViewModel { get; };
IInspectable CurrentWarnAboutMultiLinePaste;
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> WarnAboutMultiLinePasteList { get; };
}
}

View File

@ -142,8 +142,11 @@
<!-- Multi Line Paste Warning -->
<local:SettingContainer x:Uid="Globals_WarnAboutMultiLinePaste">
<ToggleSwitch IsOn="{x:Bind ViewModel.WarnAboutMultiLinePaste, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
<ComboBox AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind WarnAboutMultiLinePasteList}"
SelectedItem="{x:Bind CurrentWarnAboutMultiLinePaste, Mode=TwoWay}"
Style="{StaticResource ComboBoxSettingStyle}" />
</local:SettingContainer>
</StackPanel>

View File

@ -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);
}
}

View File

@ -1924,10 +1924,26 @@
<value>Warn when "Touch Keyboard and Handwriting Panel Service" is disabled</value>
</data>
<data name="Globals_WarnAboutLargePaste.Header" xml:space="preserve">
<value>Warn when trying to paste more than 5 KiB of characters</value>
<value>Warn when pasting more than 5 KiB</value>
</data>
<data name="Globals_WarnAboutMultiLinePaste.Header" xml:space="preserve">
<value>Warn when trying to paste a "new line" character</value>
<value>Warn when pasting newlines</value>
</data>
<data name="Globals_WarnAboutMultiLinePaste.HelpText" xml:space="preserve">
<value>If your shell does not support "bracketed paste" mode, we recommend setting this to "Always" for security reasons.</value>
<comment>"bracketed paste" is a technical term referring to escape sequences. "Always" is a setting, as in "Always VS Never".</comment>
</data>
<data name="Globals_WarnAboutMultiLinePasteAutomatic.Content" xml:space="preserve">
<value>Only if "bracketed paste" is disabled</value>
<comment>"bracketed paste" is a technical term referring to terminal escape sequences.</comment>
</data>
<data name="Globals_WarnAboutMultiLinePasteAlways.Content" xml:space="preserve">
<value>Always</value>
<comment>As in: Always VS Never</comment>
</data>
<data name="Globals_WarnAboutMultiLinePasteNever.Content" xml:space="preserve">
<value>Never</value>
<comment>As in: Always VS Never</comment>
</data>
<data name="Settings_PortableModeNote.Text" xml:space="preserve">
<value>Windows Terminal is running in portable mode.</value>

View File

@ -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);

View File

@ -38,6 +38,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Core::MatchMode> MatchMode();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Control::GraphicsAPI> GraphicsAPI();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Control::TextMeasurement> TextMeasurement();
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, winrt::Microsoft::Terminal::Control::WarnAboutMultiLinePaste> WarnAboutMultiLinePaste();
// Profile Settings
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, CloseOnExitMode> CloseOnExitMode();

View File

@ -20,6 +20,7 @@ namespace Microsoft.Terminal.Settings.Model
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Core.MatchMode> MatchMode { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Control.GraphicsAPI> GraphicsAPI { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Control.TextMeasurement> TextMeasurement { get; };
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Control.WarnAboutMultiLinePaste> WarnAboutMultiLinePaste { get; };
// Profile Settings
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.CloseOnExitMode> CloseOnExitMode { get; };

View File

@ -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);

View File

@ -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) \

View File

@ -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;
};

View File

@ -128,7 +128,7 @@ namespace SettingsModelUnitTests
"warning.confirmCloseAllTabs" : true,
"warning.inputService" : true,
"warning.largePaste" : true,
"warning.multiLinePaste" : true,
"warning.multiLinePaste" : "automatic",
"actions": [],
"keybindings": []