-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWindowsCOMFileBrowser.cs
More file actions
191 lines (165 loc) · 8.22 KB
/
Copy pathWindowsCOMFileBrowser.cs
File metadata and controls
191 lines (165 loc) · 8.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
using System;
using System.Runtime.InteropServices;
using UnityEngine;
// Terrifying!
namespace NativeFileBrowser
{
public static class WindowsCOMFileBrowser
{
public static string[] Pick(FileBrowserSettings settings)
{
// Default flags from https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog
var fosOptions = FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST | FOS_NOCHANGEDIR;
if (settings.DialogKind == DialogKind.Folder)
fosOptions |= FOS_PICKFOLDERS;
if (settings.MultiSelectKind == MultiSelectKind.Multiple)
fosOptions |= FOS_ALLOWMULTISELECT;
var dialog = new FileOpenDialogRCW();
var idialog = (IFileOpenDialog)dialog;
idialog.SetOptions(fosOptions);
if (settings.Title != null)
idialog.SetTitle(settings.Title);
if (settings.InitialFolder != null)
{
var iidShellItem2 = new Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe");
object unk;
var hr = SHCreateItemFromParsingName(settings.InitialFolder, IntPtr.Zero, ref iidShellItem2, out unk);
// most common reason to fail is probably ERROR_FILE_NOT_FOUND=2 or ERROR_PATH_NOT_FOUND=3
if (hr != 0)
Debug.LogException(Marshal.GetExceptionForHR(hr));
else if (unk != null)
idialog.SetFolder((IShellItem)unk);
}
if (settings.FileExtensionFilters != null)
{
var filterSpecs = new COMDLG_FILTERSPEC[settings.FileExtensionFilters.Length];
for (var i = 0; i < filterSpecs.Length; i++)
{
// mime type is ignored
var setting = settings.FileExtensionFilters[i];
var filterSpec = string.Join(';', setting.Patterns ?? Array.Empty<string>());
filterSpecs[i] = new COMDLG_FILTERSPEC(setting.Name ?? "", filterSpec);
}
idialog.SetFileTypes((uint)filterSpecs.Length, filterSpecs);
}
var showResult = idialog.Show(IntPtr.Zero);
string[] result;
if (showResult == ERROR_CANCELLED)
result = Array.Empty<string>();
else
{
Marshal.ThrowExceptionForHR(showResult);
var shellItems = idialog.GetResults();
result = new string[shellItems.GetCount()];
for (var i = 0u; i < result.Length; i++)
{
var shellItem = shellItems.GetItemAt(i);
// wpf uses SIGDN_DESKTOPABSOLUTEPARSING, not SIGDN_FILESYSPATH
// DesktopAbsoluteParsing still returns a full rooted C:\blahblah\file.txt path
// https://github.com/dotnet/wpf/blob/fe3d2c72ea8a7acf4371592ba41d7f7b49e141b7/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ShellProvider.cs#L939
result[i] = shellItem.GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING);
Marshal.ReleaseComObject(shellItem);
}
Marshal.ReleaseComObject(shellItems);
}
Marshal.ReleaseComObject(dialog);
return result;
}
private const int ERROR_CANCELLED = unchecked((int)0x800704C7);
// https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/ne-shobjidl_core-sigdn
private const int SIGDN_DESKTOPABSOLUTEPARSING = unchecked((int)0x80028000);
// https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/ne-shobjidl_core-_fileopendialogoptions
private const int FOS_NOCHANGEDIR = 0x8;
private const int FOS_PICKFOLDERS = 0x20;
private const int FOS_ALLOWMULTISELECT = 0x200;
private const int FOS_PATHMUSTEXIST = 0x800;
private const int FOS_FILEMUSTEXIST = 0x1000;
[DllImport("shell32.dll")]
internal static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, [In] ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out object ppv);
[ComImport,
ClassInterface(ClassInterfaceType.None),
TypeLibType(TypeLibTypeFlags.FCanCreate),
Guid("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7")]
private class FileOpenDialogRCW
{
}
// https://github.com/dotnet/wpf/blob/main/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/AppModel/ShellProvider.cs
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("D57C7288-D4AD-4768-BE02-9D969532D960")]
internal interface IFileOpenDialog
{
[PreserveSig]
int Show(IntPtr parent);
void SetFileTypes(uint cFileTypes, [In] COMDLG_FILTERSPEC[] rgFilterSpec);
void SetFileTypeIndex(uint iFileType);
uint GetFileTypeIndex();
uint Advise([MarshalAs(UnmanagedType.Interface)] IntPtr pfde);
void Unadvise(uint dwCookie);
void SetOptions(int fos);
int GetOptions();
void SetDefaultFolder(IShellItem psi);
void SetFolder(IShellItem psi);
IShellItem GetFolder();
IShellItem GetCurrentSelection();
void SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName);
[return: MarshalAs(UnmanagedType.LPWStr)]
void GetFileName();
void SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle);
void SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText);
void SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel);
IShellItem GetResult();
void AddPlace(IShellItem psi, int fdcp);
void SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);
void Close([MarshalAs(UnmanagedType.Error)] int hr);
void SetClientGuid([In] ref Guid guid);
void ClearClientData();
void SetFilter([MarshalAs(UnmanagedType.Interface)] object pFilter);
IShellItemArray GetResults();
IShellItemArray GetSelectedItems();
}
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE")]
internal interface IShellItem
{
[return: MarshalAs(UnmanagedType.Interface)]
object BindToHandler(IntPtr pbc, [In] ref Guid bhid, [In] ref Guid riid);
IShellItem GetParent();
[return: MarshalAs(UnmanagedType.LPWStr)]
string GetDisplayName(int sigdnName);
uint GetAttributes(int sfgaoMask);
int Compare(IShellItem psi, int hint);
}
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("B63EA76D-1F85-456F-A19C-48159EFA858B"),]
internal interface IShellItemArray
{
[return: MarshalAs(UnmanagedType.Interface)]
object BindToHandler(IntPtr pbc, [In] ref Guid rbhid, [In] ref Guid riid);
[return: MarshalAs(UnmanagedType.Interface)]
object GetPropertyStore(int flags, [In] ref Guid riid);
[return: MarshalAs(UnmanagedType.Interface)]
object GetPropertyDescriptionList([In] ref PKEY keyType, [In] ref Guid riid);
uint GetAttributes(int dwAttribFlags, uint sfgaoMask);
uint GetCount();
IShellItem GetItemAt(uint dwIndex);
[return: MarshalAs(UnmanagedType.Interface)]
object EnumItems();
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal readonly struct COMDLG_FILTERSPEC
{
[MarshalAs(UnmanagedType.LPWStr)]
public readonly string pszName;
[MarshalAs(UnmanagedType.LPWStr)]
public readonly string pszSpec;
public COMDLG_FILTERSPEC(string name, string spec)
{
pszName = name;
pszSpec = spec;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct PKEY
{
private readonly Guid _fmtid;
private readonly uint _pid;
}
}
}