@@ -56,6 +56,121 @@ namespace winrt
5656 using VirtualKeyModifiers = Windows::System::VirtualKeyModifiers;
5757}
5858
59+ namespace clipboard
60+ {
61+ wil::unique_close_clipboard_call open (HWND hwnd)
62+ {
63+ bool success = false ;
64+
65+ // OpenClipboard may fail to acquire the internal lock --> retry.
66+ for (DWORD sleep = 10 ;; sleep *= 2 )
67+ {
68+ if (OpenClipboard (hwnd))
69+ {
70+ success = true ;
71+ break ;
72+ }
73+ // 10 iterations
74+ if (sleep > 10000 )
75+ {
76+ break ;
77+ }
78+ Sleep (sleep);
79+ }
80+
81+ return wil::unique_close_clipboard_call{ success };
82+ }
83+
84+ void write (wil::zwstring_view text, std::string_view html, std::string_view rtf)
85+ {
86+ static const auto regular = [](const UINT format, const void * src, const size_t bytes) {
87+ wil::unique_hglobal handle{ THROW_LAST_ERROR_IF_NULL (GlobalAlloc (GMEM_MOVEABLE, bytes)) };
88+
89+ const auto locked = GlobalLock (handle.get ());
90+ memcpy (locked, src, bytes);
91+ GlobalUnlock (handle.get ());
92+
93+ THROW_LAST_ERROR_IF_NULL (SetClipboardData (format, handle.get ()));
94+ handle.release ();
95+ };
96+ static const auto registered = [](const wchar_t * format, const void * src, size_t bytes) {
97+ const auto id = RegisterClipboardFormatW (format);
98+ if (!id)
99+ {
100+ LOG_LAST_ERROR ();
101+ return ;
102+ }
103+ regular (id, src, bytes);
104+ };
105+
106+ EmptyClipboard ();
107+
108+ if (!text.empty ())
109+ {
110+ // As per: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats
111+ // CF_UNICODETEXT: [...] A null character signals the end of the data.
112+ // --> We add +1 to the length. This works because .c_str() is null-terminated.
113+ regular (CF_UNICODETEXT, text.c_str (), (text.size () + 1 ) * sizeof (wchar_t ));
114+ }
115+
116+ if (!html.empty ())
117+ {
118+ registered (L" HTML Format" , html.data (), html.size ());
119+ }
120+
121+ if (!rtf.empty ())
122+ {
123+ registered (L" Rich Text Format" , rtf.data (), rtf.size ());
124+ }
125+ }
126+
127+ winrt::hstring read ()
128+ {
129+ // This handles most cases of pasting text as the OS converts most formats to CF_UNICODETEXT automatically.
130+ if (const auto handle = GetClipboardData (CF_UNICODETEXT))
131+ {
132+ const wil::unique_hglobal_locked lock{ handle };
133+ const auto str = static_cast <const wchar_t *>(lock.get ());
134+ if (!str)
135+ {
136+ return {};
137+ }
138+
139+ const auto maxLen = GlobalSize (handle) / sizeof (wchar_t );
140+ const auto len = wcsnlen (str, maxLen);
141+ return winrt::hstring{ str, gsl::narrow_cast<uint32_t >(len) };
142+ }
143+
144+ // We get CF_HDROP when a user copied a file with Ctrl+C in Explorer and pastes that into the terminal (among others).
145+ if (const auto handle = GetClipboardData (CF_HDROP))
146+ {
147+ const wil::unique_hglobal_locked lock{ handle };
148+ const auto drop = static_cast <HDROP>(lock.get ());
149+ if (!drop)
150+ {
151+ return {};
152+ }
153+
154+ const auto cap = DragQueryFileW (drop, 0 , nullptr , 0 );
155+ if (cap == 0 )
156+ {
157+ return {};
158+ }
159+
160+ auto buffer = winrt::impl::hstring_builder{ cap };
161+ const auto len = DragQueryFileW (drop, 0 , buffer.data (), cap + 1 );
162+ if (len == 0 )
163+ {
164+ return {};
165+ }
166+
167+ return buffer.to_hstring ();
168+ }
169+
170+ return {};
171+ }
172+ } // namespace clipboard
173+
59174namespace winrt ::TerminalApp::implementation
60175{
61176 TerminalPage::TerminalPage (TerminalApp::WindowProperties properties, const TerminalApp::ContentManager& manager) :
@@ -1793,7 +1908,7 @@ namespace winrt::TerminalApp::implementation
17931908 {
17941909 term.RaiseNotice ({ this , &TerminalPage::_ControlNoticeRaisedHandler });
17951910
1796- // Add an event handler when the terminal wants to paste data from the Clipboard.
1911+ term. WriteToClipboard ({ get_weak (), &TerminalPage::_copyToClipboard });
17971912 term.PasteFromClipboard ({ this , &TerminalPage::_PasteFromClipboardHandler });
17981913
17991914 term.OpenHyperlink ({ this , &TerminalPage::_OpenHyperlinkHandler });
@@ -1815,9 +1930,7 @@ namespace winrt::TerminalApp::implementation
18151930 });
18161931
18171932 term.ShowWindowChanged ({ get_weak (), &TerminalPage::_ShowWindowChangedHandler });
1818-
18191933 term.SearchMissingCommand ({ get_weak (), &TerminalPage::_SearchMissingCommandHandler });
1820-
18211934 term.WindowSizeChanged ({ get_weak (), &TerminalPage::_WindowSizeChanged });
18221935
18231936 // Don't even register for the event if the feature is compiled off.
@@ -2739,75 +2852,6 @@ namespace winrt::TerminalApp::implementation
27392852 return dimension;
27402853 }
27412854
2742- static wil::unique_close_clipboard_call _openClipboard (HWND hwnd)
2743- {
2744- bool success = false ;
2745-
2746- // OpenClipboard may fail to acquire the internal lock --> retry.
2747- for (DWORD sleep = 10 ;; sleep *= 2 )
2748- {
2749- if (OpenClipboard (hwnd))
2750- {
2751- success = true ;
2752- break ;
2753- }
2754- // 10 iterations
2755- if (sleep > 10000 )
2756- {
2757- break ;
2758- }
2759- Sleep (sleep);
2760- }
2761-
2762- return wil::unique_close_clipboard_call{ success };
2763- }
2764-
2765- static winrt::hstring _extractClipboard ()
2766- {
2767- // This handles most cases of pasting text as the OS converts most formats to CF_UNICODETEXT automatically.
2768- if (const auto handle = GetClipboardData (CF_UNICODETEXT))
2769- {
2770- const wil::unique_hglobal_locked lock{ handle };
2771- const auto str = static_cast <const wchar_t *>(lock.get ());
2772- if (!str)
2773- {
2774- return {};
2775- }
2776-
2777- const auto maxLen = GlobalSize (handle) / sizeof (wchar_t );
2778- const auto len = wcsnlen (str, maxLen);
2779- return winrt::hstring{ str, gsl::narrow_cast<uint32_t >(len) };
2780- }
2781-
2782- // We get CF_HDROP when a user copied a file with Ctrl+C in Explorer and pastes that into the terminal (among others).
2783- if (const auto handle = GetClipboardData (CF_HDROP))
2784- {
2785- const wil::unique_hglobal_locked lock{ handle };
2786- const auto drop = static_cast <HDROP>(lock.get ());
2787- if (!drop)
2788- {
2789- return {};
2790- }
2791-
2792- const auto cap = DragQueryFileW (drop, 0 , nullptr , 0 );
2793- if (cap == 0 )
2794- {
2795- return {};
2796- }
2797-
2798- auto buffer = winrt::impl::hstring_builder{ cap };
2799- const auto len = DragQueryFileW (drop, 0 , buffer.data (), cap + 1 );
2800- if (len == 0 )
2801- {
2802- return {};
2803- }
2804-
2805- return buffer.to_hstring ();
2806- }
2807-
2808- return {};
2809- }
2810-
28112855 // Function Description:
28122856 // - This function is called when the `TermControl` requests that we send
28132857 // it the clipboard's content.
@@ -2827,36 +2871,51 @@ namespace winrt::TerminalApp::implementation
28272871 const auto weakThis = get_weak ();
28282872 const auto dispatcher = Dispatcher ();
28292873 const auto globalSettings = _settings.GlobalSettings ();
2874+ const auto bracketedPaste = eventArgs.BracketedPasteEnabled ();
28302875
28312876 // GetClipboardData might block for up to 30s for delay-rendered contents.
28322877 co_await winrt::resume_background ();
28332878
28342879 winrt::hstring text;
2835- if (const auto clipboard = _openClipboard (nullptr ))
2880+ if (const auto clipboard = clipboard::open (nullptr ))
28362881 {
2837- text = _extractClipboard ();
2882+ text = clipboard::read ();
28382883 }
28392884
2840- if (globalSettings.TrimPaste ())
2885+ if (!bracketedPaste && globalSettings.TrimPaste ())
28412886 {
2842- text = { Utils::TrimPaste (text) };
2843- if (text.empty ())
2844- {
2845- // Text is all white space, nothing to paste
2846- co_return ;
2847- }
2887+ text = winrt::hstring{ Utils::TrimPaste (text) };
2888+ }
2889+
2890+ if (text.empty ())
2891+ {
2892+ co_return ;
2893+ }
2894+
2895+ bool warnMultiLine = false ;
2896+ switch (globalSettings.WarnAboutMultiLinePaste ())
2897+ {
2898+ case WarnAboutMultiLinePaste::Automatic:
2899+ // NOTE that this is unsafe, because a shell that doesn't support bracketed paste
2900+ // will allow an attacker to enable the mode, not realize that, and then accept
2901+ // the paste as if it was a series of legitimate commands. See GH#13014.
2902+ warnMultiLine = !bracketedPaste;
2903+ break ;
2904+ case WarnAboutMultiLinePaste::Always:
2905+ warnMultiLine = true ;
2906+ break ;
2907+ default :
2908+ warnMultiLine = false ;
2909+ break ;
28482910 }
28492911
2850- // If the requesting terminal is in bracketed paste mode, then we don't need to warn about a multi-line paste.
2851- auto warnMultiLine = globalSettings.WarnAboutMultiLinePaste () && !eventArgs.BracketedPasteEnabled ();
28522912 if (warnMultiLine)
28532913 {
2854- const auto isNewLineLambda = [](auto c) { return c == L' \n ' || c == L' \r ' ; };
2855- const auto hasNewLine = std::find_if (text.cbegin (), text.cend (), isNewLineLambda) != text.cend ();
2856- warnMultiLine = hasNewLine;
2914+ const std::wstring_view view{ text };
2915+ warnMultiLine = view.find_first_of (L" \r\n " ) != std::wstring_view::npos;
28572916 }
28582917
2859- constexpr const std::size_t minimumSizeForWarning = 1024 * 5 ; // 5 KiB
2918+ constexpr std::size_t minimumSizeForWarning = 1024 * 5 ; // 5 KiB
28602919 const auto warnLargeText = text.size () > minimumSizeForWarning && globalSettings.WarnAboutLargePaste ();
28612920
28622921 if (warnMultiLine || warnLargeText)
@@ -2866,7 +2925,7 @@ namespace winrt::TerminalApp::implementation
28662925 if (const auto strongThis = weakThis.get ())
28672926 {
28682927 // We have to initialize the dialog here to be able to change the text of the text block within it
2869- FindName (L" MultiLinePasteDialog" ). try_as <WUX::Controls::ContentDialog>( );
2928+ std::ignore = FindName (L" MultiLinePasteDialog" );
28702929 ClipboardText ().Text (text);
28712930
28722931 // The vertical offset on the scrollbar does not reset automatically, so reset it manually
@@ -3047,7 +3106,7 @@ namespace winrt::TerminalApp::implementation
30473106 // - formats: dictate which formats need to be copied
30483107 // Return Value:
30493108 // - true iff we we able to copy text (if a selection was active)
3050- bool TerminalPage::_CopyText (const bool dismissSelection, const bool singleLine, const bool withControlSequences, const Windows::Foundation::IReference< CopyFormat>& formats)
3109+ bool TerminalPage::_CopyText (const bool dismissSelection, const bool singleLine, const bool withControlSequences, const CopyFormat formats)
30513110 {
30523111 if (const auto & control{ _GetActiveControl () })
30533112 {
@@ -3190,6 +3249,21 @@ namespace winrt::TerminalApp::implementation
31903249 }
31913250 }
31923251
3252+ void TerminalPage::_copyToClipboard (const IInspectable, const WriteToClipboardEventArgs args) const
3253+ {
3254+ if (const auto clipboard = clipboard::open (_hostingHwnd.value_or (nullptr )))
3255+ {
3256+ const auto plain = args.Plain ();
3257+ const auto html = args.Html ();
3258+ const auto rtf = args.Rtf ();
3259+
3260+ clipboard::write (
3261+ { plain.data (), plain.size () },
3262+ { reinterpret_cast <const char *>(html.data ()), html.size () },
3263+ { reinterpret_cast <const char *>(rtf.data ()), rtf.size () });
3264+ }
3265+ }
3266+
31933267 // Method Description:
31943268 // - Paste text from the Windows Clipboard to the focused terminal
31953269 void TerminalPage::_PasteText ()
0 commit comments