mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 18:43:54 -06:00
Restore support for pasting files (#16634)
TIL: You could Ctrl+V files into Windows Terminal and here I am, always opening the context menu and selecting "Copy as path"... smh This restores the support by adding a very rudimentary HDROP handler. The flip side of the regression is that I learned about this and so conhost also gets this now, because why not! Closes #16627 ## Validation Steps Performed * Single files can be pasted in WT and conhost ✅
This commit is contained in:
parent
c669afe2a0
commit
ef96e225da
@ -2628,6 +2628,75 @@ namespace winrt::TerminalApp::implementation
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
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.
|
||||
@ -2647,53 +2716,14 @@ namespace winrt::TerminalApp::implementation
|
||||
const auto weakThis = get_weak();
|
||||
const auto dispatcher = Dispatcher();
|
||||
const auto globalSettings = _settings.GlobalSettings();
|
||||
winrt::hstring text;
|
||||
|
||||
// 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))
|
||||
{
|
||||
// According to various reports on the internet, OpenClipboard might
|
||||
// fail to acquire the internal lock, for instance due to rdpclip.exe.
|
||||
for (int attempts = 1;;)
|
||||
{
|
||||
if (OpenClipboard(nullptr))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (attempts > 5)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
attempts++;
|
||||
Sleep(10 * attempts);
|
||||
}
|
||||
|
||||
const auto clipboardCleanup = wil::scope_exit([]() {
|
||||
CloseClipboard();
|
||||
});
|
||||
|
||||
const auto data = GetClipboardData(CF_UNICODETEXT);
|
||||
if (!data)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
const auto str = static_cast<const wchar_t*>(GlobalLock(data));
|
||||
if (!str)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
const auto dataCleanup = wil::scope_exit([&]() {
|
||||
GlobalUnlock(data);
|
||||
});
|
||||
|
||||
const auto maxLength = GlobalSize(data) / sizeof(wchar_t);
|
||||
const auto length = wcsnlen(str, maxLength);
|
||||
text = winrt::hstring{ str, gsl::narrow_cast<uint32_t>(length) };
|
||||
text = _extractClipboard();
|
||||
}
|
||||
|
||||
if (globalSettings.TrimPaste())
|
||||
|
||||
@ -59,29 +59,73 @@ void Clipboard::Paste()
|
||||
return;
|
||||
}
|
||||
|
||||
const auto handle = GetClipboardData(CF_UNICODETEXT);
|
||||
if (!handle)
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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.
|
||||
// --> Use wcsnlen() to determine the actual length.
|
||||
// NOTE: Some applications don't add a trailing null character. This includes past conhost versions.
|
||||
const auto maxLen = GlobalSize(handle) / sizeof(wchar_t);
|
||||
StringPaste(str, wcsnlen(str, maxLen));
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
PasteDrop(drop);
|
||||
}
|
||||
}
|
||||
|
||||
void Clipboard::PasteDrop(HDROP drop)
|
||||
{
|
||||
// NOTE: When asking DragQueryFileW for the required capacity it returns a length without trailing \0,
|
||||
// but then expects a capacity that includes it. If you don't make space for a trailing \0
|
||||
// then it will silently (!) cut off the end of the string. A somewhat disappointing API design.
|
||||
const auto expectedLength = DragQueryFileW(drop, 0, nullptr, 0);
|
||||
if (expectedLength == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear any selection or scrolling that may be active.
|
||||
Selection::Instance().ClearSelection();
|
||||
Scrolling::s_ClearScroll();
|
||||
// If the path contains spaces, we'll wrap it in quotes and so this allocates +2 characters ahead of time.
|
||||
// We'll first make DragQueryFileW copy its contents in the middle and then check if that contains spaces.
|
||||
// If it does, only then we'll add the quotes at the start and end.
|
||||
// This is preferable over calling StringPaste 3x (an alternative, simpler approach),
|
||||
// because the pasted content should be treated as a single atomic unit by the InputBuffer.
|
||||
const auto buffer = std::make_unique_for_overwrite<wchar_t[]>(expectedLength + 2);
|
||||
auto str = buffer.get() + 1;
|
||||
size_t len = expectedLength;
|
||||
|
||||
const wil::unique_hglobal_locked lock{ handle };
|
||||
const auto str = static_cast<const wchar_t*>(lock.get());
|
||||
if (!str)
|
||||
const auto actualLength = DragQueryFileW(drop, 0, str, expectedLength + 1);
|
||||
if (actualLength != expectedLength)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.
|
||||
// --> Use wcsnlen() to determine the actual length.
|
||||
// NOTE: Some applications don't add a trailing null character. This includes past conhost versions.
|
||||
const auto maxLen = GlobalSize(handle) / sizeof(WCHAR);
|
||||
StringPaste(str, wcsnlen(str, maxLen));
|
||||
if (wmemchr(str, L' ', len))
|
||||
{
|
||||
str = buffer.get();
|
||||
len += 2;
|
||||
til::at(str, 0) = L'"';
|
||||
til::at(str, len - 1) = L'"';
|
||||
}
|
||||
|
||||
StringPaste(str, len);
|
||||
}
|
||||
|
||||
Clipboard& Clipboard::Instance()
|
||||
@ -109,6 +153,10 @@ void Clipboard::StringPaste(_In_reads_(cchData) const wchar_t* const pData,
|
||||
|
||||
try
|
||||
{
|
||||
// Clear any selection or scrolling that may be active.
|
||||
Selection::Instance().ClearSelection();
|
||||
Scrolling::s_ClearScroll();
|
||||
|
||||
const auto vtInputMode = gci.pInputBuffer->IsInVirtualTerminalInputMode();
|
||||
const auto bracketedPasteMode = gci.GetBracketedPasteMode();
|
||||
auto inEvents = TextToKeyEvents(pData, cchData, vtInputMode && bracketedPasteMode);
|
||||
|
||||
@ -30,15 +30,15 @@ namespace Microsoft::Console::Interactivity::Win32
|
||||
static Clipboard& Instance();
|
||||
|
||||
void Copy(_In_ const bool fAlsoCopyFormatting = false);
|
||||
void StringPaste(_In_reads_(cchData) PCWCHAR pwchData,
|
||||
const size_t cchData);
|
||||
void Paste();
|
||||
void PasteDrop(HDROP drop);
|
||||
|
||||
private:
|
||||
static wil::unique_close_clipboard_call _openClipboard(HWND hwnd);
|
||||
static void _copyToClipboard(UINT format, const void* src, size_t bytes);
|
||||
static void _copyToClipboardRegisteredFormat(const wchar_t* format, const void* src, size_t bytes);
|
||||
|
||||
void StringPaste(_In_reads_(cchData) PCWCHAR pwchData, const size_t cchData);
|
||||
InputEventQueue TextToKeyEvents(_In_reads_(cchData) const wchar_t* const pData,
|
||||
const size_t cchData,
|
||||
const bool bracketedPaste = false);
|
||||
|
||||
@ -857,24 +857,7 @@ void Window::_HandleWindowPosChanged(const LPARAM lParam)
|
||||
// - <none>
|
||||
void Window::_HandleDrop(const WPARAM wParam) const
|
||||
{
|
||||
WCHAR szPath[MAX_PATH];
|
||||
BOOL fAddQuotes;
|
||||
|
||||
if (DragQueryFile((HDROP)wParam, 0, szPath, ARRAYSIZE(szPath)) != 0)
|
||||
{
|
||||
fAddQuotes = (wcschr(szPath, L' ') != nullptr);
|
||||
if (fAddQuotes)
|
||||
{
|
||||
Clipboard::Instance().StringPaste(L"\"", 1);
|
||||
}
|
||||
|
||||
Clipboard::Instance().StringPaste(szPath, wcslen(szPath));
|
||||
|
||||
if (fAddQuotes)
|
||||
{
|
||||
Clipboard::Instance().StringPaste(L"\"", 1);
|
||||
}
|
||||
}
|
||||
Clipboard::Instance().PasteDrop((HDROP)wParam);
|
||||
}
|
||||
|
||||
[[nodiscard]] LRESULT Window::_HandleGetObject(const HWND hwnd, const WPARAM wParam, const LPARAM lParam)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user