Skip to content

Commit 7442a7b

Browse files
committed
Don't trim bracketed pastes set via OSC52
1 parent 00ee884 commit 7442a7b

15 files changed

+234
-191
lines changed

src/cascadia/TerminalApp/AppActionHandlers.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,9 @@ namespace winrt::TerminalApp::implementation
548548
{
549549
if (const auto& realArgs = args.ActionArgs().try_as<CopyTextArgs>())
550550
{
551-
const auto handled = _CopyText(realArgs.DismissSelection(), realArgs.SingleLine(), realArgs.WithControlSequences(), realArgs.CopyFormatting());
551+
const auto copyFormatting = realArgs.CopyFormatting();
552+
const auto format = copyFormatting ? copyFormatting.Value() : _settings.GlobalSettings().CopyFormatting();
553+
const auto handled = _CopyText(realArgs.DismissSelection(), realArgs.SingleLine(), realArgs.WithControlSequences(), format);
552554
args.Handled(handled);
553555
}
554556
}

src/cascadia/TerminalApp/TerminalPage.cpp

Lines changed: 162 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,121 @@ namespace winrt
5757
using VirtualKeyModifiers = Windows::System::VirtualKeyModifiers;
5858
}
5959

60+
namespace clipboard
61+
{
62+
wil::unique_close_clipboard_call open(HWND hwnd)
63+
{
64+
bool success = false;
65+
66+
// OpenClipboard may fail to acquire the internal lock --> retry.
67+
for (DWORD sleep = 10;; sleep *= 2)
68+
{
69+
if (OpenClipboard(hwnd))
70+
{
71+
success = true;
72+
break;
73+
}
74+
// 10 iterations
75+
if (sleep > 10000)
76+
{
77+
break;
78+
}
79+
Sleep(sleep);
80+
}
81+
82+
return wil::unique_close_clipboard_call{ success };
83+
}
84+
85+
void write(wil::zwstring_view text, std::string_view html, std::string_view rtf)
86+
{
87+
static const auto regular = [](const UINT format, const void* src, const size_t bytes) {
88+
wil::unique_hglobal handle{ THROW_LAST_ERROR_IF_NULL(GlobalAlloc(GMEM_MOVEABLE, bytes)) };
89+
90+
const auto locked = GlobalLock(handle.get());
91+
memcpy(locked, src, bytes);
92+
GlobalUnlock(handle.get());
93+
94+
THROW_LAST_ERROR_IF_NULL(SetClipboardData(format, handle.get()));
95+
handle.release();
96+
};
97+
static const auto registered = [](const wchar_t* format, const void* src, size_t bytes) {
98+
const auto id = RegisterClipboardFormatW(format);
99+
if (!id)
100+
{
101+
LOG_LAST_ERROR();
102+
return;
103+
}
104+
regular(id, src, bytes);
105+
};
106+
107+
EmptyClipboard();
108+
109+
if (!text.empty())
110+
{
111+
// As per: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats
112+
// CF_UNICODETEXT: [...] A null character signals the end of the data.
113+
// --> We add +1 to the length. This works because .c_str() is null-terminated.
114+
regular(CF_UNICODETEXT, text.c_str(), (text.size() + 1) * sizeof(wchar_t));
115+
}
116+
117+
if (!html.empty())
118+
{
119+
registered(L"HTML Format", html.data(), html.size());
120+
}
121+
122+
if (!rtf.empty())
123+
{
124+
registered(L"Rich Text Format", rtf.data(), rtf.size());
125+
}
126+
}
127+
128+
winrt::hstring read()
129+
{
130+
// This handles most cases of pasting text as the OS converts most formats to CF_UNICODETEXT automatically.
131+
if (const auto handle = GetClipboardData(CF_UNICODETEXT))
132+
{
133+
const wil::unique_hglobal_locked lock{ handle };
134+
const auto str = static_cast<const wchar_t*>(lock.get());
135+
if (!str)
136+
{
137+
return {};
138+
}
139+
140+
const auto maxLen = GlobalSize(handle) / sizeof(wchar_t);
141+
const auto len = wcsnlen(str, maxLen);
142+
return winrt::hstring{ str, gsl::narrow_cast<uint32_t>(len) };
143+
}
144+
145+
// We get CF_HDROP when a user copied a file with Ctrl+C in Explorer and pastes that into the terminal (among others).
146+
if (const auto handle = GetClipboardData(CF_HDROP))
147+
{
148+
const wil::unique_hglobal_locked lock{ handle };
149+
const auto drop = static_cast<HDROP>(lock.get());
150+
if (!drop)
151+
{
152+
return {};
153+
}
154+
155+
const auto cap = DragQueryFileW(drop, 0, nullptr, 0);
156+
if (cap == 0)
157+
{
158+
return {};
159+
}
160+
161+
auto buffer = winrt::impl::hstring_builder{ cap };
162+
const auto len = DragQueryFileW(drop, 0, buffer.data(), cap + 1);
163+
if (len == 0)
164+
{
165+
return {};
166+
}
167+
168+
return buffer.to_hstring();
169+
}
170+
171+
return {};
172+
}
173+
} // namespace clipboard
174+
60175
namespace winrt::TerminalApp::implementation
61176
{
62177
TerminalPage::TerminalPage(TerminalApp::WindowProperties properties, const TerminalApp::ContentManager& manager) :
@@ -1708,10 +1823,9 @@ namespace winrt::TerminalApp::implementation
17081823
});
17091824

17101825
term.ShowWindowChanged({ get_weak(), &TerminalPage::_ShowWindowChangedHandler });
1711-
17121826
term.SearchMissingCommand({ get_weak(), &TerminalPage::_SearchMissingCommandHandler });
1713-
17141827
term.WindowSizeChanged({ get_weak(), &TerminalPage::_WindowSizeChanged });
1828+
term.WriteToClipboard({ get_weak(), &TerminalPage::_copyToClipboard });
17151829

17161830
// Don't even register for the event if the feature is compiled off.
17171831
if constexpr (Feature_ShellCompletions::IsEnabled())
@@ -2643,75 +2757,6 @@ namespace winrt::TerminalApp::implementation
26432757
return dimension;
26442758
}
26452759

2646-
static wil::unique_close_clipboard_call _openClipboard(HWND hwnd)
2647-
{
2648-
bool success = false;
2649-
2650-
// OpenClipboard may fail to acquire the internal lock --> retry.
2651-
for (DWORD sleep = 10;; sleep *= 2)
2652-
{
2653-
if (OpenClipboard(hwnd))
2654-
{
2655-
success = true;
2656-
break;
2657-
}
2658-
// 10 iterations
2659-
if (sleep > 10000)
2660-
{
2661-
break;
2662-
}
2663-
Sleep(sleep);
2664-
}
2665-
2666-
return wil::unique_close_clipboard_call{ success };
2667-
}
2668-
2669-
static winrt::hstring _extractClipboard()
2670-
{
2671-
// This handles most cases of pasting text as the OS converts most formats to CF_UNICODETEXT automatically.
2672-
if (const auto handle = GetClipboardData(CF_UNICODETEXT))
2673-
{
2674-
const wil::unique_hglobal_locked lock{ handle };
2675-
const auto str = static_cast<const wchar_t*>(lock.get());
2676-
if (!str)
2677-
{
2678-
return {};
2679-
}
2680-
2681-
const auto maxLen = GlobalSize(handle) / sizeof(wchar_t);
2682-
const auto len = wcsnlen(str, maxLen);
2683-
return winrt::hstring{ str, gsl::narrow_cast<uint32_t>(len) };
2684-
}
2685-
2686-
// We get CF_HDROP when a user copied a file with Ctrl+C in Explorer and pastes that into the terminal (among others).
2687-
if (const auto handle = GetClipboardData(CF_HDROP))
2688-
{
2689-
const wil::unique_hglobal_locked lock{ handle };
2690-
const auto drop = static_cast<HDROP>(lock.get());
2691-
if (!drop)
2692-
{
2693-
return {};
2694-
}
2695-
2696-
const auto cap = DragQueryFileW(drop, 0, nullptr, 0);
2697-
if (cap == 0)
2698-
{
2699-
return {};
2700-
}
2701-
2702-
auto buffer = winrt::impl::hstring_builder{ cap };
2703-
const auto len = DragQueryFileW(drop, 0, buffer.data(), cap + 1);
2704-
if (len == 0)
2705-
{
2706-
return {};
2707-
}
2708-
2709-
return buffer.to_hstring();
2710-
}
2711-
2712-
return {};
2713-
}
2714-
27152760
// Function Description:
27162761
// - This function is called when the `TermControl` requests that we send
27172762
// it the clipboard's content.
@@ -2731,24 +2776,40 @@ namespace winrt::TerminalApp::implementation
27312776
const auto weakThis = get_weak();
27322777
const auto dispatcher = Dispatcher();
27332778
const auto globalSettings = _settings.GlobalSettings();
2779+
const auto bracketedPaste = eventArgs.BracketedPasteEnabled();
27342780

27352781
// GetClipboardData might block for up to 30s for delay-rendered contents.
27362782
co_await winrt::resume_background();
27372783

27382784
winrt::hstring text;
2739-
if (const auto clipboard = _openClipboard(nullptr))
2785+
uint32_t clipboardSequenceNumber = 0;
2786+
2787+
if (const auto clipboard = clipboard::open(nullptr))
27402788
{
2741-
text = _extractClipboard();
2789+
text = clipboard::read();
2790+
clipboardSequenceNumber = GetClipboardSequenceNumber();
2791+
}
2792+
else
2793+
{
2794+
co_return;
27422795
}
27432796

2744-
if (globalSettings.TrimPaste())
2797+
if (globalSettings.TrimPaste() &&
2798+
// If we're using bracketed paste mode, and the clipboard contents originate from us
2799+
// (= clipboard sequence number matches the last written one), don't trim the paste.
2800+
// Put inversely: Trim the contents if the sequence number changed OR if we're not using bracketed pastes.
2801+
(_copyToClipboardSequenceNumber.load(std::memory_order_relaxed) != clipboardSequenceNumber || !bracketedPaste))
27452802
{
2746-
text = { Utils::TrimPaste(text) };
2747-
if (text.empty())
2748-
{
2749-
// Text is all white space, nothing to paste
2750-
co_return;
2751-
}
2803+
// NOTE: This has to be done in 2 steps, because technically speaking we
2804+
// may be the only holder of the hstring instance and assigning to it
2805+
// will first release the current memory and then assign the new value.
2806+
winrt::hstring trimmed{ Utils::TrimPaste(text) };
2807+
text = std::move(trimmed);
2808+
}
2809+
2810+
if (text.empty())
2811+
{
2812+
co_return;
27522813
}
27532814

27542815
// If the requesting terminal is in bracketed paste mode, then we don't need to warn about a multi-line paste.
@@ -2951,7 +3012,7 @@ namespace winrt::TerminalApp::implementation
29513012
// - formats: dictate which formats need to be copied
29523013
// Return Value:
29533014
// - true iff we we able to copy text (if a selection was active)
2954-
bool TerminalPage::_CopyText(const bool dismissSelection, const bool singleLine, const bool withControlSequences, const Windows::Foundation::IReference<CopyFormat>& formats)
3015+
bool TerminalPage::_CopyText(const bool dismissSelection, const bool singleLine, const bool withControlSequences, const CopyFormat formats)
29553016
{
29563017
if (const auto& control{ _GetActiveControl() })
29573018
{
@@ -3094,6 +3155,26 @@ namespace winrt::TerminalApp::implementation
30943155
}
30953156
}
30963157

3158+
void TerminalPage::_copyToClipboard(const IInspectable, const WriteToClipboardEventArgs args)
3159+
{
3160+
const auto plain = args.Plain();
3161+
const auto html = args.Html();
3162+
const auto rtf = args.Rtf();
3163+
3164+
if (auto clipboard = clipboard::open(_hostingHwnd.value_or(nullptr)))
3165+
{
3166+
clipboard::write(
3167+
{ plain.data(), plain.size() },
3168+
{ reinterpret_cast<const char*>(html.data()), html.size() },
3169+
{ reinterpret_cast<const char*>(rtf.data()), rtf.size() });
3170+
3171+
// `CloseClipboard` can still bump the sequence number,
3172+
// so we need to do this after dropping `clipboard`.
3173+
clipboard.reset();
3174+
_copyToClipboardSequenceNumber.store(GetClipboardSequenceNumber(), std::memory_order_relaxed);
3175+
}
3176+
}
3177+
30973178
// Method Description:
30983179
// - Paste text from the Windows Clipboard to the focused terminal
30993180
void TerminalPage::_PasteText()

src/cascadia/TerminalApp/TerminalPage.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ namespace winrt::TerminalApp::implementation
225225
void _UpdateTabIndices();
226226

227227
TerminalApp::TerminalTab _settingsTab{ nullptr };
228-
228+
std::atomic<uint32_t> _copyToClipboardSequenceNumber{ 0 };
229229
bool _isInFocusMode{ false };
230230
bool _isFullscreen{ false };
231231
bool _isMaximized{ false };
@@ -415,10 +415,11 @@ namespace winrt::TerminalApp::implementation
415415
bool _IsUriSupported(const winrt::Windows::Foundation::Uri& parsedUri);
416416

417417
void _ShowCouldNotOpenDialog(winrt::hstring reason, winrt::hstring uri);
418-
bool _CopyText(const bool dismissSelection, const bool singleLine, const bool withControlSequences, const Windows::Foundation::IReference<Microsoft::Terminal::Control::CopyFormat>& formats);
418+
bool _CopyText(bool dismissSelection, bool singleLine, bool withControlSequences, Microsoft::Terminal::Control::CopyFormat formats);
419419

420420
safe_void_coroutine _SetTaskbarProgressHandler(const IInspectable sender, const IInspectable eventArgs);
421421

422+
void _copyToClipboard(IInspectable, Microsoft::Terminal::Control::WriteToClipboardEventArgs args);
422423
void _PasteText();
423424

424425
safe_void_coroutine _ControlNoticeRaisedHandler(const IInspectable sender, const Microsoft::Terminal::Control::NoticeEventArgs eventArgs);

0 commit comments

Comments
 (0)