From 85f067403deea04aa48a8bca87057ccbac1fc372 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Fri, 1 Oct 2021 13:33:22 -0500 Subject: [PATCH] Replace the UWP file export with the shell32 one (#11365) Just like in #9760, we can't actually use the UWP file picker API, because it will absolutely not work at all when the Terminal is running elevated. That would prevent the picker from appearing at all. So instead, we'll just use the shell32 one manually. This also gets rid of the confirmation dialog, since the team felt we didn't really need that. We could maybe replace it with a Toast (#8592), but _meh_ * [x] closes #11356 * [x] closes #11358 * This is a lot like #9760 * introduced in #11062 * megathread: #9700 --- src/cascadia/TerminalApp/TabManagement.cpp | 55 ++++++++++++------- .../TerminalSettingsEditor/Appearances.cpp | 3 +- .../TerminalSettingsEditor/Profiles.cpp | 3 +- src/cascadia/TerminalSettingsEditor/Utils.cpp | 24 -------- src/cascadia/TerminalSettingsEditor/Utils.h | 36 ------------ .../CascadiaSettings.cpp | 11 ++++ .../TerminalSettingsModel/CascadiaSettings.h | 1 + .../CascadiaSettings.idl | 2 + src/cascadia/WinRTUtils/Utils.cpp | 33 +++++++++++ src/cascadia/WinRTUtils/WinRTUtils.vcxproj | 1 + src/cascadia/WinRTUtils/inc/Utils.h | 48 ++++++++++++++++ src/cascadia/WinRTUtils/pch.h | 5 +- 12 files changed, 139 insertions(+), 83 deletions(-) create mode 100644 src/cascadia/WinRTUtils/Utils.cpp diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index a339b2bfe..b99af3236 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -19,6 +19,9 @@ #include "ColorHelper.h" #include "DebugTapConnection.h" #include "SettingsTab.h" +#include "..\TerminalSettingsModel\FileUtils.h" + +#include using namespace winrt; using namespace winrt::Windows::Foundation::Collections; @@ -410,33 +413,45 @@ namespace winrt::TerminalApp::implementation // - tab: tab to export winrt::fire_and_forget TerminalPage::_ExportTab(const TerminalTab& tab) { + // This will be used to set up the file picker "filter", to select .txt + // files by default. + static constexpr COMDLG_FILTERSPEC supportedFileTypes[] = { + { L"Text Files (*.txt)", L"*.txt" }, + { L"All Files (*.*)", L"*.*" } + }; + // An arbitrary GUID to associate with all instances of this + // dialog, so they all re-open in the same path as they were + // open before: + static constexpr winrt::guid clientGuidExportFile{ 0xF6AF20BB, 0x0800, 0x48E6, { 0xB0, 0x17, 0xA1, 0x4C, 0xD8, 0x73, 0xDD, 0x58 } }; + try { if (const auto control{ tab.GetActiveTerminalControl() }) { - const FileSavePicker savePicker; - savePicker.as()->Initialize(*_hostingHwnd); - savePicker.SuggestedStartLocation(PickerLocationId::Downloads); - const auto fileChoices = single_threaded_vector({ L".txt" }); - savePicker.FileTypeChoices().Insert(RS_(L"PlainText"), fileChoices); - savePicker.SuggestedFileName(control.Title()); + // GH#11356 - we can't use the UWP apis for writing the file, + // because they don't work elevated (shocker) So just use the + // shell32 file picker manually. + auto path = co_await SaveFilePicker(*_hostingHwnd, [control](auto&& dialog) { + THROW_IF_FAILED(dialog->SetClientGuid(clientGuidExportFile)); + try + { + // Default to the Downloads folder + auto folderShellItem{ winrt::capture(&SHGetKnownFolderItem, FOLDERID_Downloads, KF_FLAG_DEFAULT, nullptr) }; + dialog->SetDefaultFolder(folderShellItem.get()); + } + CATCH_LOG(); // non-fatal + THROW_IF_FAILED(dialog->SetFileTypes(ARRAYSIZE(supportedFileTypes), supportedFileTypes)); + THROW_IF_FAILED(dialog->SetFileTypeIndex(1)); // the array is 1-indexed + THROW_IF_FAILED(dialog->SetDefaultExtension(L"txt")); - const StorageFile file = co_await savePicker.PickSaveFileAsync(); - if (file != nullptr) + // Default to using the tab title as the file name + THROW_IF_FAILED(dialog->SetFileName((control.Title() + L".txt").c_str())); + }); + + if (!path.empty()) { const auto buffer = control.ReadEntireBuffer(); - CachedFileManager::DeferUpdates(file); - co_await FileIO::WriteTextAsync(file, buffer); - const auto status = co_await CachedFileManager::CompleteUpdatesAsync(file); - switch (status) - { - case FileUpdateStatus::Complete: - case FileUpdateStatus::CompleteAndRenamed: - _ShowControlNoticeDialog(RS_(L"NoticeInfo"), RS_(L"ExportSuccess")); - break; - default: - _ShowControlNoticeDialog(RS_(L"NoticeError"), RS_(L"ExportFailure")); - } + CascadiaSettings::ExportFile(path, buffer); } } } diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.cpp b/src/cascadia/TerminalSettingsEditor/Appearances.cpp index db7f1c16b..0eb24bb05 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.cpp +++ b/src/cascadia/TerminalSettingsEditor/Appearances.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "pch.h" @@ -7,6 +7,7 @@ #include "EnumEntry.h" #include +#include "..\WinRTUtils\inc\Utils.h" using namespace winrt::Windows::UI::Text; using namespace winrt::Windows::UI::Xaml; diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.cpp b/src/cascadia/TerminalSettingsEditor/Profiles.cpp index 94b49c2fa..8e43f9b10 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles.cpp +++ b/src/cascadia/TerminalSettingsEditor/Profiles.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "pch.h" @@ -8,6 +8,7 @@ #include "EnumEntry.h" #include +#include "..\WinRTUtils\inc\Utils.h" using namespace winrt::Windows::UI::Text; using namespace winrt::Windows::UI::Xaml; diff --git a/src/cascadia/TerminalSettingsEditor/Utils.cpp b/src/cascadia/TerminalSettingsEditor/Utils.cpp index 9fbca2258..d91953185 100644 --- a/src/cascadia/TerminalSettingsEditor/Utils.cpp +++ b/src/cascadia/TerminalSettingsEditor/Utils.cpp @@ -15,30 +15,6 @@ using namespace winrt::Windows::UI::Xaml; UTILS_DEFINE_LIBRARY_RESOURCE_SCOPE(L"Microsoft.Terminal.Settings.Editor/Resources"); -// Function Description: -// - Helper that opens a file picker pre-seeded with image file types. -winrt::Windows::Foundation::IAsyncOperation OpenImagePicker(HWND parentHwnd) -{ - static constexpr COMDLG_FILTERSPEC supportedImageFileTypes[] = { - { L"All Supported Bitmap Types (*.jpg, *.jpeg, *.png, *.bmp, *.gif, *.tiff, *.ico)", L"*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.tiff;*.ico" }, - { L"All Files (*.*)", L"*.*" } - }; - - static constexpr winrt::guid clientGuidImagePicker{ 0x55675F54, 0x74A1, 0x4552, { 0xA3, 0x9D, 0x94, 0xAE, 0x85, 0xD8, 0xF2, 0x7A } }; - return OpenFilePicker(parentHwnd, [](auto&& dialog) { - THROW_IF_FAILED(dialog->SetClientGuid(clientGuidImagePicker)); - try - { - auto pictureFolderShellItem{ winrt::capture(&SHGetKnownFolderItem, FOLDERID_PicturesLibrary, KF_FLAG_DEFAULT, nullptr) }; - dialog->SetDefaultFolder(pictureFolderShellItem.get()); - } - CATCH_LOG(); // non-fatal - THROW_IF_FAILED(dialog->SetFileTypes(ARRAYSIZE(supportedImageFileTypes), supportedImageFileTypes)); - THROW_IF_FAILED(dialog->SetFileTypeIndex(1)); // the array is 1-indexed - THROW_IF_FAILED(dialog->SetDefaultExtension(L"jpg;jpeg;png;bmp;gif;tiff;ico")); - }); -} - namespace winrt::Microsoft::Terminal::Settings { hstring GetSelectedItemTag(winrt::Windows::Foundation::IInspectable const& comboBoxAsInspectable) diff --git a/src/cascadia/TerminalSettingsEditor/Utils.h b/src/cascadia/TerminalSettingsEditor/Utils.h index d0eddf025..ec73f41c7 100644 --- a/src/cascadia/TerminalSettingsEditor/Utils.h +++ b/src/cascadia/TerminalSettingsEditor/Utils.h @@ -97,42 +97,6 @@ public: \ private: \ static winrt::Windows::UI::Xaml::DependencyProperty _##name##Property; -// Function Description: -// - This function presents a File Open "common dialog" and returns its selected file asynchronously. -// Parameters: -// - customize: A lambda that receives an IFileDialog* to customize. -// Return value: -// (async) path to the selected item. -template -winrt::Windows::Foundation::IAsyncOperation OpenFilePicker(HWND parentHwnd, TLambda&& customize) -{ - auto fileDialog{ winrt::create_instance(CLSID_FileOpenDialog) }; - DWORD flags{}; - THROW_IF_FAILED(fileDialog->GetOptions(&flags)); - THROW_IF_FAILED(fileDialog->SetOptions(flags | FOS_FORCEFILESYSTEM | FOS_NOCHANGEDIR | FOS_DONTADDTORECENT)); // filesystem objects only; no recent places - customize(fileDialog.get()); - - auto hr{ fileDialog->Show(parentHwnd) }; - if (!SUCCEEDED(hr)) - { - if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) - { - co_return winrt::hstring{}; - } - THROW_HR(hr); - } - - winrt::com_ptr result; - THROW_IF_FAILED(fileDialog->GetResult(result.put())); - - wil::unique_cotaskmem_string filePath; - THROW_IF_FAILED(result->GetDisplayName(SIGDN_FILESYSPATH, &filePath)); - - co_return winrt::hstring{ filePath.get() }; -} - -winrt::Windows::Foundation::IAsyncOperation OpenImagePicker(HWND parentHwnd); - namespace winrt::Microsoft::Terminal::Settings { winrt::hstring GetSelectedItemTag(winrt::Windows::Foundation::IInspectable const& comboBoxAsInspectable); diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index d49ff4968..9ee9fc6b1 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -5,6 +5,8 @@ #include "CascadiaSettings.h" #include "CascadiaSettings.g.cpp" +#include "FileUtils.h" + #include #include @@ -880,3 +882,12 @@ void CascadiaSettings::CurrentDefaultTerminal(const Model::DefaultTerminal& term { _currentDefaultTerminal = terminal; } + +void CascadiaSettings::ExportFile(winrt::hstring path, winrt::hstring content) +{ + try + { + winrt::Microsoft::Terminal::Settings::Model::WriteUTF8FileAtomic({ path.c_str() }, til::u16u8(content)); + } + CATCH_LOG(); +} diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index 6ee9d4f37..39ae689e4 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -89,6 +89,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static winrt::hstring DefaultSettingsPath(); static winrt::hstring ApplicationDisplayName(); static winrt::hstring ApplicationVersion(); + static void ExportFile(winrt::hstring path, winrt::hstring content); CascadiaSettings() noexcept = default; CascadiaSettings(const winrt::hstring& userJSON, const winrt::hstring& inboxJSON); diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl index 092401ebc..e879474b2 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl @@ -18,6 +18,8 @@ namespace Microsoft.Terminal.Settings.Model static String ApplicationDisplayName { get; }; static String ApplicationVersion { get; }; + + static void ExportFile(String path, String content); CascadiaSettings(String userJSON, String inboxJSON); diff --git a/src/cascadia/WinRTUtils/Utils.cpp b/src/cascadia/WinRTUtils/Utils.cpp new file mode 100644 index 000000000..4c15fd332 --- /dev/null +++ b/src/cascadia/WinRTUtils/Utils.cpp @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "pch.h" +#include "Utils.h" + +// Function Description: +// - Helper that opens a file picker pre-seeded with image file types. +winrt::Windows::Foundation::IAsyncOperation OpenImagePicker(HWND parentHwnd) +{ + static constexpr COMDLG_FILTERSPEC supportedImageFileTypes[] = { + { L"All Supported Bitmap Types (*.jpg, *.jpeg, *.png, *.bmp, *.gif, *.tiff, *.ico)", L"*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.tiff;*.ico" }, + { L"All Files (*.*)", L"*.*" } + }; + + static constexpr winrt::guid clientGuidImagePicker{ 0x55675F54, 0x74A1, 0x4552, { 0xA3, 0x9D, 0x94, 0xAE, 0x85, 0xD8, 0xF2, 0x7A } }; + return OpenFilePicker(parentHwnd, [](auto&& dialog) { + THROW_IF_FAILED(dialog->SetClientGuid(clientGuidImagePicker)); + try + { + auto pictureFolderShellItem{ winrt::capture(&SHGetKnownFolderItem, FOLDERID_PicturesLibrary, KF_FLAG_DEFAULT, nullptr) }; + dialog->SetDefaultFolder(pictureFolderShellItem.get()); + } + CATCH_LOG(); // non-fatal + +#pragma warning(suppress : 26485) // so we can pass in the supportedImageFileTypes without the analyzer complaining + THROW_IF_FAILED(dialog->SetFileTypes(ARRAYSIZE(supportedImageFileTypes), supportedImageFileTypes)); + THROW_IF_FAILED(dialog->SetFileTypeIndex(1)); // the array is 1-indexed + THROW_IF_FAILED(dialog->SetDefaultExtension(L"jpg;jpeg;png;bmp;gif;tiff;ico")); + }); +} diff --git a/src/cascadia/WinRTUtils/WinRTUtils.vcxproj b/src/cascadia/WinRTUtils/WinRTUtils.vcxproj index 2bd60e143..0c6d74a8a 100644 --- a/src/cascadia/WinRTUtils/WinRTUtils.vcxproj +++ b/src/cascadia/WinRTUtils/WinRTUtils.vcxproj @@ -28,6 +28,7 @@ + diff --git a/src/cascadia/WinRTUtils/inc/Utils.h b/src/cascadia/WinRTUtils/inc/Utils.h index 07b9b4fe8..6c275b1e2 100644 --- a/src/cascadia/WinRTUtils/inc/Utils.h +++ b/src/cascadia/WinRTUtils/inc/Utils.h @@ -19,3 +19,51 @@ inline winrt::Windows::Foundation::Rect ScaleRect(winrt::Windows::Foundation::Re rect.Height = base::ClampMul(rect.Height, scaleLocal); return rect; } + +// Function Description: +// - This function presents a File Open "common dialog" and returns its selected file asynchronously. +// Parameters: +// - customize: A lambda that receives an IFileDialog* to customize. +// Return value: +// (async) path to the selected item. +template +winrt::Windows::Foundation::IAsyncOperation FilePicker(HWND parentHwnd, bool saveDialog, TLambda&& customize) +{ + auto fileDialog{ saveDialog ? winrt::create_instance(CLSID_FileSaveDialog) : + winrt::create_instance(CLSID_FileOpenDialog) }; + DWORD flags{}; + THROW_IF_FAILED(fileDialog->GetOptions(&flags)); + THROW_IF_FAILED(fileDialog->SetOptions(flags | FOS_FORCEFILESYSTEM | FOS_NOCHANGEDIR | FOS_DONTADDTORECENT)); // filesystem objects only; no recent places + customize(fileDialog.get()); + + const auto hr{ fileDialog->Show(parentHwnd) }; + if (!SUCCEEDED(hr)) + { + if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) + { + co_return winrt::hstring{}; + } + THROW_HR(hr); + } + + winrt::com_ptr result; + THROW_IF_FAILED(fileDialog->GetResult(result.put())); + + wil::unique_cotaskmem_string filePath; + THROW_IF_FAILED(result->GetDisplayName(SIGDN_FILESYSPATH, &filePath)); + + co_return winrt::hstring{ filePath.get() }; +} + +template +winrt::Windows::Foundation::IAsyncOperation OpenFilePicker(HWND parentHwnd, TLambda&& customize) +{ + return FilePicker(parentHwnd, false, customize); +} +template +winrt::Windows::Foundation::IAsyncOperation SaveFilePicker(HWND parentHwnd, TLambda&& customize) +{ + return FilePicker(parentHwnd, true, customize); +} + +winrt::Windows::Foundation::IAsyncOperation OpenImagePicker(HWND parentHwnd); diff --git a/src/cascadia/WinRTUtils/pch.h b/src/cascadia/WinRTUtils/pch.h index c793bb636..eab7083a0 100644 --- a/src/cascadia/WinRTUtils/pch.h +++ b/src/cascadia/WinRTUtils/pch.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. // // pch.h @@ -31,3 +31,6 @@ #include #include #include + +#include +#include