@@ -57,6 +57,121 @@ namespace winrt
57
57
using VirtualKeyModifiers = Windows::System::VirtualKeyModifiers;
58
58
}
59
59
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
+
60
175
namespace winrt ::TerminalApp::implementation
61
176
{
62
177
TerminalPage::TerminalPage (TerminalApp::WindowProperties properties, const TerminalApp::ContentManager& manager) :
@@ -1708,10 +1823,9 @@ namespace winrt::TerminalApp::implementation
1708
1823
});
1709
1824
1710
1825
term.ShowWindowChanged ({ get_weak (), &TerminalPage::_ShowWindowChangedHandler });
1711
-
1712
1826
term.SearchMissingCommand ({ get_weak (), &TerminalPage::_SearchMissingCommandHandler });
1713
-
1714
1827
term.WindowSizeChanged ({ get_weak (), &TerminalPage::_WindowSizeChanged });
1828
+ term.WriteToClipboard ({ get_weak (), &TerminalPage::_copyToClipboard });
1715
1829
1716
1830
// Don't even register for the event if the feature is compiled off.
1717
1831
if constexpr (Feature_ShellCompletions::IsEnabled ())
@@ -2643,75 +2757,6 @@ namespace winrt::TerminalApp::implementation
2643
2757
return dimension;
2644
2758
}
2645
2759
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
-
2715
2760
// Function Description:
2716
2761
// - This function is called when the `TermControl` requests that we send
2717
2762
// it the clipboard's content.
@@ -2731,24 +2776,40 @@ namespace winrt::TerminalApp::implementation
2731
2776
const auto weakThis = get_weak ();
2732
2777
const auto dispatcher = Dispatcher ();
2733
2778
const auto globalSettings = _settings.GlobalSettings ();
2779
+ const auto bracketedPaste = eventArgs.BracketedPasteEnabled ();
2734
2780
2735
2781
// GetClipboardData might block for up to 30s for delay-rendered contents.
2736
2782
co_await winrt::resume_background ();
2737
2783
2738
2784
winrt::hstring text;
2739
- if (const auto clipboard = _openClipboard (nullptr ))
2785
+ uint32_t clipboardSequenceNumber = 0 ;
2786
+
2787
+ if (const auto clipboard = clipboard::open (nullptr ))
2740
2788
{
2741
- text = _extractClipboard ();
2789
+ text = clipboard::read ();
2790
+ clipboardSequenceNumber = GetClipboardSequenceNumber ();
2791
+ }
2792
+ else
2793
+ {
2794
+ co_return ;
2742
2795
}
2743
2796
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))
2745
2802
{
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 ;
2752
2813
}
2753
2814
2754
2815
// 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
2951
3012
// - formats: dictate which formats need to be copied
2952
3013
// Return Value:
2953
3014
// - 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)
2955
3016
{
2956
3017
if (const auto & control{ _GetActiveControl () })
2957
3018
{
@@ -3094,6 +3155,26 @@ namespace winrt::TerminalApp::implementation
3094
3155
}
3095
3156
}
3096
3157
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
+
3097
3178
// Method Description:
3098
3179
// - Paste text from the Windows Clipboard to the focused terminal
3099
3180
void TerminalPage::_PasteText ()
0 commit comments