Skip to content

Commit e4515bb

Browse files
committed
Open Folders support for Windows
1 parent 580c3e9 commit e4515bb

File tree

1 file changed

+116
-24
lines changed

1 file changed

+116
-24
lines changed

AssetRipper.NativeDialogs/OpenFolderDialog.cs

Lines changed: 116 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -80,33 +80,35 @@ public static class OpenFolderDialog
8080

8181
// Create the FileOpenDialog object.
8282
hr = Windows.CoCreateInstance(&CLSID_FileOpenDialog, null, (uint)CLSCTX.CLSCTX_INPROC_SERVER, &IID_IFileOpenDialog, (void**)&pFileDialog);
83-
if (Windows.SUCCEEDED(hr))
83+
if (!Windows.SUCCEEDED(hr))
8484
{
85-
// Set the options on the dialog.
86-
uint dwOptions;
87-
pFileDialog->GetOptions(&dwOptions);
88-
pFileDialog->SetOptions(dwOptions | (uint)FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS | (uint)FILEOPENDIALOGOPTIONS.FOS_FORCEFILESYSTEM);
85+
return null;
86+
}
87+
88+
// Set the options on the dialog.
89+
uint dwOptions;
90+
pFileDialog->GetOptions(&dwOptions);
91+
pFileDialog->SetOptions(dwOptions | (uint)FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS | (uint)FILEOPENDIALOGOPTIONS.FOS_FORCEFILESYSTEM);
8992

90-
// Show the dialog
91-
hr = pFileDialog->Show(default);
93+
// Show the dialog
94+
hr = pFileDialog->Show(default);
95+
if (Windows.SUCCEEDED(hr))
96+
{
97+
IShellItem* pItem;
98+
hr = pFileDialog->GetResult(&pItem);
9299
if (Windows.SUCCEEDED(hr))
93100
{
94-
IShellItem* pItem;
95-
hr = pFileDialog->GetResult(&pItem);
101+
char* pszFilePath = null;
102+
hr = pItem->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &pszFilePath);
96103
if (Windows.SUCCEEDED(hr))
97104
{
98-
char* pszFilePath = null;
99-
hr = pItem->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &pszFilePath);
100-
if (Windows.SUCCEEDED(hr))
101-
{
102-
result = new string(pszFilePath);
103-
Windows.CoTaskMemFree(pszFilePath);
104-
}
105-
pItem->Release();
105+
result = new string(pszFilePath);
106+
Windows.CoTaskMemFree(pszFilePath);
106107
}
108+
pItem->Release();
107109
}
108-
pFileDialog->Release();
109110
}
111+
pFileDialog->Release();
110112

111113
return result;
112114
}
@@ -168,15 +170,105 @@ public static class OpenFolderDialog
168170
}
169171

170172
[SupportedOSPlatform("windows")]
171-
private static async Task<string[]?> OpenFoldersWindows()
173+
private static Task<string[]?> OpenFoldersWindows()
172174
{
173-
// Todo: proper Windows implementation
174-
string? path = await OpenFolder();
175-
if (string.IsNullOrEmpty(path))
175+
TaskCompletionSource<string[]?> tcs = new();
176+
177+
Thread thread = new(() =>
176178
{
177-
return null; // User canceled the dialog
179+
try
180+
{
181+
// Run the STA work
182+
Debug.Assert(OperatingSystem.IsWindows());
183+
string[]? result = OpenFoldersWindowsInternal();
184+
185+
// Mark task complete
186+
tcs.SetResult(result);
187+
}
188+
catch (Exception ex)
189+
{
190+
tcs.SetException(ex);
191+
}
192+
});
193+
thread.SetApartmentState(ApartmentState.STA);
194+
thread.Start();
195+
196+
return tcs.Task;
197+
}
198+
199+
[SupportedOSPlatform("windows")]
200+
private unsafe static string[]? OpenFoldersWindowsInternal()
201+
{
202+
HRESULT hr = Windows.CoInitializeEx(null, (uint)COINIT.COINIT_APARTMENTTHREADED);
203+
switch (hr.Value)
204+
{
205+
case S.S_OK:
206+
Windows.CoUninitialize();
207+
throw new InvalidOperationException("CoInitializeEx failed with S_OK, which should never happen because .NET is supposed to initialize the thread.");
208+
case S.S_FALSE:
209+
// The thread is already initialized, which is expected.
210+
break;
211+
case RPC.RPC_E_CHANGED_MODE:
212+
// The thread is already initialized with a different mode, which is unexpected.
213+
throw new InvalidOperationException("CoInitializeEx failed with RPC_E_CHANGED_MODE, which should never happen because we only call this method in STA threads.");
214+
}
215+
216+
IFileOpenDialog* pFileDialog = null;
217+
218+
// Assign the CLSID and IID for the FileOpenDialog.
219+
Guid CLSID_FileOpenDialog = new("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7");
220+
Guid IID_IFileOpenDialog = new("D57C7288-D4AD-4768-BE02-9D969532D960");
221+
222+
// Create the FileOpenDialog object.
223+
hr = Windows.CoCreateInstance(&CLSID_FileOpenDialog, null, (uint)CLSCTX.CLSCTX_INPROC_SERVER, &IID_IFileOpenDialog, (void**)&pFileDialog);
224+
if (!Windows.SUCCEEDED(hr))
225+
{
226+
return null;
178227
}
179-
return [path];
228+
229+
// Set the options on the dialog.
230+
uint dwOptions = default;
231+
pFileDialog->GetOptions(&dwOptions);
232+
pFileDialog->SetOptions(dwOptions | (uint)FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS | (uint)FILEOPENDIALOGOPTIONS.FOS_ALLOWMULTISELECT | (uint)FILEOPENDIALOGOPTIONS.FOS_FORCEFILESYSTEM);
233+
234+
string[]? result = null;
235+
236+
// Show the dialog
237+
hr = pFileDialog->Show(default);
238+
if (Windows.SUCCEEDED(hr))
239+
{
240+
IShellItemArray* pItemArray;
241+
hr = pFileDialog->GetResults(&pItemArray);
242+
if (Windows.SUCCEEDED(hr))
243+
{
244+
uint itemCount = default;
245+
pItemArray->GetCount(&itemCount);
246+
if (itemCount > 0)
247+
{
248+
result = new string[itemCount];
249+
for (uint i = 0; i < itemCount; i++)
250+
{
251+
IShellItem* pItem;
252+
hr = pItemArray->GetItemAt(i, &pItem);
253+
if (Windows.SUCCEEDED(hr))
254+
{
255+
char* pszFilePath = null;
256+
hr = pItem->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &pszFilePath);
257+
if (Windows.SUCCEEDED(hr))
258+
{
259+
result[i] = new string(pszFilePath);
260+
Windows.CoTaskMemFree(pszFilePath);
261+
}
262+
pItem->Release();
263+
}
264+
}
265+
}
266+
pItemArray->Release();
267+
}
268+
}
269+
pFileDialog->Release();
270+
271+
return result;
180272
}
181273

182274
[SupportedOSPlatform("macos")]

0 commit comments

Comments
 (0)