Skip to content

Commit 959c423

Browse files
authored
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.
1 parent b310b1c commit 959c423

File tree

5 files changed

+119
-50
lines changed

5 files changed

+119
-50
lines changed

.github/actions/spelling/dictionary/apis.txt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@ bitfields
66
CLASSNOTAVAILABLE
77
cmdletbinding
88
COLORPROPERTY
9+
COMDLG
910
CXICON
1011
CYICON
1112
D2DERR_SHADER_COMPILE_FAILED
1213
dataobject
1314
DERR
1415
dlldata
16+
DONTADDTORECENT
1517
environstrings
1618
EXPCMDFLAGS
1719
EXPCMDSTATE
20+
FILTERSPEC
21+
FORCEFILESYSTEM
1822
FORCEMINIMIZE
1923
frac
2024
fullkbd
@@ -32,12 +36,13 @@ IAsync
3236
IBind
3337
IBox
3438
IClass
35-
IConnection
3639
IComparable
40+
IConnection
3741
ICustom
3842
IDialog
3943
IDirect
4044
IExplorer
45+
IFile
4146
IInheritable
4247
IMap
4348
IObject
@@ -63,6 +68,7 @@ NCLBUTTONDBLCLK
6368
NCRBUTTONDBLCLK
6469
NOAGGREGATION
6570
NOASYNC
71+
NOCHANGEDIR
6672
NOPROGRESS
6773
NOREDIRECTIONBITMAP
6874
ntprivapi
@@ -72,10 +78,11 @@ otms
7278
OUTLINETEXTMETRICW
7379
overridable
7480
PAGESCROLL
81+
PICKFOLDERS
7582
pmr
7683
REGCLS
77-
RETURNCMD
7884
REGCLS
85+
RETURNCMD
7986
rfind
8087
roundf
8188
RSHIFT

.github/actions/spelling/expect/expect.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2397,6 +2397,7 @@ titlebar
23972397
TITLEISLINKNAME
23982398
TJson
23992399
tl
2400+
TLambda
24002401
TLEN
24012402
Tlg
24022403
Tlgdata

src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,12 @@
300300
</Reference>
301301
</ItemGroup>
302302

303+
<ItemDefinitionGroup>
304+
<Link>
305+
<AdditionalDependencies>shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
306+
</Link>
307+
</ItemDefinitionGroup>
308+
303309
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
304310
<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')" />
305311
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">

src/cascadia/TerminalSettingsEditor/Profiles.cpp

Lines changed: 101 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,71 @@ using namespace winrt::Windows::UI::Xaml::Data;
1515
using namespace winrt::Windows::UI::Xaml::Navigation;
1616
using namespace winrt::Windows::Foundation;
1717
using namespace winrt::Windows::Foundation::Collections;
18-
using namespace winrt::Windows::Storage;
19-
using namespace winrt::Windows::Storage::AccessCache;
20-
using namespace winrt::Windows::Storage::Pickers;
2118
using namespace winrt::Microsoft::Terminal::Settings::Model;
2219

2320
static const std::array<winrt::guid, 2> InBoxProfileGuids{
2421
winrt::guid{ 0x61c54bbd, 0xc2c6, 0x5271, { 0x96, 0xe7, 0x00, 0x9a, 0x87, 0xff, 0x44, 0xbf } }, // Windows Powershell
2522
winrt::guid{ 0x0caa0dad, 0x35be, 0x5f56, { 0xa8, 0xff, 0xaf, 0xce, 0xee, 0xaa, 0x61, 0x01 } } // Command Prompt
2623
};
2724

25+
// Function Description:
26+
// - This function presents a File Open "common dialog" and returns its selected file asynchronously.
27+
// Parameters:
28+
// - customize: A lambda that receives an IFileDialog* to customize.
29+
// Return value:
30+
// (async) path to the selected item.
31+
template<typename TLambda>
32+
static winrt::Windows::Foundation::IAsyncOperation<winrt::hstring> OpenFilePicker(TLambda&& customize)
33+
{
34+
auto fileDialog{ winrt::create_instance<IFileDialog>(CLSID_FileOpenDialog) };
35+
DWORD flags{};
36+
THROW_IF_FAILED(fileDialog->GetOptions(&flags));
37+
THROW_IF_FAILED(fileDialog->SetOptions(flags | FOS_FORCEFILESYSTEM | FOS_NOCHANGEDIR | FOS_DONTADDTORECENT)); // filesystem objects only; no recent places
38+
customize(fileDialog.get());
39+
40+
auto hr{ fileDialog->Show(NULL) };
41+
if (!SUCCEEDED(hr))
42+
{
43+
if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED))
44+
{
45+
co_return winrt::hstring{};
46+
}
47+
THROW_HR(hr);
48+
}
49+
50+
winrt::com_ptr<IShellItem> result;
51+
THROW_IF_FAILED(fileDialog->GetResult(result.put()));
52+
53+
wil::unique_cotaskmem_string filePath;
54+
THROW_IF_FAILED(result->GetDisplayName(SIGDN_FILESYSPATH, &filePath));
55+
56+
co_return winrt::hstring{ filePath.get() };
57+
}
58+
59+
// Function Description:
60+
// - Helper that opens a file picker pre-seeded with image file types.
61+
static winrt::Windows::Foundation::IAsyncOperation<winrt::hstring> OpenImagePicker()
62+
{
63+
static constexpr COMDLG_FILTERSPEC supportedImageFileTypes[] = {
64+
{ L"All Supported Bitmap Types (*.jpg, *.jpeg, *.png, *.bmp, *.gif, *.tiff, *.ico)", L"*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.tiff;*.ico" },
65+
{ L"All Files (*.*)", L"*.*" }
66+
};
67+
68+
static constexpr winrt::guid clientGuidImagePicker{ 0x55675F54, 0x74A1, 0x4552, { 0xA3, 0x9D, 0x94, 0xAE, 0x85, 0xD8, 0xF2, 0x7A } };
69+
return OpenFilePicker([](auto&& dialog) {
70+
THROW_IF_FAILED(dialog->SetClientGuid(clientGuidImagePicker));
71+
try
72+
{
73+
auto pictureFolderShellItem{ winrt::capture<IShellItem>(&SHGetKnownFolderItem, FOLDERID_PicturesLibrary, KF_FLAG_DEFAULT, nullptr) };
74+
dialog->SetDefaultFolder(pictureFolderShellItem.get());
75+
}
76+
CATCH_LOG(); // non-fatal
77+
THROW_IF_FAILED(dialog->SetFileTypes(ARRAYSIZE(supportedImageFileTypes), supportedImageFileTypes));
78+
THROW_IF_FAILED(dialog->SetFileTypeIndex(1)); // the array is 1-indexed
79+
THROW_IF_FAILED(dialog->SetDefaultExtension(L"jpg;jpeg;png;bmp;gif;tiff;ico"));
80+
});
81+
}
82+
2883
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
2984
{
3085
Windows::Foundation::Collections::IObservableVector<Editor::Font> ProfileViewModel::_MonospaceFontList{ nullptr };
@@ -582,74 +637,74 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
582637
{
583638
auto lifetime = get_strong();
584639

585-
FileOpenPicker picker;
586-
587-
_State.WindowRoot().TryPropagateHostingWindow(picker); // if we don't do this, there's no HWND for it to attach to
588-
picker.ViewMode(PickerViewMode::Thumbnail);
589-
picker.SuggestedStartLocation(PickerLocationId::PicturesLibrary);
590-
591-
// Converted into a BitmapImage. This list of supported image file formats is from BitmapImage documentation
592-
// https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Xaml.Media.Imaging.BitmapImage?view=winrt-19041#remarks
593-
picker.FileTypeFilter().ReplaceAll({ L".jpg", L".jpeg", L".png", L".bmp", L".gif", L".tiff", L".ico" });
594-
595-
StorageFile file = co_await picker.PickSingleFileAsync();
596-
if (file != nullptr)
640+
auto file = co_await OpenImagePicker();
641+
if (!file.empty())
597642
{
598-
_State.Profile().BackgroundImagePath(file.Path());
643+
_State.Profile().BackgroundImagePath(file);
599644
}
600645
}
601646

602647
fire_and_forget Profiles::Icon_Click(IInspectable const&, RoutedEventArgs const&)
603648
{
604649
auto lifetime = get_strong();
605650

606-
FileOpenPicker picker;
607-
608-
_State.WindowRoot().TryPropagateHostingWindow(picker); // if we don't do this, there's no HWND for it to attach to
609-
picker.ViewMode(PickerViewMode::Thumbnail);
610-
picker.SuggestedStartLocation(PickerLocationId::PicturesLibrary);
611-
612-
// Converted into a BitmapIconSource. This list of supported image file formats is from BitmapImage documentation
613-
// https://docs.microsoft.com/en-us/uwp/api/Windows.UI.Xaml.Media.Imaging.BitmapImage?view=winrt-19041#remarks
614-
picker.FileTypeFilter().ReplaceAll({ L".jpg", L".jpeg", L".png", L".bmp", L".gif", L".tiff", L".ico" });
615-
616-
StorageFile file = co_await picker.PickSingleFileAsync();
617-
if (file != nullptr)
651+
auto file = co_await OpenImagePicker();
652+
if (!file.empty())
618653
{
619-
_State.Profile().Icon(file.Path());
654+
_State.Profile().Icon(file);
620655
}
621656
}
622657

623658
fire_and_forget Profiles::Commandline_Click(IInspectable const&, RoutedEventArgs const&)
624659
{
625660
auto lifetime = get_strong();
626661

627-
FileOpenPicker picker;
662+
static constexpr COMDLG_FILTERSPEC supportedFileTypes[] = {
663+
{ L"Executable Files (*.exe, *.cmd, *.bat)", L"*.exe;*.cmd;*.bat" },
664+
{ L"All Files (*.*)", L"*.*" }
665+
};
628666

629-
_State.WindowRoot().TryPropagateHostingWindow(picker); // if we don't do this, there's no HWND for it to attach to
630-
picker.ViewMode(PickerViewMode::Thumbnail);
631-
picker.SuggestedStartLocation(PickerLocationId::ComputerFolder);
632-
picker.FileTypeFilter().ReplaceAll({ L".bat", L".exe", L".cmd" });
667+
static constexpr winrt::guid clientGuidExecutables{ 0x2E7E4331, 0x0800, 0x48E6, { 0xB0, 0x17, 0xA1, 0x4C, 0xD8, 0x73, 0xDD, 0x58 } };
668+
auto path = co_await OpenFilePicker([](auto&& dialog) {
669+
THROW_IF_FAILED(dialog->SetClientGuid(clientGuidExecutables));
670+
try
671+
{
672+
auto folderShellItem{ winrt::capture<IShellItem>(&SHGetKnownFolderItem, FOLDERID_ComputerFolder, KF_FLAG_DEFAULT, nullptr) };
673+
dialog->SetDefaultFolder(folderShellItem.get());
674+
}
675+
CATCH_LOG(); // non-fatal
676+
THROW_IF_FAILED(dialog->SetFileTypes(ARRAYSIZE(supportedFileTypes), supportedFileTypes));
677+
THROW_IF_FAILED(dialog->SetFileTypeIndex(1)); // the array is 1-indexed
678+
THROW_IF_FAILED(dialog->SetDefaultExtension(L"exe;cmd;bat"));
679+
});
633680

634-
StorageFile file = co_await picker.PickSingleFileAsync();
635-
if (file != nullptr)
681+
if (!path.empty())
636682
{
637-
_State.Profile().Commandline(file.Path());
683+
_State.Profile().Commandline(path);
638684
}
639685
}
640686

641687
fire_and_forget Profiles::StartingDirectory_Click(IInspectable const&, RoutedEventArgs const&)
642688
{
643689
auto lifetime = get_strong();
644-
FolderPicker picker;
645-
_State.WindowRoot().TryPropagateHostingWindow(picker); // if we don't do this, there's no HWND for it to attach to
646-
picker.SuggestedStartLocation(PickerLocationId::DocumentsLibrary);
647-
picker.FileTypeFilter().ReplaceAll({ L"*" });
648-
StorageFolder folder = co_await picker.PickSingleFolderAsync();
649-
if (folder != nullptr)
650-
{
651-
StorageApplicationPermissions::FutureAccessList().AddOrReplace(L"PickedFolderToken", folder);
652-
_State.Profile().StartingDirectory(folder.Path());
690+
auto folder = co_await OpenFilePicker([](auto&& dialog) {
691+
static constexpr winrt::guid clientGuidFolderPicker{ 0xAADAA433, 0xB04D, 0x4BAE, { 0xB1, 0xEA, 0x1E, 0x6C, 0xD1, 0xCD, 0xA6, 0x8B } };
692+
THROW_IF_FAILED(dialog->SetClientGuid(clientGuidFolderPicker));
693+
try
694+
{
695+
auto folderShellItem{ winrt::capture<IShellItem>(&SHGetKnownFolderItem, FOLDERID_ComputerFolder, KF_FLAG_DEFAULT, nullptr) };
696+
dialog->SetDefaultFolder(folderShellItem.get());
697+
}
698+
CATCH_LOG(); // non-fatal
699+
700+
DWORD flags{};
701+
THROW_IF_FAILED(dialog->GetOptions(&flags));
702+
THROW_IF_FAILED(dialog->SetOptions(flags | FOS_PICKFOLDERS)); // folders only
703+
});
704+
705+
if (!folder.empty())
706+
{
707+
_State.Profile().StartingDirectory(folder);
653708
}
654709
}
655710

src/cascadia/TerminalSettingsEditor/pch.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
#include <winrt/Windows.Foundation.Collections.h>
3030
#include <winrt/Windows.Storage.h>
3131
#include <winrt/Windows.Storage.AccessCache.h>
32-
#include <winrt/Windows.Storage.Pickers.h>
3332

3433
#include <winrt/Windows.UI.h>
3534
#include <winrt/Windows.UI.Core.h>
@@ -54,7 +53,8 @@
5453
#include <winrt/Microsoft.Terminal.Control.h>
5554
#include <winrt/Microsoft.Terminal.Settings.Model.h>
5655

57-
#include "shobjidl_core.h"
56+
#include <shlobj.h>
57+
#include <shobjidl_core.h>
5858
#include <dwrite.h>
5959
#include <dwrite_1.h>
6060

0 commit comments

Comments
 (0)