Replace Windows.Storage.Pickers with Common File Dialogs (#9760)

Using Pickers from an elevated application yields an
ERROR_ACCESS_DENIED. Of course it does: it was designed for the modern
app platform.

Using the common dialog infrastructure has some downsides¹, but it
doesn't crash and is just as flexible.

I've added some fun templated functions that help us with the
complexity.

Fixes #8957

¹You've got to use raw COM, and it runs in-proc instead of out-of-proc.

## Validation Steps Performed
I tested every picker.
This commit is contained in:
Dustin L. Howett 2021-04-12 06:12:08 -07:00 committed by GitHub
parent b310b1cffc
commit 959c423e7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 118 additions and 49 deletions

View file

@ -6,15 +6,19 @@ bitfields
CLASSNOTAVAILABLE
cmdletbinding
COLORPROPERTY
COMDLG
CXICON
CYICON
D2DERR_SHADER_COMPILE_FAILED
dataobject
DERR
dlldata
DONTADDTORECENT
environstrings
EXPCMDFLAGS
EXPCMDSTATE
FILTERSPEC
FORCEFILESYSTEM
FORCEMINIMIZE
frac
fullkbd
@ -32,12 +36,13 @@ IAsync
IBind
IBox
IClass
IConnection
IComparable
IConnection
ICustom
IDialog
IDirect
IExplorer
IFile
IInheritable
IMap
IObject
@ -63,6 +68,7 @@ NCLBUTTONDBLCLK
NCRBUTTONDBLCLK
NOAGGREGATION
NOASYNC
NOCHANGEDIR
NOPROGRESS
NOREDIRECTIONBITMAP
ntprivapi
@ -72,10 +78,11 @@ otms
OUTLINETEXTMETRICW
overridable
PAGESCROLL
PICKFOLDERS
pmr
REGCLS
RETURNCMD
REGCLS
RETURNCMD
rfind
roundf
RSHIFT

View file

@ -2397,6 +2397,7 @@ titlebar
TITLEISLINKNAME
TJson
tl
TLambda
TLEN
Tlg
Tlgdata

View file

@ -300,6 +300,12 @@
</Reference>
</ItemGroup>
<ItemDefinitionGroup>
<Link>
<AdditionalDependencies>shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">

View file

@ -15,9 +15,6 @@ using namespace winrt::Windows::UI::Xaml::Data;
using namespace winrt::Windows::UI::Xaml::Navigation;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Foundation::Collections;
using namespace winrt::Windows::Storage;
using namespace winrt::Windows::Storage::AccessCache;
using namespace winrt::Windows::Storage::Pickers;
using namespace winrt::Microsoft::Terminal::Settings::Model;
static const std::array<winrt::guid, 2> InBoxProfileGuids{
@ -25,6 +22,64 @@ static const std::array<winrt::guid, 2> InBoxProfileGuids{
winrt::guid{ 0x0caa0dad, 0x35be, 0x5f56, { 0xa8, 0xff, 0xaf, 0xce, 0xee, 0xaa, 0x61, 0x01 } } // Command Prompt
};
// 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<typename TLambda>
static winrt::Windows::Foundation::IAsyncOperation<winrt::hstring> OpenFilePicker(TLambda&& customize)
{
auto fileDialog{ winrt::create_instance<IFileDialog>(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(NULL) };
if (!SUCCEEDED(hr))
{
if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED))
{
co_return winrt::hstring{};
}
THROW_HR(hr);
}
winrt::com_ptr<IShellItem> 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() };
}
// Function Description:
// - Helper that opens a file picker pre-seeded with image file types.
static winrt::Windows::Foundation::IAsyncOperation<winrt::hstring> OpenImagePicker()
{
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([](auto&& dialog) {
THROW_IF_FAILED(dialog->SetClientGuid(clientGuidImagePicker));
try
{
auto pictureFolderShellItem{ winrt::capture<IShellItem>(&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::Editor::implementation
{
Windows::Foundation::Collections::IObservableVector<Editor::Font> ProfileViewModel::_MonospaceFontList{ nullptr };
@ -582,20 +637,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
auto lifetime = get_strong();
FileOpenPicker picker;
_State.WindowRoot().TryPropagateHostingWindow(picker); // if we don't do this, there's no HWND for it to attach to
picker.ViewMode(PickerViewMode::Thumbnail);
picker.SuggestedStartLocation(PickerLocationId::PicturesLibrary);
// Converted into a BitmapImage. This list of supported image file formats is from BitmapImage documentation
// https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Xaml.Media.Imaging.BitmapImage?view=winrt-19041#remarks
picker.FileTypeFilter().ReplaceAll({ L".jpg", L".jpeg", L".png", L".bmp", L".gif", L".tiff", L".ico" });
StorageFile file = co_await picker.PickSingleFileAsync();
if (file != nullptr)
auto file = co_await OpenImagePicker();
if (!file.empty())
{
_State.Profile().BackgroundImagePath(file.Path());
_State.Profile().BackgroundImagePath(file);
}
}
@ -603,20 +648,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
auto lifetime = get_strong();
FileOpenPicker picker;
_State.WindowRoot().TryPropagateHostingWindow(picker); // if we don't do this, there's no HWND for it to attach to
picker.ViewMode(PickerViewMode::Thumbnail);
picker.SuggestedStartLocation(PickerLocationId::PicturesLibrary);
// Converted into a BitmapIconSource. This list of supported image file formats is from BitmapImage documentation
// https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Xaml.Media.Imaging.BitmapImage?view=winrt-19041#remarks
picker.FileTypeFilter().ReplaceAll({ L".jpg", L".jpeg", L".png", L".bmp", L".gif", L".tiff", L".ico" });
StorageFile file = co_await picker.PickSingleFileAsync();
if (file != nullptr)
auto file = co_await OpenImagePicker();
if (!file.empty())
{
_State.Profile().Icon(file.Path());
_State.Profile().Icon(file);
}
}
@ -624,32 +659,52 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
auto lifetime = get_strong();
FileOpenPicker picker;
static constexpr COMDLG_FILTERSPEC supportedFileTypes[] = {
{ L"Executable Files (*.exe, *.cmd, *.bat)", L"*.exe;*.cmd;*.bat" },
{ L"All Files (*.*)", L"*.*" }
};
_State.WindowRoot().TryPropagateHostingWindow(picker); // if we don't do this, there's no HWND for it to attach to
picker.ViewMode(PickerViewMode::Thumbnail);
picker.SuggestedStartLocation(PickerLocationId::ComputerFolder);
picker.FileTypeFilter().ReplaceAll({ L".bat", L".exe", L".cmd" });
static constexpr winrt::guid clientGuidExecutables{ 0x2E7E4331, 0x0800, 0x48E6, { 0xB0, 0x17, 0xA1, 0x4C, 0xD8, 0x73, 0xDD, 0x58 } };
auto path = co_await OpenFilePicker([](auto&& dialog) {
THROW_IF_FAILED(dialog->SetClientGuid(clientGuidExecutables));
try
{
auto folderShellItem{ winrt::capture<IShellItem>(&SHGetKnownFolderItem, FOLDERID_ComputerFolder, 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"exe;cmd;bat"));
});
StorageFile file = co_await picker.PickSingleFileAsync();
if (file != nullptr)
if (!path.empty())
{
_State.Profile().Commandline(file.Path());
_State.Profile().Commandline(path);
}
}
fire_and_forget Profiles::StartingDirectory_Click(IInspectable const&, RoutedEventArgs const&)
{
auto lifetime = get_strong();
FolderPicker picker;
_State.WindowRoot().TryPropagateHostingWindow(picker); // if we don't do this, there's no HWND for it to attach to
picker.SuggestedStartLocation(PickerLocationId::DocumentsLibrary);
picker.FileTypeFilter().ReplaceAll({ L"*" });
StorageFolder folder = co_await picker.PickSingleFolderAsync();
if (folder != nullptr)
auto folder = co_await OpenFilePicker([](auto&& dialog) {
static constexpr winrt::guid clientGuidFolderPicker{ 0xAADAA433, 0xB04D, 0x4BAE, { 0xB1, 0xEA, 0x1E, 0x6C, 0xD1, 0xCD, 0xA6, 0x8B } };
THROW_IF_FAILED(dialog->SetClientGuid(clientGuidFolderPicker));
try
{
auto folderShellItem{ winrt::capture<IShellItem>(&SHGetKnownFolderItem, FOLDERID_ComputerFolder, KF_FLAG_DEFAULT, nullptr) };
dialog->SetDefaultFolder(folderShellItem.get());
}
CATCH_LOG(); // non-fatal
DWORD flags{};
THROW_IF_FAILED(dialog->GetOptions(&flags));
THROW_IF_FAILED(dialog->SetOptions(flags | FOS_PICKFOLDERS)); // folders only
});
if (!folder.empty())
{
StorageApplicationPermissions::FutureAccessList().AddOrReplace(L"PickedFolderToken", folder);
_State.Profile().StartingDirectory(folder.Path());
_State.Profile().StartingDirectory(folder);
}
}

View file

@ -29,7 +29,6 @@
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.Storage.AccessCache.h>
#include <winrt/Windows.Storage.Pickers.h>
#include <winrt/Windows.UI.h>
#include <winrt/Windows.UI.Core.h>
@ -54,7 +53,8 @@
#include <winrt/Microsoft.Terminal.Control.h>
#include <winrt/Microsoft.Terminal.Settings.Model.h>
#include "shobjidl_core.h"
#include <shlobj.h>
#include <shobjidl_core.h>
#include <dwrite.h>
#include <dwrite_1.h>