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:
parent
b310b1cffc
commit
959c423e7a
11
.github/actions/spelling/dictionary/apis.txt
vendored
11
.github/actions/spelling/dictionary/apis.txt
vendored
|
@ -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
|
||||
|
|
1
.github/actions/spelling/expect/expect.txt
vendored
1
.github/actions/spelling/expect/expect.txt
vendored
|
@ -2397,6 +2397,7 @@ titlebar
|
|||
TITLEISLINKNAME
|
||||
TJson
|
||||
tl
|
||||
TLambda
|
||||
TLEN
|
||||
Tlg
|
||||
Tlgdata
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in a new issue